Skip to content

fix(sdk-core): skip message whitelist check #6667

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
8 changes: 4 additions & 4 deletions modules/account-lib/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ export { Vet };
import * as CosmosSharedCoin from '@bitgo/sdk-coin-cosmos';
export { CosmosSharedCoin };

import { validateAgainstMessageTemplates, MIDNIGHT_TNC_HASH } from './utils';
import { MIDNIGHT_TNC_HASH } from './utils';
export { MIDNIGHT_TNC_HASH };

const coinBuilderMap = {
Expand Down Expand Up @@ -431,11 +431,11 @@ export async function verifyMessage(
const messageBuilder = messageBuilderFactory.getMessageBuilder(messageStandardType);
messageBuilder.setPayload(messageRaw);
const message = await messageBuilder.build();
const isValidMessageEncoded = await message.verifyEncodedPayload(messageEncoded, metadata);
if (!isValidMessageEncoded) {
const isValidRawMessage = message.verifyRawMessage(messageRaw);
if (!isValidRawMessage) {
return false;
}
return validateAgainstMessageTemplates(messageRaw);
return await message.verifyEncodedPayload(messageEncoded, metadata);
} catch (e) {
console.error(`Error verifying message for coin ${coinName}:`, e);
return false;
Expand Down
35 changes: 35 additions & 0 deletions modules/account-lib/test/unit/verifyMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,20 @@ describe('verifyMessage', () => {
);
should.equal(result, false);
});

it('should return false if encoded payload verification fails', async () => {
const coinName = 'eth';
const messageRaw = testnetMessageRaw;
const invalidEncodedHex = '0123456789abcdef'; // Invalid encoded payload

const result = await accountLib.verifyMessage(
coinName,
messageRaw,
invalidEncodedHex,
MessageStandardType.EIP191,
);
should.equal(result, false);
});
});

describe('CIP8 Message', function () {
Expand Down Expand Up @@ -86,5 +100,26 @@ describe('verifyMessage', () => {
);
should.equal(result, true);
});

it('should return false when raw message validation fails for ADA', async () => {
const coinName = 'ada';
const invalidMessageRaw = 'Invalid ADA message format';
cip8MessageBuilder.setPayload(testnetMessageRaw);
cip8MessageBuilder.addSigner(adaTestnetOriginAddress);
const message = await cip8MessageBuilder.build();
const messageEncodedHex = (await message.getSignablePayload()).toString('hex');

const metadata = {
signers: [adaTestnetOriginAddress],
};
const result = await accountLib.verifyMessage(
coinName,
invalidMessageRaw,
messageEncodedHex,
MessageStandardType.CIP8,
metadata,
);
should.equal(result, false);
});
});
});
21 changes: 21 additions & 0 deletions modules/sdk-coin-ada/src/lib/messages/cip8/cip8Message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,27 @@ export class Cip8Message extends BaseMessage {
return signablePayloadHex === messageEncodedHex;
}

/**
* Verifies whether a raw message meets CIP-8 specific requirements for Midnight Glacier Drop claims
* Only allows messages that match the exact Midnight Glacier Drop claim format
* @param rawMessage The raw message content to verify as a string
* @returns True if the raw message matches the expected Midnight Glacier Drop claim format, false otherwise
* @example
* ```typescript
* // Valid format: "STAR 100 to addr1abc123... 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b"
* const message = await builder.build();
* const isValid = message.verifyRawMessage("STAR 100 to addr1xyz... 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b");
* // Returns true only for properly formatted Midnight Glacier Drop claims
* ```
*/
verifyRawMessage(rawMessage: string): boolean {
const MIDNIGHT_TNC_HASH = '31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';
const MIDNIGHT_GLACIER_DROP_CLAIM_MESSAGE_TEMPLATE = `STAR \\d+ to addr(?:1|_test1)[a-z0-9]{50,} ${MIDNIGHT_TNC_HASH}`;

const regex = new RegExp(`^${MIDNIGHT_GLACIER_DROP_CLAIM_MESSAGE_TEMPLATE}$`, 's');
return regex.test(rawMessage);
}

