From 56c12e5adaa6cafacd286b9a7c366956351bf000 Mon Sep 17 00:00:00 2001 From: Bhavi Dhingra Date: Thu, 19 Jun 2025 14:29:45 +0530 Subject: [PATCH] feat(sdk-core): create message sign request TICKET: COIN-4593 --- modules/bitgo/test/v2/unit/wallet.ts | 65 ++++++++++++------- .../src/bitgo/utils/tss/baseTSSUtils.ts | 55 ++++++++++++++++ .../sdk-core/src/bitgo/utils/tss/baseTypes.ts | 3 + modules/sdk-core/src/bitgo/wallet/wallet.ts | 46 +++++++++++++ 4 files changed, 146 insertions(+), 23 deletions(-) diff --git a/modules/bitgo/test/v2/unit/wallet.ts b/modules/bitgo/test/v2/unit/wallet.ts index d50fce6595..7d5ab1dcb7 100644 --- a/modules/bitgo/test/v2/unit/wallet.ts +++ b/modules/bitgo/test/v2/unit/wallet.ts @@ -9,33 +9,33 @@ import * as nock from 'nock'; import * as _ from 'lodash'; import { + BaseTssUtils, common, CustomSigningFunction, + Ecdsa, ECDSAUtils, EDDSAUtils, + GetUserPrvOptions, + Keychains, + KeyType, + ManageUnspentsOptions, + MessageTypes, + PopulatedIntent, + PrebuildTransactionWithIntentOptions, RequestTracer, + SendManyOptions, + SignatureShareType, + SignedMessage, + SignTypedDataVersion, TokenType, TssUtils, TxRequest, - Wallet, - SignatureShareType, - Ecdsa, - Keychains, + TxRequestVersion, TypedData, TypedMessage, - MessageTypes, - SignTypedDataVersion, - GetUserPrvOptions, - ManageUnspentsOptions, - SignedMessage, - BaseTssUtils, - KeyType, - SendManyOptions, - PopulatedIntent, - TxRequestVersion, + Wallet, WalletSignMessageOptions, WalletSignTypedDataOptions, - PrebuildTransactionWithIntentOptions, } from '@bitgo/sdk-core'; import { TestBitGo } from '@bitgo/sdk-test'; @@ -3467,14 +3467,24 @@ describe('V2 Wallet:', function () { nock.cleanAll(); }); - it('should throw error for unsupported coins', async function () { - await tssSolWallet - .signMessage({ - reqId, - message: { messageRaw }, - prv: 'secretKey', - }) - .should.be.rejectedWith('Message signing not supported for Testnet Solana'); + describe('should throw error for unsupported coins', function () { + it('sol signMessage', async function () { + await tssSolWallet + .signMessage({ + reqId, + message: { messageRaw }, + prv: 'secretKey', + }) + .should.be.rejectedWith('Message signing not supported for Testnet Solana'); + }); + + it('sol create signMessage tx request', async function () { + await tssSolWallet + .createSignMessageRequest({ + messageRaw, + }) + .should.be.rejectedWith('Message signing not supported for Testnet Solana'); + }); }); messageSigningCoins.map((coinName) => { @@ -3483,6 +3493,15 @@ describe('V2 Wallet:', function () { tssEthWallet = new Wallet(bitgo, bitgo.coin(coinName), ethWalletData); const txRequestId = txRequestForMessageSigning.txRequestId; + it('should create tx Request with signMessage intent', async function () { + nock(bgUrl).post(`/api/v2/wallet/${tssEthWallet.id()}/msgrequests`).reply(200, txRequestForMessageSigning); + + const txRequest = await tssEthWallet.createSignMessageRequest({ + messageRaw, + }); + txRequest.should.deepEqual(txRequestForMessageSigning); + }); + it('should sign message', async function () { const signMessageTssSpy = sinon.spy(tssEthWallet, 'signMessageTss' as any); nock(bgUrl) diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts index fdd24e2c5d..fba8caa4ec 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts @@ -43,6 +43,7 @@ import { RequestTracer } from '../util'; import * as openpgp from 'openpgp'; import { envRequiresBitgoPubGpgKeyConfig, getBitgoMpcGpgPubKey } from '../../tss/bitgoPubKeys'; import { getBitgoGpgPubKey } from '../opengpgUtils'; +import assert from 'assert'; /** * BaseTssUtil class which different signature schemes have to extend @@ -379,6 +380,35 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil return this.createTxRequestBase(intentOptions, apiVersion, preview, params.reqId); } + /** + * Create a sign message request + * + * @param params - the parameters for the sign message request + * @param apiVersion - the API version to use, defaults to 'full' + */ + async createSignMessageRequest( + params: IntentOptionsForMessage, + apiVersion: TxRequestVersion = 'full' + ): Promise { + assert( + params.intentType === 'signMessage', + 'Intent type must be signMessage for createMsgRequestWithSignMessageIntent' + ); + const intent: PopulatedIntentForMessageSigning = { + custodianMessageId: params.custodianMessageId, + intentType: params.intentType, + sequenceId: params.sequenceId, + comment: params.comment, + memo: params.memo?.value, + isTss: params.isTss, + messageRaw: params.messageRaw, + messageStandardType: params.messageStandardType, + messageEncoded: params.messageEncoded ?? '', + }; + + return this.createSignMessageRequestBase(intent, apiVersion, params.reqId); + } + /** * Create a tx request from params for type data signing * @@ -432,6 +462,31 @@ export default class BaseTssUtils extends MpcUtils implements ITssUtil .result(); } + /** + * Calls Bitgo API to create msg request. + * + * @private + */ + private async createSignMessageRequestBase( + intent: PopulatedIntentForMessageSigning, + apiVersion: TxRequestVersion, + reqId?: IRequestTracer + ): Promise { + const whitelistedParams = { + intent: { + ...intent, + }, + apiVersion, + }; + + const reqTracer = reqId || new RequestTracer(); + this.bitgo.setRequestTracer(reqTracer); + return this.bitgo + .post(this.bitgo.url(`/wallet/${this.wallet.id()}/msgrequests`, 2)) + .send(whitelistedParams) + .result(); + } + /** * Call delete signature shares for a txRequest, the endpoint delete the signatures and return them * diff --git a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts index 442107eb05..e038ef229d 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts @@ -9,6 +9,7 @@ import { KeyShare } from './ecdsa'; import { EcdsaTypes } from '@bitgo/sdk-lib-mpc'; import { TssEcdsaStep1ReturnMessage, TssEcdsaStep2ReturnMessage, TxRequestChallengeResponse } from '../../tss/types'; import { AShare, DShare, SShare } from '../../tss/ecdsa/types'; +import { MessageStandardType } from '../messageTypes'; export type TxRequestVersion = 'full' | 'lite'; export interface HopParams { @@ -172,6 +173,7 @@ interface IntentOptionsBase { export interface IntentOptionsForMessage extends IntentOptionsBase { messageRaw: string; messageEncoded?: string; + messageStandardType?: MessageStandardType; } export interface IntentOptionsForTypedData extends IntentOptionsBase { @@ -226,6 +228,7 @@ export interface PopulatedIntentForMessageSigning extends PopulatedIntentBase { messageRaw: string; messageEncoded: string; custodianMessageId?: string; + messageStandardType?: MessageStandardType; } export interface PopulatedIntentForTypedDataSigning extends PopulatedIntentBase { diff --git a/modules/sdk-core/src/bitgo/wallet/wallet.ts b/modules/sdk-core/src/bitgo/wallet/wallet.ts index 3c3869a5c0..39e7bafde6 100644 --- a/modules/sdk-core/src/bitgo/wallet/wallet.ts +++ b/modules/sdk-core/src/bitgo/wallet/wallet.ts @@ -34,6 +34,7 @@ import { inferAddressType, IntentOptionsForMessage, IntentOptionsForTypedData, + MessageStandardType, RequestTracer, RequestType, TokenTransferRecipientParams, @@ -2093,6 +2094,51 @@ export class Wallet implements IWallet { return this.signMessageTss(presign); } + /** + * Prepares and creates a sign message request for TSS wallets, that can be used later for signing. + * + * @param params - Parameters for creating the sign message request + * @returns Promise - The created transaction request for signing a message + */ + async createSignMessageRequest(params: { + messageRaw: string; + messageStandardType?: MessageStandardType; + custodianMessageId?: string; + reqId?: RequestTracer; + }): Promise { + if (this._wallet.multisigType !== 'tss') { + throw new Error('Message signing only supported for TSS wallets'); + } + + if (!this.baseCoin.supportsMessageSigning()) { + throw new Error(`Message signing not supported for ${this.baseCoin.getFullName()}`); + } + + if (!params.messageRaw) { + throw new Error('message required to create message sign request'); + } + + const reqId = params.reqId || new RequestTracer(); + + try { + const intentOption: IntentOptionsForMessage = { + custodianMessageId: params.custodianMessageId, + reqId, + intentType: 'signMessage', + isTss: true, + messageRaw: params.messageRaw, + messageStandardType: params.messageStandardType, + }; + + if (!this.tssUtils) { + throw new Error('TSS utilities not available for this wallet'); + } + return await this.tssUtils.createSignMessageRequest(intentOption); + } catch (error) { + throw new Error(`Failed to create message sign request: ${error}`); + } + } + /** * Get the user private key from either a derivation or an encrypted keychain * @param [params.keychain / params.key] (object) or params.prv (string)