Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
RequestTracer,
SignatureShareRecord,
SignatureShareType,
TssTxRecipientSource,
TxRequest,
Wallet,
} from '@bitgo/sdk-core';
Expand Down Expand Up @@ -199,6 +200,31 @@ describe('signTxRequest:', function () {
nockPromises[2].isDone().should.be.true();
});

it('successfully signs when recipientSource is explicit and txParams.recipients is non-empty', async function () {
const nockPromises = [
await nockTxRequestResponseSignatureShareRoundOne(bitgoParty, txRequest, bitgoGpgKey),
await nockTxRequestResponseSignatureShareRoundTwo(bitgoParty, txRequest, bitgoGpgKey),
await nockTxRequestResponseSignatureShareRoundThree(txRequest),
await nockSendTxRequest(txRequest),
];
await Promise.all(nockPromises);

const userShare = fs.readFileSync(shareFiles[vector.party1]);
const userPrvBase64 = Buffer.from(userShare).toString('base64');
await tssUtils.signTxRequest({
txRequest,
prv: userPrvBase64,
reqId,
recipientSource: TssTxRecipientSource.Explicit,
txParams: {
recipients: [{ address: '0x0000000000000000000000000000000000000001', amount: '1' }],
},
});
nockPromises[0].isDone().should.be.true();
nockPromises[1].isDone().should.be.true();
nockPromises[2].isDone().should.be.true();
});

