Skip to content

Commit ddea4c9

Browse files
committed
fix(sdk-core): use async decrypt and getUserPrv at v2 call sites
- fix getUserPrv -> getUserPrvAsync in EVM hop and inscription builder - fix decryptKeychainPrivateKey -> decryptKeychainPrivateKeyAsync in verifyKey - add EdDSA v2 signing dispatch tests - fix validateAdata test to use 3-arg domain separator signature WCN-32
1 parent f27db52 commit ddea4c9

8 files changed

Lines changed: 95 additions & 29 deletions

File tree

modules/abstract-eth/src/abstractEthLikeNewCoins.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2554,7 +2554,7 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
25542554
const walletPassphrase = buildParams.walletPassphrase;
25552555

25562556
const userKeychain = await this.keychains().get({ id: wallet.keyIds()[0] });
2557-
const userPrv = await wallet.getUserPrv({ keychain: userKeychain, walletPassphrase });
2557+
const userPrv = await wallet.getUserPrvAsync({ keychain: userKeychain, walletPassphrase });
25582558
const userPrvBuffer = bip32.fromBase58(userPrv).privateKey;
25592559
if (!userPrvBuffer) {
25602560
throw new Error('invalid userPrv');

modules/abstract-utxo/src/impl/btc/inscriptionBuilder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ export class InscriptionBuilder implements IInscriptionBuilder {
262262
inscriptionData: Buffer
263263
): Promise<SubmitTransactionResponse> {
264264
const userKeychain = await this.wallet.baseCoin.keychains().get({ id: this.wallet.keyIds()[KeyIndices.USER] });
265-
const xprv = await this.wallet.getUserPrv({ keychain: userKeychain, walletPassphrase });
265+
const xprv = await this.wallet.getUserPrvAsync({ keychain: userKeychain, walletPassphrase });
266266

267267
const halfSignedCommitTransaction = (await this.wallet.signTransaction({
268268
prv: xprv,
@@ -302,7 +302,7 @@ export class InscriptionBuilder implements IInscriptionBuilder {
302302
txPrebuild: PrebuildTransactionResult
303303
): Promise<SubmitTransactionResponse> {
304304
const userKeychain = await this.wallet.baseCoin.keychains().get({ id: this.wallet.keyIds()[KeyIndices.USER] });
305-
const prv = await this.wallet.getUserPrv({ keychain: userKeychain, walletPassphrase });
305+
const prv = await this.wallet.getUserPrvAsync({ keychain: userKeychain, walletPassphrase });
306306

307307
const halfSigned = (await this.wallet.signTransaction({ prv, txPrebuild })) as HalfSignedUtxoTransaction;
308308
return this.wallet.submitTransaction({ halfSigned });

modules/abstract-utxo/src/verifyKey.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import assert from 'assert';
77

88
import buildDebug from 'debug';
99
import { BIP32, message } from '@bitgo/wasm-utxo';
10-
import { BitGoBase, decryptKeychainPrivateKey, KeyIndices } from '@bitgo/sdk-core';
10+
import { BitGoBase, decryptKeychainPrivateKeyAsync, KeyIndices } from '@bitgo/sdk-core';
1111

1212
import { VerifyKeySignaturesOptions, VerifyUserPublicKeyOptions } from './abstractUtxoCoin';
1313
import { ParsedTransaction } from './transaction/types';
@@ -94,7 +94,7 @@ export async function verifyUserPublicKey(bitgo: BitGoBase, params: VerifyUserPu
9494

9595
let userPrv = userKeychain.prv;
9696
if (!userPrv && txParams.walletPassphrase) {
97-
userPrv = await decryptKeychainPrivateKey(bitgo, userKeychain, txParams.walletPassphrase);
97+
userPrv = await decryptKeychainPrivateKeyAsync(bitgo, userKeychain, txParams.walletPassphrase);
9898
}
9999

100100
if (!userPrv) {

modules/bitgo/test/v2/unit/internal/tssUtils/ecdsaMPCv2/signTxRequest.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -269,34 +269,31 @@ describe('signTxRequest:', function () {
269269
const userShare = fs.readFileSync(shareFiles[vector.party1]);
270270
const userPrvBase64 = Buffer.from(userShare).toString('base64');
271271

272-
// Encrypt the prv with v2 to trigger the v2 path
273272
const encryptedPrv = await bitgo.encryptAsync({
274273
input: userPrvBase64,
275274
password: walletPassphrase,
276275
encryptionVersion: 2,
277276
});
278277
JSON.parse(encryptedPrv).v.should.equal(2);
279278

280-
// Round 1: encrypt session + GPG key with v2 + adata (purely local, no server call)
281279
const round1Result = await tssUtils.createOfflineRound1Share({
282280
txRequest,
283281
prv: userPrvBase64,
284282
walletPassphrase,
285283
encryptedPrv,
286284
});
287285

288-
// Verify round 1 output has v2 envelopes with adata
289286
const r1SessionEnvelope = JSON.parse(round1Result.encryptedRound1Session);
290287
r1SessionEnvelope.v.should.equal(2);
291288
r1SessionEnvelope.should.have.property('adata');
292289
r1SessionEnvelope.should.have.property('hkdfSalt');
290+
r1SessionEnvelope.adata.should.containEql('DKLS23_SIGNING_ROUND1_STATE');
293291

294292
const r1GpgEnvelope = JSON.parse(round1Result.encryptedUserGpgPrvKey);
295293
r1GpgEnvelope.v.should.equal(2);
296294
r1GpgEnvelope.should.have.property('adata');
297-
r1SessionEnvelope.adata.should.equal(r1GpgEnvelope.adata);
295+
r1GpgEnvelope.adata.should.containEql('DKLS23_SIGNING_USER_GPG_KEY');
298296

299-
// Nock BitGo round 1 response and submit
300297
await nockTxRequestResponseSignatureShareRoundOne(bitgoParty, txRequest, bitgoGpgKey);
301298
const transactions = getRoute('ecdsa');
302299
const round1TxRequestResponse = await bitgo
@@ -307,7 +304,6 @@ describe('signTxRequest:', function () {
307304
})
308305
.result();
309306

310-
// Merge server response with original txRequest (server only returns signatureShares)
311307
const round1TxReq: TxRequest = {
312308
...txRequest,
313309
transactions: [
@@ -318,7 +314,6 @@ describe('signTxRequest:', function () {
318314
],
319315
};
320316

321-
// Round 2: decrypt v2 round 1 session (validates adata), encrypt round 2 session
322317
const round2Result = await tssUtils.createOfflineRound2Share({
323318
txRequest: round1TxReq,
324319
prv: userPrvBase64,
@@ -328,13 +323,11 @@ describe('signTxRequest:', function () {
328323
encryptedRound1Session: round1Result.encryptedRound1Session,
329324
});
330325

331-
// Verify round 2 output has v2 envelope with adata
332326
const r2Envelope = JSON.parse(round2Result.encryptedRound2Session);
333327
r2Envelope.v.should.equal(2);
334328
r2Envelope.should.have.property('adata');
335-
r2Envelope.adata.should.equal(r1SessionEnvelope.adata);
329+
r2Envelope.adata.should.containEql('DKLS23_SIGNING_ROUND2_STATE');
336330

337-
// Nock BitGo round 2 response and submit
338331
await nockTxRequestResponseSignatureShareRoundTwo(bitgoParty, txRequest, bitgoGpgKey);
339332
const round2TxRequestResponse = await bitgo
340333
.post(bitgo.url(`/wallet/${txRequest.walletId}/txrequests/${txRequest.txRequestId + transactions}/sign`, 2))
@@ -354,7 +347,6 @@ describe('signTxRequest:', function () {
354347
],
355348
};
356349

357-
// Round 3: decrypt v2 round 2 session (validates adata), produce final signature share
358350
const round3Result = await tssUtils.createOfflineRound3Share({
359351
txRequest: round2TxReq,
360352
prv: userPrvBase64,
@@ -367,15 +359,29 @@ describe('signTxRequest:', function () {
367359
round3Result.should.have.property('signatureShareRound3');
368360
});
369361

362+
it('validateAdata accepts v2 envelopes with matching adata and domain separator', async function () {
363+
const adata = 'txhash:m/0/1';
364+
const domainSep = 'DKLS23_SIGNING_ROUND1_STATE';
365+
const ct = await bitgo.encryptAsync({
366+
input: 'test-data',
367+
password: 'testpass',
368+
encryptionVersion: 2,
369+
adata: `${domainSep}:${adata}`,
370+
});
371+
372+
(tssUtils as any).validateAdata(adata, ct, domainSep);
373+
});
374+
370375
it('validateAdata rejects v2 envelopes with mismatched adata', async function () {
376+
const domainSep = 'DKLS23_SIGNING_ROUND1_STATE';
371377
const ct = await bitgo.encryptAsync({
372378
input: 'test-data',
373379
password: 'testpass',
374380
encryptionVersion: 2,
375-
adata: 'context-A',
381+
adata: `${domainSep}:context-A`,
376382
});
377383

378-
(() => (tssUtils as any).validateAdata('context-B', ct)).should.throw(/Adata does not match/);
384+
(() => (tssUtils as any).validateAdata('context-B', ct, domainSep)).should.throw(/Adata does not match/);
379385
});
380386
});
381387

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,64 @@ describe('TSS Utils:', async function () {
592592
});
593593
});
594594

595+
describe('v2 encryption (EdDSA signing dispatch)', function () {
596+
const signingTxRequest: TxRequest = {
597+
txRequestId: 'v2-signing-test',
598+
unsignedTxs: [
599+
{
600+
serializedTxHex: 'test-payload',
601+
signableHex: 'deadbeef',
602+
derivationPath: 'm/0',
603+
},
604+
],
605+
date: new Date().toISOString(),
606+
intent: { intentType: 'payment' },
607+
latest: true,
608+
state: 'pendingUserSignature',
609+
walletType: 'hot',
610+
walletId: 'walletId',
611+
policiesChecked: true,
612+
version: 1,
613+
userId: 'userId',
614+
};
615+
616+
it('v2 R-share round-trip: encrypt via commitment, verify envelope, decrypt via createRShare', async function () {
617+
const passphrase = 'test-passphrase';
618+
const prv = JSON.stringify(validUserSigningMaterial);
619+
620+
// 1. Encrypt prv with v2
621+
const encryptedPrv = await bitgo.encryptAsync({
622+
input: prv,
623+
password: passphrase,
624+
encryptionVersion: 2,
625+
});
626+
JSON.parse(encryptedPrv).v.should.equal(2);
627+
628+
// 2. Create commitment -- R-share should be a v2 envelope
629+
const commitResult = await tssUtils.createCommitmentShareFromTxRequest({
630+
txRequest: signingTxRequest,
631+
prv,
632+
walletPassphrase: passphrase,
633+
bitgoGpgPubKey: bitgoGpgKey.publicKey,
634+
encryptedPrv,
635+
});
636+
637+
const rShareEnvelope = JSON.parse(commitResult.encryptedUserToBitgoRShare.share);
638+
rShareEnvelope.v.should.equal(2);
639+
rShareEnvelope.should.have.property('hkdfSalt');
640+
641+
// 3. Round-trip: decrypt the v2 R-share
642+
const { rShare } = await tssUtils.createRShareFromTxRequest({
643+
txRequest: signingTxRequest,
644+
walletPassphrase: passphrase,
645+
encryptedUserToBitgoRShare: commitResult.encryptedUserToBitgoRShare,
646+
});
647+
648+
should.exist(rShare.xShare);
649+
should.exist(rShare.rShares);
650+
});
651+
});
652+
595653
describe('signTxRequest:', function () {
596654
const txRequestId = 'randomid';
597655
const txRequest: TxRequest = {

modules/bitgo/test/v2/unit/wallet.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ describe('V2 Wallet:', function () {
352352
prv,
353353
coldDerivationSeed: '123',
354354
};
355-
(await wallet.getUserPrv(userPrvOptions)).should.eql(derivedPrv);
355+
wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv);
356356
});
357357

358358
it('should use the user keychain derivedFromParentWithSeed as the cold derivation seed if none is provided', async () => {
@@ -365,7 +365,7 @@ describe('V2 Wallet:', function () {
365365
type: 'independent',
366366
},
367367
};
368-
(await wallet.getUserPrv(userPrvOptions)).should.eql(derivedPrv);
368+
wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv);
369369
});
370370

371371
it('should prefer the explicit cold derivation seed to the user keychain derivedFromParentWithSeed', async () => {
@@ -379,7 +379,7 @@ describe('V2 Wallet:', function () {
379379
type: 'independent',
380380
},
381381
};
382-
(await wallet.getUserPrv(userPrvOptions)).should.eql(derivedPrv);
382+
wallet.getUserPrv(userPrvOptions).should.eql(derivedPrv);
383383
});
384384

385385
it('should return the prv provided for TSS SMC', async () => {
@@ -407,7 +407,7 @@ describe('V2 Wallet:', function () {
407407
prv,
408408
keychain,
409409
};
410-
(await wallet.getUserPrv(userPrvOptions)).should.eql(prv);
410+
wallet.getUserPrv(userPrvOptions).should.eql(prv);
411411
});
412412
});
413413

modules/bitgo/test/v2/unit/wallets.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
Wallet,
2020
isWalletWithKeychains,
2121
OptionalKeychainEncryptedKey,
22-
decryptKeychainPrivateKey,
22+
decryptKeychainPrivateKeyAsync,
2323
makeRandomKey,
2424
getSharedSecret,
2525
BulkWalletShareOptions,
@@ -2566,7 +2566,7 @@ describe('V2 Wallets:', function () {
25662566
const keychainTest: OptionalKeychainEncryptedKey = {
25672567
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
25682568
};
2569-
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2569+
const userPrv = await decryptKeychainPrivateKeyAsync(bitgo, keychainTest, walletPassphrase);
25702570
if (!userPrv) {
25712571
throw new Error('Unable to decrypt user keychain');
25722572
}
@@ -2638,7 +2638,7 @@ describe('V2 Wallets:', function () {
26382638
const keychainTest: OptionalKeychainEncryptedKey = {
26392639
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
26402640
};
2641-
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2641+
const userPrv = await decryptKeychainPrivateKeyAsync(bitgo, keychainTest, walletPassphrase);
26422642
if (!userPrv) {
26432643
throw new Error('Unable to decrypt user keychain');
26442644
}
@@ -2717,7 +2717,7 @@ describe('V2 Wallets:', function () {
27172717
const keychainTest: OptionalKeychainEncryptedKey = {
27182718
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
27192719
};
2720-
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2720+
const userPrv = await decryptKeychainPrivateKeyAsync(bitgo, keychainTest, walletPassphrase);
27212721
if (!userPrv) {
27222722
throw new Error('Unable to decrypt user keychain');
27232723
}
@@ -2785,7 +2785,7 @@ describe('V2 Wallets:', function () {
27852785
const keychainTest: OptionalKeychainEncryptedKey = {
27862786
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
27872787
};
2788-
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2788+
const userPrv = await decryptKeychainPrivateKeyAsync(bitgo, keychainTest, walletPassphrase);
27892789
if (!userPrv) {
27902790
throw new Error('Unable to decrypt user keychain');
27912791
}
@@ -2886,7 +2886,7 @@ describe('V2 Wallets:', function () {
28862886
const keychainTest: OptionalKeychainEncryptedKey = {
28872887
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
28882888
};
2889-
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2889+
const userPrv = await decryptKeychainPrivateKeyAsync(bitgo, keychainTest, walletPassphrase);
28902890
if (!userPrv) {
28912891
throw new Error('Unable to decrypt user keychain');
28922892
}
@@ -2988,7 +2988,7 @@ describe('V2 Wallets:', function () {
29882988
const keychainTest: OptionalKeychainEncryptedKey = {
29892989
encryptedPrv: bitgo.encrypt({ input: fromUserPrv.toString(), password: walletPassphrase }),
29902990
};
2991-
const userPrv = await decryptKeychainPrivateKey(bitgo, keychainTest, walletPassphrase);
2991+
const userPrv = await decryptKeychainPrivateKeyAsync(bitgo, keychainTest, walletPassphrase);
29922992
if (!userPrv) {
29932993
throw new Error('Unable to decrypt user keychain');
29942994
}

modules/sdk-core/src/bitgo/wallet/iWallets.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export const GenerateLightningWalletOptionsCodec = t.intersection(
107107
}),
108108
t.partial({
109109
lightningProvider: t.union([t.literal('amboss'), t.literal('voltage')]),
110+
// Codec intentionally accepts only 2: v1 is the implicit default and never sent on the wire.
110111
encryptionVersion: t.literal(2),
111112
}),
112113
],
@@ -124,6 +125,7 @@ export const GenerateGoAccountWalletOptionsCodec = t.intersection(
124125
type: t.literal('trading'),
125126
}),
126127
t.partial({
128+
// Codec intentionally accepts only 2: v1 is the implicit default and never sent on the wire.
127129
encryptionVersion: t.literal(2),
128130
}),
129131
],

0 commit comments

Comments
 (0)