Skip to content

Commit 14ac60e

Browse files
committed
fix(sdk-core): coins specific message sign validate
SC-2419 TICKET: SC-2419
1 parent 2b3634d commit 14ac60e

File tree

7 files changed

+226
-0
lines changed

7 files changed

+226
-0
lines changed

modules/account-lib/src/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,10 @@ export async function verifyMessage(
431431
const messageBuilder = messageBuilderFactory.getMessageBuilder(messageStandardType);
432432
messageBuilder.setPayload(messageRaw);
433433
const message = await messageBuilder.build();
434+
const isValidRawMessage = message.verifyRawMessage(messageRaw);
435+
if (!isValidRawMessage) {
436+
return false;
437+
}
434438
const isValidMessageEncoded = await message.verifyEncodedPayload(messageEncoded, metadata);
435439
if (!isValidMessageEncoded) {
436440
return false;

modules/account-lib/test/unit/verifyMessage.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,20 @@ describe('verifyMessage', () => {
5353
);
5454
should.equal(result, false);
5555
});
56+
57+
it('should return false if encoded payload verification fails', async () => {
58+
const coinName = 'eth';
59+
const messageRaw = testnetMessageRaw;
60+
const invalidEncodedHex = '0123456789abcdef'; // Invalid encoded payload
61+
62+
const result = await accountLib.verifyMessage(
63+
coinName,
64+
messageRaw,
65+
invalidEncodedHex,
66+
MessageStandardType.EIP191,
67+
);
68+
should.equal(result, false);
69+
});
5670
});
5771

5872
describe('CIP8 Message', function () {
@@ -86,5 +100,26 @@ describe('verifyMessage', () => {
86100
);
87101
should.equal(result, true);
88102
});
103+
104+
it('should return false when raw message validation fails for ADA', async () => {
105+
const coinName = 'ada';
106+
const invalidMessageRaw = 'Invalid ADA message format';
107+
cip8MessageBuilder.setPayload(testnetMessageRaw);
108+
cip8MessageBuilder.addSigner(adaTestnetOriginAddress);
109+
const message = await cip8MessageBuilder.build();
110+
const messageEncodedHex = (await message.getSignablePayload()).toString('hex');
111+
112+
const metadata = {
113+
signers: [adaTestnetOriginAddress],
114+
};
115+
const result = await accountLib.verifyMessage(
116+
coinName,
117+
invalidMessageRaw,
118+
messageEncodedHex,
119+
MessageStandardType.CIP8,
120+
metadata,
121+
);
122+
should.equal(result, false);
123+
});
89124
});
90125
});

modules/sdk-coin-ada/src/lib/messages/cip8/cip8Message.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,27 @@ export class Cip8Message extends BaseMessage {
8686
return signablePayloadHex === messageEncodedHex;
8787
}
8888

