@@ -4,16 +4,17 @@ import * as blake2 from 'blake2';
44import bs58check from 'bs58check' ;
55import Client from 'mina-signer' ;
66import { submitterAllowed } from './submitterValidator' ;
7+ import { CallableRequest , onCall } from 'firebase-functions/v2/https' ;
78
89interface SignatureJson {
9- field : string ;
10- scalar : string ;
10+ field : string ;
11+ scalar : string ;
1112}
1213
1314interface HeartbeatData {
14- publicKey : string ;
15- data : string ;
16- signature : SignatureJson ;
15+ publicKey : string ;
16+ data : string ;
17+ signature : SignatureJson ;
1718}
1819
1920const minaClient = new Client ( { network : 'testnet' } ) ;
@@ -24,101 +25,103 @@ admin.initializeApp();
2425const HEARTBEAT_RATE_LIMIT_MS = 15000 ;
2526
2627function validateSignature (
27- data : string ,
28- signature : SignatureJson ,
29- publicKeyBase58 : string
28+ data : string ,
29+ signature : SignatureJson ,
30+ publicKeyBase58 : string ,
3031) : boolean {
32+ try {
33+ const h = blake2 . createHash ( 'blake2b' , { digestLength : 32 } ) ;
34+ h . update ( Buffer . from ( data ) ) ;
35+ const digest : string = h . digest ( ) . toString ( 'hex' ) ;
36+
3137 try {
32- const h = blake2 . createHash ( 'blake2b' , { digestLength : 32 } ) ;
33- h . update ( Buffer . from ( data ) ) ;
34- const digest : string = h . digest ( ) . toString ( 'hex' ) ;
35-
36- try {
37- // TODO: remove this validation later, since the list is
38- // hardcoded and we check that the key is there,
39- // we know it is valid.
40- let publicKeyBytes : Uint8Array ;
41- try {
42- publicKeyBytes = bs58check . decode ( publicKeyBase58 ) ;
43- } catch ( e ) {
44- console . error ( 'Failed to decode public key:' , e ) ;
45- return false ;
46- }
47-
48- if ( publicKeyBytes [ 0 ] !== 0xcb ) {
49- console . error ( 'Invalid public key prefix' ) ;
50- return false ;
51- }
52-
53- return minaClient . verifyMessage ( {
54- data : digest ,
55- signature,
56- publicKey : publicKeyBase58 ,
57- } ) ;
58- } catch ( e ) {
59- console . error ( 'Error parsing signature or verifying:' , e ) ;
60- return false ;
61- }
62- } catch ( e ) {
63- console . error ( 'Error in signature validation:' , e ) ;
38+ // TODO: remove this validation later, since the list is
39+ // hardcoded and we check that the key is there,
40+ // we know it is valid.
41+ let publicKeyBytes : Uint8Array ;
42+ try {
43+ publicKeyBytes = bs58check . decode ( publicKeyBase58 ) ;
44+ } catch ( e ) {
45+ console . error ( 'Failed to decode public key:' , e ) ;
6446 return false ;
47+ }
48+
49+ if ( publicKeyBytes [ 0 ] !== 0xcb ) {
50+ console . error ( 'Invalid public key prefix' ) ;
51+ return false ;
52+ }
53+
54+ return minaClient . verifyMessage ( {
55+ data : digest ,
56+ signature,
57+ publicKey : publicKeyBase58 ,
58+ } ) ;
59+ } catch ( e ) {
60+ console . error ( 'Error parsing signature or verifying:' , e ) ;
61+ return false ;
6562 }
63+ } catch ( e ) {
64+ console . error ( 'Error in signature validation:' , e ) ;
65+ return false ;
66+ }
6667}
6768
68- export const handleValidationAndStore = functions
69- . region ( 'us-central1' )
70- . https . onCall ( async ( data : HeartbeatData , context : functions . https . CallableContext ) => {
71- console . log ( 'Received data:' , data ) ;
72- const { publicKey, data : inputData , signature } = data ;
69+ export const handleValidationAndStore = onCall (
70+ { region : 'us-central1' } ,
71+ async ( request : CallableRequest < HeartbeatData > ) => {
72+ console . log ( 'Received data:' , request . data ) ;
73+ const data = request . data ;
74+ const { publicKey, data : inputData , signature } = data ;
75+
76+ if ( ! submitterAllowed ( publicKey ) ) {
77+ throw new functions . https . HttpsError (
78+ 'permission-denied' ,
79+ 'Public key not authorized' ,
80+ ) ;
81+ }
7382
74- if ( ! submitterAllowed ( publicKey ) ) {
75- throw new functions . https . HttpsError (
76- 'permission-denied' ,
77- 'Public key not authorized'
78- ) ;
79- }
83+ const rateLimitRef = admin . firestore ( ) . collection ( 'publicKeyRateLimits' ) . doc ( publicKey ) ;
8084
81- const rateLimitRef = admin . firestore ( ) . collection ( 'publicKeyRateLimits' ) . doc ( publicKey ) ;
82-
83- try {
84- await admin . firestore ( ) . runTransaction ( async ( transaction ) => {
85- const doc = await transaction . get ( rateLimitRef ) ;
86- const now = Date . now ( ) ;
87- const cutoff = now - HEARTBEAT_RATE_LIMIT_MS ;
88-
89- if ( doc . exists ) {
90- const lastCall = doc . data ( ) ?. [ 'lastCall' ] ;
91- if ( lastCall > cutoff ) {
92- throw new functions . https . HttpsError (
93- 'resource-exhausted' ,
94- 'Rate limit exceeded for this public key'
95- ) ;
96- }
97- }
98-
99- transaction . set ( rateLimitRef , { lastCall : now } , { merge : true } ) ;
100- } ) ;
101-
102- if ( ! validateSignature ( inputData , signature , publicKey ) ) {
103- throw new functions . https . HttpsError (
104- 'unauthenticated' ,
105- 'Signature validation failed'
106- ) ;
107- }
108-
109- await admin . firestore ( ) . collection ( 'heartbeat' ) . add ( data ) ;
110-
111- return { message : 'Data validated and stored successfully' } ;
112- } catch ( error ) {
113- console . error ( 'Error during data validation and storage:' , error ) ;
114- if ( error instanceof functions . https . HttpsError ) {
115- throw error ;
116- }
85+ try {
86+ await admin . firestore ( ) . runTransaction ( async ( transaction ) => {
87+ const doc = await transaction . get ( rateLimitRef ) ;
88+ const now = Date . now ( ) ;
89+ const cutoff = now - HEARTBEAT_RATE_LIMIT_MS ;
90+
91+ if ( doc . exists ) {
92+ const lastCall = doc . data ( ) ?. [ 'lastCall' ] ;
93+ if ( lastCall > cutoff ) {
11794 throw new functions . https . HttpsError (
118- 'internal ',
119- 'An error occurred during validation or storage'
95+ 'resource-exhausted ',
96+ 'Rate limit exceeded for this public key' ,
12097 ) ;
98+ }
12199 }
122- } ) ;
100+
101+ transaction . set ( rateLimitRef , { lastCall : now } , { merge : true } ) ;
102+ } ) ;
103+
104+ if ( ! validateSignature ( inputData , signature , publicKey ) ) {
105+ throw new functions . https . HttpsError (
106+ 'unauthenticated' ,
107+ 'Signature validation failed' ,
108+ ) ;
109+ }
110+
111+ await admin . firestore ( ) . collection ( 'heartbeat' ) . add ( data ) ;
112+
113+ return { message : 'Data validated and stored successfully' } ;
114+ } catch ( error ) {
115+ console . error ( 'Error during data validation and storage:' , error ) ;
116+ if ( error instanceof functions . https . HttpsError ) {
117+ throw error ;
118+ }
119+ throw new functions . https . HttpsError (
120+ 'internal' ,
121+ 'An error occurred during validation or storage' ,
122+ ) ;
123+ }
124+ } ,
125+ ) ;
123126
124127export { validateSignature } ;
0 commit comments