11/* eslint-disable @typescript-eslint/no-unsafe-assignment -- for expect.any() */
2+ import * as ed25519 from "@noble/ed25519" ;
3+ import { sha512 } from "@noble/hashes/sha512" ;
4+ import type { Hex } from "viem" ;
5+ import { bytesToHex , hexToBytes } from "viem" ;
26import {
37 sign ,
48 verify ,
@@ -8,10 +12,16 @@ import {
812 encodeSignature ,
913 decodeHeader ,
1014 decodePayload ,
11- decodeSignature ,
15+ decodeCustodyTypeSignature ,
16+ decodeAppKeyTypeSignature ,
1217 constructJSONFarcasterSignatureAccountAssociationPaylod ,
18+ signMessageWithAppKey ,
1319} from "./json-signature" ;
1420
21+ process . env . NEYNAR_API_KEY = "NEYNAR_FRAMES_JS" ;
22+
23+ ed25519 . etc . sha512Sync = ( ...m ) => sha512 ( ed25519 . etc . concatBytes ( ...m ) ) ;
24+
1525const fcDemoSignature = {
1626 header :
1727 "eyJmaWQiOjM2MjEsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHgyY2Q4NWEwOTMyNjFmNTkyNzA4MDRBNkVBNjk3Q2VBNENlQkVjYWZFIn0" ,
@@ -33,58 +43,144 @@ const framesJsDemoSignature = {
3343const framesJsDemoCompactSignature =
3444 "eyJmaWQiOjM0MTc5NCwidHlwZSI6ImN1c3RvZHkiLCJrZXkiOiIweDc4Mzk3RDlEMTg1RDNhNTdEMDEyMTNDQmUzRWMxRWJBQzNFRWM3N2QifQ.eyJkb21haW4iOiJmcmFtZXNqcy5vcmcifQ.MHgwOWExNWMyZDQ3ZDk0NTM5NWJjYTJlNGQzNDg3MzYxMGUyNGZiMDFjMzc0NTUzYTJmOTM2NjM3YjU4YTA5NzdjNzAxOWZiYzljNGUxY2U5ZmJjOGMzNWVjYTllNzViMTM5Zjg3ZGQyNTBlMzhkMjBmM2YyZmEyNDk2MDQ1NGExMjFi" ;
3545
36- const signatures = [ framesJsDemoSignature , fcDemoSignature ] ;
46+ const custodySignatures = [ framesJsDemoSignature , fcDemoSignature ] ;
3747
38- const compactSignatures = [
48+ const dummyAppKeySignature = {
49+ header :
50+ "eyJmaWQiOjM0MTc5NCwidHlwZSI6ImFwcF9rZXkiLCJrZXkiOiIweGJkZGVhNDQ2ODUxZDYwZjQ4OTAxNjU1NDc4YTIwNTQ3MmNjOTJmNGUwMzdiNTIzNmE1YzVhYmZjMWI4ZTA5MWIifQ" ,
51+ payload : "eyJ0ZXN0Ijp0cnVlfQ" ,
52+ signature :
53+ "Y1C9-m6EIAPDqd8-2NrSXBKrpvWKUfA3Qjy865De5yUu7MV_b1TjsQKtwqbaVv_UzFz5ghmvygVbGjhx-kbRDw" ,
54+ } ;
55+
56+ const dummyAppKeyCompactSignature = `${ dummyAppKeySignature . header } .${ dummyAppKeySignature . payload } .${ dummyAppKeySignature . signature } ` ;
57+ const appKeySignatures = [ dummyAppKeySignature ] ;
58+
59+ const custodyCompactSignatures = [
3960 framesJsDemoCompactSignature ,
4061 fcDemoCompactSignature ,
4162] ;
63+ const appKeyCompactSignatures = [ dummyAppKeyCompactSignature ] ;
4264
4365describe ( "verifyCompact" , ( ) => {
44- it . each ( compactSignatures ) ( "verifies valid message" , async ( signature ) => {
45- await expect ( verifyCompact ( signature ) ) . resolves . toBe ( true ) ;
66+ describe ( "custody" , ( ) => {
67+ it . each ( custodyCompactSignatures ) (
68+ "verifies valid message" ,
69+ async ( signature ) => {
70+ await expect ( verifyCompact ( signature ) ) . resolves . toBe ( true ) ;
71+ }
72+ ) ;
73+ } ) ;
74+
75+ describe ( "app_key" , ( ) => {
76+ it . each ( appKeyCompactSignatures ) (
77+ "verifies valid message" ,
78+ async ( signature ) => {
79+ await expect ( verifyCompact ( signature ) ) . resolves . toBe ( true ) ;
80+ }
81+ ) ;
4682 } ) ;
4783} ) ;
4884
4985describe ( "verify" , ( ) => {
50- it . each ( signatures ) ( "verifies valid message" , async ( signature ) => {
51- await expect ( verify ( signature ) ) . resolves . toBe ( true ) ;
86+ describe ( "custody" , ( ) => {
87+ it . each ( custodySignatures ) ( "verifies valid message" , async ( signature ) => {
88+ await expect ( verify ( signature ) ) . resolves . toBe ( true ) ;
89+ } ) ;
90+ } ) ;
91+
92+ describe ( "app_key" , ( ) => {
93+ it . each ( appKeySignatures ) ( "verifies valid message" , async ( signature ) => {
94+ await expect ( verify ( signature ) ) . resolves . toBe ( true ) ;
95+ } ) ;
5296 } ) ;
5397} ) ;
5498
5599describe ( "sign" , ( ) => {
56- it ( "signs any payload" , async ( ) => {
57- const signature = await sign ( {
58- fid : 1 ,
59- payload : { test : true } ,
60- signer : {
61- type : "custody" ,
62- custodyAddress : "0x1234567890abcdef1234567890abcdef12345678" ,
63- } ,
64- signMessage : ( message ) => {
65- expect ( typeof message === "string" ) . toBe ( true ) ;
66- expect ( message . length ) . toBeGreaterThan ( 0 ) ;
100+ describe ( "custody" , ( ) => {
101+ it ( "signs any payload" , async ( ) => {
102+ const signature = await sign ( {
103+ fid : 1 ,
104+ payload : { test : true } ,
105+ signer : {
106+ type : "custody" ,
107+ custodyAddress : "0x1234567890abcdef1234567890abcdef12345678" ,
108+ } ,
109+ signMessage : ( message ) => {
110+ expect ( typeof message === "string" ) . toBe ( true ) ;
111+ expect ( message . length ) . toBeGreaterThan ( 0 ) ;
67112
68- return Promise . resolve ( "0x0000000" ) ;
69- } ,
70- } ) ;
113+ return Promise . resolve ( "0x0000000" ) ;
114+ } ,
115+ } ) ;
71116
72- expect ( signature ) . toMatchObject ( {
73- compact : expect . any ( String ) ,
74- json : {
75- header : expect . any ( String ) ,
76- payload : expect . any ( String ) ,
77- signature : expect . any ( String ) ,
78- } ,
117+ expect ( signature ) . toMatchObject ( {
118+ compact : expect . any ( String ) ,
119+ json : {
120+ header : expect . any ( String ) ,
121+ payload : expect . any ( String ) ,
122+ signature : expect . any ( String ) ,
123+ } ,
124+ } ) ;
125+
126+ expect ( decodePayload ( signature . json . payload ) ) . toEqual ( { test : true } ) ;
127+ expect ( decodeHeader ( signature . json . header ) ) . toEqual ( {
128+ fid : 1 ,
129+ type : "custody" ,
130+ key : "0x1234567890abcdef1234567890abcdef12345678" ,
131+ } ) ;
132+ expect ( decodeCustodyTypeSignature ( signature . json . signature ) ) . toEqual (
133+ "0x0000000"
134+ ) ;
79135 } ) ;
136+ } ) ;
80137
81- expect ( decodePayload ( signature . json . payload ) ) . toEqual ( { test : true } ) ;
82- expect ( decodeHeader ( signature . json . header ) ) . toEqual ( {
83- fid : 1 ,
84- type : "custody" ,
85- key : "0x1234567890abcdef1234567890abcdef12345678" ,
138+ describe ( "app_key" , ( ) => {
139+ it ( "signs any payload" , async ( ) => {
140+ const privateKey = ed25519 . utils . randomPrivateKey ( ) ;
141+ let messageSignature : Hex = "0x" ;
142+ const signature = await sign ( {
143+ fid : 1 ,
144+ payload : { test : true } ,
145+ signer : {
146+ type : "app_key" ,
147+ appKey : bytesToHex ( ed25519 . getPublicKey ( privateKey ) ) ,
148+ } ,
149+ signMessage : ( message ) => {
150+ expect ( typeof message === "string" ) . toBe ( true ) ;
151+ expect ( message . length ) . toBeGreaterThan ( 0 ) ;
152+
153+ messageSignature = bytesToHex (
154+ ed25519 . sign ( Buffer . from ( message , "utf-8" ) , privateKey )
155+ ) ;
156+
157+ return Promise . resolve ( messageSignature ) ;
158+ } ,
159+ } ) ;
160+
161+ expect ( signature ) . toMatchObject ( {
162+ compact : expect . any ( String ) ,
163+ json : {
164+ header : expect . any ( String ) ,
165+ payload : expect . any ( String ) ,
166+ signature : expect . any ( String ) ,
167+ } ,
168+ } ) ;
169+
170+ expect ( Buffer . from ( signature . json . signature , "base64url" ) ) . toHaveProperty (
171+ "byteLength" ,
172+ 64
173+ ) ;
174+ expect ( decodePayload ( signature . json . payload ) ) . toEqual ( { test : true } ) ;
175+ expect ( decodeHeader ( signature . json . header ) ) . toEqual ( {
176+ fid : 1 ,
177+ type : "app_key" ,
178+ key : bytesToHex ( ed25519 . getPublicKey ( privateKey ) ) ,
179+ } ) ;
180+ expect ( decodeAppKeyTypeSignature ( signature . json . signature ) ) . toEqual (
181+ messageSignature
182+ ) ;
86183 } ) ;
87- expect ( decodeSignature ( signature . json . signature ) ) . toEqual ( "0x0000000" ) ;
88184 } ) ;
89185} ) ;
90186
@@ -140,17 +236,77 @@ describe("decodePayload", () => {
140236
141237describe ( "encodeSignature" , ( ) => {
142238 it ( "encodes signature" , ( ) => {
143- const value = encodeSignature ( "0x0000000" ) ;
239+ const input = "0x0000000" ;
240+ const value = encodeSignature ( Buffer . from ( "0x0000000" , "utf-8" ) ) ;
144241
145242 expect ( typeof value ) . toBe ( "string" ) ;
243+ expect ( Buffer . from ( input , "utf-8" ) . toString ( "base64url" ) ) . toEqual ( value ) ;
244+ } ) ;
245+
246+ it ( "encodes signature as Buffer" , ( ) => {
247+ const input = hexToBytes ( "0x0000001" ) ;
248+ const value = encodeSignature ( Buffer . from ( input ) ) ;
249+
250+ expect ( Buffer . from ( input ) . toString ( "base64url" ) ) . toEqual ( value ) ;
251+ } ) ;
252+ } ) ;
253+
254+ describe ( "decodeAppKeyTypeSignature" , ( ) => {
255+ it ( "decodes signature (string)" , ( ) => {
256+ const buf = Buffer . from ( "0x0000000" , "utf-8" ) ;
257+ const encodedSignature = encodeSignature ( buf ) ;
258+ const value = decodeAppKeyTypeSignature ( encodedSignature ) ;
259+
260+ expect ( value ) . toBe ( bytesToHex ( buf ) ) ;
261+ } ) ;
262+
263+ it ( "decodes signature (from buffer)" , ( ) => {
264+ const input = hexToBytes ( "0x0000001" ) ;
265+ const encodedSignature = encodeSignature ( Buffer . from ( input ) ) ;
266+ const value = decodeAppKeyTypeSignature ( encodedSignature ) ;
267+
268+ expect ( value ) . toBe ( bytesToHex ( input ) ) ;
269+ } ) ;
270+ } ) ;
271+
272+ describe ( "decodeCustodyTypeSignature" , ( ) => {
273+ it ( "decodes signature (string)" , ( ) => {
274+ const buf = Buffer . from ( "0x0000000" , "utf-8" ) ;
275+ const encodedSignature = encodeSignature ( buf ) ;
276+ const value = decodeCustodyTypeSignature ( encodedSignature ) ;
277+
278+ expect ( value ) . toBe ( buf . toString ( "utf-8" ) ) ;
279+ } ) ;
280+
281+ it ( "decodes signature (from buffer)" , ( ) => {
282+ const input = "0x0000001" ;
283+ const encodedSignature = encodeSignature ( Buffer . from ( input , "utf-8" ) ) ;
284+ const value = decodeCustodyTypeSignature ( encodedSignature ) ;
285+
286+ expect ( value ) . toBe ( input ) ;
146287 } ) ;
147288} ) ;
148289
149- describe ( "decodeSignature" , ( ) => {
150- it ( "decodes signature" , ( ) => {
151- const encodedSignature = encodeSignature ( "0x0000000" ) ;
152- const value = decodeSignature ( encodedSignature ) ;
290+ describe ( "signMessageWithAppKey" , ( ) => {
291+ it ( "signs any payload" , async ( ) => {
292+ const privateKey = ed25519 . utils . randomPrivateKey ( ) ;
293+ const signature = await sign ( {
294+ fid : 1 ,
295+ payload : { test : true } ,
296+ signer : {
297+ type : "app_key" ,
298+ appKey : bytesToHex ( ed25519 . getPublicKey ( privateKey ) ) ,
299+ } ,
300+ signMessage : signMessageWithAppKey ( privateKey ) ,
301+ } ) ;
153302
154- expect ( value ) . toBe ( "0x0000000" ) ;
303+ expect ( signature ) . toMatchObject ( {
304+ compact : expect . any ( String ) ,
305+ json : {
306+ header : expect . any ( String ) ,
307+ payload : expect . any ( String ) ,
308+ signature : expect . any ( String ) ,
309+ } ,
310+ } ) ;
155311 } ) ;
156312} ) ;
0 commit comments