@hwlt/era-connect 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -5
- package/cardano/package.json +6 -0
- package/dist/cardano-DP6U_K1J.cjs +105 -0
- package/dist/cardano-DP6U_K1J.cjs.map +1 -0
- package/dist/cardano-Do1ahUlK.d.cts +62 -0
- package/dist/cardano-OzjWZHh6.d.ts +62 -0
- package/dist/cardano-yuOygqwp.js +94 -0
- package/dist/cardano-yuOygqwp.js.map +1 -0
- package/dist/cardano.cjs +10 -0
- package/dist/cardano.d.cts +3 -0
- package/dist/cardano.d.ts +3 -0
- package/dist/cardano.js +3 -0
- package/dist/derive-C4JdycUh.js +117 -0
- package/dist/derive-C4JdycUh.js.map +1 -0
- package/dist/derive-CuILJRPT.cjs +176 -0
- package/dist/derive-CuILJRPT.cjs.map +1 -0
- package/dist/index.cjs +133 -92
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -7
- package/dist/index.d.ts +64 -7
- package/dist/index.js +120 -85
- package/dist/index.js.map +1 -1
- package/dist/ton-5Uvi5ifg.js +86 -0
- package/dist/ton-5Uvi5ifg.js.map +1 -0
- package/dist/ton-C0UP-cf_.d.ts +51 -0
- package/dist/ton-DUJGXdGa.cjs +97 -0
- package/dist/ton-DUJGXdGa.cjs.map +1 -0
- package/dist/ton-DmeIvknN.d.cts +51 -0
- package/dist/ton.cjs +10 -0
- package/dist/ton.d.cts +3 -0
- package/dist/ton.d.ts +3 -0
- package/dist/ton.js +3 -0
- package/dist/verify.cjs +302 -13
- package/dist/verify.cjs.map +1 -1
- package/dist/verify.d.cts +56 -1
- package/dist/verify.d.ts +56 -1
- package/dist/verify.js +301 -15
- package/dist/verify.js.map +1 -1
- package/package.json +32 -8
- package/ton/package.json +6 -0
package/dist/verify.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { A as utf8Encode, D as hexToBytes, E as equalBytes, T as concatBytes, Z as EraSdkError, k as utf8Decode, w as bytesToHex } from "./shared-B8Wc7ViU.js";
|
|
1
|
+
import { A as utf8Encode, D as hexToBytes, E as equalBytes, P as parsePath, T as concatBytes, Z as EraSdkError, k as utf8Decode, w as bytesToHex } from "./shared-B8Wc7ViU.js";
|
|
2
|
+
import { a as cardanoSoftDerivePath } from "./derive-C4JdycUh.js";
|
|
3
|
+
import { n as parseWitnessSet } from "./cardano-yuOygqwp.js";
|
|
2
4
|
import { n as EvmDataType, r as foldRecoveryId } from "./evm-kJb3-oiR.js";
|
|
5
|
+
import { n as TonDataType } from "./ton-5Uvi5ifg.js";
|
|
3
6
|
import { a as firstVarint, i as firstBytes, o as readFields, r as splitSignedTronTx } from "./messages-17Oup0-Z.js";
|
|
4
7
|
import { secp256k1 } from "@noble/curves/secp256k1";
|
|
5
8
|
import { sha256 } from "@noble/hashes/sha2";
|
|
6
9
|
import { keccak_256 } from "@noble/hashes/sha3";
|
|
7
10
|
import { createBase58check } from "@scure/base";
|
|
8
11
|
import { ed25519 } from "@noble/curves/ed25519";
|
|
12
|
+
import { blake2b } from "@noble/hashes/blake2b";
|
|
9
13
|
//#region src/verify/psbt-reader.ts
|
|
10
14
|
const MAGIC = [
|
|
11
15
|
112,
|
|
@@ -21,7 +25,7 @@ const PsbtInputType = {
|
|
|
21
25
|
taprootKeySpendSignature: 19,
|
|
22
26
|
taprootScriptSpendSignature: 20
|
|
23
27
|
};
|
|
24
|
-
function err(message) {
|
|
28
|
+
function err$1(message) {
|
|
25
29
|
return new EraSdkError("malformed-reply", `psbt: ${message}`);
|
|
26
30
|
}
|
|
27
31
|
var Reader = class {
|
|
@@ -34,7 +38,7 @@ var Reader = class {
|
|
|
34
38
|
}
|
|
35
39
|
u8() {
|
|
36
40
|
const b = this.bytes[this.offset];
|
|
37
|
-
if (b === void 0) throw err("truncated");
|
|
41
|
+
if (b === void 0) throw err$1("truncated");
|
|
38
42
|
this.offset += 1;
|
|
39
43
|
return b;
|
|
40
44
|
}
|
|
@@ -56,12 +60,12 @@ var Reader = class {
|
|
|
56
60
|
}
|
|
57
61
|
let value = 0n;
|
|
58
62
|
for (let i = 0; i < width; i++) value |= BigInt(this.u8()) << BigInt(8 * i);
|
|
59
|
-
if (value < minimum) throw err("non-minimal compact-size encoding");
|
|
60
|
-
if (value > BigInt(Number.MAX_SAFE_INTEGER)) throw err("length exceeds safe range");
|
|
63
|
+
if (value < minimum) throw err$1("non-minimal compact-size encoding");
|
|
64
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER)) throw err$1("length exceeds safe range");
|
|
61
65
|
return Number(value);
|
|
62
66
|
}
|
|
63
67
|
take(length) {
|
|
64
|
-
if (length > this.remaining) throw err("length exceeds input");
|
|
68
|
+
if (length > this.remaining) throw err$1("length exceeds input");
|
|
65
69
|
const out = this.bytes.slice(this.offset, this.offset + length);
|
|
66
70
|
this.offset += length;
|
|
67
71
|
return out;
|
|
@@ -77,7 +81,7 @@ function readMap(reader) {
|
|
|
77
81
|
const key = reader.take(keyLength);
|
|
78
82
|
const value = reader.take(reader.compactSize());
|
|
79
83
|
const keyId = Array.from(key).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
80
|
-
if (seen.has(keyId)) throw err("duplicate key within one map");
|
|
84
|
+
if (seen.has(keyId)) throw err$1("duplicate key within one map");
|
|
81
85
|
seen.add(keyId);
|
|
82
86
|
entries.push({
|
|
83
87
|
keyType: key[0],
|
|
@@ -91,7 +95,7 @@ function countTxInputsOutputs(tx) {
|
|
|
91
95
|
const reader = new Reader(tx);
|
|
92
96
|
reader.take(4);
|
|
93
97
|
const inputs = reader.compactSize();
|
|
94
|
-
if (inputs === 0) throw err("unsigned transaction has zero inputs (or carries witness data)");
|
|
98
|
+
if (inputs === 0) throw err$1("unsigned transaction has zero inputs (or carries witness data)");
|
|
95
99
|
for (let i = 0; i < inputs; i++) {
|
|
96
100
|
reader.take(36);
|
|
97
101
|
reader.take(reader.compactSize());
|
|
@@ -103,7 +107,7 @@ function countTxInputsOutputs(tx) {
|
|
|
103
107
|
reader.take(reader.compactSize());
|
|
104
108
|
}
|
|
105
109
|
reader.take(4);
|
|
106
|
-
if (reader.remaining !== 0) throw err("trailing bytes after the unsigned transaction");
|
|
110
|
+
if (reader.remaining !== 0) throw err$1("trailing bytes after the unsigned transaction");
|
|
107
111
|
return {
|
|
108
112
|
inputs,
|
|
109
113
|
outputs
|
|
@@ -111,25 +115,25 @@ function countTxInputsOutputs(tx) {
|
|
|
111
115
|
}
|
|
112
116
|
function parsePsbt(bytes) {
|
|
113
117
|
const reader = new Reader(bytes);
|
|
114
|
-
for (const expected of MAGIC) if (reader.u8() !== expected) throw err("bad magic");
|
|
118
|
+
for (const expected of MAGIC) if (reader.u8() !== expected) throw err$1("bad magic");
|
|
115
119
|
const globalMap = readMap(reader);
|
|
116
120
|
let unsignedTx = null;
|
|
117
121
|
let version = 0;
|
|
118
122
|
for (const entry of globalMap) {
|
|
119
123
|
if (entry.keyType === 0 && entry.keyData.length === 0) unsignedTx = entry.value;
|
|
120
124
|
if (entry.keyType === 251 && entry.keyData.length === 0) {
|
|
121
|
-
if (entry.value.length !== 4) throw err("bad version field");
|
|
125
|
+
if (entry.value.length !== 4) throw err$1("bad version field");
|
|
122
126
|
version = (entry.value[0] | entry.value[1] << 8 | entry.value[2] << 16 | entry.value[3] << 24) >>> 0;
|
|
123
127
|
}
|
|
124
128
|
}
|
|
125
|
-
if (unsignedTx === null) throw err("no global unsigned transaction — not a PSBT v0");
|
|
126
|
-
if (version !== 0) throw err(`unsupported PSBT version ${version}`);
|
|
129
|
+
if (unsignedTx === null) throw err$1("no global unsigned transaction — not a PSBT v0");
|
|
130
|
+
if (version !== 0) throw err$1(`unsupported PSBT version ${version}`);
|
|
127
131
|
const counts = countTxInputsOutputs(unsignedTx);
|
|
128
132
|
const inputs = [];
|
|
129
133
|
for (let i = 0; i < counts.inputs; i++) inputs.push(readMap(reader));
|
|
130
134
|
const outputs = [];
|
|
131
135
|
for (let i = 0; i < counts.outputs; i++) outputs.push(readMap(reader));
|
|
132
|
-
if (reader.remaining !== 0) throw err("trailing bytes after the output maps");
|
|
136
|
+
if (reader.remaining !== 0) throw err$1("trailing bytes after the output maps");
|
|
133
137
|
return {
|
|
134
138
|
unsignedTx,
|
|
135
139
|
version,
|
|
@@ -245,6 +249,150 @@ function message(e) {
|
|
|
245
249
|
return e instanceof EraSdkError || e instanceof Error ? e.message : String(e);
|
|
246
250
|
}
|
|
247
251
|
//#endregion
|
|
252
|
+
//#region src/verify/cardano.ts
|
|
253
|
+
/**
|
|
254
|
+
* Recompute the digest the device signs — BLAKE2b-256 of the ENCODED FIRST
|
|
255
|
+
* ELEMENT of the transaction CBOR array (the tx body) — and verify every
|
|
256
|
+
* `[vkey, signature]` pair against it. With `account` + `signerPaths`, the
|
|
257
|
+
* vkeys are additionally required to be exactly the soft-derived children of
|
|
258
|
+
* YOUR linked account at the request's own paths.
|
|
259
|
+
*/
|
|
260
|
+
function verifyCardanoSignature(args) {
|
|
261
|
+
let witnesses;
|
|
262
|
+
try {
|
|
263
|
+
witnesses = args.witnesses ?? (args.witnessSet ? parseWitnessSet(args.witnessSet) : []);
|
|
264
|
+
} catch (e) {
|
|
265
|
+
return failed(`witness set is not readable: ${e.message}`);
|
|
266
|
+
}
|
|
267
|
+
if (witnesses.length === 0) return failed("no witnesses to verify");
|
|
268
|
+
let digest;
|
|
269
|
+
try {
|
|
270
|
+
digest = blake2b(firstArrayItemBytes(args.signData), { dkLen: 32 });
|
|
271
|
+
} catch (e) {
|
|
272
|
+
return failed(`signData is not a readable transaction array: ${e.message}`);
|
|
273
|
+
}
|
|
274
|
+
for (const witness of witnesses) {
|
|
275
|
+
let ok;
|
|
276
|
+
try {
|
|
277
|
+
ok = ed25519.verify(witness.signature, digest, witness.vkey);
|
|
278
|
+
} catch (e) {
|
|
279
|
+
return failed(`Cardano signature could not be checked: ${e.message}`);
|
|
280
|
+
}
|
|
281
|
+
if (!ok) return failed("a witness signature does not verify against its own vkey");
|
|
282
|
+
}
|
|
283
|
+
if (args.account && args.signerPaths && args.signerPaths.length > 0) {
|
|
284
|
+
const accountLevels = parsePath(args.account.accountPath);
|
|
285
|
+
const expected = /* @__PURE__ */ new Map();
|
|
286
|
+
for (const path of new Set(args.signerPaths)) {
|
|
287
|
+
const levels = parsePath(path);
|
|
288
|
+
if (levels.length !== accountLevels.length + 2 || !accountLevels.every((l, i) => levels[i].index === l.index && levels[i].hardened === l.hardened) || levels.slice(accountLevels.length).some((l) => l.hardened)) return failed(`signer path ${path} does not extend the account path with two soft components`);
|
|
289
|
+
const tail = levels.slice(accountLevels.length).map((l) => l.index);
|
|
290
|
+
const vkey = cardanoSoftDerivePath(args.account.publicKey, args.account.chainCode, tail);
|
|
291
|
+
expected.set(bytesToHex(vkey), path);
|
|
292
|
+
}
|
|
293
|
+
for (const [vkeyHex, path] of expected) if (!witnesses.some((w) => bytesToHex(w.vkey) === vkeyHex)) return failed(`no witness for the requested signer path ${path}`);
|
|
294
|
+
for (const witness of witnesses) if (!expected.has(bytesToHex(witness.vkey))) return failed("the witness set carries a key your request did not ask for");
|
|
295
|
+
}
|
|
296
|
+
return verified;
|
|
297
|
+
}
|
|
298
|
+
function firstArrayItemBytes(bytes) {
|
|
299
|
+
const top = bytes[0];
|
|
300
|
+
if (top === void 0) throw new Error("empty input");
|
|
301
|
+
if (top >> 5 !== 4) throw new Error("not a CBOR array");
|
|
302
|
+
let start;
|
|
303
|
+
if ((top & 31) === 31) start = 1;
|
|
304
|
+
else {
|
|
305
|
+
const head = readHead(bytes, 0);
|
|
306
|
+
if (head.value === 0n) throw new Error("transaction array is empty");
|
|
307
|
+
start = head.next;
|
|
308
|
+
}
|
|
309
|
+
const end = skipItem(bytes, start, 0);
|
|
310
|
+
return bytes.slice(start, end);
|
|
311
|
+
}
|
|
312
|
+
function readHead(bytes, offset) {
|
|
313
|
+
const initial = bytes[offset];
|
|
314
|
+
if (initial === void 0) throw new Error("truncated");
|
|
315
|
+
const info = initial & 31;
|
|
316
|
+
if (info < 24) return {
|
|
317
|
+
value: BigInt(info),
|
|
318
|
+
next: offset + 1
|
|
319
|
+
};
|
|
320
|
+
if (info === 31) return {
|
|
321
|
+
value: -1n,
|
|
322
|
+
next: offset + 1
|
|
323
|
+
};
|
|
324
|
+
let width;
|
|
325
|
+
if (info === 24) width = 1;
|
|
326
|
+
else if (info === 25) width = 2;
|
|
327
|
+
else if (info === 26) width = 4;
|
|
328
|
+
else if (info === 27) width = 8;
|
|
329
|
+
else throw new Error("reserved length encoding");
|
|
330
|
+
let value = 0n;
|
|
331
|
+
for (let i = 0; i < width; i++) {
|
|
332
|
+
const b = bytes[offset + 1 + i];
|
|
333
|
+
if (b === void 0) throw new Error("truncated");
|
|
334
|
+
value = value << 8n | BigInt(b);
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
value,
|
|
338
|
+
next: offset + 1 + width
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
/** Returns the offset just past the item starting at `offset`. */
|
|
342
|
+
function skipItem(bytes, offset, depth) {
|
|
343
|
+
if (depth > 32) throw new Error("nesting too deep");
|
|
344
|
+
const initial = bytes[offset];
|
|
345
|
+
if (initial === void 0) throw new Error("truncated");
|
|
346
|
+
const major = initial >> 5;
|
|
347
|
+
const head = readHead(bytes, offset);
|
|
348
|
+
switch (major) {
|
|
349
|
+
case 0:
|
|
350
|
+
case 1:
|
|
351
|
+
if (head.value === -1n) throw new Error("malformed integer");
|
|
352
|
+
return head.next;
|
|
353
|
+
case 2:
|
|
354
|
+
case 3: {
|
|
355
|
+
if (head.value === -1n) {
|
|
356
|
+
let pos = head.next;
|
|
357
|
+
while (bytes[pos] !== 255) {
|
|
358
|
+
const chunk = readHead(bytes, pos);
|
|
359
|
+
if (chunk.value < 0n) throw new Error("malformed chunk");
|
|
360
|
+
pos = chunk.next + Number(chunk.value);
|
|
361
|
+
if (pos > bytes.length) throw new Error("truncated string");
|
|
362
|
+
}
|
|
363
|
+
return pos + 1;
|
|
364
|
+
}
|
|
365
|
+
const end = head.next + Number(head.value);
|
|
366
|
+
if (end > bytes.length) throw new Error("truncated string");
|
|
367
|
+
return end;
|
|
368
|
+
}
|
|
369
|
+
case 4:
|
|
370
|
+
case 5: {
|
|
371
|
+
const perEntry = major === 5 ? 2 : 1;
|
|
372
|
+
if (head.value === -1n) {
|
|
373
|
+
let pos = head.next;
|
|
374
|
+
while (bytes[pos] !== 255) for (let i = 0; i < perEntry; i++) pos = skipItem(bytes, pos, depth + 1);
|
|
375
|
+
return pos + 1;
|
|
376
|
+
}
|
|
377
|
+
let pos = head.next;
|
|
378
|
+
const count = Number(head.value) * perEntry;
|
|
379
|
+
if (count > 1e6) throw new Error("container too large");
|
|
380
|
+
for (let i = 0; i < count; i++) pos = skipItem(bytes, pos, depth + 1);
|
|
381
|
+
return pos;
|
|
382
|
+
}
|
|
383
|
+
case 6:
|
|
384
|
+
if (head.value === -1n) throw new Error("malformed tag");
|
|
385
|
+
return skipItem(bytes, head.next, depth + 1);
|
|
386
|
+
case 7:
|
|
387
|
+
if ((initial & 31) === 31) throw new Error("unexpected break");
|
|
388
|
+
if ((initial & 31) === 25) return offset + 3;
|
|
389
|
+
if ((initial & 31) === 26) return offset + 5;
|
|
390
|
+
if ((initial & 31) === 27) return offset + 9;
|
|
391
|
+
return head.next;
|
|
392
|
+
default: throw new Error("unreachable");
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
//#endregion
|
|
248
396
|
//#region src/verify/evm.ts
|
|
249
397
|
/** EIP-191: 0x19 || "Ethereum Signed Message:\n" || len(message). */
|
|
250
398
|
function personalSignDigest(message) {
|
|
@@ -300,6 +448,144 @@ function verifySolanaSignature(args) {
|
|
|
300
448
|
return ok ? verified : failed("the signature does not belong to this account");
|
|
301
449
|
}
|
|
302
450
|
//#endregion
|
|
451
|
+
//#region src/verify/ton-boc.ts
|
|
452
|
+
/**
|
|
453
|
+
* Minimal TON Bag-of-Cells reader + cell representation hash — exactly the
|
|
454
|
+
* subset the device's signer implements: generic BoC, ordinary level-0 cells,
|
|
455
|
+
* first root. The transaction digest the device signs IS the root cell's
|
|
456
|
+
* representation hash, so this must agree with the firmware bit for bit.
|
|
457
|
+
*
|
|
458
|
+
* Hardened for scanned/untrusted input: size caps, bounds checks, and
|
|
459
|
+
* forward-only references (the standard BoC topological order; a backward
|
|
460
|
+
* reference would make the single-pass hash read an uncomputed child).
|
|
461
|
+
*/
|
|
462
|
+
const BOC_MAGIC = 3052313714;
|
|
463
|
+
const MAX_CELLS = 256;
|
|
464
|
+
const MAX_CELL_DATA_BYTES = 128;
|
|
465
|
+
const MAX_REFS = 4;
|
|
466
|
+
function err(message) {
|
|
467
|
+
return new EraSdkError("malformed-reply", `ton boc: ${message}`);
|
|
468
|
+
}
|
|
469
|
+
/** Representation hash of the ROOT cell of a BoC — the bytes TON signs. */
|
|
470
|
+
function bocRootHash(boc) {
|
|
471
|
+
if (boc.length < 10) throw err("too short");
|
|
472
|
+
if ((boc[0] << 24 | boc[1] << 16 | boc[2] << 8 | boc[3]) >>> 0 !== BOC_MAGIC) throw err("not a generic BoC");
|
|
473
|
+
const flags = boc[4];
|
|
474
|
+
const hasIdx = flags >> 7 & 1;
|
|
475
|
+
const refSize = flags & 7;
|
|
476
|
+
const offSize = boc[5];
|
|
477
|
+
if (refSize === 0 || refSize > 4) throw err("bad ref size");
|
|
478
|
+
if (offSize === 0 || offSize > 8) throw err("bad offset size");
|
|
479
|
+
let pos = 6;
|
|
480
|
+
const readInt = (byteLen) => {
|
|
481
|
+
let value = 0;
|
|
482
|
+
for (let i = 0; i < byteLen; i++) {
|
|
483
|
+
if (pos >= boc.length) throw err("truncated header");
|
|
484
|
+
value = value * 256 + boc[pos++];
|
|
485
|
+
}
|
|
486
|
+
if (!Number.isSafeInteger(value)) throw err("header value out of range");
|
|
487
|
+
return value;
|
|
488
|
+
};
|
|
489
|
+
const cellCount = readInt(refSize);
|
|
490
|
+
const rootCount = readInt(refSize);
|
|
491
|
+
readInt(refSize);
|
|
492
|
+
readInt(offSize);
|
|
493
|
+
if (rootCount === 0) throw err("no roots");
|
|
494
|
+
if (cellCount === 0 || cellCount > MAX_CELLS) throw err("cell count out of range");
|
|
495
|
+
const rootIndex = readInt(refSize);
|
|
496
|
+
for (let i = 1; i < rootCount; i++) readInt(refSize);
|
|
497
|
+
if (rootIndex >= cellCount) throw err("root index out of range");
|
|
498
|
+
if (hasIdx) pos += cellCount * offSize;
|
|
499
|
+
const cells = [];
|
|
500
|
+
for (let i = 0; i < cellCount; i++) {
|
|
501
|
+
if (pos + 2 > boc.length) throw err("truncated cell");
|
|
502
|
+
const d1 = boc[pos++];
|
|
503
|
+
const d2 = boc[pos++];
|
|
504
|
+
const refCount = d1 & 7;
|
|
505
|
+
if (refCount > MAX_REFS) throw err("too many references");
|
|
506
|
+
const dataByteLen = d2 + 1 >> 1;
|
|
507
|
+
const incomplete = (d2 & 1) === 1;
|
|
508
|
+
if (dataByteLen > MAX_CELL_DATA_BYTES) throw err("cell data too large");
|
|
509
|
+
if (pos + dataByteLen > boc.length) throw err("truncated cell data");
|
|
510
|
+
const data = boc.slice(pos, pos + dataByteLen);
|
|
511
|
+
pos += dataByteLen;
|
|
512
|
+
let dataBits;
|
|
513
|
+
if (incomplete && dataByteLen > 0) {
|
|
514
|
+
const last = data[dataByteLen - 1];
|
|
515
|
+
if (last === 0) dataBits = (dataByteLen - 1) * 8;
|
|
516
|
+
else {
|
|
517
|
+
let trailingZeros = 0;
|
|
518
|
+
for (let b = 0; b < 8; b++) {
|
|
519
|
+
if (last & 1 << b) break;
|
|
520
|
+
trailingZeros++;
|
|
521
|
+
}
|
|
522
|
+
dataBits = dataByteLen * 8 - 1 - trailingZeros;
|
|
523
|
+
}
|
|
524
|
+
} else dataBits = dataByteLen * 8;
|
|
525
|
+
const refs = [];
|
|
526
|
+
for (let r = 0; r < refCount; r++) {
|
|
527
|
+
const ref = readInt(refSize);
|
|
528
|
+
if (ref >= cellCount) throw err("reference out of range");
|
|
529
|
+
if (ref <= i) throw err("non-topological cell reference");
|
|
530
|
+
refs.push(ref);
|
|
531
|
+
}
|
|
532
|
+
cells.push({
|
|
533
|
+
dataBits,
|
|
534
|
+
data,
|
|
535
|
+
refs,
|
|
536
|
+
depth: 0,
|
|
537
|
+
hash: /* @__PURE__ */ new Uint8Array(0)
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
for (let i = cellCount - 1; i >= 0; i--) {
|
|
541
|
+
const cell = cells[i];
|
|
542
|
+
let maxChildDepth = -1;
|
|
543
|
+
for (const r of cell.refs) maxChildDepth = Math.max(maxChildDepth, cells[r].depth);
|
|
544
|
+
cell.depth = cell.refs.length === 0 ? 0 : maxChildDepth + 1;
|
|
545
|
+
const dataBytes = cell.dataBits + 7 >> 3;
|
|
546
|
+
const incomplete = cell.dataBits % 8 !== 0;
|
|
547
|
+
const repr = [cell.refs.length, dataBytes * 2 - (incomplete ? 1 : 0)];
|
|
548
|
+
for (let b = 0; b < dataBytes; b++) repr.push(cell.data[b] ?? 0);
|
|
549
|
+
if (incomplete && dataBytes > 0) {
|
|
550
|
+
const shift = 7 - cell.dataBits % 8;
|
|
551
|
+
const last = repr.length - 1;
|
|
552
|
+
repr[last] = (repr[last] | 1 << shift) & 255 << shift & 255;
|
|
553
|
+
}
|
|
554
|
+
for (const r of cell.refs) repr.push(cells[r].depth >> 8 & 255, cells[r].depth & 255);
|
|
555
|
+
let bytes = new Uint8Array(repr);
|
|
556
|
+
for (const r of cell.refs) bytes = concatBytes(bytes, cells[r].hash);
|
|
557
|
+
cell.hash = sha256(bytes);
|
|
558
|
+
}
|
|
559
|
+
return cells[rootIndex].hash;
|
|
560
|
+
}
|
|
561
|
+
//#endregion
|
|
562
|
+
//#region src/verify/ton.ts
|
|
563
|
+
/**
|
|
564
|
+
* Recompute the exact digest the device signs — the BoC ROOT CELL's
|
|
565
|
+
* representation hash for a transaction, or the TON Connect proof digest
|
|
566
|
+
* `sha256(0xFFFF || "ton-connect" || sha256(payload))` — and verify the
|
|
567
|
+
* Ed25519 signature against the linked key.
|
|
568
|
+
*/
|
|
569
|
+
function verifyTonSignature(args) {
|
|
570
|
+
if (args.signature.length !== 64) return failed("signature must be 64 bytes");
|
|
571
|
+
if (args.publicKey.length !== 32) return failed("public key must be 32 bytes");
|
|
572
|
+
let digest;
|
|
573
|
+
if (args.dataType === TonDataType.tonProof) digest = sha256(concatBytes(new Uint8Array([255, 255]), utf8Encode("ton-connect"), sha256(args.signData)));
|
|
574
|
+
else if (args.dataType === TonDataType.transaction) try {
|
|
575
|
+
digest = bocRootHash(args.signData);
|
|
576
|
+
} catch (e) {
|
|
577
|
+
return failed(`signData is not a readable BoC: ${e.message}`);
|
|
578
|
+
}
|
|
579
|
+
else return failed(`unknown dataType ${args.dataType}`);
|
|
580
|
+
let ok;
|
|
581
|
+
try {
|
|
582
|
+
ok = ed25519.verify(args.signature, digest, args.publicKey);
|
|
583
|
+
} catch (e) {
|
|
584
|
+
return failed(`TON signature could not be checked: ${e.message}`);
|
|
585
|
+
}
|
|
586
|
+
return ok ? verified : failed("the signature does not belong to this account");
|
|
587
|
+
}
|
|
588
|
+
//#endregion
|
|
303
589
|
//#region src/verify/tron.ts
|
|
304
590
|
const base58check = createBase58check(sha256);
|
|
305
591
|
/**
|
|
@@ -433,6 +719,6 @@ function compareWindow(repliedBytes, latestBlock) {
|
|
|
433
719
|
return verified;
|
|
434
720
|
}
|
|
435
721
|
//#endregion
|
|
436
|
-
export { PsbtInputType, parsePsbt, verifyBtcMessageHeader, verifyEvmSignature, verifySignedPsbt, verifySolanaSignature, verifyTronSignature };
|
|
722
|
+
export { PsbtInputType, bocRootHash, parsePsbt, verifyBtcMessageHeader, verifyCardanoSignature, verifyEvmSignature, verifySignedPsbt, verifySolanaSignature, verifyTonSignature, verifyTronSignature };
|
|
437
723
|
|
|
438
724
|
//# sourceMappingURL=verify.js.map
|