/**
* Validates required fields and returns common setup objects
* @private
Expand Down
29 changes: 29 additions & 0 deletions modules/sdk-coin-ada/test/resources/cip8Resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,33 @@ export const cip8TestResources = {
signablePayloads: {
simple: 'a0', // Example CBOR hex for simple message (will be replaced with actual values)
},

// Midnight Glacier Drop claim message test data
midnightGlacierDrop: {
validMessages: {
mainnet:
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
testnet:
'STAR 250 to addr_test1qpxecfjurjtcnalwy6gxcqzp09je55gvfv79hghqst8p7p6dnsn9c8yh38m7uf5sdsqyz7t9nfgscjeutw3wpqkwrursutfm7h 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
},
invalidMessages: {
missingStarPrefix:
'100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
invalidNumber:
'STAR abc to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
invalidAddress: 'STAR 100 to invalid_address 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
shortAddress: 'STAR 100 to addr1short 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
wrongHash:
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an wronghashhere',
missingHash:
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an',
extraContent:
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b extra content',
caseSensitive:
'star 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
},
tnc: {
hash: '31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
},
},
};
107 changes: 107 additions & 0 deletions modules/sdk-coin-ada/test/unit/messages/cip8/cip8Message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,111 @@ describe('Cip8Message', function () {
should.throws(() => message.getBroadcastableSignatures(), /Payload is required to build a CIP8 message/);
});
});

describe('verifyRawMessage', function () {
it('should return true for valid Midnight Glacier Drop claim message', function () {
const message = new Cip8Message(createDefaultMessageOptions());
const validMessage =
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';

const result = message.verifyRawMessage(validMessage);
result.should.be.true();
});

it('should return true for valid Midnight Glacier Drop claim message with testnet address', function () {
const message = new Cip8Message(createDefaultMessageOptions());
const validTestnetMessage =
'STAR 250 to addr_test1qpxecfjurjtcnalwy6gxcqzp09je55gvfv79hghqst8p7p6dnsn9c8yh38m7uf5sdsqyz7t9nfgscjeutw3wpqkwrursutfm7h 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';

const result = message.verifyRawMessage(validTestnetMessage);
result.should.be.true();
});

it('should return false for message without STAR prefix', function () {
const message = new Cip8Message(createDefaultMessageOptions());
const invalidMessage =
'100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';

const result = message.verifyRawMessage(invalidMessage);
result.should.be.false();
});

it('should return false for message with invalid number format', function () {
const message = new Cip8Message(createDefaultMessageOptions());
const invalidMessage =
'STAR abc to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';

const result = message.verifyRawMessage(invalidMessage);
result.should.be.false();
});

it('should return false for message with invalid address format', function () {
const message = new Cip8Message(createDefaultMessageOptions());
const invalidMessage =
'STAR 100 to invalid_address 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';

const result = message.verifyRawMessage(invalidMessage);
result.should.be.false();
});

it('should return false for message with short address', function () {
const message = new Cip8Message(createDefaultMessageOptions());
const invalidMessage = 'STAR 100 to addr1short 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';

const result = message.verifyRawMessage(invalidMessage);
result.should.be.false();
});

it('should return false for message with wrong TnC hash', function () {
const message = new Cip8Message(createDefaultMessageOptions());
const invalidMessage =
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an wronghashhere';

const result = message.verifyRawMessage(invalidMessage);
result.should.be.false();
});

it('should return false for message with missing TnC hash', function () {
const message = new Cip8Message(createDefaultMessageOptions());
const invalidMessage =
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an';

const result = message.verifyRawMessage(invalidMessage);
result.should.be.false();
});

it('should return false for message with extra content', function () {
const message = new Cip8Message(createDefaultMessageOptions());
const invalidMessage =
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b extra content';

const result = message.verifyRawMessage(invalidMessage);
result.should.be.false();
});

it('should return false for empty message', function () {
const message = new Cip8Message(createDefaultMessageOptions());
const emptyMessage = '';

const result = message.verifyRawMessage(emptyMessage);
result.should.be.false();
});

it('should return false for completely different message format', function () {
const message = new Cip8Message(createDefaultMessageOptions());
const differentMessage = 'Hello, this is a regular message';

const result = message.verifyRawMessage(differentMessage);
result.should.be.false();
});

it('should handle case sensitivity correctly', function () {
const message = new Cip8Message(createDefaultMessageOptions());
const caseInsensitiveMessage =
'star 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';

const result = message.verifyRawMessage(caseInsensitiveMessage);
result.should.be.false(); // Should be case sensitive
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -180,4 +180,14 @@ export abstract class BaseMessage implements IMessage {
}
return signablePayloadHex === messageEncodedHex;
}

/**
* Verifies whether a raw message payload meets coin-specific format requirements
* Base implementation validates that the message is not null, undefined, or empty
* @param rawMessage The raw message content to verify as a string
* @returns True if the raw message is valid and can be safely processed, false otherwise
*/
verifyRawMessage(rawMessage: string): boolean {
return Boolean(rawMessage?.trim());
}
}
14 changes: 14 additions & 0 deletions modules/sdk-core/src/account-lib/baseCoin/messages/iface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,20 @@ export interface IMessage {
* @returns A Promise resolving to true if the message is valid, false otherwise
*/
verifyEncodedPayload(messageEncodedHex: string, metadata?: Record<string, unknown>): Promise<boolean>;

/**
* Verifies whether a raw message payload meets coin-specific format requirements
* This method performs validation on the raw message content before signing
* @param rawMessage The raw message content to verify as a string
* @returns True if the raw message is valid and can be safely processed, false otherwise
* @example
* ```typescript
* const message = await builder.build();
* const isValid = message.verifyRawMessage("Hello World");
* // Returns true for most coins, false for coins with strict format requirements
* ```
*/
verifyRawMessage(rawMessage: string): boolean;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,4 +237,98 @@ describe('Base Message', () => {
should.deepEqual(parsed, expectedBroadcastString);
});
});

