11import { Buffer } from "buffer" ;
2+ import type { Hex } from "viem" ;
23
34enum COSEKEYS {
45 kty = 1 , // Key Type
@@ -80,6 +81,71 @@ function decodeValue(buffer: Buffer, offset: number): [number | Buffer, number]
8081 }
8182}
8283
84+ // Encode an integer in CBOR format
85+ function encodeInt ( int : number ) : Buffer {
86+ if ( int >= 0 && int <= 23 ) {
87+ // Small positive integer (0–23)
88+ return Buffer . from ( [ int ] ) ;
89+ } else if ( int >= 24 && int <= 255 ) {
90+ // 1-byte positive integer
91+ return Buffer . from ( [ 0x18 , int ] ) ;
92+ } else if ( int >= 256 && int <= 65535 ) {
93+ // 2-byte positive integer
94+ const buf = Buffer . alloc ( 3 ) ;
95+ buf [ 0 ] = 0x19 ;
96+ buf . writeUInt16BE ( int , 1 ) ;
97+ return buf ;
98+ } else if ( int < 0 && int >= - 24 ) {
99+ // Small negative integer (-1 to -24)
100+ return Buffer . from ( [ 0x20 - ( int + 1 ) ] ) ;
101+ } else if ( int < - 24 && int >= - 256 ) {
102+ // 1-byte negative integer
103+ return Buffer . from ( [ 0x38 , - int - 1 ] ) ;
104+ } else if ( int < - 256 && int >= - 65536 ) {
105+ // 2-byte negative integer
106+ const buf = Buffer . alloc ( 3 ) ;
107+ buf [ 0 ] = 0x39 ;
108+ buf . writeUInt16BE ( - int - 1 , 1 ) ;
109+ return buf ;
110+ } else {
111+ throw new Error ( "Unsupported integer range" ) ;
112+ }
113+ }
114+
115+ // Encode a byte array in CBOR format
116+ function encodeBytes ( bytes : Buffer ) : Buffer {
117+ if ( bytes . length <= 23 ) {
118+ return Buffer . concat ( [ Buffer . from ( [ 0x40 + bytes . length ] ) , bytes ] ) ; // Byte array with small length
119+ } else if ( bytes . length < 256 ) {
120+ return Buffer . concat ( [ Buffer . from ( [ 0x58 , bytes . length ] ) , bytes ] ) ; // Byte array with 1-byte length prefix
121+ } else {
122+ throw new Error ( "Unsupported byte array length" ) ;
123+ }
124+ }
125+
126+ // Encode a map in CBOR format
127+ function encodeMap ( map : COSEPublicKeyMap ) : Buffer {
128+ const encodedItems : Buffer [ ] = [ ] ;
129+
130+ // CBOR map header, assuming the map size fits within small integer encoding
131+ const mapHeader = 0xA0 | map . size ;
132+ encodedItems . push ( Buffer . from ( [ mapHeader ] ) ) ;
133+
134+ map . forEach ( ( value , key ) => {
135+ // Encode the key
136+ encodedItems . push ( encodeInt ( key ) ) ;
137+
138+ // Encode the value based on its type (Buffer or number)
139+ if ( Buffer . isBuffer ( value ) ) {
140+ encodedItems . push ( encodeBytes ( value ) ) ;
141+ } else {
142+ encodedItems . push ( encodeInt ( value ) ) ;
143+ }
144+ } ) ;
145+
146+ return Buffer . concat ( encodedItems ) ;
147+ }
148+
83149/**
84150 * Decodes a CBOR-encoded COSE public key (from WebAuthn credential) and returns the x,y coordinates.
85151 * @param publicPasskey - CBOR-encoded COSE public key as Uint8Array
@@ -92,3 +158,25 @@ export const getPublicKeyBytesFromPasskeySignature = (publicPasskey: Uint8Array)
92158
93159 return [ Buffer . from ( x ) , Buffer . from ( y ) ] ;
94160} ;
161+
162+ /**
163+ * Encodes x,y hex coordinates into a CBOR-encoded COSE public key format.
164+ * This is the inverse of getPublicKeyBytesFromPasskeySignature.
165+ * @param coordinates - Tuple of [x, y] coordinates as hex strings
166+ * @returns CBOR-encoded COSE public key as Uint8Array
167+ */
168+ export const getPasskeySignatureFromPublicKeyBytes = ( coordinates : readonly [ Hex , Hex ] ) : Uint8Array => {
169+ const [ xHex , yHex ] = coordinates ;
170+ const x = Buffer . from ( xHex . slice ( 2 ) , "hex" ) ;
171+ const y = Buffer . from ( yHex . slice ( 2 ) , "hex" ) ;
172+
173+ const cosePublicKey : COSEPublicKeyMap = new Map ( ) ;
174+ cosePublicKey . set ( COSEKEYS . kty , 2 ) ; // Type 2 for EC keys
175+ cosePublicKey . set ( COSEKEYS . alg , - 7 ) ; // -7 for ES256 algorithm
176+ cosePublicKey . set ( COSEKEYS . crv , 1 ) ; // Curve ID (1 for P-256)
177+ cosePublicKey . set ( COSEKEYS . x , x ) ;
178+ cosePublicKey . set ( COSEKEYS . y , y ) ;
179+
180+ const encodedPublicKey = encodeMap ( cosePublicKey ) ;
181+ return new Uint8Array ( encodedPublicKey ) ;
182+ } ;
0 commit comments