Skip to content

Commit 7e749c9

Browse files
fix(wallet,dapp): use TON proof for auth signing and update dapp verification (#987)
- Wallet: use generateTonProof for TON auth signing instead of plain text signing, preserving the existing TON Connect proof format - Dapp: update isValidTonSignature to reconstruct the TON proof message bytes (domain, iat, statement) and verify the ed25519 signature against the double-SHA256 hash, matching the wallet's signing format - Dapp: move TON proof verification logic to helpers/ton.ts - Dapp: replace Buffer usage with Uint8Array for stricter TS compat Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 872d960 commit 7e749c9

File tree

3 files changed

+127
-17
lines changed

3 files changed

+127
-17
lines changed

advanced/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ import {
116116
} from "@walletconnect/utils";
117117
import { BIP122_DUST_LIMIT } from "../chains/bip122";
118118
import { getTronWeb } from "../helpers/tron";
119-
import { signVerify } from "@ton/crypto";
119+
import { verifyTonProofSignature } from "../helpers/ton";
120120
/**
121121
* Types
122122
*/
@@ -2716,14 +2716,14 @@ async function isValidTonSignature(params: {
27162716
signatureMeta?: string;
27172717
}) {
27182718
const { message, signature, iss, signatureMeta = "" } = params;
2719+
const address = getDidAddress(iss)!;
27192720

2720-
const valid = await signVerify(
2721-
Buffer.from(message, "utf-8"),
2722-
Buffer.from(signature, "base64"),
2723-
Buffer.from(signatureMeta, "base64"),
2724-
);
2725-
2726-
return valid;
2721+
return verifyTonProofSignature({
2722+
message,
2723+
signature,
2724+
address,
2725+
publicKeyHex: signatureMeta,
2726+
});
27272727
}
27282728
export function isValidSignature(params: {
27292729
message: string;

advanced/dapps/react-dapp-v2/src/helpers/ton.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
Address,
88
} from "@ton/core";
99
import { TonClient, Transaction } from "@ton/ton";
10+
import { signVerify } from "@ton/crypto";
11+
import { sha256 } from "@noble/hashes/sha2.js";
1012

1113
let clients = new Map<string, TonClient>();
1214
function getTonClient(chainId: string) {
@@ -133,6 +135,117 @@ async function retry<T>(
133135
throw lastError;
134136
}
135137

138+
const TON_PROOF_PREFIX = "ton-proof-item-v2/";
139+
const TON_CONNECT_PREFIX = "ton-connect";
140+
141+
function parseSiweField(message: string, field: string): string {
142+
const match = message.match(new RegExp(`${field}: (.+)`));
143+
return match?.[1] ?? "";
144+
}
145+
146+
function parseSiweStatement(message: string): string {
147+
const lines = message.split("\n");
148+
const statementLines: string[] = [];
149+
let collecting = false;
150+
for (const line of lines) {
151+
if (collecting) {
152+
if (line.startsWith("URI: ")) break;
153+
statementLines.push(line);
154+
}
155+
if (line === "") collecting = true;
156+
}
157+
return statementLines.join("\n").trim();
158+
}
159+
160+
function concatBytes(...arrays: Uint8Array[]): Uint8Array {
161+
const totalLength = arrays.reduce((sum, arr) => sum + arr.length, 0);
162+
const result = new Uint8Array(totalLength);
163+
let offset = 0;
164+
for (const arr of arrays) {
165+
result.set(arr, offset);
166+
offset += arr.length;
167+
}
168+
return result;
169+
}
170+
171+
function writeUint32BE(value: number): Uint8Array {
172+
const buf = new Uint8Array(4);
173+
new DataView(buf.buffer).setUint32(0, value, false);
174+
return buf;
175+
}
176+
177+
function writeUint32LE(value: number): Uint8Array {
178+
const buf = new Uint8Array(4);
179+
new DataView(buf.buffer).setUint32(0, value, true);
180+
return buf;
181+
}
182+
183+
function writeBigUint64LE(value: bigint): Uint8Array {
184+
const buf = new Uint8Array(8);
185+
new DataView(buf.buffer).setBigUint64(0, value, true);
186+
return buf;
187+
}
188+
189+
function buildTonProofMessageBytes(
190+
address: Address,
191+
domain: string,
192+
timestamp: number,
193+
payload: string
194+
): Uint8Array {
195+
const wc = writeUint32BE(address.workChain);
196+
const ts = writeBigUint64LE(BigInt(timestamp));
197+
const dl = writeUint32LE(domain.length);
198+
const encoder = new TextEncoder();
199+
200+
const m = concatBytes(
201+
encoder.encode(TON_PROOF_PREFIX),
202+
wc,
203+
new Uint8Array(address.hash),
204+
dl,
205+
encoder.encode(domain),
206+
ts,
207+
encoder.encode(payload),
208+
);
209+
210+
const messageHash = sha256(m);
211+
212+
const fullMes = concatBytes(
213+
new Uint8Array([0xff, 0xff]),
214+
encoder.encode(TON_CONNECT_PREFIX),
215+
messageHash,
216+
);
217+
218+
return sha256(fullMes);
219+
}
220+
221+
export async function verifyTonProofSignature(params: {
222+
message: string;
223+
signature: string;
224+
address: string;
225+
publicKeyHex: string;
226+
}): Promise<boolean> {
227+
const { message, signature, address, publicKeyHex } = params;
228+
229+
const tonAddress = Address.parse(address);
230+
const domain = message.split(" wants you to sign in")[0];
231+
const iat = parseSiweField(message, "Issued At");
232+
const statement = parseSiweStatement(message);
233+
const timestamp = Math.floor(new Date(iat).getTime() / 1000);
234+
235+
const dataToVerify = buildTonProofMessageBytes(
236+
tonAddress,
237+
domain,
238+
timestamp,
239+
statement
240+
);
241+
242+
return signVerify(
243+
Buffer.from(dataToVerify),
244+
Buffer.from(signature, "base64"),
245+
Buffer.from(publicKeyHex, "hex")
246+
);
247+
}
248+
136249
async function getTransactionByInMessage(
137250
inMessageBoc: string,
138251
client: TonClient

advanced/wallets/react-wallet-v2/src/utils/AuthUtil.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,15 +131,12 @@ export async function signMessage(AuthMessage: AuthMessage) {
131131
const eip155Result = await eip155Wallets[AuthMessage.address].signMessage(AuthMessage.message)
132132
return { signature: eip155Result, type: getSignatureType(parsed.namespace) }
133133
case 'ton':
134-
if (AuthMessage.statement) {
135-
const tonResult = await tonWallets[AuthMessage.address].generateTonProof({
136-
iat: AuthMessage.iat,
137-
domain: AuthMessage.domain,
138-
payload: AuthMessage.statement,
139-
})
140-
return { signature: tonResult.signature, publicKey: tonResult.publicKey, type: 'ton' }
141-
}
142-
break
134+
const tonResult = await tonWallets[AuthMessage.address].generateTonProof({
135+
iat: AuthMessage.iat,
136+
domain: AuthMessage.domain,
137+
payload: AuthMessage.statement ?? '',
138+
})
139+
return { signature: tonResult.signature, publicKey: tonResult.publicKey, type: 'ton' }
143140
case 'solana':
144141
const solanaResult = await solanaWallets[AuthMessage.address].signMessage({
145142
message: bs58.encode(new Uint8Array(Buffer.from(AuthMessage.message)))

0 commit comments

Comments
 (0)