describe('verifyEncodedPayload', () => {
let message: TestMessage;

beforeEach(() => {
message = new TestMessage({
coinConfig,
payload: 'test payload',
});
});

it('should return true when encoded message matches signable payload', async () => {
const signablePayload = await message.getSignablePayload();
const expectedHex = (signablePayload as Buffer).toString('hex');

const result = await message.verifyEncodedPayload(expectedHex);
should.equal(result, true);
});

it('should return false when encoded message does not match signable payload', async () => {
const wrongHex = '1234567890abcdef';

const result = await message.verifyEncodedPayload(wrongHex);
should.equal(result, false);
});

it('should handle string signable payload', async () => {
// Create a custom test message that returns string payload
class StringTestMessage extends TestMessage {
async getSignablePayload(): Promise<string | Buffer> {
return 'string payload';
}
}

const messageWithStringPayload = new StringTestMessage({
coinConfig,
payload: 'test',
});

const result = await messageWithStringPayload.verifyEncodedPayload('string payload');
should.equal(result, true);
});

it('should accept optional metadata parameter', async () => {
const signablePayload = await message.getSignablePayload();
const expectedHex = (signablePayload as Buffer).toString('hex');
const metadata = { chainId: 1, version: '1.0' };

const result = await message.verifyEncodedPayload(expectedHex, metadata);
should.equal(result, true);
});
});

describe('verifyRawMessage', () => {
let message: TestMessage;

beforeEach(() => {
message = new TestMessage({
coinConfig,
payload: 'test payload',
});
});

it('should return true for any non-empty raw message (base implementation)', () => {
const result = message.verifyRawMessage('Any message content');
should.equal(result, true);
});

it('should return false for empty string', () => {
const result = message.verifyRawMessage('');
should.equal(result, false);
});

it('should return false for null', () => {
const result = message.verifyRawMessage(null as any);
should.equal(result, false);
});

it('should return false for undefined', () => {
const result = message.verifyRawMessage(undefined as any);
should.equal(result, false);
});

it('should return false for whitespace-only string', () => {
const result = message.verifyRawMessage(' \t\n\r ');
should.equal(result, false);
});

it('should return true for JSON format', () => {
const jsonMessage = JSON.stringify({ message: 'test', data: [1, 2, 3] });
const result = message.verifyRawMessage(jsonMessage);
should.equal(result, true);
});
});
});
Loading