89+
/**
90+
* Verifies whether a raw message meets CIP-8 specific requirements for Midnight Glacier Drop claims
91+
* Only allows messages that match the exact Midnight Glacier Drop claim format
92+
* @param rawMessage The raw message content to verify as a string
93+
* @returns True if the raw message matches the expected Midnight Glacier Drop claim format, false otherwise
94+
* @example
95+
* ```typescript
96+
* // Valid format: "STAR 100 to addr1abc123... 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b"
97+
* const message = await builder.build();
98+
* const isValid = message.verifyRawMessage("STAR 100 to addr1xyz... 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b");
99+
* // Returns true only for properly formatted Midnight Glacier Drop claims
100+
* ```
101+
*/
102+
verifyRawMessage(rawMessage: string): boolean {
103+
const MIDNIGHT_TNC_HASH = '31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';
104+
const MIDNIGHT_GLACIER_DROP_CLAIM_MESSAGE_TEMPLATE = `STAR \\d+ to addr(?:1|_test1)[a-z0-9]{50,} ${MIDNIGHT_TNC_HASH}`;
105+
106+
const regex = new RegExp(`^${MIDNIGHT_GLACIER_DROP_CLAIM_MESSAGE_TEMPLATE}$`, 's');
107+
return regex.test(rawMessage);
108+
}
109+
89110
/**
90111
* Validates required fields and returns common setup objects
91112
* @private

modules/sdk-coin-ada/test/resources/cip8Resources.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,33 @@ export const cip8TestResources = {
4545
signablePayloads: {
4646
simple: 'a0', // Example CBOR hex for simple message (will be replaced with actual values)
4747
},
48+
49+
// Midnight Glacier Drop claim message test data
50+
midnightGlacierDrop: {
51+
validMessages: {
52+
mainnet:
53+
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
54+
testnet:
55+
'STAR 250 to addr_test1qpxecfjurjtcnalwy6gxcqzp09je55gvfv79hghqst8p7p6dnsn9c8yh38m7uf5sdsqyz7t9nfgscjeutw3wpqkwrursutfm7h 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
56+
},
57+
invalidMessages: {
58+
missingStarPrefix:
59+
'100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
60+
invalidNumber:
61+
'STAR abc to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
62+
invalidAddress: 'STAR 100 to invalid_address 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
63+
shortAddress: 'STAR 100 to addr1short 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
64+
wrongHash:
65+
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an wronghashhere',
66+
missingHash:
67+
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an',
68+
extraContent:
69+
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b extra content',
70+
caseSensitive:
71+
'star 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
72+
},
73+
tnc: {
74+
hash: '31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b',
75+
},
76+
},
4877
};

modules/sdk-coin-ada/test/unit/messages/cip8/cip8Message.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,111 @@ describe('Cip8Message', function () {
154154
should.throws(() => message.getBroadcastableSignatures(), /Payload is required to build a CIP8 message/);
155155
});
156156
});
157+
158+
describe('verifyRawMessage', function () {
159+
it('should return true for valid Midnight Glacier Drop claim message', function () {
160+
const message = new Cip8Message(createDefaultMessageOptions());
161+
const validMessage =
162+
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';
163+
164+
const result = message.verifyRawMessage(validMessage);
165+
result.should.be.true();
166+
});
167+
168+
it('should return true for valid Midnight Glacier Drop claim message with testnet address', function () {
169+
const message = new Cip8Message(createDefaultMessageOptions());
170+
const validTestnetMessage =
171+
'STAR 250 to addr_test1qpxecfjurjtcnalwy6gxcqzp09je55gvfv79hghqst8p7p6dnsn9c8yh38m7uf5sdsqyz7t9nfgscjeutw3wpqkwrursutfm7h 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';
172+
173+
const result = message.verifyRawMessage(validTestnetMessage);
174+
result.should.be.true();
175+
});
176+
177+
it('should return false for message without STAR prefix', function () {
178+
const message = new Cip8Message(createDefaultMessageOptions());
179+
const invalidMessage =
180+
'100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';
181+
182+
const result = message.verifyRawMessage(invalidMessage);
183+
result.should.be.false();
184+
});
185+
186+
it('should return false for message with invalid number format', function () {
187+
const message = new Cip8Message(createDefaultMessageOptions());
188+
const invalidMessage =
189+
'STAR abc to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';
190+
191+
const result = message.verifyRawMessage(invalidMessage);
192+
result.should.be.false();
193+
});
194+
195+
it('should return false for message with invalid address format', function () {
196+
const message = new Cip8Message(createDefaultMessageOptions());
197+
const invalidMessage =
198+
'STAR 100 to invalid_address 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';
199+
200+
const result = message.verifyRawMessage(invalidMessage);
201+
result.should.be.false();
202+
});
203+
204+
it('should return false for message with short address', function () {
205+
const message = new Cip8Message(createDefaultMessageOptions());
206+
const invalidMessage = 'STAR 100 to addr1short 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';
207+
208+
const result = message.verifyRawMessage(invalidMessage);
209+
result.should.be.false();
210+
});
211+
212+
it('should return false for message with wrong TnC hash', function () {
213+
const message = new Cip8Message(createDefaultMessageOptions());
214+
const invalidMessage =
215+
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an wronghashhere';
216+
217+
const result = message.verifyRawMessage(invalidMessage);
218+
result.should.be.false();
219+
});
220+
221+
it('should return false for message with missing TnC hash', function () {
222+
const message = new Cip8Message(createDefaultMessageOptions());
223+
const invalidMessage =
224+
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an';
225+
226+
const result = message.verifyRawMessage(invalidMessage);
227+
result.should.be.false();
228+
});
229+
230+
it('should return false for message with extra content', function () {
231+
const message = new Cip8Message(createDefaultMessageOptions());
232+
const invalidMessage =
233+
'STAR 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b extra content';
234+
235+
const result = message.verifyRawMessage(invalidMessage);
236+
result.should.be.false();
237+
});
238+
239+
it('should return false for empty message', function () {
240+
const message = new Cip8Message(createDefaultMessageOptions());
241+
const emptyMessage = '';
242+
243+
const result = message.verifyRawMessage(emptyMessage);
244+
result.should.be.false();
245+
});
246+
247+
it('should return false for completely different message format', function () {
248+
const message = new Cip8Message(createDefaultMessageOptions());
249+
const differentMessage = 'Hello, this is a regular message';
250+
251+
const result = message.verifyRawMessage(differentMessage);
252+
result.should.be.false();
253+
});
254+
255+
it('should handle case sensitivity correctly', function () {
256+
const message = new Cip8Message(createDefaultMessageOptions());
257+
const caseInsensitiveMessage =
258+
'star 100 to addr1qxy2lshz9na88lslkj8gzd0y7t9h8j7jr0sgg30qnrylvfx4u2hwvqalq5fj9vmhxf06jgz0zt2j2qxjmzwf3rhqzqsehw0an 31a6bab50a84b8439adcfb786bb2020f6807e6e8fda629b424110fc7bb1c6b8b';
259+
260+
const result = message.verifyRawMessage(caseInsensitiveMessage);
261+
result.should.be.false(); // Should be case sensitive
262+
});
263+
});
157264
});

modules/sdk-core/src/account-lib/baseCoin/messages/baseMessage.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,20 @@ export abstract class BaseMessage implements IMessage {
180180
}
181181
return signablePayloadHex === messageEncodedHex;
182182
}
183+
184+
/**
185+
* Verifies whether a raw message payload meets coin-specific format requirements
186+
* Base implementation accepts all messages - subclasses should override for specific validation
187+
* @param rawMessage The raw message content to verify as a string
188+
* @returns True if the raw message is valid and can be safely processed, false otherwise
189+
* @example
190+
* ```typescript
191+
* // Base implementation always returns true - allows any message format
192+
* const isValid = message.verifyRawMessage("Any message content");
193+
* // Specific coin implementations override this for format validation
194+
* ```
195+
*/
196+
verifyRawMessage(rawMessage: string): boolean {
197+
return true;
198+
}
183199
}

modules/sdk-core/src/account-lib/baseCoin/messages/iface.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,20 @@ export interface IMessage {
8282
* @returns A Promise resolving to true if the message is valid, false otherwise
8383
*/
8484
verifyEncodedPayload(messageEncodedHex: string, metadata?: Record<string, unknown>): Promise<boolean>;
85+
86+
/**
87+
* Verifies whether a raw message payload meets coin-specific format requirements
88+
* This method performs validation on the raw message content before signing
89+
* @param rawMessage The raw message content to verify as a string
90+
* @returns True if the raw message is valid and can be safely processed, false otherwise
91+
* @example
92+
* ```typescript
93+
* const message = await builder.build();
94+
* const isValid = message.verifyRawMessage("Hello World");
95+
* // Returns true for most coins, false for coins with strict format requirements
96+
* ```
97+
*/
98+
verifyRawMessage(rawMessage: string): boolean;
8599
}
86100

87101
/**

0 commit comments

Comments
 (0)