@@ -11,7 +11,7 @@ import { fetchLocalConfig } from "@toruslabs/fnd-base";
1111import { keccak256 } from "@toruslabs/metadata-helpers" ;
1212import { SessionManager } from "@toruslabs/session-manager" ;
1313import { Torus as TorusUtils , TorusKey } from "@toruslabs/torus.js" ;
14- import { Client , getDKLSCoeff , setupSockets } from "@toruslabs/tss-client" ;
14+ import { BatchSignParams , Client , getDKLSCoeff , setupSockets } from "@toruslabs/tss-client" ;
1515import type { WasmLib as DKLSWasmLib } from "@toruslabs/tss-dkls-lib" ;
1616import { sign as signEd25519 } from "@toruslabs/tss-frost-client" ;
1717import type { WasmLib as FrostWasmLib } from "@toruslabs/tss-frost-lib" ;
@@ -754,6 +754,22 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
754754 throw CoreKitError . default ( `sign not supported for key type ${ this . keyType } ` ) ;
755755 }
756756
757+ public async sign_batch ( data : Buffer [ ] , hashed : boolean [ ] = [ false ] , secp256k1Precompute ?: Secp256k1PrecomputedClient ) : Promise < Buffer [ ] > {
758+ // NOTE: Checks here must ensure a batch is only submitted to dkls, frost does not support batch signatures.
759+ if ( this . keyType === KeyType . secp256k1 ) {
760+ this . wasmLib = await this . loadTssWasm ( ) ;
761+ const sigs = await this . sign_ECDSA_secp256k1_batch ( data , hashed , secp256k1Precompute ) ;
762+ const results = [ ] ;
763+ for ( let i = 0 ; i < sigs . length ; i ++ ) {
764+ const sig = sigs [ i ] ;
765+ results . push ( Buffer . concat ( [ sig . r , sig . s , Buffer . from ( [ sig . v ] ) ] ) ) ;
766+ }
767+ return results ;
768+ } else if ( this . keyType === KeyType . ed25519 ) {
769+ throw CoreKitError . default ( `batch signing is only supported by dkls for key type ${ this . keyType } ` ) ;
770+ }
771+ }
772+
757773 // mutation function
758774 async deleteFactor ( factorPub : Point , factorKey ?: BNString ) : Promise < void > {
759775 if ( ! this . state . factorKey ) {
@@ -1355,8 +1371,7 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
13551371 const { r, s, recoveryParam } = await client . sign ( hashedData . toString ( "base64" ) , true , "" , "keccak256" , {
13561372 signatures,
13571373 } ) ;
1358- // skip await cleanup
1359- client . cleanup ( { signatures, server_coeffs : serverCoeffs } ) ;
1374+ await client . cleanup ( { signatures, server_coeffs : serverCoeffs } ) ;
13601375 return { v : recoveryParam , r : scalarBNToBufferSEC1 ( r ) , s : scalarBNToBufferSEC1 ( s ) } ;
13611376 } ;
13621377 if ( ! hashed ) {
@@ -1385,6 +1400,72 @@ export class Web3AuthMPCCoreKit implements ICoreKit {
13851400 }
13861401 }
13871402
1403+ private async sign_ECDSA_secp256k1_batch ( data : Buffer [ ] , hashed : boolean [ ] = [ false ] , precomputedTssClient ?: Secp256k1PrecomputedClient ) {
1404+ // TODO: See comments in this function, these are bugs that need fixing throughout by the original author.
1405+
1406+ if ( data . length <= 1 ) {
1407+ throw CoreKitError . default ( "Not a batch" ) ;
1408+ }
1409+
1410+ if ( data . length > 5 ) {
1411+ throw CoreKitError . default ( "Batch size too large" ) ;
1412+ }
1413+
1414+ if ( hashed . length !== data . length ) {
1415+ throw CoreKitError . default ( "Hashed size must be same as data size" ) ;
1416+ }
1417+
1418+ const batchSignParams : BatchSignParams [ ] = [ ] ;
1419+
1420+ for ( let i = 0 ; i < data . length ; i ++ ) {
1421+ let dataItem = data [ i ] ;
1422+ const hashedItem = hashed [ i ] ;
1423+ if ( ! hashedItem ) {
1424+ dataItem = keccak256 ( dataItem ) ;
1425+ }
1426+
1427+ batchSignParams . push ( {
1428+ msg : dataItem . toString ( "base64" ) , // This is the hashed or unhashed message, should be sent as supplied.
1429+ hash_only : true , // This should indicate IF the message is hashed or not, not that it needs hashing above.
1430+ original_message : "" , // This should be the unhashed message, should be sent only if message is hashed.
1431+ hash_algo : "keccak256" , // Constant, only supported value
1432+ } ) ;
1433+ }
1434+
1435+ const executeBatchSign = async ( client : Client , serverCoeffs : Record < string , string > , hashedData : BatchSignParams [ ] , signatures : string [ ] ) => {
1436+ const sigs = await client . batch_sign ( hashedData , { signatures } ) ;
1437+ await client . cleanup ( { signatures, server_coeffs : serverCoeffs } ) ; // server_coeffs are only needed in precompute, nowhere else.
1438+
1439+ const results = [ ] ;
1440+ for ( let i = 0 ; i < sigs . length ; i ++ ) {
1441+ const { r, s, recoveryParam } = sigs [ i ] ;
1442+ results . push ( { v : recoveryParam , r : scalarBNToBufferSEC1 ( r ) , s : scalarBNToBufferSEC1 ( s ) } ) ;
1443+ }
1444+ return results ;
1445+ } ;
1446+
1447+ const isAlreadyPrecomputed = precomputedTssClient ?. client && precomputedTssClient ?. serverCoeffs ;
1448+ const { client, serverCoeffs } = isAlreadyPrecomputed ? precomputedTssClient : await this . precompute_secp256k1 ( ) ;
1449+
1450+ const { signatures } = this ;
1451+ if ( ! signatures ) {
1452+ throw CoreKitError . signaturesNotPresent ( ) ;
1453+ }
1454+
1455+ try {
1456+ return await executeBatchSign ( client , serverCoeffs , batchSignParams , signatures ) ;
1457+ } catch ( error ) {
1458+ if ( ! isAlreadyPrecomputed ) {
1459+ throw error ;
1460+ }
1461+ // Retry with new client if precomputed client failed, this is to handle the case when precomputed session might have expired
1462+ const { client : newClient , serverCoeffs : newServerCoeffs } = await this . precompute_secp256k1 ( ) ;
1463+ const result = await executeBatchSign ( newClient , newServerCoeffs , batchSignParams , signatures ) ;
1464+
1465+ return result ;
1466+ }
1467+ }
1468+
13881469 private async sign_ed25519 ( data : Buffer , hashed : boolean = false ) : Promise < Buffer > {
13891470 if ( hashed ) {
13901471 throw CoreKitError . default ( "hashed data not supported for ed25519" ) ;
0 commit comments