@ -11,8 +11,8 @@
// Lazy import logger to avoid ES module issues when loaded by Capacitor CLI (CommonJS)
// Lazy import logger to avoid ES module issues when loaded by Capacitor CLI (CommonJS)
// Logger is only used inside functions, not at module scope
// Logger is only used inside functions, not at module scope
let logger : { error : ( . . . args : unknown [ ] ) = > void ; custom : ( emoji : string , . . . args : unknown [ ] ) = > void } | null = null ;
let logger : { error : ( . . . args : unknown [ ] ) = > void ; custom : ( emoji : string , . . . args : unknown [ ] ) = > void ; info : ( . . . args : unknown [ ] ) = > void } | null = null ;
const getLogger = async ( ) : Promise < { error : ( . . . args : unknown [ ] ) = > void ; custom : ( emoji : string , . . . args : unknown [ ] ) = > void } | null > = > {
const getLogger = async ( ) : Promise < { error : ( . . . args : unknown [ ] ) = > void ; custom : ( emoji : string , . . . args : unknown [ ] ) = > void ; info : ( . . . args : unknown [ ] ) = > void } | null > = > {
if ( ! logger ) {
if ( ! logger ) {
const loggerModule = await import ( '../lib/logger' ) ;
const loggerModule = await import ( '../lib/logger' ) ;
logger = loggerModule . logger ;
logger = loggerModule . logger ;
@ -25,7 +25,14 @@ export const TEST_USER_ZERO_CONFIG = {
identity : {
identity : {
did : "did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F" ,
did : "did:ethr:0x0000694B58C2cC69658993A90D3840C560f2F51F" ,
name : "User Zero" ,
name : "User Zero" ,
seedPhrase : "rigid shrug mobile smart veteran half all pond toilet brave review universe ship congress found yard skate elite apology jar uniform subway slender luggage"
seedPhrase : "rigid shrug mobile smart veteran half all pond toilet brave review universe ship congress found yard skate elite apology jar uniform subway slender luggage" ,
// User Zero's known public key for verification
// Public Key (hex): 03b231e73413f1d36b3327763ad9a44bb81fc91480a14977ab3cfd32df0564af1d
// Public Key (base64): A7Ix5zQT8dNrMyd2OtmkS7gfyRSAoUl3qzz9Mt8FZK8d
// Derivation Path: m/84737769'/0'/0'/0'
expectedPublicKeyHex : "03b231e73413f1d36b3327763ad9a44bb81fc91480a14977ab3cfd32df0564af1d" ,
expectedPublicKeyBase64 : "A7Ix5zQT8dNrMyd2OtmkS7gfyRSAoUl3qzz9Mt8FZK8d" ,
derivationPath : "m/84737769'/0'/0'/0'"
} ,
} ,
// API Configuration
// API Configuration
@ -226,10 +233,15 @@ export const MOCK_STARRED_PROJECTS_RESPONSE = {
/ * *
/ * *
* Generate ES256K signed JWT token for User Zero using DID - based signing
* Generate ES256K signed JWT token for User Zero using DID - based signing
*
*
* This function mimics TimeSafari ' s createEndorserJwtForKey ( ) function ,
* This function generates a JWT token signed with User Zero ' s private key ,
* using did - jwt library with ES256K algorithm ( DID - based signing ) .
* using User Zero ' s DID and seed phrase from TEST_USER_ZERO_CONFIG .
*
*
* @returns Promise < string > ES256K signed JWT token
* Uses TimeSafari ' s approach ( mimics createEndorserJwtForKey ( ) function ) :
* - did - jwt library with ES256K algorithm ( DID - based signing )
* - Ethereum private key derived from User Zero ' s seed phrase
* - JWT payload includes User Zero ' s DID as issuer and subject
*
* @returns Promise < string > ES256K signed JWT token for User Zero
* /
* /
export async function generateEndorserJWT ( ) : Promise < string > {
export async function generateEndorserJWT ( ) : Promise < string > {
try {
try {
@ -237,44 +249,148 @@ export async function generateEndorserJWT(): Promise<string> {
const { createJWT , SimpleSigner } = await import ( 'did-jwt' ) ;
const { createJWT , SimpleSigner } = await import ( 'did-jwt' ) ;
const { HDNodeWallet , Mnemonic } = await import ( 'ethers' ) ;
const { HDNodeWallet , Mnemonic } = await import ( 'ethers' ) ;
// Derive Ethereum private key from seed phrase
// Derive Ethereum private key from User Zero's seed phrase
// Using the same derivation as TimeSafari (m/44'/60'/0'/0/0 - Ethereum standard)
// Using User Zero's specific derivation path: m/84737769'/0'/0'/0'
const mnemonic = Mnemonic . fromPhrase ( TEST_USER_ZERO_CONFIG . identity . seedPhrase ) ;
// This is the path used when User Zero was registered on the server
const wallet = HDNodeWallet . fromMnemonic ( mnemonic ) ;
// NOTE: Must use fromMnemonic() with the Mnemonic object, not fromPhrase() with phrase string
const userZeroSeedPhrase = TEST_USER_ZERO_CONFIG . identity . seedPhrase ;
const mnemonic = Mnemonic . fromPhrase ( userZeroSeedPhrase ) ;
const derivationPath = "m/84737769'/0'/0'/0'" ;
const wallet = HDNodeWallet . fromMnemonic ( mnemonic , derivationPath ) ;
const privateKeyHex = wallet . privateKey . slice ( 2 ) ; // Remove '0x' prefix
const privateKeyHex = wallet . privateKey . slice ( 2 ) ; // Remove '0x' prefix
// Create signer for ES256K (Ethereum secp256k1 curve)
// Extract public key from wallet in compressed format
// ethers.js wallet.signingKey.compressedPublicKey provides the compressed format
// Compressed format: 33 bytes (0x02 or 0x03 prefix + 32-byte x-coordinate)
const signingKey = wallet . signingKey ;
const compressedPublicKey = signingKey . compressedPublicKey ; // This should be the compressed public key
const publicKeyHexRaw = compressedPublicKey ? compressedPublicKey . slice ( 2 ) . toLowerCase ( ) : null ;
// Fallback: if compressed public key not available, use first 66 hex chars from uncompressed
// Note: This is a workaround - we should have compressedPublicKey from signingKey
const publicKeyHexFinal = publicKeyHexRaw || wallet . publicKey . slice ( 2 ) . substring ( 0 , 66 ) . toLowerCase ( ) ;
// Verify derived address matches User Zero's DID
const derivedAddress = wallet . address . toLowerCase ( ) ;
const expectedAddress = TEST_USER_ZERO_CONFIG . identity . did . replace ( 'did:ethr:' , '' ) . toLowerCase ( ) ;
const logger = await getLogger ( ) ;
// Verify public key matches expected (if provided)
const expectedPublicKeyHex = TEST_USER_ZERO_CONFIG . identity . expectedPublicKeyHex ;
// Comprehensive verification logging
if ( derivedAddress !== expectedAddress ) {
logger . error ( '⚠️ Key derivation address mismatch!' , {
derivedAddress : derivedAddress ,
expectedAddress : expectedAddress ,
derivationPath : derivationPath ,
note : 'JWT signing may fail if server expects different key'
} ) ;
logger . error ( '⚠️ CRITICAL: Derived address does not match User Zero DID!' , {
derived : derivedAddress ,
expected : expectedAddress ,
path : derivationPath ,
warning : 'JWT verification will likely fail on server'
} ) ;
} else {
logger . custom ( '✅' , 'Key derivation verified - address matches User Zero DID' ) ;
}
// Verify public key if expected value is provided
if ( expectedPublicKeyHex && publicKeyHexFinal ) {
// Compare the derived public key with expected (both should be compressed format)
const publicKeyMatchesActual = publicKeyHexFinal . toLowerCase ( ) === expectedPublicKeyHex . toLowerCase ( ) ;
if ( ! publicKeyMatchesActual ) {
logger . error ( '⚠️ CRITICAL: Derived public key does not match expected!' , {
derivedPublicKey : publicKeyHexFinal ,
expectedPublicKey : expectedPublicKeyHex ,
derivationPath : derivationPath ,
warning : 'Server will not recognize this public key - JWT verification will fail'
} ) ;
logger . error ( '⚠️ Public Key Mismatch Details:' , {
derived : publicKeyHexFinal.substring ( 0 , 10 ) + '...' + publicKeyHexFinal . substring ( publicKeyHexFinal . length - 10 ) ,
expected : expectedPublicKeyHex.substring ( 0 , 10 ) + '...' + expectedPublicKeyHex . substring ( expectedPublicKeyHex . length - 10 ) ,
derivedLength : publicKeyHexFinal.length ,
expectedLength : expectedPublicKeyHex.length ,
derivedPrefix : publicKeyHexFinal.substring ( 0 , 2 ) , // Should be 02 or 03
expectedPrefix : expectedPublicKeyHex.substring ( 0 , 2 ) ,
note : 'Verify derivation path and seed phrase are correct'
} ) ;
} else {
logger . custom ( '✅' , 'Public key verified - matches expected User Zero public key' ) ;
logger . info ( '✅ Public Key Verification:' , {
derived : publicKeyHexFinal.substring ( 0 , 20 ) + '...' + publicKeyHexFinal . substring ( publicKeyHexFinal . length - 20 ) ,
expected : expectedPublicKeyHex.substring ( 0 , 20 ) + '...' + expectedPublicKeyHex . substring ( expectedPublicKeyHex . length - 20 ) ,
match : true
} ) ;
}
}
// Log comprehensive key derivation diagnostics
logger . info ( '🔐 JWT Key Derivation: Complete diagnostics' , {
address : derivedAddress ,
addressMatch : derivedAddress === expectedAddress ,
derivationPath : derivationPath ,
publicKeyHex : publicKeyHexFinal ? ( publicKeyHexFinal . substring ( 0 , 20 ) + '...' + publicKeyHexFinal . substring ( publicKeyHexFinal . length - 20 ) ) : 'N/A' ,
expectedPublicKeyHex : expectedPublicKeyHex ? ( expectedPublicKeyHex . substring ( 0 , 20 ) + '...' + expectedPublicKeyHex . substring ( expectedPublicKeyHex . length - 20 ) ) : 'N/A' ,
publicKeyMatch : expectedPublicKeyHex && publicKeyHexFinal ? ( publicKeyHexFinal . toLowerCase ( ) === expectedPublicKeyHex . toLowerCase ( ) ) : null ,
publicKeyLength : publicKeyHexFinal?.length || 0 ,
expectedPublicKeyLength : expectedPublicKeyHex?.length || 0 ,
hasCompressedPublicKey : ! ! compressedPublicKey
} ) ;
// Create signer for ES256K (Ethereum secp256k1 curve) using User Zero's private key
const signer = SimpleSigner ( privateKeyHex ) ;
const signer = SimpleSigner ( privateKeyHex ) ;
// Create JWT payload with standard claims
// Create JWT payload with User Zero's DID as issuer and subject
const nowEpoch = Math . floor ( Date . now ( ) / 1000 ) ;
const nowEpoch = Math . floor ( Date . now ( ) / 1000 ) ;
const expiresIn = TEST_USER_ZERO_CONFIG . api . jwtExpirationMinutes * 60 ;
const expiresIn = TEST_USER_ZERO_CONFIG . api . jwtExpirationMinutes * 60 ;
const userZeroDid = TEST_USER_ZERO_CONFIG . identity . did ;
const payload = {
const payload = {
// Standard JWT claims
// Standard JWT claims - all using User Zero's DID
iat : nowEpoch ,
iat : nowEpoch ,
exp : nowEpoch + expiresIn ,
exp : nowEpoch + expiresIn ,
iss : TEST_USER_ZERO_CONFIG.identity.did ,
iss : userZeroDid , // User Zero's DID as issuer
sub : TEST_USER_ZERO_CONFIG.identity.did ,
sub : userZeroDid , // User Zero's DID as subject
// Additional claims that endorser-ch might expect
// NOTE: aud (audience) claim is omitted because server's did-jwt verifyJWT()
aud : "endorser-ch"
// requires audience option when aud is present, but server isn't configured
// to validate it. Server will reject JWTs with aud claim until it adds support.
} ;
} ;
// Create ES256K signed JWT (ES256K is the default algorithm for did-jwt)
// Create ES256K signed JWT using User Zero's DID and private key
// ES256K is the default algorithm for did-jwt
// NOTE: sub claim is included in payload; aud is omitted because server's
// did-jwt verifyJWT() requires audience option when aud is present, but
// server isn't configured to validate it
const jwt = await createJWT ( payload , {
const jwt = await createJWT ( payload , {
issuer : TEST_USER_ZERO_CONFIG.identity.did ,
issuer : userZeroDid , // User Zero's DID (overwrites payload.iss)
signer : signer ,
signer : signer , // User Zero's private key signer
expiresIn : expiresIn
expiresIn : expiresIn
} ) ;
} ) ;
const log = await getLogger ( ) ;
logger . custom ( "🔐" , "JWT generated for User Zero - Algorithm: ES256K, DID:" , userZeroDid . substring ( 0 , 30 ) + "..." ) ;
log . custom ( "🔐" , "JWT generated - Algorithm: ES256K, DID:" , TEST_USER_ZERO_CONFIG . identity . did . substring ( 0 , 30 ) + "..." ) ;
logger . custom ( "🔐" , "JWT length:" , jwt . length , "characters" ) ;
log . custom ( "🔐" , "JWT length:" , jwt . length , "characters" ) ;
// Enhanced diagnostic logging
logger . info ( '🔐 JWT Generation Diagnostics:' , {
algorithm : 'ES256K' ,
issuerDid : userZeroDid ,
issuerAddress : userZeroDid.replace ( 'did:ethr:' , '' ) . toLowerCase ( ) ,
derivedAddress : derivedAddress ,
derivationPath : derivationPath ,
addressMatch : derivedAddress === expectedAddress ,
jwtLength : jwt.length ,
jwtPreview : jwt.substring ( 0 , 20 ) + '...' + jwt . substring ( jwt . length - 20 ) ,
expiresIn : expiresIn ,
nowEpoch : nowEpoch
} ) ;
return jwt ;
return jwt ;
} catch ( error ) {
} catch ( error ) {
const log = await getLogger ( ) ;
const logger = await getLogger ( ) ;
log . error ( 'Failed to generate ES256K JWT:' , error ) ;
logger . error ( 'Failed to generate ES256K JWT:' , error ) ;
throw new Error ( ` JWT generation failed: ${ error instanceof Error ? error.message : String ( error ) } ` ) ;
throw new Error ( ` JWT generation failed: ${ error instanceof Error ? error.message : String ( error ) } ` ) ;
}
}
}
}