it('successfully signs a txRequest with backup key for a dkls hot wallet with WP', async function () {
const nockPromises = [
await nockTxRequestResponseSignatureShareRoundOne(bitgoParty, txRequest, bitgoGpgKey, 1),
Expand Down
4 changes: 2 additions & 2 deletions modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
SignatureShareRecord,
TSSParams,
TSSParamsForMessage,
TSSParamsWithPrv,
TssSignTxRequestParamsWithPrv,
TxRequest,
TxRequestVersion,
} from './baseTypes';
Expand Down Expand Up @@ -198,7 +198,7 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
throw new Error('Method not implemented.');
}

signTxRequest(params: TSSParamsWithPrv): Promise<TxRequest> {
signTxRequest(params: TssSignTxRequestParamsWithPrv): Promise<TxRequest> {
throw new Error('Method not implemented.');
}

Expand Down
68 changes: 56 additions & 12 deletions modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Key, SerializedKeyPair } from 'openpgp';
import { IRequestTracer } from '../../../api';
import { KeychainsTriplet, ParsedTransaction, TransactionParams } from '../../baseCoin';
import { type ITransactionRecipient, KeychainsTriplet, ParsedTransaction, TransactionParams } from '../../baseCoin';
import { ApiKeyShare, Keychain } from '../../keychain';
import { ApiVersion, Memo, WalletType } from '../../wallet';
import { EDDSA, GShare, Signature, SignShare } from '../../../account-lib/mpc/tss';
Expand Down Expand Up @@ -532,16 +532,6 @@ export interface EncryptedSignerShareRecord extends ShareBaseRecord {
type: EncryptedSignerShareType;
}

export type TSSParamsWithPrv = TSSParams & {
prv: string;
mpcv2PartyId?: 0 | 1;
};

export type TSSParamsForMessageWithPrv = TSSParamsForMessage & {
prv: string;
mpcv2PartyId?: 0 | 1;
};

export type BitgoPubKeyType = 'nitro' | 'onprem';

export type TSSParams = {
Expand All @@ -557,6 +547,60 @@ export type TSSParamsForMessage = TSSParams & {
bufferToSign: Buffer;
};

/** At least one recipient (when using `recipientSource: TssTxRecipientSource.Explicit`). */
export type NonEmptyRecipientList = [ITransactionRecipient, ...ITransactionRecipient[]];

/** txParams including a non-empty recipients list for strict signing verification typing. */
export type TransactionParamsWithMandatoryRecipients = TransactionParams & {
recipients: NonEmptyRecipientList;
};

export const TssTxRecipientSource = {
/** Require txParams.recipients with at least one entry (enforced by TypeScript for this branch). */
Explicit: 'explicit',
/**
* Default: txParams may be omitted or partial; verification uses coin-specific rules
* (for example recipients from txRequest context).
*/
Resolved: 'resolved',
} as const;

export type TssTxRecipientSource = (typeof TssTxRecipientSource)[keyof typeof TssTxRecipientSource];

export type TssSignTxExplicitRecipientParams = {
txRequest: string | TxRequest;
reqId: IRequestTracer;
apiVersion?: ApiVersion;
recipientSource: typeof TssTxRecipientSource.Explicit;
txParams: TransactionParamsWithMandatoryRecipients;
};

export type TssSignTxResolvedRecipientParams = {
txRequest: string | TxRequest;
reqId: IRequestTracer;
apiVersion?: ApiVersion;
recipientSource?: typeof TssTxRecipientSource.Resolved;
txParams?: TransactionParams;
};

/**
* Parameters for TSS transaction signing ({@link ITssUtils.signTxRequest}).
* Set {@link TssTxRecipientSource.Explicit} to require a non-empty txParams.recipients array at compile time.
*/
export type TssSignTxRequestParams = TssSignTxExplicitRecipientParams | TssSignTxResolvedRecipientParams;

export type TssSignTxRequestParamsWithPrv = TssSignTxRequestParams & {
prv: string;
mpcv2PartyId?: 0 | 1;
};

export type TSSParamsWithPrv = TssSignTxRequestParamsWithPrv;

export type TSSParamsForMessageWithPrv = TSSParamsForMessage & {
prv: string;
mpcv2PartyId?: 0 | 1;
};

export interface BitgoHeldBackupKeyShare {
commonKeychain?: string;
id: string;
Expand Down Expand Up @@ -714,7 +758,7 @@ export interface ITssUtils<KeyShare = EDDSA.KeyShare> {
originalPasscodeEncryptionCode?: string;
isThirdPartyBackup?: boolean;
}): Promise<KeychainsTriplet>;
signTxRequest(params: { txRequest: string | TxRequest; prv: string; reqId: IRequestTracer }): Promise<TxRequest>;
signTxRequest(params: TssSignTxRequestParamsWithPrv): Promise<TxRequest>;
signTxRequestForMessage(params: TSSParams): Promise<TxRequest>;
signEddsaTssUsingExternalSigner(
txRequest: string | TxRequest,
Expand Down
17 changes: 16 additions & 1 deletion modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@ import {
TSSParamsForMessage,
TSSParamsForMessageWithPrv,
TSSParamsWithPrv,
TssSignTxRequestParamsWithPrv,
TssTxRecipientSource,
TxRequest,
} from '../baseTypes';
import { getTxRequest } from '../../../tss';
import { AShare, DShare, EncryptedNShare, SendShareType, SShare, WShare, OShare } from '../../../tss/ecdsa/types';
import { createShareProof, generateGPGKeyPair, getBitgoGpgPubKey } from '../../opengpgUtils';
import { BitGoBase } from '../../../bitgoBase';
import { InvalidTransactionError } from '../../../errors';
import { verifyWalletSignature } from '../../../tss/ecdsa/ecdsa';
import { signMessageWithDerivedEcdhKey, verifyEcdhSignature } from '../../../ecdh';
import { getTxRequestChallenge } from '../../../tss/common';
Expand Down Expand Up @@ -745,6 +748,16 @@ export class EcdsaUtils extends BaseEcdsaUtils {
const unsignedTx =
txRequest.apiVersion === 'full' ? txRequest.transactions![0].unsignedTx : txRequest.unsignedTxs[0];

if (
'recipientSource' in params &&
params.recipientSource === TssTxRecipientSource.Explicit &&
!params.txParams?.recipients?.length
) {
throw new InvalidTransactionError(
'recipientSource "explicit" requires txParams.recipients with at least one recipient.'
);
}

// For ICP transactions, the HSM signs the serializedTxHex, while the user signs the signableHex separately.
// Verification cannot be performed directly on the signableHex alone. However, we can parse the serializedTxHex
// to regenerate the signableHex and compare it against the provided value for verification.
Expand Down Expand Up @@ -862,9 +875,11 @@ export class EcdsaUtils extends BaseEcdsaUtils {
* @param {string | TxRequest} params.txRequest - transaction request object or id
* @param {string} params.prv - decrypted private key
* @param {string} params.reqId - request id
* @param params.recipientSource - optional; use TssTxRecipientSource.Explicit with a non-empty
* txParams.recipients list when you want TypeScript to enforce passing recipient details at compile time.
* @returns {Promise<TxRequest>} fully signed TxRequest object
*/
async signTxRequest(params: TSSParamsWithPrv): Promise<TxRequest> {
async signTxRequest(params: TssSignTxRequestParamsWithPrv): Promise<TxRequest> {
this.bitgo.setRequestTracer(params.reqId);
return this.signRequestBase(params, RequestType.tx);
}
Expand Down
17 changes: 16 additions & 1 deletion modules/sdk-core/src/bitgo/utils/tss/ecdsa/ecdsaMPCv2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,14 @@ import {
TSSParamsForMessage,
TSSParamsForMessageWithPrv,
TSSParamsWithPrv,
TssSignTxRequestParamsWithPrv,
TssTxRecipientSource,
TxRequest,
} from '../baseTypes';
import { BaseEcdsaUtils } from './base';
import { EcdsaMPCv2KeyGenSendFn, KeyGenSenderForEnterprise } from './ecdsaMPCv2KeyGenSender';
import { envRequiresBitgoPubGpgKeyConfig, isBitgoMpcPubKey } from '../../../tss/bitgoPubKeys';
import { InvalidTransactionError } from '../../../errors';

export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
private static readonly DKLS23_SIGNING_USER_GPG_KEY = 'DKLS23_SIGNING_USER_GPG_KEY';
Expand Down Expand Up @@ -697,10 +700,12 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
* @param {string} params.prv - decrypted private key
* @param {string} params.reqId - request id
* @param {string} params.mpcv2PartyId - party id for the signer involved in this mpcv2 request (either 0 for user or 1 for backup)
* @param params.recipientSource - optional; use TssTxRecipientSource.Explicit with a non-empty txParams.recipients
* list when you want TypeScript to enforce passing recipient details at compile time.
* @returns {Promise<TxRequest>} fully signed TxRequest object
*/

async signTxRequest(params: TSSParamsWithPrv): Promise<TxRequest> {
async signTxRequest(params: TssSignTxRequestParamsWithPrv): Promise<TxRequest> {
this.bitgo.setRequestTracer(params.reqId);
return this.signRequestBase(params, RequestType.tx);
}
Expand Down Expand Up @@ -741,6 +746,16 @@ export class EcdsaMPCv2Utils extends BaseEcdsaUtils {
const unsignedTx =
txRequest.apiVersion === 'full' ? txRequest.transactions![0].unsignedTx : txRequest.unsignedTxs[0];

if (
'recipientSource' in params &&
params.recipientSource === TssTxRecipientSource.Explicit &&
!params.txParams?.recipients?.length
) {
throw new InvalidTransactionError(
'recipientSource "explicit" requires txParams.recipients with at least one recipient.'
);
}

// For ICP transactions, the HSM signs the serializedTxHex, while the user signs the signableHex separately.
// Verification cannot be performed directly on the signableHex alone. However, we can parse the serializedTxHex
// to regenerate the signableHex and compare it against the provided value for verification.
Expand Down
3 changes: 2 additions & 1 deletion modules/sdk-core/src/bitgo/utils/tss/eddsa/eddsa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
SignatureShareType,
TSSParamsForMessageWithPrv,
TSSParamsWithPrv,
TssSignTxRequestParamsWithPrv,
TxRequest,
UnsignedTransactionTss,
} from '../baseTypes';
Expand Down Expand Up @@ -571,7 +572,7 @@ export class EddsaUtils extends baseTSSUtils<KeyShare> {
@param params - parameters for signing the transaction request
* @returns {Promise<TxRequest>} fully signed TxRequest object
*/
async signTxRequest(params: TSSParamsWithPrv): Promise<TxRequest> {
async signTxRequest(params: TssSignTxRequestParamsWithPrv): Promise<TxRequest> {
return this.signRequestBase(params, RequestType.tx);
}

Expand Down
Loading