Skip to content

Commit 21a3c93

Browse files
committed
fix(crypto): fix utf-8 encoding during L1 signing
- Add debug logs to track utf-8 byte encoding/decoding - Compare utf-8 bytes between TextEncoder/TextDecoder and ethers.toUtf8Bytes - Attempt to fix encoding issue by using ethers.toUtf8Bytes directly for signing - Add validation that encoded bytes match expected connection string
1 parent 02b2314 commit 21a3c93

File tree

4 files changed

+75
-7
lines changed

4 files changed

+75
-7
lines changed

packages/internal/toolkit/src/crypto.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,37 @@ export async function signRaw(
4343
signer: Signer,
4444
): Promise<string> {
4545
console.log('signRaw.payload', { payload });
46-
console.log('signRaw.toUtf8Bytes', { toUtf8Bytes: toUtf8Bytes(payload).toString() });
47-
const signature = deserializeSignature(await signer.signMessage(toUtf8Bytes(payload)));
46+
console.log('signRaw.toUtf8Bytes', { bytes: toUtf8Bytes(payload).toString() });
47+
console.log('signRaw.payload.normalize() === payload', { result: payload === payload.normalize() });
48+
49+
// prevent utf-8 encoding issues
50+
const encoder = new TextEncoder();
51+
const buffer = encoder.encode(payload);
52+
// use this message to sign
53+
const message = new TextDecoder('utf-8').decode(buffer);
54+
55+
const buffer2 = Buffer.from(payload, 'utf8');
56+
const message2 = new TextDecoder('utf-8').decode(buffer2);
57+
58+
// compare message utf8 bytes with payload.normalize()
59+
console.log('signRaw.message === payload.normalize()', { result: message === payload.normalize() });
60+
console.log('signRaw.message2 === payload.normalize()', { result: message2 === payload.normalize() });
61+
62+
// output utf8 bytes
63+
console.log('signRaw.message', { message, bytes: toUtf8Bytes(message).toString() });
64+
console.log('signRaw.message2', { message2, bytes: toUtf8Bytes(message2).toString() });
65+
66+
// compare utf8 bytes output
67+
console.log(
68+
'signRaw.toUtf8Bytes === toUtf8Bytes(message)',
69+
{ result: toUtf8Bytes(payload).toString() === toUtf8Bytes(message).toString() },
70+
);
71+
console.log(
72+
'signRaw.toUtf8Bytes === toUtf8Bytes(message2)',
73+
{ result: toUtf8Bytes(payload).toString() === toUtf8Bytes(message2).toString() },
74+
);
75+
76+
const signature = deserializeSignature(await signer.signMessage(toUtf8Bytes(message)));
4877
return serializeEthSignature(signature);
4978
}
5079

packages/x-client/src/utils/crypto/crypto.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,18 @@ export async function signRaw(
4848
payload: string,
4949
signer: Signer,
5050
): Promise<string> {
51-
const signature = deserializeSignature(await signer.signMessage(payload));
51+
// prevent utf-8 encoding issues
52+
const encoder = new TextEncoder();
53+
const buffer = encoder.encode(payload);
54+
const message = new TextDecoder('utf-8').decode(buffer);
55+
56+
const buffer2 = Buffer.from(payload, 'utf8');
57+
const message2 = new TextDecoder('utf-8').decode(buffer2);
58+
59+
console.log('signRaw.message', { message });
60+
console.log('signRaw.message2', { message2 });
61+
62+
const signature = deserializeSignature(await signer.signMessage(message));
5263
return serializeEthSignature(signature);
5364
}
5465

packages/x-provider/src/imx-wallet/ImxSigner.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { StarkSigner } from '@imtbl/x-client';
2-
import { toUtf8Bytes } from 'ethers';
32
import {
43
COMMUNICATION_TYPE,
54
ResponseEventType,
@@ -29,9 +28,6 @@ export class ImxSigner implements StarkSigner {
2928
}
3029

3130
public signMessage(rawMessage: string): Promise<string> {
32-
console.log('signMessage.rawMessage', { rawMessage });
33-
console.log('signMessage.toUtf8Bytes.toString()', { toUtf8Bytes: toUtf8Bytes(rawMessage).toString() });
34-
3531
return new Promise((resolve, reject) => {
3632
const listener = (event: MessageEvent) => {
3733
messageResponseListener<SignMessageResponse>(

packages/x-provider/src/imx-wallet/imxWallet.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Environment } from '@imtbl/config';
22
import {
33
BrowserProvider,
4+
toUtf8Bytes,
45
toUtf8String,
56
} from 'ethers';
67
import {
@@ -27,6 +28,37 @@ const DEFAULT_CONNECTION_BYTES = new Uint8Array([
2728
97, 99, 116, 105, 111, 110, 32, 119, 105, 116, 104, 32, 73, 109, 109, 117,
2829
116, 97, 98, 108, 101, 32, 88, 46,
2930
]);
31+
const DEFAULT_CONNECTION_STRING_1 = 'Only sign this request if you’ve initiated an action with Immutable X.';
32+
const DEFAULT_CONNECTION_STRING_2 = Buffer.from(DEFAULT_CONNECTION_STRING_1, 'utf8').toString('utf8');
33+
console.log('DEFAULT_CONNECTION_STRING_2', { string: DEFAULT_CONNECTION_STRING_2 });
34+
35+
// log utf8 bytes
36+
console.log('DEFAULT_CONNECTION_BYTES.toString()', { bytes: DEFAULT_CONNECTION_BYTES.toString() });
37+
console.log('DEFAULT_CONNECTION_STRING_1', { bytes: toUtf8Bytes(DEFAULT_CONNECTION_STRING_1).toString() });
38+
console.log('DEFAULT_CONNECTION_STRING_2', { bytes: toUtf8Bytes(DEFAULT_CONNECTION_STRING_2).toString() });
39+
console.log(
40+
'DEFAULT_CONNECTION_STRING_1.normalize()',
41+
{ bytes: toUtf8Bytes(DEFAULT_CONNECTION_STRING_1.normalize()).toString() },
42+
);
43+
console.log(
44+
'DEFAULT_CONNECTION_STRING_2.normalize()',
45+
{ bytes: toUtf8Bytes(DEFAULT_CONNECTION_STRING_2.normalize()).toString() },
46+
);
47+
console.log(
48+
'Buffer.from(DEFAULT_CONNECTION_STRING_1, utf8).toString()',
49+
{ bytes: Buffer.from(DEFAULT_CONNECTION_STRING_1, 'utf8').toString() },
50+
);
51+
52+
// console.log if the bytes of DEFAULT_CONNECTION_STRING_1 and DEFAULT_CONNECTION_STRING_2 are the same as DEFAULT_CONNECTION_BYTES
53+
console.log(
54+
'DEFAULT_CONNECTION_BYTES === toUtf8Bytes(DEFAULT_CONNECTION_STRING_1)',
55+
{ bytes: DEFAULT_CONNECTION_BYTES.toString() === toUtf8Bytes(DEFAULT_CONNECTION_STRING_1).toString() },
56+
);
57+
console.log(
58+
'DEFAULT_CONNECTION_BYTES === toUtf8Bytes(DEFAULT_CONNECTION_STRING_2)',
59+
{ bytes: DEFAULT_CONNECTION_BYTES.toString() === toUtf8Bytes(DEFAULT_CONNECTION_STRING_2).toString() },
60+
);
61+
3062
const CONNECTION_FAILED_ERROR = 'The L2 IMX Wallet connection has failed';
3163

3264
export async function connect(

0 commit comments

Comments
 (0)