Skip to content

Commit 3cfdbbf

Browse files
committed
feat: add batch signing support for dkls
1 parent 78457af commit 3cfdbbf

File tree

4 files changed

+181
-73
lines changed

4 files changed

+181
-73
lines changed

demo/redirect-flow-example/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 95 additions & 68 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
"@toruslabs/session-manager": "^3.1.0",
5858
"@toruslabs/openlogin-utils": "^8.2.1",
5959
"@toruslabs/torus.js": "^15.1.0",
60-
"@toruslabs/tss-client": "^3.1.0",
60+
"@toruslabs/tss-client": "^3.3.0-alpha.0",
6161
"@toruslabs/tss-frost-client": "0.3.1",
6262
"@toruslabs/tss-frost-common": "^1.0.1",
6363
"bn.js": "^5.2.1",

src/mpcCoreKit.ts

Lines changed: 84 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { fetchLocalConfig } from "@toruslabs/fnd-base";
1111
import { keccak256 } from "@toruslabs/metadata-helpers";
1212
import { SessionManager } from "@toruslabs/session-manager";
1313
import { 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";
1515
import type { WasmLib as DKLSWasmLib } from "@toruslabs/tss-dkls-lib";
1616
import { sign as signEd25519 } from "@toruslabs/tss-frost-client";
1717
import 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

Comments
 (0)