From 2a38dba9e22a18fbf7d2051da4a5348832374d14 Mon Sep 17 00:00:00 2001 From: Alejandro Busse Date: Wed, 14 Jun 2023 19:00:08 -0300 Subject: [PATCH] feat(express): implement EdDSA commitments for external signer implemented EdDSA commitment step for external signer added unit test WP-94 --- modules/express/src/clientRoutes.ts | 45 +++++- .../test/unit/clientRoutes/externalSign.ts | 139 ++++++------------ .../src/bitgo/utils/tss/baseTSSUtils.ts | 51 +++++-- .../sdk-core/src/bitgo/utils/tss/baseTypes.ts | 28 +++- .../src/bitgo/utils/tss/ecdsa/ecdsa.ts | 4 +- .../src/bitgo/utils/tss/eddsa/eddsa.ts | 90 ++++++++++-- modules/sdk-core/src/bitgo/wallet/iWallet.ts | 2 + modules/sdk-core/src/bitgo/wallet/wallet.ts | 11 +- 8 files changed, 234 insertions(+), 136 deletions(-) diff --git a/modules/express/src/clientRoutes.ts b/modules/express/src/clientRoutes.ts index c0c4ed2e62..097faa283a 100755 --- a/modules/express/src/clientRoutes.ts +++ b/modules/express/src/clientRoutes.ts @@ -8,7 +8,9 @@ import { UnsupportedCoinError, GShare, SignShare, - YShare, + CustomCommitmentGeneratingFunction, + CommitmentShareRecord, + EncryptedSignerShareRecord, } from '@bitgo/sdk-core'; import { BitGo, BitGoOptions, Coin, CustomSigningFunction, SignedTransaction, SignedTransactionRequest } from 'bitgo'; import * as bodyParser from 'body-parser'; @@ -409,13 +411,17 @@ export async function handleV2GenerateShareTSS(req: express.Request): Promise { + const { body: result } = await retryPromise( + () => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/commitment`).type('json').send(params), + (err, tryCount) => { + debug(`failed to connect to external signer (attempt ${tryCount}, error: ${err.message})`); + } + ); + return result; + }; +} + export function createCustomRShareGenerator(externalSignerUrl: string, coin: string): CustomRShareGeneratingFunction { - return async function (params): Promise<{ rShare: SignShare; signingKeyYShare: YShare }> { + return async function (params): Promise<{ rShare: SignShare }> { const { body: rShare } = await retryPromise( () => superagent.post(`${externalSignerUrl}/api/v2/${coin}/tssshare/R`).type('json').send(params), (err, tryCount) => { diff --git a/modules/express/test/unit/clientRoutes/externalSign.ts b/modules/express/test/unit/clientRoutes/externalSign.ts index df39dc4357..456f5907d6 100644 --- a/modules/express/test/unit/clientRoutes/externalSign.ts +++ b/modules/express/test/unit/clientRoutes/externalSign.ts @@ -47,6 +47,16 @@ describe('External signer', () => { bgUrl = common.Environments[bitgo.getEnv()].uri; hdTree = await Ed25519BIP32.initialize(); MPC = await Eddsa.initialize(hdTree); + + const bitgoPublicKey = + '-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nxk8EZIol0hMFK4EEAAoCAwTBJZKgCNfBZuD5AgIDM2hQky3Iw3T6EITaMnW2\nG9uKxFadVpslF0Dyp+kieW7JYPffUzSI+mCR7L/4rSsnLLHszRZiaXRnbyA8\nYml0Z29AdGVzdC5jb20+wowEEBMIAB0FAmSKJdIECwkHCAMVCAoEFgACAQIZ\nAQIbAwIeAQAhCRAL9sROnSoDRhYhBEFZxeQAYNOvaj3GZAv2xE6dKgNGj7MB\nAOJBnZqaWPway3B4fNB/Mi0v1wb9d2uDD28SgzzpsV/YAP90cryseKMF+dKw\n+to1vXTl8xb49cIU9gvcJYLqYUd+Fs5TBGSKJdISBSuBBAAKAgMErB+qJoUf\nvTyMP/9GGNsHY7ykqbwi/QYjim4bR560TyRQ8LKaxGwHN/1cbq4iQt45lYK2\nWpNQovBJ6U3DwKUFnQMBCAfCeAQYEwgACQUCZIol0gIbDAAhCRAL9sROnSoD\nRhYhBEFZxeQAYNOvaj3GZAv2xE6dKgNGSA8A/25BLEgyRERJFlDvGnavxRKu\nhHHV6kyzK9speNeTs1vzAP0cFkbE5Kvg6Xz9lag+cr6rFwrHC8m7znTbrbHq\n6eOi3w==\n=XFoJ\n-----END PGP PUBLIC KEY BLOCK-----\n'; + const constants = { + mpc: { + bitgoPublicKey, + }, + }; + + nock(bgUrl).persist().get('/api/v1/client/constants').reply(200, { ttl: 3600, constants }); }); after(() => { @@ -91,7 +101,8 @@ describe('External signer', () => { signTransactionStub.restore(); envStub.restore(); }); - it('should read an encrypted prv from signerFileSystemPath and pass it to R and G share generators', async () => { + + it('should read an encrypted prv from signerFileSystemPath and pass it to commitment, R and G share generators', async () => { const walletID = '62fe536a6b4cf70007acb48c0e7bb0b0'; const user = MPC.keyShare(1, 2, 3); const backup = MPC.keyShare(2, 2, 3); @@ -112,41 +123,9 @@ describe('External signer', () => { .value({ WALLET_62fe536a6b4cf70007acb48c0e7bb0b0_PASSPHRASE: walletPassphrase }); const tMessage = 'testMessage'; const bgTest = new BitGo({ env: 'test' }); - const reqR = { - bitgo: bgTest, - body: { - txRequest: { - apiVersion: 'full', - walletId: walletID, - transactions: [ - { - unsignedTx: { - derivationPath: 'm/0', - signableHex: tMessage, - }, - }, - ], - }, - }, - params: { - coin: 'tsol', - sharetype: 'R', - }, - config: { - signerFileSystemPath: 'signerFileSystemPath', - }, - } as unknown as express.Request; - const result = await handleV2GenerateShareTSS(reqR); - const bitgoCombine = MPC.keyCombine(bitgo.uShare, [result.signingKeyYShare, backup.yShares[3]]); - const bitgoSignShare = await MPC.signShare(Buffer.from(tMessage, 'hex'), bitgoCombine.pShare, [ - bitgoCombine.jShares[1], - ]); - const signatureShareRec = { - from: SignatureShareType.BITGO, - to: SignatureShareType.USER, - share: bitgoSignShare.rShares[1].r + bitgoSignShare.rShares[1].R, - }; - const reqG = { + const derivationPath = 'm/0'; + + const reqCommitment = { bitgo: bgTest, body: { txRequest: { @@ -155,82 +134,42 @@ describe('External signer', () => { transactions: [ { unsignedTx: { - derivationPath: 'm/0', + derivationPath, signableHex: tMessage, }, }, ], }, - userToBitgoRShare: result.rShare, - bitgoToUserRShare: signatureShareRec, }, params: { coin: 'tsol', - sharetype: 'G', + sharetype: 'commitment', }, config: { signerFileSystemPath: 'signerFileSystemPath', }, } as unknown as express.Request; - const userGShare = await handleV2GenerateShareTSS(reqG); - const userToBitgoRShare = { - i: ShareKeyPosition.BITGO, - j: ShareKeyPosition.USER, - u: result.signingKeyYShare.u, - v: result.rShare.rShares[3].v, - r: result.rShare.rShares[3].r, - R: result.rShare.rShares[3].R, - }; - const bitgoGShare = MPC.sign( - Buffer.from(tMessage, 'hex'), - bitgoSignShare.xShare, - [userToBitgoRShare], - [backup.yShares[3]] - ); - const signature = MPC.signCombine([userGShare, bitgoGShare]); - const veriResult = MPC.verify(Buffer.from(tMessage, 'hex'), signature); - veriResult.should.be.true(); - readFileStub.restore(); - envStub.restore(); - }); - - it('should read an encrypted prv from signerFileSystemPath and pass it to R and G share generators, with commitment', async () => { - const walletID = '62fe536a6b4cf70007acb48c0e7bb0b0'; - const user = MPC.keyShare(1, 2, 3); - const backup = MPC.keyShare(2, 2, 3); - const bitgo = MPC.keyShare(3, 2, 3); - const userSigningMaterial = { - uShare: user.uShare, - bitgoYShare: bitgo.yShares[1], - backupYShare: backup.yShares[1], - }; - const bg = new BitGo({ env: 'test' }); - const walletPassphrase = 'testPass'; - const validPrv = bg.encrypt({ input: JSON.stringify(userSigningMaterial), password: walletPassphrase }); - const output: Output = {}; - output[walletID] = validPrv; - const readFileStub = sinon.stub(fs.promises, 'readFile').resolves(JSON.stringify(output)); - const envStub = sinon - .stub(process, 'env') - .value({ WALLET_62fe536a6b4cf70007acb48c0e7bb0b0_PASSPHRASE: walletPassphrase }); - const tMessage = 'testMessage'; - const bgTest = new BitGo({ env: 'test' }); + const cResult = await handleV2GenerateShareTSS(reqCommitment); + cResult.should.have.property('userToBitgoCommitment'); + cResult.should.have.property('encryptedSignerShare'); + cResult.should.have.property('encryptedUserToBitgoRShare'); + const encryptedUserToBitgoRShare = cResult.encryptedUserToBitgoRShare; const reqR = { bitgo: bgTest, body: { txRequest: { apiVersion: 'full', - state: 'pendingCommitment', walletId: walletID, transactions: [ { unsignedTx: { - derivationPath: 'm/0', + derivationPath, signableHex: tMessage, }, }, ], }, + encryptedUserToBitgoRShare, }, params: { coin: 'tsol', @@ -240,8 +179,16 @@ describe('External signer', () => { signerFileSystemPath: 'signerFileSystemPath', }, } as unknown as express.Request; - const result = await handleV2GenerateShareTSS(reqR); - const bitgoCombine = MPC.keyCombine(bitgo.uShare, [result.signingKeyYShare, backup.yShares[3]]); + const rResult = await handleV2GenerateShareTSS(reqR); + rResult.should.have.property('rShare'); + + const signingKey = MPC.keyDerive( + userSigningMaterial.uShare, + [userSigningMaterial.bitgoYShare, userSigningMaterial.backupYShare], + derivationPath + ); + + const bitgoCombine = MPC.keyCombine(bitgo.uShare, [signingKey.yShares[3], backup.yShares[3]]); const bitgoSignShare = await MPC.signShare(Buffer.from(tMessage, 'hex'), bitgoCombine.pShare, [ bitgoCombine.jShares[1], ]); @@ -265,13 +212,13 @@ describe('External signer', () => { transactions: [ { unsignedTx: { - derivationPath: 'm/0', + derivationPath, signableHex: tMessage, }, }, ], }, - userToBitgoRShare: result.rShare, + userToBitgoRShare: rResult.rShare, bitgoToUserRShare: signatureShareRec, bitgoToUserCommitment: bitgoToUserCommitmentShare, }, @@ -284,14 +231,18 @@ describe('External signer', () => { }, } as unknown as express.Request; const userGShare = await handleV2GenerateShareTSS(reqG); + userGShare.should.have.property('i'); + userGShare.should.have.property('y'); + userGShare.should.have.property('gamma'); + userGShare.should.have.property('R'); const userToBitgoRShare = { i: ShareKeyPosition.BITGO, j: ShareKeyPosition.USER, - u: result.signingKeyYShare.u, - v: result.rShare.rShares[3].v, - r: result.rShare.rShares[3].r, - R: result.rShare.rShares[3].R, - commitment: result.rShare.rShares[3].commitment, + u: signingKey.yShares[3].u, + v: rResult.rShare.rShares[3].v, + r: rResult.rShare.rShares[3].r, + R: rResult.rShare.rShares[3].R, + commitment: rResult.rShare.rShares[3].commitment, }; const bitgoGShare = MPC.sign( Buffer.from(tMessage, 'hex'), diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts index 187d26ced1..32895e5bc5 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts @@ -25,8 +25,10 @@ import { PopulatedIntentForTypedDataSigning, CreateBitGoKeychainParamsBase, CommitmentShareRecord, + EncryptedSignerShareRecord, + CustomCommitmentGeneratingFunction, } from './baseTypes'; -import { GShare, SignShare, YShare } from '../../../account-lib/mpc/tss'; +import { GShare, SignShare } from '../../../account-lib/mpc/tss'; /** * BaseTssUtil class which different signature schemes have to extend @@ -117,34 +119,59 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil */ signUsingExternalSigner( txRequest: string | TxRequest, + externalSignerCommitmentGenerator: CustomCommitmentGeneratingFunction, externalSignerRShareGenerator: CustomRShareGeneratingFunction, externalSignerGShareGenerator: CustomGShareGeneratingFunction ): Promise { throw new Error('Method not implemented.'); } + /** + * Create an Commitment (User to BitGo) share from an unsigned transaction and private user signing material + * EDDSA only + * + * @param {Object} params - params object + * @param {TxRequest} params.txRequest - transaction request with unsigned transaction + * @param {string} params.prv - user signing material + * @param {string} params.walletPassphrase - wallet passphrase + * + * @returns {Promise<{ userToBitgoCommitment: CommitmentShareRecor, encryptedSignerShare: EncryptedSignerShareRecord }>} - Commitment Share and the Encrypted Signer Share to BitGo + */ + createCommitmentShareFromTxRequest(params: { txRequest: TxRequest; prv: string; walletPassphrase: string }): Promise<{ + userToBitgoCommitment: CommitmentShareRecord; + encryptedSignerShare: EncryptedSignerShareRecord; + encryptedUserToBitgoRShare: EncryptedSignerShareRecord; + }> { + throw new Error('Method not implemented.'); + } + /** * Create an R (User to BitGo) share from an unsigned transaction and private user signing material * - * @param {TxRequest} txRequest - transaction request with unsigned transaction - * @param {string} prv - user signing material - * @returns {Promise<{ rShare: SignShare; signingKeyYShare: YShare }>} - R Share and the Signing Key's Y share to BitGo + * @param {Object} params - params object + * @param {TxRequest} params.txRequest - transaction request with unsigned transaction + * @param {string} params.prv - user signing material + * @param {string} [params.walletPassphrase] - wallet passphrase + * @param {EncryptedSignerShareRecord} [params.encryptedUserToBitgoRShare] - encrypted user to bitgo R share generated in the commitment phase + * @returns {Promise<{ rShare: SignShare }>} - R Share to BitGo */ createRShareFromTxRequest(params: { txRequest: TxRequest; - prv: string; - }): Promise<{ rShare: SignShare; signingKeyYShare: YShare }> { + walletPassphrase: string; + encryptedUserToBitgoRShare: EncryptedSignerShareRecord; + }): Promise<{ rShare: SignShare }> { throw new Error('Method not implemented.'); } /** * Create a G (User to BitGo) share from an unsigned transaction and private user signing material * - * @param {TxRequest} txRequest - transaction request with unsigned transaction - * @param {string} prv - user signing material - * @param {SignatureShareRecord} bitgoToUserRShare - BitGo to User R Share - * @param {SignShare} userToBitgoRShare - User to BitGo R Share - * @param {string} [bitgoToUserCommitment] - BitGo to User Commitment + * @param {Object} params - params object + * @param {TxRequest} params.txRequest - transaction request with unsigned transaction + * @param {string} params.prv - user signing material + * @param {SignatureShareRecord} params.bitgoToUserRShare - BitGo to User R Share + * @param {SignShare} params.userToBitgoRShare - User to BitGo R Share + * @param {CommitmentShareRecord} params.bitgoToUserCommitment - BitGo to User Commitment * @returns {Promise} - GShare from User to BitGo */ createGShareFromTxRequest(params: { @@ -152,7 +179,7 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil prv: string; bitgoToUserRShare: SignatureShareRecord; userToBitgoRShare: SignShare; - bitgoToUserCommitment?: CommitmentShareRecord; + bitgoToUserCommitment: CommitmentShareRecord; }): Promise { throw new Error('Method not implemented.'); } diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts index 280623c077..f14ea3e5f8 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts @@ -3,7 +3,7 @@ import { IRequestTracer } from '../../../api'; import { KeychainsTriplet } from '../../baseCoin'; import { ApiKeyShare, Keychain } from '../../keychain'; import { ApiVersion, Memo, WalletType } from '../../wallet'; -import { EDDSA, GShare, SignShare, YShare } from '../../../account-lib/mpc/tss'; +import { EDDSA, GShare, SignShare } from '../../../account-lib/mpc/tss'; import { KeyShare } from './ecdsa'; import { Hash } from 'crypto'; @@ -33,8 +33,18 @@ export interface TokenEnablement { address?: string; // Some chains like Solana require tokens to be enabled for specific address. If absent, we will enable it for the wallet's root address } +export interface CustomCommitmentGeneratingFunction { + (params: { txRequest: TxRequest }): Promise<{ + userToBitgoCommitment: CommitmentShareRecord; + encryptedSignerShare: EncryptedSignerShareRecord; + encryptedUserToBitgoRShare: EncryptedSignerShareRecord; + }>; +} + export interface CustomRShareGeneratingFunction { - (params: { txRequest: TxRequest }): Promise<{ rShare: SignShare; signingKeyYShare: YShare }>; + (params: { txRequest: TxRequest; encryptedUserToBitgoRShare: EncryptedSignerShareRecord }): Promise<{ + rShare: SignShare; + }>; } export interface CustomGShareGeneratingFunction { @@ -42,6 +52,7 @@ export interface CustomGShareGeneratingFunction { txRequest: TxRequest; userToBitgoRShare: SignShare; bitgoToUserRShare: SignatureShareRecord; + bitgoToUserCommitment: CommitmentShareRecord; }): Promise; } export enum TokenType { @@ -270,6 +281,7 @@ export interface ExchangeCommitmentResponse { export enum EncryptedSignerShareType { ENCRYPTED_SIGNER_SHARE = 'encryptedSignerShare', + ENCRYPTED_R_SHARE = 'encryptedRShare', } export interface EncryptedSignerShareRecord extends ShareBaseRecord { type: EncryptedSignerShareType; @@ -338,18 +350,26 @@ export interface ITssUtils { signTxRequestForMessage(params: TSSParams): Promise; signUsingExternalSigner( txRequest: string | TxRequest, + externalSignerCommitmentGenerator: CustomCommitmentGeneratingFunction, externalSignerRShareGenerator: CustomRShareGeneratingFunction, externalSignerGShareGenerator: CustomGShareGeneratingFunction ): Promise; + createCommitmentShareFromTxRequest(params: { txRequest: TxRequest; prv: string; walletPassphrase: string }): Promise<{ + userToBitgoCommitment: CommitmentShareRecord; + encryptedSignerShare: EncryptedSignerShareRecord; + encryptedUserToBitgoRShare: EncryptedSignerShareRecord; + }>; createRShareFromTxRequest(params: { txRequest: TxRequest; - prv: string; - }): Promise<{ rShare: SignShare; signingKeyYShare: YShare }>; + walletPassphrase: string; + encryptedUserToBitgoRShare: EncryptedSignerShareRecord; + }): Promise<{ rShare: SignShare }>; createGShareFromTxRequest(params: { txRequest: TxRequest; prv: string; bitgoToUserRShare: SignatureShareRecord; userToBitgoRShare: SignShare; + bitgoToUserCommitment: CommitmentShareRecord; }): Promise; prebuildTxWithIntent( params: PrebuildTransactionWithIntentOptions, diff --git a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts index 9fcb996105..f446443b4b 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts @@ -36,7 +36,7 @@ import { BackupProvider, IWallet } from '../../../wallet'; import { buildNShareFromAPIKeyShare, getParticipantFromIndex, verifyWalletSignature } from '../../../tss/ecdsa/ecdsa'; import { signMessageWithDerivedEcdhKey, verifyEcdhSignature } from '../../../ecdh'; import { getTxRequestChallenge } from '../../../tss/common'; -import { TxRequestChallengeResponse } from '../../../tss/types'; +import { ShareKeyPosition, TxRequestChallengeResponse } from '../../../tss/types'; const encryptNShare = ECDSAMethods.encryptNShare; @@ -649,7 +649,7 @@ export class EcdsaUtils extends baseTSSUtils { derivationPath ); - const bitgoIndex = 3; + const bitgoIndex = ShareKeyPosition.BITGO; const userIndex = userSigningMaterial.pShare.i; const challenges = await this.getEcdsaSigningChallenges(txRequest.txRequestId, requestType, signingKey.xShare.n, 0); diff --git a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts index 5651d89255..0ac05eb70a 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts @@ -16,11 +16,13 @@ import { getTxRequest, offerUserToBitgoRShare, sendUserToBitgoGShare, + ShareKeyPosition, SigningMaterial, } from '../../../tss'; import { CommitmentShareRecord, CommitmentType, + CustomCommitmentGeneratingFunction, CustomGShareGeneratingFunction, CustomRShareGeneratingFunction, EncryptedSignerShareRecord, @@ -364,10 +366,16 @@ export class EddsaUtils extends baseTSSUtils { return keychains; } - async createRShareFromTxRequest(params: { + async createCommitmentShareFromTxRequest(params: { txRequest: TxRequest; prv: string; - }): Promise<{ rShare: SignShare; signingKeyYShare: YShare }> { + walletPassphrase: string; + }): Promise<{ + userToBitgoCommitment: CommitmentShareRecord; + encryptedSignerShare: EncryptedSignerShareRecord; + encryptedUserToBitgoRShare: EncryptedSignerShareRecord; + }> { + const bitgoIndex = ShareKeyPosition.BITGO; const { txRequest, prv } = params; const txRequestResolved: TxRequest = txRequest; @@ -394,8 +402,38 @@ export class EddsaUtils extends baseTSSUtils { const signablePayload = Buffer.from(unsignedTx.signableHex, 'hex'); const userSignShare = await createUserSignShare(signablePayload, signingKey.pShare); + const commitment = userSignShare.rShares[bitgoIndex]?.commitment; + assert(commitment, 'Unable to find commitment in userSignShare'); + const userToBitgoCommitment = this.createUserToBitgoCommitmentShare(commitment); + + const signerShare = signingKey.yShares[bitgoIndex].u + signingKey.yShares[bitgoIndex].chaincode; + const bitgoGpgKey = await getBitgoGpgPubKey(this.bitgo); + const userToBitgoEncryptedSignerShare = await encryptText(signerShare, bitgoGpgKey); - return { rShare: userSignShare, signingKeyYShare: signingKey.yShares[3] }; + const encryptedSignerShare = this.createUserToBitgoEncryptedSignerShare(userToBitgoEncryptedSignerShare); + const stringifiedRShare = JSON.stringify(userSignShare); + const encryptedRShare = this.bitgo.encrypt({ input: stringifiedRShare, password: params.walletPassphrase }); + const encryptedUserToBitgoRShare = this.createUserToBitgoEncryptedRShare(encryptedRShare); + + return { userToBitgoCommitment, encryptedSignerShare, encryptedUserToBitgoRShare }; + } + + async createRShareFromTxRequest(params: { + txRequest: TxRequest; + walletPassphrase: string; + encryptedUserToBitgoRShare: EncryptedSignerShareRecord; + }): Promise<{ rShare: SignShare }> { + const { walletPassphrase, encryptedUserToBitgoRShare } = params; + + const decryptedRShare = this.bitgo.decrypt({ + input: encryptedUserToBitgoRShare.share, + password: walletPassphrase, + }); + const rShare = JSON.parse(decryptedRShare); + assert(rShare.xShare, 'Unable to find xShare in decryptedRShare'); + assert(rShare.rShares, 'Unable to find rShares in decryptedRShare'); + + return { rShare }; } async createGShareFromTxRequest(params: { @@ -403,7 +441,7 @@ export class EddsaUtils extends baseTSSUtils { prv: string; bitgoToUserRShare: SignatureShareRecord; userToBitgoRShare: SignShare; - bitgoToUserCommitment?: CommitmentShareRecord; + bitgoToUserCommitment: CommitmentShareRecord; }): Promise { let txRequestResolved: TxRequest; @@ -441,6 +479,7 @@ export class EddsaUtils extends baseTSSUtils { async signUsingExternalSigner( txRequest: string | TxRequest, + externalSignerCommitmentGenerator: CustomCommitmentGeneratingFunction, externalSignerRShareGenerator: CustomRShareGeneratingFunction, externalSignerGShareGenerator: CustomGShareGeneratingFunction ): Promise { @@ -453,19 +492,31 @@ export class EddsaUtils extends baseTSSUtils { txRequestResolved = txRequest; txRequestId = txRequest.txRequestId; } - const rSignShareTransactionParams = { + + const { userToBitgoCommitment, encryptedSignerShare, encryptedUserToBitgoRShare } = + await externalSignerCommitmentGenerator({ txRequest: txRequestResolved }); + + const { commitmentShare: bitgoToUserCommitment } = await exchangeEddsaCommitments( + this.bitgo, + this.wallet.id(), + txRequestId, + userToBitgoCommitment, + encryptedSignerShare, + 'full' + ); + + const { rShare } = await externalSignerRShareGenerator({ txRequest: txRequestResolved, - }; - const { rShare, signingKeyYShare } = await externalSignerRShareGenerator(rSignShareTransactionParams); - const signerShare = signingKeyYShare.u + signingKeyYShare.chaincode; - const bitgoGpgKey = await getBitgoGpgPubKey(this.bitgo); - const encryptedSignerShare = await encryptText(signerShare, bitgoGpgKey); - await offerUserToBitgoRShare(this.bitgo, this.wallet.id(), txRequestId, rShare, encryptedSignerShare, 'full'); + encryptedUserToBitgoRShare, + }); + + await offerUserToBitgoRShare(this.bitgo, this.wallet.id(), txRequestId, rShare, encryptedSignerShare.share, 'full'); const bitgoToUserRShare = await getBitgoToUserRShare(this.bitgo, this.wallet.id(), txRequestId); const gSignShareTransactionParams = { txRequest: txRequestResolved, bitgoToUserRShare: bitgoToUserRShare, userToBitgoRShare: rShare, + bitgoToUserCommitment, }; const gShare = await externalSignerGShareGenerator(gSignShareTransactionParams); await sendUserToBitgoGShare(this.bitgo, this.wallet.id(), txRequestId, gShare, 'full'); @@ -517,10 +568,10 @@ export class EddsaUtils extends baseTSSUtils { const userSignShare = await createUserSignShare(signablePayload, signingKey.pShare); - const bitgoIndex = 3; + const bitgoIndex = ShareKeyPosition.BITGO; const signerShare = signingKey.yShares[bitgoIndex].u + signingKey.yShares[bitgoIndex].chaincode; const bitgoGpgKey = await getBitgoGpgPubKey(this.bitgo); - const bitgoToUserEncryptedSignerShare = await encryptText(signerShare, bitgoGpgKey); + const userToBitgoEncryptedSignerShare = await encryptText(signerShare, bitgoGpgKey); const userGpgKey = await generateGPGKeyPair('secp256k1'); const privateShareProof = await createShareProof(userGpgKey.privateKey, signingKey.yShares[bitgoIndex].u, 'eddsa'); @@ -532,7 +583,7 @@ export class EddsaUtils extends baseTSSUtils { assert(userToBitgoCommitment, 'Missing userToBitgoCommitment commitment'); const commitmentShare = this.createUserToBitgoCommitmentShare(userToBitgoCommitment); - const encryptedSignerShare = this.createUserToBitgoEncryptedSignerShare(bitgoToUserEncryptedSignerShare); + const encryptedSignerShare = this.createUserToBitgoEncryptedSignerShare(userToBitgoEncryptedSignerShare); const { commitmentShare: bitgoToUserCommitment } = await exchangeEddsaCommitments( this.bitgo, @@ -548,7 +599,7 @@ export class EddsaUtils extends baseTSSUtils { this.wallet.id(), txRequestId, userSignShare, - bitgoToUserEncryptedSignerShare, + userToBitgoEncryptedSignerShare, apiVersion, vssProof, privateShareProof, @@ -603,6 +654,15 @@ export class EddsaUtils extends baseTSSUtils { type: EncryptedSignerShareType.ENCRYPTED_SIGNER_SHARE, }; } + + createUserToBitgoEncryptedRShare(encryptedRShare: string): EncryptedSignerShareRecord { + return { + from: SignatureShareType.USER, + to: SignatureShareType.BITGO, + share: encryptedRShare, + type: EncryptedSignerShareType.ENCRYPTED_R_SHARE, + }; + } } /** * @deprecated - use EddsaUtils diff --git a/modules/sdk-core/src/bitgo/wallet/iWallet.ts b/modules/sdk-core/src/bitgo/wallet/iWallet.ts index 7441273645..d6372a80e3 100644 --- a/modules/sdk-core/src/bitgo/wallet/iWallet.ts +++ b/modules/sdk-core/src/bitgo/wallet/iWallet.ts @@ -14,6 +14,7 @@ import { IPendingApproval, PendingApprovalData } from '../pendingApproval'; import { IStakingWallet } from '../staking'; import { ITradingAccount } from '../trading'; import { + CustomCommitmentGeneratingFunction, CustomGShareGeneratingFunction, CustomRShareGeneratingFunction, TokenEnablement, @@ -156,6 +157,7 @@ export interface WalletSignBaseOptions { export interface WalletSignTransactionOptions extends WalletSignBaseOptions { txPrebuild?: TransactionPrebuild; + customCommitmentGeneratingFunction?: CustomCommitmentGeneratingFunction; customRShareGeneratingFunction?: CustomRShareGeneratingFunction; customGShareGeneratingFunction?: CustomGShareGeneratingFunction; apiVersion?: ApiVersion; diff --git a/modules/sdk-core/src/bitgo/wallet/wallet.ts b/modules/sdk-core/src/bitgo/wallet/wallet.ts index ad47a4396f..ab09ae3868 100644 --- a/modules/sdk-core/src/bitgo/wallet/wallet.ts +++ b/modules/sdk-core/src/bitgo/wallet/wallet.ts @@ -1594,7 +1594,11 @@ export class Wallet implements IWallet { async signTransaction(params: WalletSignTransactionOptions = {}): Promise { const { txPrebuild, apiVersion } = params; - if (_.isFunction(params.customGShareGeneratingFunction) && _.isFunction(params.customRShareGeneratingFunction)) { + if ( + _.isFunction(params.customCommitmentGeneratingFunction) && + _.isFunction(params.customGShareGeneratingFunction) && + _.isFunction(params.customRShareGeneratingFunction) + ) { // invoke external signer TSS for EdDSA workflow return this.signTransactionTssExternalSignerEdDSA(params, this.baseCoin); } @@ -2780,6 +2784,10 @@ export class Wallet implements IWallet { throw new Error('TxRequestId required to sign TSS transactions with External Signer.'); } + if (!params.customCommitmentGeneratingFunction) { + throw new Error('Generator function for commitment required to sign transactions with External Signer.'); + } + if (!params.customRShareGeneratingFunction) { throw new Error('Generator function for R share required to sign transactions with External Signer.'); } @@ -2791,6 +2799,7 @@ export class Wallet implements IWallet { try { const signedTxRequest = await this.tssUtils!.signUsingExternalSigner( txRequestId, + params.customCommitmentGeneratingFunction, params.customRShareGeneratingFunction, params.customGShareGeneratingFunction );