Skip to content

Commit b032e79

Browse files
committed
feat(sdk-core): handle TSS EDDSA message signing
TICKET: COIN-4820
1 parent e612b96 commit b032e79

File tree

5 files changed

+230
-40
lines changed

5 files changed

+230
-40
lines changed

modules/bitgo/test/v2/unit/internal/tssUtils/common.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ExchangeCommitmentResponse, SignatureShareRecord } from '@bitgo/sdk-core';
1+
import { ExchangeCommitmentResponse, RequestType, SignatureShareRecord } from '@bitgo/sdk-core';
22
import * as nock from 'nock';
33
import * as _ from 'lodash';
44

@@ -19,10 +19,14 @@ export async function nockSendSignatureShare(
1919
signatureShare: unknown;
2020
signerShare?: string;
2121
tssType?: 'eddsa' | 'ecdsa';
22+
requestType?: RequestType;
23+
apiMode?: 'lite' | 'full';
2224
},
2325
status = 200
2426
): Promise<nock.Scope> {
25-
const transactions = getRoute(params.tssType);
27+
params.requestType = params.requestType || RequestType.tx;
28+
params.apiMode = params.apiMode || 'lite';
29+
const transactions = getRoute(params.tssType, params.requestType, params.apiMode);
2630
return nock('https://bitgo.fakeurl')
2731
.persist(true)
2832
.post(`/api/v2/wallet/${params.walletId}/txrequests/${params.txRequestId + transactions}/signatureshares`)
@@ -97,10 +101,22 @@ export async function nockExchangeCommitments(params: {
97101
.reply(200, params.response);
98102
}
99103

100-
export function getRoute(tssType: 'eddsa' | 'ecdsa' = 'eddsa'): string {
101-
if (tssType === 'ecdsa') {
102-
return '/transactions/0';
104+
export function getRoute(
105+
tssType: 'eddsa' | 'ecdsa' = 'eddsa',
106+
requestType: RequestType = RequestType.tx,
107+
apiMode: 'full' | 'lite' = 'lite'
108+
): string {
109+
switch (requestType) {
110+
case RequestType.tx:
111+
if (tssType === 'ecdsa' || apiMode === 'full') {
112+
return '/transactions/0';
113+
}
114+
break;
115+
case RequestType.message:
116+
if (tssType === 'ecdsa' || apiMode === 'full') {
117+
return '/messages/0';
118+
}
119+
break;
103120
}
104-
105121
return '';
106122
}

modules/bitgo/test/v2/unit/internal/tssUtils/eddsa.ts

Lines changed: 121 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,35 @@ import * as should from 'should';
66
import * as sinon from 'sinon';
77

88
import { TestableBG, TestBitGo } from '@bitgo/sdk-test';
9-
import { BitGo } from '../../../../../src/bitgo';
9+
import { BitGo } from '../../../../../src';
1010
import {
11+
BaseCoin,
12+
CommitmentShareRecord,
13+
CommitmentType,
1114
common,
15+
createSharedDataProof,
16+
Ed25519BIP32,
17+
Eddsa,
18+
EncryptedSignerShareType,
19+
ExchangeCommitmentResponse,
1220
Keychain,
21+
KeyShare,
1322
RequestTracer,
23+
RequestType,
1424
SignatureShareRecord,
1525
SignatureShareType,
1626
TssUtils,
1727
TxRequest,
1828
Wallet,
19-
Eddsa,
20-
KeyShare,
21-
Ed25519BIP32,
22-
createSharedDataProof,
23-
CommitmentShareRecord,
24-
CommitmentType,
25-
ExchangeCommitmentResponse,
26-
EncryptedSignerShareType,
27-
BaseCoin,
2829
} from '@bitgo/sdk-core';
2930
import { createWalletSignatures } from '../../tss/helpers';
3031
import {
31-
nockSendSignatureShare,
32-
nockGetTxRequest,
3332
nockCreateTxRequest,
3433
nockDeleteSignatureShare,
35-
nockSendTxRequest,
3634
nockExchangeCommitments,
35+
nockGetTxRequest,
36+
nockSendSignatureShare,
37+
nockSendTxRequest,
3738
} from './common';
3839

3940
openpgp.config.rejectCurves = new Set();
@@ -724,6 +725,113 @@ describe('TSS Utils:', async function () {
724725
});
725726
});
726727

728+
describe('signTxRequestForMessage:', function () {
729+
const txRequestId = 'randomid-abc';
730+
const messageRaw = 'hello world';
731+
const messageEncoded = Buffer.from(`${messageRaw}`).toString('hex');
732+
const bufferToSign = Buffer.from(messageEncoded, 'hex');
733+
const txRequest: TxRequest = {
734+
txRequestId,
735+
transactions: [],
736+
messages: [
737+
{
738+
state: 'pendingSignature',
739+
signatureShares: [],
740+
messageRaw,
741+
messageEncoded,
742+
derivationPath: 'm/0',
743+
},
744+
],
745+
unsignedTxs: [],
746+
date: new Date().toISOString(),
747+
intent: {
748+
intentType: 'payment',
749+
},
750+
latest: true,
751+
state: 'pendingUserSignature',
752+
walletType: 'hot',
753+
walletId: 'walletId',
754+
policiesChecked: true,
755+
version: 1,
756+
userId: 'userId',
757+
apiVersion: 'full',
758+
};
759+
760+
beforeEach(async function () {
761+
const rShare = validUserSignShare.rShares[3];
762+
const signatureShare: SignatureShareRecord = {
763+
from: SignatureShareType.USER,
764+
to: SignatureShareType.BITGO,
765+
share: rShare.r + rShare.R,
766+
};
767+
768+
await nockSendSignatureShare({
769+
walletId: wallet.id(),
770+
txRequestId: txRequest.txRequestId,
771+
signatureShare,
772+
requestType: RequestType.message,
773+
apiMode: 'full',
774+
});
775+
776+
const signatureShare2: SignatureShareRecord = {
777+
from: SignatureShareType.BITGO,
778+
to: SignatureShareType.USER,
779+
share: validBitgoToUserSignShare.rShares[1].r + validBitgoToUserSignShare.rShares[1].R,
780+
};
781+
txRequest.messages![0].signatureShares.push(signatureShare2);
782+
const response = { txRequests: [{ ...txRequest, apiVersion: 'full' }] };
783+
await nockGetTxRequest({ walletId: wallet.id(), txRequestId: txRequest.txRequestId, response });
784+
785+
const bitgoToUserCommitmentShare: CommitmentShareRecord = {
786+
from: SignatureShareType.BITGO,
787+
to: SignatureShareType.USER,
788+
type: CommitmentType.COMMITMENT,
789+
share: validBitgoToUserSignShare.rShares[1].commitment,
790+
};
791+
const exchangeCommitResponse: ExchangeCommitmentResponse = { commitmentShare: bitgoToUserCommitmentShare };
792+
await nockExchangeCommitments({
793+
walletId: wallet.id(),
794+
txRequestId: txRequest.txRequestId,
795+
response: exchangeCommitResponse,
796+
apiMode: 'full',
797+
});
798+
});
799+
800+
afterEach(async function () {
801+
txRequest.messages![0].signatureShares = [];
802+
});
803+
804+
it('signTxRequest should succeed with txRequest object as input', async function () {
805+
const signedTxRequest = await tssUtils.signTxRequestForMessage({
806+
messageRaw,
807+
bufferToSign,
808+
txRequest,
809+
prv: JSON.stringify(validUserSigningMaterial),
810+
reqId,
811+
});
812+
signedTxRequest.messages!.should.deepEqual(txRequest.messages);
813+
814+
sandbox.verifyAndRestore();
815+
});
816+
817+
it('signTxRequest should succeed with txRequest id as input', async function () {
818+
const getTxRequest = sandbox.stub(tssUtils, 'getTxRequest');
819+
getTxRequest.resolves(txRequest);
820+
getTxRequest.calledWith(txRequestId);
821+
822+
const signedTxRequest = await tssUtils.signTxRequestForMessage({
823+
txRequest: txRequestId,
824+
prv: JSON.stringify(validUserSigningMaterial),
825+
reqId,
826+
messageRaw,
827+
bufferToSign,
828+
});
829+
signedTxRequest.messages!.should.deepEqual(txRequest.messages);
830+
831+
sandbox.verifyAndRestore();
832+
});
833+
});
834+
727835
describe('prebuildTxWithIntent:', async function () {
728836
it('should build single recipient tx', async function () {
729837
const nockedCreateTx = await nockCreateTxRequest({

modules/sdk-core/src/bitgo/tss/eddsa/eddsa.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ export async function createUserSignShare(signablePayload: Buffer, pShare: PShar
130130
* @param {SignShare} userSignShare - the User Sign Share
131131
* @param {SignatureShareRecord} bitgoToUserRShare - the Bitgo to User RShare
132132
* @param {YShare} backupToUserYShare - the backup key Y share received during wallet creation
133+
* @param {YShare} bitgoToUserYShare - the Bitgo to User YShare
133134
* @param {Buffer} signablePayload - the signable payload from a tx
134135
* @param {CommitmentShareRecord} [bitgoToUserCommitment] - the Bitgo to User Commitment
135136
* @returns {Promise<GShare>} - the User to Bitgo GShare
@@ -193,8 +194,10 @@ export async function createUserToBitGoGShare(
193194
* @param {String} txRequestId - the txRequest Id
194195
* @param {SignShare} userSignShare - the user Sign Share
195196
* @param {String} encryptedSignerShare - signer share encrypted to bitgo key
197+
* @param {String} apiMode - the api mode, defaults to 'lite'
196198
* @returns {Promise<void>}
197199
* @param {IRequestTracer} reqId - the request tracer request id
200+
* @param {RequestType} requestType - the request type, defaults to RequestType.tx
198201
*/
199202
export async function offerUserToBitgoRShare(
200203
bitgo: BitGoBase,
@@ -203,7 +206,8 @@ export async function offerUserToBitgoRShare(
203206
userSignShare: SignShare,
204207
encryptedSignerShare: string,
205208
apiMode: 'full' | 'lite' = 'lite',
206-
reqId?: IRequestTracer
209+
reqId?: IRequestTracer,
210+
requestType: RequestType = RequestType.tx
207211
): Promise<void> {
208212
const rShare: RShare = userSignShare.rShares[ShareKeyPosition.BITGO];
209213
if (_.isNil(rShare)) {
@@ -218,13 +222,12 @@ export async function offerUserToBitgoRShare(
218222
share: rShare.r + rShare.R,
219223
};
220224

221-
// TODO (BG-57944): implement message signing for EDDSA
222225
await sendSignatureShare(
223226
bitgo,
224227
walletId,
225228
txRequestId,
226229
signatureShare,
227-
RequestType.tx,
230+
requestType,
228231
encryptedSignerShare,
229232
'eddsa',
230233
apiMode,
@@ -240,19 +243,26 @@ export async function offerUserToBitgoRShare(
240243
* @param {String} walletId - the wallet id
241244
* @param {String} txRequestId - the txRequest Id
242245
* @param {IRequestTracer} reqId - the request tracer request id
246+
* @param {RequestType} requestType - the request type, defaults to RequestType.tx
243247
* @returns {Promise<SignatureShareRecord>} - a Signature Share
244248
*/
245249
export async function getBitgoToUserRShare(
246250
bitgo: BitGoBase,
247251
walletId: string,
248252
txRequestId: string,
249-
reqId?: IRequestTracer
253+
reqId?: IRequestTracer,
254+
requestType: RequestType = RequestType.tx
250255
): Promise<SignatureShareRecord> {
251256
const txRequest = await getTxRequest(bitgo, walletId, txRequestId, reqId);
252257
let signatureShares;
253258
if (txRequest.apiVersion === 'full') {
254-
assert(txRequest.transactions, 'transactions required as part of txRequest');
255-
signatureShares = txRequest.transactions[0].signatureShares;
259+
if (requestType === RequestType.tx) {
260+
assert(txRequest.transactions, 'transactions required as part of txRequest');
261+
signatureShares = txRequest.transactions[0].signatureShares;
262+
} else if (requestType === RequestType.message) {
263+
assert(txRequest.messages, 'messages required as part of txRequest');
264+
signatureShares = txRequest.messages[0].signatureShares;
265+
}
256266
} else {
257267
signatureShares = txRequest.signatureShares;
258268
}
@@ -276,7 +286,9 @@ export async function getBitgoToUserRShare(
276286
* @param {String} walletId - the wallet id
277287
* @param {String} txRequestId - the txRequest Id
278288
* @param {GShare} userToBitgoGShare - the User to Bitgo GShare
289+
* @param {String} apiMode - the api mode, defaults to 'lite'
279290
* @param {IRequestTracer} reqId - the request tracer request id
291+
* @param {RequestType} requestType - the request type, defaults to RequestType.tx
280292
* @returns {Promise<void>}
281293
*/
282294
export async function sendUserToBitgoGShare(
@@ -285,7 +297,8 @@ export async function sendUserToBitgoGShare(
285297
txRequestId: string,
286298
userToBitgoGShare: GShare,
287299
apiMode: 'full' | 'lite' = 'lite',
288-
reqId?: IRequestTracer
300+
reqId?: IRequestTracer,
301+
requestType: RequestType = RequestType.tx
289302
): Promise<void> {
290303
if (userToBitgoGShare.i !== ShareKeyPosition.USER) {
291304
throw new Error('Invalid GShare, doesnt belong to the User');
@@ -296,13 +309,12 @@ export async function sendUserToBitgoGShare(
296309
share: userToBitgoGShare.R + userToBitgoGShare.gamma,
297310
};
298311

299-
// TODO (BG-57944): implement message signing for EDDSA
300312
await sendSignatureShare(
301313
bitgo,
302314
walletId,
303315
txRequestId,
304316
signatureShare,
305-
RequestType.tx,
317+
requestType,
306318
undefined,
307319
'eddsa',
308320
apiMode,

modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,9 +355,12 @@ export type TxRequest = {
355355
signatureShares: SignatureShareRecord[];
356356
messageRaw: string;
357357
messageEncoded?: string;
358+
messageBroadcastable?: string;
359+
messageStandardType?: MessageStandardType;
358360
derivationPath: string;
359361
combineSigShare?: string;
360362
txHash?: string;
363+
commitmentShares?: CommitmentShareRecord[];
361364
}[];
362365
apiVersion?: TxRequestVersion;
363366
latest: boolean;

0 commit comments

Comments
 (0)