Skip to content
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
15 changes: 15 additions & 0 deletions packages/bitcore-wallet-client/src/lib/tsssign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export class TssSign extends EventEmitter {
#credentials: Credentials;
#subscriptionId: ReturnType<typeof setInterval>;
#subscriptionRunning: boolean;
#emittedParticipants: Set<string>;
id: string;


Expand Down Expand Up @@ -136,6 +137,8 @@ export class TssSign extends EventEmitter {
const msg = await this.#sign.initJoin();
const m = this.#tssKey.metadata.m;
await this.#request.post('/v1/tss/sign/' + this.id, { message: msg, m });
this.#emittedParticipants = new Set([this.#credentials.copayerId]);
this.emit('copayerReady', this.#credentials.copayerId);
return this;
}

Expand Down Expand Up @@ -180,6 +183,7 @@ export class TssSign extends EventEmitter {
* - `signature` => ISignature: The signature is ready. Emits the signature object
* - `complete` => void: The signature generation process is complete
* - `error` => Error: An error occurred during the process. Emits the error. Note that this will not stop the subscription.
* - `copayerReady` => string: A copayer has joined the session. Emits the copayer ID.
* @returns {NodeJS.Timeout} Subscription ID
*/
subscribe(params: {
Expand All @@ -203,6 +207,16 @@ export class TssSign extends EventEmitter {
const prevRound = thisRound - 1; // Get previous round's messages
const { body } = await this.#request.get(`/v1/tss/sign/${this.id}/${prevRound}`) as RequestResponse;

if (body.participants?.length) {
for (const copayerId of body.participants as string[]) {
if (!this.#emittedParticipants?.has(copayerId)) {
this.#emittedParticipants ??= new Set();
this.#emittedParticipants.add(copayerId);
this.emit('copayerReady', copayerId);
}
}
}

const hasEveryoneSubmitted = body.messages?.length === this.#tssKey.metadata.m - 1; // subtract yourself
if (hasEveryoneSubmitted && !this.#sign.isSignatureReady()) {
this.emit('roundready', thisRound);
Expand Down Expand Up @@ -270,6 +284,7 @@ export class TssSign extends EventEmitter {
}
this.#subscriptionId = null;
this.#subscriptionRunning = false;
this.#emittedParticipants = null;
}

/**
Expand Down
27 changes: 22 additions & 5 deletions packages/bitcore-wallet-client/test/tss.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,22 @@ describe('TSS', function() {
sig0.id.should.equal(sig1.id);
});

it(happyPath('should emit copayerReady for self on start() and for remote on subscribe()'), async function() {
const copayerReadyIds: string[] = [];
const sigA = new TssSign({ baseUrl: '/bws/api', request: request(app), credentials: party0Creds, tssKey: party0TssKey });
const sigB = new TssSign({ baseUrl: '/bws/api', request: request(app), credentials: party1Creds, tssKey: party1TssKey });
sigA.on('copayerReady', (id) => copayerReadyIds.push(id));
await sigA.start({ id: 'copayer-ready-test', messageHash, derivationPath });
copayerReadyIds.should.deep.equal([party0Creds.copayerId]);
await sigB.start({ id: 'copayer-ready-test', messageHash, derivationPath });
sigA.on('error', (e) => { should.not.exist(e?.message ?? e); });
const roundsubmitted = new Promise(r => sigA.once('roundsubmitted', r));
sigA.subscribe({ timeout: 10, iterHandler: () => sigA.unsubscribe() });
await roundsubmitted;
copayerReadyIds.should.include(party0Creds.copayerId);
copayerReadyIds.should.include(party1Creds.copayerId);
});

it('should reject too many participants', async function() {
const sig2 = new TssSign({
baseUrl: '/bws/api',
Expand Down Expand Up @@ -715,11 +731,12 @@ describe('TSS', function() {
const error = new Promise<Error>(r => sig0.on('error', r));
sig0.subscribe({ timeout: 10, iterHandler: () => sig0.unsubscribe() });
const e = await error;
emitSpy.callCount.should.equal(3);
emitSpy.args[0][0].should.equal('roundready');
emitSpy.args[1][0].should.equal('roundprocessed');
emitSpy.args[2][0].should.equal('error');
emitSpy.args[2][1].should.equal(e);
emitSpy.callCount.should.equal(4);
emitSpy.args[0][0].should.equal('copayerReady');
emitSpy.args[1][0].should.equal('roundready');
emitSpy.args[2][0].should.equal('roundprocessed');
emitSpy.args[3][0].should.equal('error');
emitSpy.args[3][1].should.equal(e);
e.message.should.include('TSS_ROUND_ALREADY_DONE');
});

Expand Down
4 changes: 2 additions & 2 deletions packages/bitcore-wallet-service/src/lib/routes/tss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,8 @@ export class TssRouter {
try {
const { id, round } = req.params as { [key: string]: string };
const copayerId = req.headers['x-identity'];
const { messages, signature } = await TssSign.getMessagesForParty({ id, round: parseInt(round), copayerId });
return res.json({ messages, signature });
const { messages, signature, participants } = await TssSign.getMessagesForParty({ id, round: parseInt(round), copayerId });
return res.json({ messages, signature, participants });
} catch (err) {
return returnError(err ?? 'unknown', res, req);
}
Expand Down
13 changes: 9 additions & 4 deletions packages/bitcore-wallet-service/src/lib/tss.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,9 @@ class TssKeyGenClass {
export const TssKeyGen = new TssKeyGenClass();

class TssSignClass {
async getMessagesForParty(params: { id: string; round: number; copayerId: string }): Promise<{ messages?: ITssSigMessageObject[]; signature?: ITssSigMessageObject['signature'] }> {
async getMessagesForParty(params: { id: string; round: number; copayerId: string }): Promise<{ messages?: ITssSigMessageObject[]; signature?: ITssSigMessageObject['signature']; participants?: string[] }> {
const { id, round, copayerId } = params;

const storage = WalletService.getStorage();
const session = await storage.fetchTssSigSession({ id });
if (!session) {
Expand All @@ -297,14 +297,19 @@ class TssSignClass {
}

const otherPartyMsgs = session.rounds[round].filter(m => m.fromPartyId != party.partyId);
const participants = otherPartyMsgs.map(m => {
const p = session.participants.find(p => p.partyId === m.fromPartyId);
return p?.copayerId;
}).filter(Boolean) as string[];

if (otherPartyMsgs.length === session.m - 1) {
const messages = otherPartyMsgs.map(m => m.messages);
for (const m of messages) {
m.p2pMessages = m.p2pMessages.filter(m => m.to == party.partyId);
}
return { messages, signature: session.signature };
return { messages, signature: session.signature, participants };
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you wanting the participants to be returned to the client in real time? Currently, this logic is only hit once all participants have joined (per line 300: if (otherPartyMsgs.length === session.m - 1)). If you want it to be real time, we should move this new logic to be between lines 299-300 and then return participants in the returned object on line 311 (after the if block) as well.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we change it as noted above, then the client will just need to be aware that the copayerReady event will fire duplicates.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, yes thanks. I moved that logic outside of the if. I also added an emittedParticipants array to avoid duplicates on the client side. In any case, this shouldn’t cause any issues in the app, since it’s just used to show a checkmark next to the copayer’s name in the UI.

}
return {};
return { participants };
}

async processMessage(params: { id: string; message: ITssSigMessageObject; m?: string | number; copayerId: string }) {
Expand Down