Skip to content

Commit b4fa6f8

Browse files
committed
chore: build and sign message sample script
TICKET: COIN-4593
1 parent 2152ad4 commit b4fa6f8

File tree

7 files changed

+153
-61
lines changed

7 files changed

+153
-61
lines changed

examples/ts/build-message.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/**
2+
* Pre-build a message from the wallet
3+
*
4+
* This tool will help you see how to use the BitGo API to easily build
5+
* a message from a wallet.
6+
*
7+
* Copyright 2025, BitGo, Inc. All Rights Reserved.
8+
*/
9+
10+
import {BitGoAPI} from '@bitgo/sdk-api';
11+
import {Hteth} from "@bitgo/sdk-coin-eth";
12+
import {MessageStandardType} from "@bitgo/sdk-core"; // Replace with your given coin (e.g. Ltc, Tltc)
13+
require('dotenv').config({ path: '../../.env' });
14+
15+
const bitgo = new BitGoAPI({
16+
accessToken: process.env.TESTNET_ACCESS_TOKEN,
17+
env: 'test', // Change this to env: 'production' when you are ready for production
18+
});
19+
20+
// Set the coin name to match the blockchain and network
21+
// doge = dogecoin, tdoge = testnet dogecoin
22+
const coin = 'hteth';
23+
bitgo.register(coin, Hteth.createInstance);
24+
25+
const id = '';
26+
27+
async function main() {
28+
const wallet = await bitgo.coin(coin).wallets().get({ id });
29+
console.log(`Wallet label: ${wallet.label()}`);
30+
31+
const txRequest = await wallet.buildSignMessageRequest({
32+
message: {
33+
messageRaw: 'Hello, BitGo!',
34+
messageStandardType: MessageStandardType.EIP191,
35+
},
36+
});
37+
console.dir(txRequest);
38+
}
39+
40+
main().catch((e) => console.log(e));

