Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revert batch signing #79

Merged
merged 3 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 1 addition & 101 deletions packages/tss-client/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { keccak256 } from "ethereum-cryptography/keccak";
import type { Socket } from "socket.io-client";

import { DELIMITERS, WEB3_SESSION_HEADER_KEY } from "./constants";
import { BatchSignParams, Msg } from "./interfaces";
import { Msg } from "./interfaces";
import { getEc } from "./utils";

const MSG_READ_TIMEOUT = 10_000;
Expand Down Expand Up @@ -260,106 +260,6 @@ export class Client {
});
}

async batch_sign(batch: BatchSignParams[], additionalParams?: Record<string, unknown>): Promise<{ r: BN; s: BN; recoveryParam: number }[]> {
if (batch.length <= 1) {
throw new Error("Not a batch");
}

if (batch.length > 5) {
throw new Error("Batch size is too large");
}

if (this._consumed === true) {
throw new Error("This instance has already signed a message and cannot be reused");
}

if (this._ready === false) {
throw new Error("client is not ready");
}

// check message hashing
for (let i = 0; i < batch.length; i++) {
if (!batch[i].hash_only) {
if (batch[i].hash_algo === "keccak256") {
if (Buffer.from(keccak256(Buffer.from(batch[i].original_message))).toString("base64") !== batch[i].msg) {
throw new Error("hash of original message does not match msg");
}
} else {
throw new Error(`hash algo ${batch[i].hash_algo} not supported`);
}
}
}

this._startSignTime = Date.now();
const sigFragments: Map<number, string[]> = new Map(); // message index, fragment
for (let i = 0; i < batch.length; i++) {
sigFragments.set(i, []);
}

for (let i = 0; i < this.parties.length; i++) {
const party = i;
if (party === this.index) {
// create signature fragment for this client
for (let m = 0; m < batch.length; m++) {
const currentFragments = sigFragments.get(m);
const fragment = await this.tssLib.local_sign(batch[m].msg, batch[m].hash_only, this.precomputed_value);
currentFragments.push(fragment);
sigFragments.set(m, currentFragments);
}
} else {
// collect signature fragment from all peers
const endpoint = this.lookupEndpoint(this.session, party);
const response = await fetch(`${endpoint}/batch_sign`, {
method: "POST",
headers: {
"Content-Type": "application/json",
[WEB3_SESSION_HEADER_KEY]: this.sid,
},
body: JSON.stringify({
session: this.session,
sender: this.index,
recipient: party,
batch,
...additionalParams,
}),
}).then((res) => res.json());

response.sigs.forEach((res: string, m: number) => {
const currentFragments = sigFragments.get(m);
currentFragments.push(res);
sigFragments.set(m, currentFragments);
});
}
}

const R = await this.tssLib.get_r_from_precompute(this.precomputed_value);
const result: { r: BN; s: BN; recoveryParam: number }[] = [];

for (let m = 0; m < batch.length; m++) {
const sig = await this.tssLib.local_verify(batch[m].msg, batch[m].hash_only, R, sigFragments.get(m), this.pubKey);

const sigHex = Buffer.from(sig, "base64").toString("hex");
const r = new BN(sigHex.slice(0, 64), 16);
let s = new BN(sigHex.slice(64), 16);
let recoveryParam = Buffer.from(R, "base64")[63] % 2;
if (this._sLessThanHalf) {
const ec = getEc();
const halfOfSecp256k1n = ec.n.div(new BN(2));
if (s.gt(halfOfSecp256k1n)) {
s = ec.n.sub(s);
recoveryParam = (recoveryParam + 1) % 2;
}
}
result.push({ r, s, recoveryParam });
}
this._endSignTime = Date.now();

this._consumed = true;
this._ready = false;
this._readyResolve = null;
return result;
}

