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