examples/ts/sign-message.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Sign a Message from an MPC wallet at BitGo.
3+
*
4+
* Copyright 2025, BitGo, Inc. All Rights Reserved.
5+
*/
6+
import { BitGo } from 'bitgo';
7+
import { MessageStandardType } from '@bitgo/sdk-core';
8+
9+
const bitgo = new BitGo({ env: 'test' });
10+
11+
const coin = 'hteth';
12+
const basecoin = bitgo.coin(coin);
13+
const accessToken = '';
14+
const walletId = '';
15+
const walletPassphrase = '';
16+
17+
async function signMessage(): Promise<void> {
18+
await bitgo.authenticateWithAccessToken({ accessToken });
19+
const walletInstance = await basecoin.wallets().get({ id: walletId });
20+
21+
const messageTxn = await walletInstance.signMessage({
22+
message: {
23+
messageRaw: 'Hello BitGo!',
24+
messageStandardType: MessageStandardType.EIP191,
25+
},
26+
walletPassphrase,
27+
});
28+
29+
console.log(messageTxn);
30+
}
31+
32+
signMessage().catch(console.error);

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

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
Keychains,
2020
KeyType,
2121
ManageUnspentsOptions,
22+
MessageStandardType,
2223
MessageTypes,
2324
PopulatedIntent,
2425
PrebuildTransactionWithIntentOptions,
@@ -3414,6 +3415,10 @@ describe('V2 Wallet:', function () {
34143415

34153416
describe('Message Signing', function () {
34163417
const txHash = '0xrrrsss1b';
3418+
const messageRaw = 'hello world';
3419+
const messageEncoded = Buffer.from(`\u0019Ethereum Signed Message:\n${messageRaw.length}${messageRaw}`).toString(
3420+
'hex'
3421+
);
34173422
const txRequestForMessageSigning: TxRequest = {
34183423
txRequestId: reqId.toString(),
34193424
transactions: [],
@@ -3438,19 +3443,19 @@ describe('V2 Wallet:', function () {
34383443
signatureShares: [{ from: SignatureShareType.USER, to: SignatureShareType.USER, share: '' }],
34393444
combineSigShare: '0:rrr:sss:3',
34403445
txHash,
3446+
messageEncoded,
34413447
},
34423448
],
34433449
};
34443450
let signTxRequestForMessage;
34453451
const messageSigningCoins = ['teth', 'tpolygon'];
3446-
const messageRaw = 'test';
34473452
const expected: SignedMessage = {
34483453
txRequestId: reqId.toString(),
34493454
txHash,
34503455
signature: txHash,
34513456
messageRaw,
34523457
coin: 'teth',
3453-
messageEncoded: Buffer.from('\u0019Ethereum Signed Message:\n4test').toString('hex'),
3458+
messageEncoded,
34543459
};
34553460

34563461
beforeEach(async function () {
@@ -3480,8 +3485,11 @@ describe('V2 Wallet:', function () {
34803485

34813486
it('sol create signMessage tx request', async function () {
34823487
await tssSolWallet
3483-
.createSignMessageRequest({
3484-
messageRaw,
3488+
.buildSignMessageRequest({
3489+
message: {
3490+
messageRaw,
3491+
messageStandardType: MessageStandardType.EIP191,
3492+
},
34853493
})
34863494
.should.be.rejectedWith('Message signing not supported for Testnet Solana');
34873495
});
@@ -3496,13 +3504,16 @@ describe('V2 Wallet:', function () {
34963504
it('should create tx Request with signMessage intent', async function () {
34973505
nock(bgUrl).post(`/api/v2/wallet/${tssEthWallet.id()}/msgrequests`).reply(200, txRequestForMessageSigning);
34983506

3499-
const txRequest = await tssEthWallet.createSignMessageRequest({
3500-
messageRaw,
3507+
const txRequest = await tssEthWallet.buildSignMessageRequest({
3508+
message: {
3509+
messageRaw,
3510+
messageStandardType: MessageStandardType.EIP191,
3511+
},
35013512
});
35023513
txRequest.should.deepEqual(txRequestForMessageSigning);
35033514
});
35043515

3505-
it('should sign message', async function () {
3516+
it(`[${coinName}] should sign message`, async function () {
35063517
const signMessageTssSpy = sinon.spy(tssEthWallet, 'signMessageTss' as any);
35073518
nock(bgUrl)
35083519
.get(
@@ -3514,7 +3525,7 @@ describe('V2 Wallet:', function () {
35143525

35153526
const signMessage = await tssEthWallet.signMessage({
35163527
reqId,
3517-
message: { messageRaw, txRequestId },
3528+
message: { messageRaw, txRequestId, messageStandardType: MessageStandardType.EIP191 },
35183529
prv: 'secretKey',
35193530
});
35203531
signMessage.should.deepEqual(expectedWithCoinField);
@@ -3524,14 +3535,14 @@ describe('V2 Wallet:', function () {
35243535
);
35253536
});
35263537

3527-
it('should sign message when custodianMessageId is provided', async function () {
3538+
it(`[${coinName}] should sign message when custodianMessageId is provided`, async function () {
35283539
const signMessageTssSpy = sinon.spy(tssEthWallet, 'signMessageTss' as any);
3529-
nock(bgUrl).post(`/api/v2/wallet/${tssEthWallet.id()}/txrequests`).reply(200, txRequestForMessageSigning);
3540+
nock(bgUrl).post(`/api/v2/wallet/${tssEthWallet.id()}/msgrequests`).reply(200, txRequestForMessageSigning);
35303541

35313542
const signMessage = await tssEthWallet.signMessage({
35323543
custodianMessageId: 'unittest',
35333544
reqId,
3534-
message: { messageRaw },
3545+
message: { messageRaw, messageStandardType: MessageStandardType.EIP191 },
35353546
prv: 'secretKey',
35363547
});
35373548
signMessage.should.deepEqual(expectedWithCoinField);
@@ -3541,13 +3552,13 @@ describe('V2 Wallet:', function () {
35413552
);
35423553
});
35433554

3544-
it('should sign message when txRequestId not provided', async function () {
3555+
it(`[${coinName}] should sign message when txRequestId not provided`, async function () {
35453556
const signMessageTssSpy = sinon.spy(tssEthWallet, 'signMessageTss' as any);
3546-
nock(bgUrl).post(`/api/v2/wallet/${tssEthWallet.id()}/txrequests`).reply(200, txRequestForMessageSigning);
3557+
nock(bgUrl).post(`/api/v2/wallet/${tssEthWallet.id()}/msgrequests`).reply(200, txRequestForMessageSigning);
35473558

35483559
const signMessage = await tssEthWallet.signMessage({
35493560
reqId,
3550-
message: { messageRaw },
3561+
message: { messageRaw, messageStandardType: MessageStandardType.EIP191 },
35513562
prv: 'secretKey',
35523563
});
35533564
signMessage.should.deepEqual(expectedWithCoinField);
@@ -3561,7 +3572,7 @@ describe('V2 Wallet:', function () {
35613572
await tssEthWallet
35623573
.signMessage({
35633574
reqId,
3564-
message: { messageRaw, txRequestId },
3575+
message: { messageRaw, txRequestId, messageStandardType: MessageStandardType.EIP191 },
35653576
prv: '',
35663577
})
35673578
.should.be.rejectedWith('keychain does not have property encryptedPrv');

modules/sdk-core/src/bitgo/baseCoin/iBaseCoin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ export interface SignedMessage {
419419
signature: string;
420420
messageRaw: string;
421421
messageEncoded?: string;
422+
messageStandardType?: MessageStandardType;
422423
txRequestId: string;
423424
}
424425

modules/sdk-core/src/bitgo/utils/tss/baseTSSUtils.ts

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { IRequestTracer } from '../../../api';
2+
import * as openpgp from 'openpgp';
23
import { Key, readKey, SerializedKeyPair } from 'openpgp';
34
import { IBaseCoin, KeychainsTriplet } from '../../baseCoin';
45
import { BitGoBase } from '../../bitgoBase';
@@ -10,40 +11,40 @@ import * as _ from 'lodash';
1011
import {
1112
BitgoGPGPublicKey,
1213
BitgoHeldBackupKeyShare,
14+
CommitmentShareRecord,
15+
CreateBitGoKeychainParamsBase,
16+
CreateKeychainParamsBase,
17+
CustomCommitmentGeneratingFunction,
1318
CustomGShareGeneratingFunction,
19+
CustomKShareGeneratingFunction,
20+
CustomMPCv2SigningRound1GeneratingFunction,
21+
CustomMPCv2SigningRound2GeneratingFunction,
22+
CustomMPCv2SigningRound3GeneratingFunction,
23+
CustomMuDeltaShareGeneratingFunction,
24+
CustomPaillierModulusGetterFunction,
1425
CustomRShareGeneratingFunction,
26+
CustomSShareGeneratingFunction,
27+
EncryptedSignerShareRecord,
28+
IntentOptionsForMessage,
29+
IntentOptionsForTypedData,
1530
ITssUtils,
31+
PopulatedIntentForMessageSigning,
32+
PopulatedIntentForTypedDataSigning,
1633
PrebuildTransactionWithIntentOptions,
34+
RequestType,
1735
SignatureShareRecord,
1836
TSSParams,
19-
TxRequest,
20-
TxRequestVersion,
21-
CreateKeychainParamsBase,
22-
IntentOptionsForMessage,
23-
PopulatedIntentForMessageSigning,
24-
IntentOptionsForTypedData,
25-
PopulatedIntentForTypedDataSigning,
26-
CreateBitGoKeychainParamsBase,
27-
CommitmentShareRecord,
28-
EncryptedSignerShareRecord,
29-
CustomCommitmentGeneratingFunction,
3037
TSSParamsForMessage,
31-
RequestType,
32-
CustomPaillierModulusGetterFunction,
33-
CustomKShareGeneratingFunction,
34-
CustomMuDeltaShareGeneratingFunction,
35-
CustomSShareGeneratingFunction,
36-
CustomMPCv2SigningRound1GeneratingFunction,
37-
CustomMPCv2SigningRound2GeneratingFunction,
38-
CustomMPCv2SigningRound3GeneratingFunction,
3938
TSSParamsWithPrv,
39+
TxRequest,
40+
TxRequestVersion,
4041
} from './baseTypes';
4142
import { GShare, SignShare } from '../../../account-lib/mpc/tss';
4243
import { RequestTracer } from '../util';
43-
import * as openpgp from 'openpgp';
4444
import { envRequiresBitgoPubGpgKeyConfig, getBitgoMpcGpgPubKey } from '../../tss/bitgoPubKeys';
4545
import { getBitgoGpgPubKey } from '../opengpgUtils';
4646
import assert from 'assert';
47+
import { MessageStandardType } from '../messageTypes';
4748

4849
/**
4950
* BaseTssUtil class which different signature schemes have to extend
@@ -356,6 +357,7 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
356357

357358
/**
358359
* Create a tx request from params for message signing
360+
* @deprecated Use createSignMessageRequest instead
359361
*
360362
* @param params
361363
* @param apiVersion
@@ -386,7 +388,7 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
386388
* @param params - the parameters for the sign message request
387389
* @param apiVersion - the API version to use, defaults to 'full'
388390
*/
389-
async createSignMessageRequest(
391+
async buildSignMessageRequest(
390392
params: IntentOptionsForMessage,
391393
apiVersion: TxRequestVersion = 'full'
392394
): Promise<TxRequest> {
@@ -402,11 +404,11 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
402404
memo: params.memo?.value,
403405
isTss: params.isTss,
404406
messageRaw: params.messageRaw,
405-
messageStandardType: params.messageStandardType,
407+
messageStandardType: params.messageStandardType ?? MessageStandardType.UNKNOWN,
406408
messageEncoded: params.messageEncoded ?? '',
407409
};
408410

409-
return this.createSignMessageRequestBase(intent, apiVersion, params.reqId);
411+
return this.buildSignMessageRequestBase(intent, apiVersion, params.reqId);
410412
}
411413

412414
/**
@@ -467,7 +469,7 @@ export default class BaseTssUtils<KeyShare> extends MpcUtils implements ITssUtil
467469
*
468470
* @private
469471
*/
470-
private async createSignMessageRequestBase(
472+
private async buildSignMessageRequestBase(
471473
intent: PopulatedIntentForMessageSigning,
472474
apiVersion: TxRequestVersion,
473475
reqId?: IRequestTracer

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
CustomSShareGeneratingFunction,
2828
TokenEnablement,
2929
TokenTransferRecipientParams,
30+
TxRequest,
3031
} from '../utils';
3132
import { SerializedNtilde } from '../../account-lib/mpc/tss/ecdsa/types';
3233
import { IAddressBook } from '../address-book';
@@ -909,6 +910,7 @@ export interface IWallet {
909910
sendTokenEnablement(params?: PrebuildAndSignTransactionOptions): Promise<any>;
910911
sendTokenEnablements(params?: BuildTokenEnablementOptions): Promise<any>;
911912
signMessage(params: WalletSignMessageOptions): Promise<SignedMessage>;
913+
buildSignMessageRequest(params: WalletSignMessageOptions): Promise<TxRequest>;
912914
signTypedData(params: WalletSignTypedDataOptions): Promise<SignedMessage>;
913915
fetchCrossChainUTXOs(params: FetchCrossChainUTXOsOptions): Promise<CrossChainUTXO[]>;
914916
getChallengesForEcdsaSigning(): Promise<WalletEcdsaChallenges>;

0 commit comments

Comments
 (0)