77import { setupOutputConnection } from '../utils/webhookUtils' ;
88// import { rm, } from 'fs/promises';
99import type * as express from 'express' ;
10- import { createPrivateKey , sign as cryptoSign } from 'crypto' ;
10+ import { createPrivateKey , sign as cryptoSign , randomBytes } from 'crypto' ;
11+ import { signAsync as ed25519SignAsync } from './crypto/ED25519' ;
1112
1213export interface IPaymentPayload {
1314 x402Version : number ;
@@ -45,14 +46,8 @@ export interface IPaymentRequirements {
4546
4647
4748export async function webhookTrigger ( this : IWebhookFunctions ) : Promise < IWebhookResponseData > {
48- const webhookType = this . getNodeParameter ( 'webhookType' ) as string ;
4949 const body = this . getBodyData ( ) ;
50-
51- if ( webhookType === 'x402' ) {
52- return await handleX402Webhook . call ( this , body ) ;
53- } else {
54- throw new Error ( `Unsupported webhook type: ${ webhookType } ` ) ;
55- }
50+ return await handleX402Webhook . call ( this , body ) ;
5651}
5752
5853async function handleX402Webhook (
@@ -397,36 +392,64 @@ function detectSigningAlg(keyObj: ReturnType<typeof createPrivateKey>): {
397392 throw new Error ( `Unsupported key type: ${ type } . Use Ed25519 or EC P-256.` ) ;
398393}
399394
400- function buildCdpJwt ( params : {
395+ async function buildCdpJwtAsync ( params : {
401396 apiKeyId : string ;
402397 apiKeySecret : string ;
403398 method : string ;
404399 host : string ;
405400 path : string ;
406401 expiresInSec ?: number ;
407- audience ?: string [ ] ;
408- } ) : string {
409- const { apiKeyId, apiKeySecret, method, host, path, expiresInSec = 120 , audience = [ 'cdp-api' ] } = params ;
410- const keyObj = toKeyObjectFromSecret ( apiKeySecret ) ;
411- const { algHeader, signAlg } = detectSigningAlg ( keyObj ) ;
402+ } ) : Promise < string > {
403+ const { apiKeyId, apiKeySecret, method, host, path, expiresInSec = 120 } = params ;
404+
405+ let algHeader : 'EdDSA' | 'ES256' ;
406+ let signAlg : null | 'sha256' = null ;
407+ let keyObj : ReturnType < typeof createPrivateKey > | undefined ;
408+ let useEd25519Noble = false ;
409+
410+ if ( apiKeySecret . startsWith ( '-----BEGIN' ) ) {
411+ keyObj = toKeyObjectFromSecret ( apiKeySecret ) ;
412+ const det = detectSigningAlg ( keyObj ) ;
413+ algHeader = det . algHeader ;
414+ signAlg = det . signAlg ;
415+ } else {
416+ // Sign exactly like our ED25519.ts usage: expect base64-encoded 32-byte Ed25519 secret
417+ useEd25519Noble = true ;
418+ algHeader = 'EdDSA' ;
419+ }
412420
413421 const now = Math . floor ( Date . now ( ) / 1000 ) ;
414- const header = { alg : algHeader , typ : 'JWT' , kid : apiKeyId } as const ;
422+ const header = {
423+ alg : algHeader ,
424+ typ : 'JWT' ,
425+ kid : apiKeyId ,
426+ nonce : randomBytes ( 16 ) . toString ( 'hex' ) ,
427+ } as const ;
415428 const payload = {
416- iss : 'coinbase-cloud ' ,
429+ iss : 'cdp ' ,
417430 sub : apiKeyId ,
418- iat : now ,
419431 nbf : now ,
420432 exp : now + expiresInSec ,
421- audience : undefined ,
422- aud : audience ,
423- uris : [ `${ method . toUpperCase ( ) } ${ host } ${ path } ` ] ,
433+ uri : `${ method . toUpperCase ( ) } ${ host } ${ path } ` ,
424434 } ;
425435
426436 const encHeader = base64UrlEncode ( JSON . stringify ( header ) ) ;
427437 const encPayload = base64UrlEncode ( JSON . stringify ( payload ) ) ;
428438 const signingInput = Buffer . from ( `${ encHeader } .${ encPayload } ` ) ;
429- const signature = cryptoSign ( signAlg as any , signingInput , keyObj ) ;
439+
440+ let signature : Buffer ;
441+ if ( useEd25519Noble ) {
442+ // Expect raw 32-byte Ed25519 secret in base64
443+ const raw = Buffer . from ( apiKeySecret , 'base64' ) ;
444+ if ( raw . length !== 32 ) {
445+ throw new Error ( 'Invalid Ed25519 secret: expected base64-encoded 32-byte key' ) ;
446+ }
447+ const sig = await ed25519SignAsync ( signingInput , raw ) ;
448+ signature = Buffer . from ( sig ) ;
449+ } else {
450+ // Use Node crypto with either Ed25519 (signAlg null) or ES256
451+ signature = cryptoSign ( signAlg as any , signingInput , keyObj ! ) ;
452+ }
430453 const encSig = base64UrlEncode ( signature ) ;
431454 return `${ encHeader } .${ encPayload } .${ encSig } ` ;
432455}
@@ -438,7 +461,7 @@ async function verifyX402Payment(
438461 paymentRequirements : PaymentRequirements ,
439462) : Promise < { isValid : boolean ; invalidReason ?: string } >
440463{
441- const token = buildCdpJwt ( { apiKeyId, apiKeySecret, method : 'POST' , host : CDP_HOST , path : FACILITATOR_VERIFY_PATH } ) ;
464+ const token = await buildCdpJwtAsync ( { apiKeyId, apiKeySecret, method : 'POST' , host : CDP_HOST , path : FACILITATOR_VERIFY_PATH } ) ;
442465 const res = await fetch ( `https://${ CDP_HOST } ${ FACILITATOR_VERIFY_PATH } ` , {
443466 method : 'POST' ,
444467 headers : {
@@ -462,7 +485,7 @@ async function settleX402Payment(
462485 paymentRequirements : PaymentRequirements ,
463486) : Promise < { success : boolean ; txHash ?: string ; error ?: string } >
464487{
465- const token = buildCdpJwt ( { apiKeyId, apiKeySecret, method : 'POST' , host : CDP_HOST , path : FACILITATOR_SETTLE_PATH } ) ;
488+ const token = await buildCdpJwtAsync ( { apiKeyId, apiKeySecret, method : 'POST' , host : CDP_HOST , path : FACILITATOR_SETTLE_PATH } ) ;
466489 const res = await fetch ( `https://${ CDP_HOST } ${ FACILITATOR_SETTLE_PATH } ` , {
467490 method : 'POST' ,
468491 headers : {
0 commit comments