diff --git a/modules/bitgo/test/v2/unit/wallet.ts b/modules/bitgo/test/v2/unit/wallet.ts index d50fce6595..0fcee2ee45 100644 --- a/modules/bitgo/test/v2/unit/wallet.ts +++ b/modules/bitgo/test/v2/unit/wallet.ts @@ -9,33 +9,34 @@ import * as nock from 'nock'; import * as _ from 'lodash'; import { + BaseTssUtils, common, CustomSigningFunction, + Ecdsa, ECDSAUtils, EDDSAUtils, + GetUserPrvOptions, + Keychains, + KeyType, + ManageUnspentsOptions, + MessageType, + 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 +3468,25 @@ 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, + messageType: MessageType.STRING, + }) + .should.be.rejectedWith('Message signing not supported for Testnet Solana'); + }); }); messageSigningCoins.map((coinName) => { @@ -3483,6 +3495,16 @@ 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, + messageType: MessageType.STRING, + }); + 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 ce6b173515..9009cc0eb3 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, + messageType: params.messageType, + 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..6d2f884849 100644 --- a/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts +++ b/modules/sdk-core/src/bitgo/utils/tss/baseTypes.ts @@ -54,6 +54,12 @@ export enum MPCType { ECDSA = 'ecdsa', } +export enum MessageType { + STRING = 'string', + EIP712 = 'eip712', + CIP8 = 'cip8', +} + export interface CustomPaillierModulusGetterFunction { (params: { txRequest: TxRequest }): Promise<{ userPaillierModulus: string; @@ -172,6 +178,7 @@ interface IntentOptionsBase { export interface IntentOptionsForMessage extends IntentOptionsBase { messageRaw: string; messageEncoded?: string; + messageType?: MessageType | string; } export interface IntentOptionsForTypedData extends IntentOptionsBase { @@ -226,6 +233,7 @@ export interface PopulatedIntentForMessageSigning extends PopulatedIntentBase { messageRaw: string; messageEncoded: string; custodianMessageId?: string; + messageType?: MessageType | string; } 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..3e147842a9 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, + MessageType, 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; + messageType?: MessageType | string; + 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, + messageType: params.messageType, + }; + + 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)