async sign(
msg: string,
hash_only: boolean,
Expand Down
7 changes: 0 additions & 7 deletions packages/tss-client/src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,6 @@ export interface Msg {
msg_data: string;
}

export interface BatchSignParams {
msg: string;
hash_only: boolean;
original_message: string;
hash_algo: string;
}

export type PointHex = {
x: string;
y: string;
Expand Down
87 changes: 1 addition & 86 deletions packages/web-example/src/local.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-shadow */
/* eslint-disable no-console */
import { privateToAddress } from "@ethereumjs/util";
import { BatchSignParams, Client } from "@toruslabs/tss-client";
import { Client } from "@toruslabs/tss-client";
import { load as loadLib } from "@toruslabs/tss-dkls-lib";
import BN from "bn.js";
import eccrypto, { generatePrivate } from "eccrypto";
Expand Down Expand Up @@ -237,96 +237,11 @@ const runPreNonceTest = async () => {
client.log("client cleaned up");
};

const runBatchTest = async () => {
// this identifier is only required for testing,
// so that clients cannot override shares of actual users incase
// share route is exposed in production, which is exposed only in development/testing
// by default.
const testingRouteIdentifier = "testingShares";
const randomNonce = keccak256(generatePrivate().toString("hex") + Date.now());
const vid = `test_verifier_name${DELIMITERS.Delimiter1}test_verifier_id`;
const session = `${testingRouteIdentifier}${vid}${DELIMITERS.Delimiter2}default${DELIMITERS.Delimiter3}0${
DELIMITERS.Delimiter4
}${randomNonce.toString("hex")}${testingRouteIdentifier}`;

// generate mock signatures.
const signatures = getSignatures();

// const session = `test:${Date.now()}`;

const parties = 4;
const clientIndex = parties - 1;

// generate endpoints for servers
const { endpoints, tssWSEndpoints, partyIndexes } = generateEndpoints(parties, clientIndex);

// setup mock shares, sockets and tss wasm files.
const [{ pubKey, privKey }, sockets] = await Promise.all([setupMockShares(endpoints, partyIndexes, session), setupSockets(tssWSEndpoints)]);

const serverCoeffs: Record<number, string> = {};
const participatingServerDKGIndexes = [1, 2, 3];

for (let i = 0; i < participatingServerDKGIndexes.length; i++) {
const serverIndex = participatingServerDKGIndexes[i];
serverCoeffs[serverIndex] = new BN(1).toString("hex");
}
// get the shares.
const share = await localStorageDB.get(`session-${session}:share`);

// Load WASM lib.
const tssLib = await loadLib();

// initialize client.
const client = new Client(session, clientIndex, partyIndexes, endpoints, sockets, share, pubKey, true, tssLib);
client.log = log;
// initiate precompute
client.precompute({ signatures, server_coeffs: serverCoeffs, _transport: 1 });
await client.ready();

const messages: BatchSignParams[] = [];
messages.push({
msg: msgHash.toString("base64"),
hash_only: true,
original_message: msg,
hash_algo: "keccak256",
});
messages.push({
msg: msgHash.toString("base64"),
hash_only: true,
original_message: msg,
hash_algo: "keccak256",
});
// initiate signature.
const sigs = await client.batch_sign(messages, { signatures });

for (let i = 0; i < sigs.length; i++) {
const signature = sigs[i];
const message = messages[i];
const msgHash = message.msg;
const buffer = Buffer.from(msgHash, "base64");
const hexToDecimal = (x: Buffer) => ec.keyFromPrivate(x).getPrivate().toString(10);
const pubk = ec.recoverPubKey(hexToDecimal(buffer), signature, signature.recoveryParam, "hex");

client.log(`pubkey, ${JSON.stringify(pubKey)}`);
client.log(`msgHash: 0x${buffer.toString("hex")}`);
client.log(`signature: 0x${signature.r.toString(16, 64)}${signature.s.toString(16, 64)}${new BN(27 + signature.recoveryParam).toString(16)}`);
client.log(`address: 0x${Buffer.from(privateToAddress(privKey.toArrayLike(Buffer, "be", 32))).toString("hex")}`);
const passed = ec.verify(buffer, signature, pubk);

client.log(`passed: ${passed}`);
}
client.log(`precompute time: ${client._endPrecomputeTime - client._startPrecomputeTime}`);
client.log(`signing time: ${client._endSignTime - client._startSignTime}`);
await client.cleanup({ signatures });
client.log("client cleaned up");
};

export const runLocalServerTest = async () => {
try {
// for (let i = 0; i < 20; i++) {
await runPreNonceTest();
await runPostNonceTest();
await runBatchTest();
// }
console.log("test succeeded");
document.title = "Test succeeded";
Expand Down