@@ -387,7 +387,7 @@ export async function createWebAuthnCredential(options: CreateCredentialOptions)
387387 const coseKey = authenticatorData . slice ( coseKeyOffset ) ;
388388
389389 // Parse COSE key to extract public key coordinates
390- const [ xBuffer , yBuffer ] = parseCOSEKey ( coseKey ) ;
390+ const [ xBuffer , yBuffer ] = getPublicKeyBytesFromPasskeySignature ( coseKey ) ;
391391
392392 console . log ( {
393393 credential,
@@ -495,10 +495,11 @@ function decodeValue(buffer: Uint8Array, offset: number): [number | Uint8Array,
495495
496496/**
497497 * Parse COSE key to extract P-256 public key coordinates
498+ * Browser-compatible version using Uint8Array (no Node Buffer dependency)
498499 * @param publicPasskey - CBOR-encoded COSE public key
499500 * @returns Tuple of [x, y] coordinates as Uint8Arrays
500501 */
501- function parseCOSEKey ( publicPasskey : Uint8Array ) : [ Uint8Array , Uint8Array ] {
502+ export function getPublicKeyBytesFromPasskeySignature ( publicPasskey : Uint8Array ) : [ Uint8Array , Uint8Array ] {
502503 const cosePublicKey = decodeMap ( publicPasskey ) ;
503504 const x = cosePublicKey . get ( COSEKEYS . x ) as Uint8Array ;
504505 const y = cosePublicKey . get ( COSEKEYS . y ) as Uint8Array ;
@@ -514,6 +515,109 @@ function parseCOSEKey(publicPasskey: Uint8Array): [Uint8Array, Uint8Array] {
514515 return [ x , y ] ;
515516}
516517
518+ // ============================================================================
519+ // COSE/CBOR Encoding Functions
520+ // ============================================================================
521+
522+ // Encode an integer in CBOR format
523+ function encodeInt ( int : number ) : Uint8Array {
524+ if ( int >= 0 && int <= 23 ) {
525+ return new Uint8Array ( [ int ] ) ;
526+ } else if ( int >= 24 && int <= 255 ) {
527+ return new Uint8Array ( [ 0x18 , int ] ) ;
528+ } else if ( int >= 256 && int <= 65535 ) {
529+ const buf = new Uint8Array ( 3 ) ;
530+ buf [ 0 ] = 0x19 ;
531+ buf [ 1 ] = ( int >> 8 ) & 0xFF ;
532+ buf [ 2 ] = int & 0xFF ;
533+ return buf ;
534+ } else if ( int < 0 && int >= - 24 ) {
535+ return new Uint8Array ( [ 0x20 - ( int + 1 ) ] ) ;
536+ } else if ( int < - 24 && int >= - 256 ) {
537+ return new Uint8Array ( [ 0x38 , - int - 1 ] ) ;
538+ } else if ( int < - 256 && int >= - 65536 ) {
539+ const buf = new Uint8Array ( 3 ) ;
540+ buf [ 0 ] = 0x39 ;
541+ const value = - int - 1 ;
542+ buf [ 1 ] = ( value >> 8 ) & 0xFF ;
543+ buf [ 2 ] = value & 0xFF ;
544+ return buf ;
545+ } else {
546+ throw new Error ( "Unsupported integer range" ) ;
547+ }
548+ }
549+
550+ // Encode a byte array in CBOR format
551+ function encodeBytes ( bytes : Uint8Array ) : Uint8Array {
552+ if ( bytes . length <= 23 ) {
553+ const result = new Uint8Array ( 1 + bytes . length ) ;
554+ result [ 0 ] = 0x40 + bytes . length ;
555+ result . set ( bytes , 1 ) ;
556+ return result ;
557+ } else if ( bytes . length < 256 ) {
558+ const result = new Uint8Array ( 2 + bytes . length ) ;
559+ result [ 0 ] = 0x58 ;
560+ result [ 1 ] = bytes . length ;
561+ result . set ( bytes , 2 ) ;
562+ return result ;
563+ } else {
564+ throw new Error ( "Unsupported byte array length" ) ;
565+ }
566+ }
567+
568+ // Encode a map in CBOR format
569+ function encodeMap ( map : COSEPublicKeyMap ) : Uint8Array {
570+ const encodedItems : Uint8Array [ ] = [ ] ;
571+
572+ // CBOR map header
573+ const mapHeader = 0xA0 | map . size ;
574+ encodedItems . push ( new Uint8Array ( [ mapHeader ] ) ) ;
575+
576+ map . forEach ( ( value , key ) => {
577+ // Encode the key
578+ encodedItems . push ( encodeInt ( key ) ) ;
579+
580+ // Encode the value based on its type
581+ if ( value instanceof Uint8Array ) {
582+ encodedItems . push ( encodeBytes ( value ) ) ;
583+ } else {
584+ encodedItems . push ( encodeInt ( value ) ) ;
585+ }
586+ } ) ;
587+
588+ // Concatenate all encoded items
589+ const totalLength = encodedItems . reduce ( ( sum , item ) => sum + item . length , 0 ) ;
590+ const result = new Uint8Array ( totalLength ) ;
591+ let offset = 0 ;
592+ for ( const item of encodedItems ) {
593+ result . set ( item , offset ) ;
594+ offset += item . length ;
595+ }
596+ return result ;
597+ }
598+
599+ /**
600+ * Encodes x,y hex coordinates into a CBOR-encoded COSE public key format.
601+ * Browser-compatible version using Uint8Array (no Node Buffer dependency)
602+ * This is the inverse of getPublicKeyBytesFromPasskeySignature.
603+ * @param coordinates - Tuple of [x, y] coordinates as hex strings
604+ * @returns CBOR-encoded COSE public key as Uint8Array
605+ */
606+ export function getPasskeySignatureFromPublicKeyBytes ( coordinates : readonly [ Hex , Hex ] ) : Uint8Array {
607+ const [ xHex , yHex ] = coordinates ;
608+ const x = hexToBytes ( xHex ) ;
609+ const y = hexToBytes ( yHex ) ;
610+
611+ const cosePublicKey : COSEPublicKeyMap = new Map ( ) ;
612+ cosePublicKey . set ( COSEKEYS . kty , 2 ) ; // Type 2 for EC keys
613+ cosePublicKey . set ( COSEKEYS . alg , - 7 ) ; // -7 for ES256 algorithm
614+ cosePublicKey . set ( COSEKEYS . crv , 1 ) ; // Curve ID (1 for P-256)
615+ cosePublicKey . set ( COSEKEYS . x , x ) ;
616+ cosePublicKey . set ( COSEKEYS . y , y ) ;
617+
618+ return encodeMap ( cosePublicKey ) ;
619+ }
620+
517621export async function getPasskeyCredential ( ) {
518622 const credential = await navigator . credentials . get ( {
519623 publicKey : {
0 commit comments