@hwlt/era-connect 0.4.0 → 0.5.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.
Files changed (64) hide show
  1. package/README.md +3 -9
  2. package/bch/package.json +6 -0
  3. package/dist/bch-CK6dKGQA.d.ts +74 -0
  4. package/dist/bch-CpLACx0j.cjs +139 -0
  5. package/dist/bch-CpLACx0j.cjs.map +1 -0
  6. package/dist/bch-DHsIVJqK.d.cts +74 -0
  7. package/dist/bch-dMESlQfv.js +134 -0
  8. package/dist/bch-dMESlQfv.js.map +1 -0
  9. package/dist/bch.cjs +13 -0
  10. package/dist/bch.d.cts +26 -0
  11. package/dist/bch.d.ts +26 -0
  12. package/dist/bch.js +4 -0
  13. package/dist/{btc-B_Nq2H4T.js → btc-BmVgvvBd.js} +2 -2
  14. package/dist/btc-BmVgvvBd.js.map +1 -0
  15. package/dist/{btc-ByzjzUKs.cjs → btc-BxNww6kW.cjs} +2 -2
  16. package/dist/btc-BxNww6kW.cjs.map +1 -0
  17. package/dist/{btc-B8LfSEkv.d.ts → btc-uzt0uC-Q.d.ts} +6 -4
  18. package/dist/{btc-C4dVkxHb.d.cts → btc-wogJ4INA.d.cts} +6 -4
  19. package/dist/btc.cjs +1 -1
  20. package/dist/btc.d.cts +1 -1
  21. package/dist/btc.d.ts +1 -1
  22. package/dist/btc.js +1 -1
  23. package/dist/cashaddr-BIzawizX.js +134 -0
  24. package/dist/cashaddr-BIzawizX.js.map +1 -0
  25. package/dist/cashaddr-Ci0_8ITN.cjs +151 -0
  26. package/dist/cashaddr-Ci0_8ITN.cjs.map +1 -0
  27. package/dist/{derive-CK9Nc9Jc.js → derive-00JUOkmn.js} +7 -2
  28. package/dist/derive-00JUOkmn.js.map +1 -0
  29. package/dist/{derive-BayrpfJk.cjs → derive-BuNsfWlg.cjs} +12 -1
  30. package/dist/derive-BuNsfWlg.cjs.map +1 -0
  31. package/dist/gzip-CLzDX7AZ.cjs +79 -0
  32. package/dist/gzip-CLzDX7AZ.cjs.map +1 -0
  33. package/dist/gzip-DyZLtgJJ.js +68 -0
  34. package/dist/gzip-DyZLtgJJ.js.map +1 -0
  35. package/dist/index.cjs +44 -3
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +28 -3
  38. package/dist/index.d.ts +28 -3
  39. package/dist/index.js +43 -4
  40. package/dist/index.js.map +1 -1
  41. package/dist/{messages-DbnXy3dC.js → messages--XbVef_k.js} +38 -2
  42. package/dist/messages--XbVef_k.js.map +1 -0
  43. package/dist/{messages-BZXDY29v.cjs → messages-orAQ52CI.cjs} +43 -1
  44. package/dist/messages-orAQ52CI.cjs.map +1 -0
  45. package/dist/{tron-fufBDNuQ.js → tron-BpUEYTPT.js} +4 -67
  46. package/dist/tron-BpUEYTPT.js.map +1 -0
  47. package/dist/{tron-B9wXdzlA.cjs → tron-DYPOg5cm.cjs} +5 -68
  48. package/dist/{tron-fufBDNuQ.js.map → tron-DYPOg5cm.cjs.map} +1 -1
  49. package/dist/tron.cjs +2 -2
  50. package/dist/tron.js +2 -2
  51. package/dist/verify.cjs +224 -17
  52. package/dist/verify.cjs.map +1 -1
  53. package/dist/verify.d.cts +67 -1
  54. package/dist/verify.d.ts +67 -1
  55. package/dist/verify.js +222 -18
  56. package/dist/verify.js.map +1 -1
  57. package/package.json +13 -1
  58. package/dist/btc-B_Nq2H4T.js.map +0 -1
  59. package/dist/btc-ByzjzUKs.cjs.map +0 -1
  60. package/dist/derive-BayrpfJk.cjs.map +0 -1
  61. package/dist/derive-CK9Nc9Jc.js.map +0 -1
  62. package/dist/messages-BZXDY29v.cjs.map +0 -1
  63. package/dist/messages-DbnXy3dC.js.map +0 -1
  64. package/dist/tron-B9wXdzlA.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages-orAQ52CI.cjs","names":["utf8Encode","concatBytes","EraSdkError","EraSdkError","utf8Decode","hexToBytes"],"sources":["../src/tron-proto/wire.ts","../src/tron-proto/messages.ts"],"sourcesContent":["import { concatBytes, utf8Encode } from '../core/bytes';\nimport { EraSdkError } from '../core/errors';\n\n/**\n * Minimal protobuf wire codec for the Tron signing envelope. Varint and\n * length-delimited wire types only — the schema uses nothing else.\n *\n * Writer semantics mirror the reference implementation: EXPLICITLY SET fields\n * are written even when they hold default values (a `timestamp` of 0 is\n * `tag, 0x00` on the wire), and fields are emitted in ascending field-number\n * order. Byte-exactness against the golden fixtures depends on both.\n */\n\nexport class ProtoWriter {\n private readonly parts: Uint8Array[] = [];\n\n varintField(field: number, value: number | bigint): this {\n this.parts.push(tag(field, 0), varint(value));\n return this;\n }\n\n stringField(field: number, value: string): this {\n const bytes = utf8Encode(value);\n this.parts.push(tag(field, 2), varint(bytes.length), bytes);\n return this;\n }\n\n bytesField(field: number, value: Uint8Array): this {\n this.parts.push(tag(field, 2), varint(value.length), value);\n return this;\n }\n\n messageField(field: number, encoded: Uint8Array): this {\n this.parts.push(tag(field, 2), varint(encoded.length), encoded);\n return this;\n }\n\n finish(): Uint8Array {\n return concatBytes(...this.parts);\n }\n}\n\nfunction tag(field: number, wireType: number): Uint8Array {\n return varint((field << 3) | wireType);\n}\n\nexport function varint(value: number | bigint): Uint8Array {\n let v = typeof value === 'number' ? BigInt(value) : value;\n if (v < 0n) throw new EraSdkError('protobuf-error', 'negative varint');\n const out: number[] = [];\n do {\n let byte = Number(v & 0x7fn);\n v >>= 7n;\n if (v > 0n) byte |= 0x80;\n out.push(byte);\n } while (v > 0n);\n return new Uint8Array(out);\n}\n\nexport interface ProtoField {\n readonly field: number;\n readonly wireType: number;\n /** Set for wire type 0. */\n readonly value: bigint;\n /** Set for wire type 2 (verbatim slice of the input). */\n readonly bytes: Uint8Array;\n}\n\n/**\n * Hardened field reader: varint shift capped, lengths bounds-checked before\n * slicing, unknown fields skippable by wire type, group wire types refused.\n */\nexport function readFields(bytes: Uint8Array): ProtoField[] {\n const fields: ProtoField[] = [];\n let offset = 0;\n\n const readVarint = (): bigint => {\n let result = 0n;\n let shift = 0n;\n for (;;) {\n const byte = bytes[offset];\n if (byte === undefined) throw new EraSdkError('protobuf-error', 'truncated varint');\n offset += 1;\n result |= BigInt(byte & 0x7f) << shift;\n if ((byte & 0x80) === 0) return result;\n shift += 7n;\n if (shift > 63n) throw new EraSdkError('protobuf-error', 'varint exceeds 64 bits');\n }\n };\n\n while (offset < bytes.length) {\n const key = readVarint();\n const field = Number(key >> 3n);\n const wireType = Number(key & 7n);\n if (field === 0) throw new EraSdkError('protobuf-error', 'field number 0');\n switch (wireType) {\n case 0: {\n fields.push({ field, wireType, value: readVarint(), bytes: EMPTY });\n break;\n }\n case 1: {\n if (offset + 8 > bytes.length) throw new EraSdkError('protobuf-error', 'truncated fixed64');\n fields.push({ field, wireType, value: 0n, bytes: bytes.slice(offset, offset + 8) });\n offset += 8;\n break;\n }\n case 2: {\n const length = readVarint();\n if (length > BigInt(bytes.length - offset)) {\n throw new EraSdkError('protobuf-error', 'length-delimited field exceeds input');\n }\n const len = Number(length);\n fields.push({ field, wireType, value: 0n, bytes: bytes.slice(offset, offset + len) });\n offset += len;\n break;\n }\n case 5: {\n if (offset + 4 > bytes.length) throw new EraSdkError('protobuf-error', 'truncated fixed32');\n fields.push({ field, wireType, value: 0n, bytes: bytes.slice(offset, offset + 4) });\n offset += 4;\n break;\n }\n default:\n throw new EraSdkError('protobuf-error', `unsupported wire type ${wireType}`);\n }\n }\n return fields;\n}\n\nconst EMPTY = new Uint8Array(0);\n\n/** First occurrence of a length-delimited field, or null. */\nexport function firstBytes(fields: ProtoField[], field: number): Uint8Array | null {\n for (const f of fields) {\n if (f.field === field && f.wireType === 2) return f.bytes;\n }\n return null;\n}\n\n/** First occurrence of a varint field, or null. */\nexport function firstVarint(fields: ProtoField[], field: number): bigint | null {\n for (const f of fields) {\n if (f.field === field && f.wireType === 0) return f.value;\n }\n return null;\n}\n","import { hexToBytes, utf8Decode } from '../core/bytes';\nimport { EraSdkError } from '../core/errors';\nimport { firstBytes, ProtoWriter, readFields } from './wire';\n\n/**\n * The Tron signing envelope (\"QrCode Protocol\"):\n * `Base -> Payload -> SignTransaction -> TronTx`, gzip-compressed and wrapped\n * in a `keystone-sign-request` (6101) CBOR map. Schemas are vendored under\n * `proto/tron/`; this codec hand-writes the five messages the wallet side\n * speaks, with reference-exact field emission (explicitly-set defaults ARE\n * written; ascending field order).\n */\n\nexport interface TronLatestBlock {\n /** FULL 64-hex block id of a live now-block (the device slices ref_block_hash from it). */\n readonly hash: string;\n readonly number: number;\n readonly timestamp: number;\n}\n\nexport interface TronSignRequestProto {\n /** Lowercase, ZERO-PADDED 8-hex source fingerprint (the wire demands the string form). */\n readonly xfpHex: string;\n /** Hyphenated UUID string; echoed by the device as the only reply binding. */\n readonly signId: string;\n readonly hdPath: string;\n readonly timestamp: number;\n readonly decimals: number;\n readonly token: string;\n readonly contractAddress?: string | undefined;\n readonly from?: string | undefined;\n readonly to?: string | undefined;\n readonly memo?: string | undefined;\n readonly value?: string | undefined;\n readonly fee?: number | undefined;\n readonly latestBlock: TronLatestBlock;\n /** Serialized `Transaction.raw_data` — the signing source of truth. */\n readonly rawData: Uint8Array;\n}\n\nconst PAYLOAD_TYPE_SIGN_TX = 2;\n\nexport function encodeSignRequestProto(req: TronSignRequestProto): Uint8Array {\n if (req.fee !== undefined && (req.fee < 0 || req.fee > 0x7fffffff)) {\n throw new EraSdkError('invalid-props', 'tron fee must fit a positive int32');\n }\n const latestBlock = new ProtoWriter()\n .stringField(1, req.latestBlock.hash)\n .varintField(2, req.latestBlock.number)\n .varintField(3, req.latestBlock.timestamp)\n .finish();\n\n const tronTx = new ProtoWriter().stringField(1, req.token);\n if (req.contractAddress !== undefined) tronTx.stringField(2, req.contractAddress);\n if (req.from !== undefined) tronTx.stringField(3, req.from);\n if (req.to !== undefined) tronTx.stringField(4, req.to);\n if (req.memo !== undefined) tronTx.stringField(5, req.memo);\n if (req.value !== undefined) tronTx.stringField(6, req.value);\n tronTx.messageField(7, latestBlock);\n if (req.fee !== undefined) tronTx.varintField(9, req.fee);\n tronTx.bytesField(10, req.rawData);\n\n const signTx = new ProtoWriter()\n .stringField(1, 'TRON')\n .stringField(2, req.signId)\n .stringField(3, req.hdPath)\n .varintField(4, req.timestamp)\n .varintField(5, req.decimals)\n .messageField(8, tronTx.finish())\n .finish();\n\n const payload = new ProtoWriter()\n .varintField(1, PAYLOAD_TYPE_SIGN_TX)\n .stringField(2, req.xfpHex)\n .messageField(4, signTx)\n .finish();\n\n return new ProtoWriter()\n .varintField(1, 2) // Base.version\n .stringField(2, 'QrCode Protocol')\n .messageField(3, payload)\n .finish();\n}\n\nexport interface BchProtoInput {\n /** Display-order (big-endian) txid, 64 hex chars — a STRING on the wire. */\n readonly txidHex: string;\n readonly index: number;\n /** UTXO value in satoshis. */\n readonly value: bigint;\n /** Compressed public key, 66 hex chars — a STRING on the wire. */\n readonly publicKeyHex: string;\n /** Full derivation path of the key that owns the UTXO. */\n readonly ownerKeyPath: string;\n}\n\nexport interface BchProtoOutput {\n /** CashAddr (verbatim; the device accepts both prefixed and bare form). */\n readonly address: string;\n readonly value: bigint;\n readonly isChange: boolean;\n readonly changeAddressPath?: string | undefined;\n}\n\nexport interface BchSignRequestProto {\n /** Lowercase, ZERO-PADDED 8-hex source fingerprint (the wire demands the string form). */\n readonly xfpHex: string;\n /** Hyphenated UUID string; echoed by the device as the only reply binding. */\n readonly signId: string;\n readonly timestamp: number;\n /** Fee in satoshis — shown on the device; MUST equal inputs minus outputs. */\n readonly fee: bigint;\n readonly dustThreshold: number;\n readonly memo?: string | undefined;\n readonly inputs: readonly BchProtoInput[];\n readonly outputs: readonly BchProtoOutput[];\n}\n\n/**\n * The BCH leg of the same envelope: `Base -> Payload -> SignTransaction ->\n * BchTx` at oneof tag 10, with FLAT inputs (value/publicKey are direct\n * fields, no nested Utxo sub-message). Unlike the Tron writer, default\n * values are OMITTED here — proto3 emission, which is what the reference\n * wallet capture the firmware fixture pins does. `hdPath` (SignTransaction\n * field 3) is deliberately absent: the reference never sends it and the\n * device reads the per-input `ownerKeyPath` instead.\n */\nexport function encodeBchSignRequestProto(req: BchSignRequestProto): Uint8Array {\n const bchTx = new ProtoWriter();\n if (req.fee !== 0n) bchTx.varintField(1, req.fee);\n if (req.dustThreshold !== 0) bchTx.varintField(2, req.dustThreshold);\n if (req.memo !== undefined && req.memo !== '') bchTx.stringField(3, req.memo);\n for (const input of req.inputs) {\n const w = new ProtoWriter().stringField(1, input.txidHex);\n if (input.index !== 0) w.varintField(2, input.index);\n if (input.value !== 0n) w.varintField(3, input.value);\n w.stringField(4, input.publicKeyHex);\n w.stringField(5, input.ownerKeyPath);\n bchTx.messageField(4, w.finish());\n }\n for (const output of req.outputs) {\n const w = new ProtoWriter().stringField(1, output.address);\n if (output.value !== 0n) w.varintField(2, output.value);\n if (output.isChange) w.varintField(3, 1);\n if (output.changeAddressPath !== undefined && output.changeAddressPath !== '') {\n w.stringField(4, output.changeAddressPath);\n }\n bchTx.messageField(5, w.finish());\n }\n\n const signTx = new ProtoWriter().stringField(1, 'BCH').stringField(2, req.signId);\n if (req.timestamp !== 0) signTx.varintField(4, req.timestamp);\n signTx.varintField(5, 8); // decimal: BCH is always 8\n signTx.messageField(10, bchTx.finish());\n\n const payload = new ProtoWriter()\n .varintField(1, PAYLOAD_TYPE_SIGN_TX)\n .stringField(2, req.xfpHex)\n .messageField(4, signTx.finish())\n .finish();\n\n return new ProtoWriter()\n .varintField(1, 2) // Base.version\n .stringField(2, 'QrCode Protocol')\n .messageField(3, payload)\n .finish();\n}\n\nexport interface TronSignResultProto {\n readonly signId: string;\n readonly txId: string;\n readonly rawTx: string;\n}\n\n/** Parse a `keystone-sign-result` Base protobuf. Unknown fields are skipped. */\nexport function decodeSignResultProto(bytes: Uint8Array): TronSignResultProto {\n const base = readFields(bytes);\n const payloadBytes = firstBytes(base, 3);\n if (!payloadBytes) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result carries no payload');\n }\n const payload = readFields(payloadBytes);\n const resultBytes = firstBytes(payload, 7); // Payload.signTxResult\n if (!resultBytes) {\n return { signId: '', txId: '', rawTx: '' };\n }\n const result = readFields(resultBytes);\n return {\n signId: text(firstBytes(result, 1)),\n txId: text(firstBytes(result, 2)),\n rawTx: text(firstBytes(result, 3)),\n };\n}\n\nfunction text(bytes: Uint8Array | null): string {\n if (!bytes) return '';\n try {\n return utf8Decode(bytes);\n } catch {\n throw new EraSdkError('malformed-reply', 'protobuf string field is not valid UTF-8');\n }\n}\n\nexport interface SignedTronTx {\n /** Verbatim `raw_data` slice — the digest is sha256 over the device's own serialization. */\n readonly rawData: Uint8Array;\n readonly signatures: readonly Uint8Array[];\n}\n\n/**\n * Split a signed Tron network `Transaction` frame (`{1: raw_data, 2: signature*}`)\n * from the reply's `rawTx` hex. Top-level fields must be length-delimited —\n * anything else is not a transaction frame.\n */\nexport function splitSignedTronTx(rawTxHex: string): SignedTronTx {\n let frame: Uint8Array;\n try {\n frame = hexToBytes(rawTxHex);\n } catch {\n throw new EraSdkError('malformed-reply', 'signed Tron transaction is not hex');\n }\n const fields = readFields(frame);\n let rawData: Uint8Array | null = null;\n const signatures: Uint8Array[] = [];\n for (const f of fields) {\n if (f.wireType !== 2) {\n throw new EraSdkError('malformed-reply', 'unexpected wire type in signed Tron transaction');\n }\n if (f.field === 1 && rawData === null) rawData = f.bytes;\n if (f.field === 2) signatures.push(f.bytes);\n }\n if (rawData === null) {\n throw new EraSdkError('malformed-reply', 'signed Tron transaction carries no raw_data');\n }\n return { rawData, signatures };\n}\n"],"mappings":"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"}
@@ -1,70 +1,7 @@
1
- import { B as cbMap, H as cbText, K as EraSdkError, N as asBytes, T as concatBytes, W as mapGet, _ as Ur, c as resolveRequestId, g as uuidStringify, h as normalizeRequestId, l as toUr, n as makeSignRequest, o as requireUrType, r as requireReplyMap, s as resolveContext, x as cborEncode, y as crc32, z as cbBytes } from "./shared-Bbgejrus.js";
1
+ import { B as cbMap, H as cbText, K as EraSdkError, N as asBytes, W as mapGet, _ as Ur, c as resolveRequestId, g as uuidStringify, h as normalizeRequestId, l as toUr, n as makeSignRequest, o as requireUrType, r as requireReplyMap, s as resolveContext, x as cborEncode, z as cbBytes } from "./shared-Bbgejrus.js";
2
2
  import { i as parsePath, r as normalizeXfp, s as xfpToHex } from "./keypath-Bvv_cfHt.js";
3
- import { n as encodeSignRequestProto, r as splitSignedTronTx, t as decodeSignResultProto } from "./messages-DbnXy3dC.js";
4
- import { Gunzip, gzipSync } from "fflate";
5
- //#region src/tron-proto/gzip.ts
6
- /** Smallest possible gzip stream: 10-byte header + 8-byte trailer. */
7
- const MIN_GZIP_BYTES = 18;
8
- /** Bytes fed to the inflater per step; bounds the overshoot past the cap. */
9
- const SLICE_BYTES = 1024;
10
- /** Deterministic gzip (fixed level, zeroed mtime) for reproducible request bytes. */
11
- function gzipCompress(data) {
12
- return gzipSync(data, {
13
- level: 9,
14
- mtime: 0
15
- });
16
- }
17
- /**
18
- * Inflate with a hard output ceiling.
19
- *
20
- * A one-shot gunzip allocates the entire output before anyone can refuse it —
21
- * gzip reaches ~1000:1 in practice, so a few hundred scanned bytes could ask
22
- * for an arbitrary allocation. The decoder is therefore driven in slices and
23
- * the output counted as it arrives; the moment the total would pass the cap,
24
- * feeding stops and the reply is refused.
25
- *
26
- * The trailer's ISIZE (declared inflated length) is used twice: as a cheap
27
- * refusal of honest bombs before any work, and as a truncation check at the
28
- * end — an inflater hands back a partial buffer for a truncated stream
29
- * without erroring, and a genuine device reply always declares honestly.
30
- */
31
- function gunzipCapped(data, maxOutputBytes) {
32
- if (data.length < MIN_GZIP_BYTES) throw new EraSdkError("gzip-error", "compressed payload is too short to be a gzip stream");
33
- if (data[0] !== 31 || data[1] !== 139) throw new EraSdkError("gzip-error", "compressed payload is not a gzip stream");
34
- const n = data.length;
35
- const isize = (data[n - 4] | data[n - 3] << 8 | data[n - 2] << 16 | data[n - 1] << 24) >>> 0;
36
- if (isize > maxOutputBytes) throw new EraSdkError("gzip-error", `compressed payload declares ${isize} bytes, over the ${maxOutputBytes} byte ceiling`);
37
- const chunks = [];
38
- let total = 0;
39
- let overflowed = false;
40
- let malformed = null;
41
- const gunzip = new Gunzip((chunk) => {
42
- if (overflowed) return;
43
- if (total + chunk.length > maxOutputBytes) {
44
- overflowed = true;
45
- return;
46
- }
47
- chunks.push(chunk);
48
- total += chunk.length;
49
- });
50
- try {
51
- for (let offset = 0; offset < n && !overflowed; offset += SLICE_BYTES) {
52
- const end = Math.min(offset + SLICE_BYTES, n);
53
- const isLast = end === n;
54
- gunzip.push(data.slice(offset, end), isLast);
55
- }
56
- } catch (e) {
57
- malformed = e.message ?? "inflate error";
58
- }
59
- if (overflowed) throw new EraSdkError("gzip-error", `compressed payload inflates past the ${maxOutputBytes} byte ceiling`);
60
- if (malformed !== null) throw new EraSdkError("gzip-error", `compressed payload is malformed: ${malformed}`);
61
- const out = concatBytes(...chunks);
62
- if (out.length !== isize) throw new EraSdkError("gzip-error", `compressed payload inflated to ${out.length} bytes but declares ${isize} — truncated or malformed`);
63
- const declaredCrc = (data[n - 8] | data[n - 7] << 8 | data[n - 6] << 16 | data[n - 5] << 24) >>> 0;
64
- if (crc32(out) !== declaredCrc) throw new EraSdkError("gzip-error", "compressed payload is malformed: CRC mismatch");
65
- return out;
66
- }
67
- //#endregion
3
+ import { n as gzipCompress, t as gunzipCapped } from "./gzip-DyZLtgJJ.js";
4
+ import { i as splitSignedTronTx, r as encodeSignRequestProto, t as decodeSignResultProto } from "./messages--XbVef_k.js";
68
5
  //#region src/chains/tron.ts
69
6
  /**
70
7
  * Ceilings on the gzip blob a `keystone-sign-result` may carry. Tron is the
@@ -156,4 +93,4 @@ function signIdToBytes(signId) {
156
93
  //#endregion
157
94
  export { TronChain as t };
158
95
 
159
- //# sourceMappingURL=tron-fufBDNuQ.js.map
96
+ //# sourceMappingURL=tron-BpUEYTPT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tron-BpUEYTPT.js","names":["UrValue"],"sources":["../src/chains/tron.ts"],"sourcesContent":["import { cborEncode } from '../cbor/encode';\nimport { asBytes, cbBytes, cbMap, cbText, mapGet } from '../cbor/model';\nimport { EraSdkError } from '../core/errors';\nimport { normalizeRequestId, uuidStringify } from '../core/rand';\nimport { normalizeXfp, parsePath, xfpToHex } from '../registry/keypath';\nimport { gunzipCapped, gzipCompress } from '../tron-proto/gzip';\nimport type { SignedTronTx, TronLatestBlock } from '../tron-proto/messages';\nimport {\n decodeSignResultProto,\n encodeSignRequestProto,\n splitSignedTronTx,\n} from '../tron-proto/messages';\nimport type { Ur } from '../ur/ur';\nimport { Ur as UrValue } from '../ur/ur';\nimport type { ChainContext, EraConnectConfig, ExpectedReply, SignRequest } from './shared';\nimport {\n makeSignRequest,\n requireReplyMap,\n requireUrType,\n resolveContext,\n resolveRequestId,\n toUr,\n} from './shared';\n\nexport type { TronLatestBlock } from '../tron-proto/messages';\n\nexport interface TronSignRequestProps {\n readonly requestId?: Uint8Array | string;\n /**\n * Serialized `Transaction.raw_data` — THE signing source of truth. The\n * device signs `sha256(rawData) = txID` and returns the transaction with\n * `raw_data` unmodified.\n */\n readonly rawData: Uint8Array;\n /** Full signing path, e.g. `m/44'/195'/0'/0/0`. */\n readonly path: string;\n readonly xfp: string | number;\n /**\n * Reference block context. Source it from a LIVE now-block query and pass\n * the FULL 64-hex block id.\n */\n readonly latestBlock: TronLatestBlock;\n /** On-device display only; safe to omit for opaque dApp transactions. */\n readonly display?: {\n readonly token?: string;\n readonly contractAddress?: string;\n readonly from?: string;\n readonly to?: string;\n readonly value?: string;\n readonly memo?: string;\n readonly fee?: number;\n readonly decimals?: number;\n };\n readonly timestamp?: number;\n readonly origin?: string;\n}\n\nexport interface TronSignatureResult {\n readonly requestId: Uint8Array;\n /** `sha256(raw_data)` hex, as computed by the device. */\n readonly txId: string;\n /** Hex of the fully assembled signed transaction — broadcast as-is. */\n readonly rawTx: string;\n /** The signed frame split into `raw_data` + signatures (65-byte r||s||recovery each). */\n readonly signedTx: SignedTronTx;\n}\n\n/**\n * Ceilings on the gzip blob a `keystone-sign-result` may carry. Tron is the\n * only chain whose reply is compressed, so it is the only one where a few\n * hundred scanned bytes can ask for an arbitrary allocation. Generous\n * multiples of the largest real device reply.\n */\nconst MAX_COMPRESSED_BYTES = 8 * 1024;\nconst MAX_INFLATED_BYTES = 64 * 1024;\n\nconst REPLY_TYPES = ['keystone-sign-result'] as const;\n\n/**\n * Tron signing rides the structured `keystone-sign-request` (6101) envelope —\n * a gzip-compressed protobuf inside CBOR `{1: gzip(protobuf), 2: origin}`.\n * The registry's generic `tron-sign-request` (5101) is NOT accepted by the\n * device and gets no response; do not emit it.\n */\nexport class TronChain {\n private readonly context: ChainContext;\n\n constructor(config?: EraConnectConfig) {\n this.context = resolveContext(config);\n }\n\n /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */\n generateSignRequest(props: TronSignRequestProps): SignRequest<TronSignatureResult> {\n const requestId = resolveRequestId(this.context, props.requestId);\n parsePath(props.path); // validate shape; the wire carries the string form\n const xfp = normalizeXfp(props.xfp);\n if (props.rawData.length === 0) {\n throw new EraSdkError('invalid-props', 'rawData must not be empty');\n }\n if (!/^[0-9a-fA-F]{64}$/.test(props.latestBlock.hash)) {\n throw new EraSdkError(\n 'invalid-props',\n 'latestBlock.hash must be the FULL 64-hex block id (the device slices ref_block_hash from it)',\n );\n }\n\n const proto = encodeSignRequestProto({\n // Zero-padded to eight characters: the firmware parses this string with\n // a hex reader that yields 0 for anything shorter than 4 bytes, and a\n // zero fingerprint fails validation — a wallet whose fingerprint starts\n // with a zero byte (1 in 256) could not sign at all without the pad.\n xfpHex: xfpToHex(xfp),\n signId: uuidStringify(requestId),\n hdPath: props.path,\n timestamp: props.timestamp ?? 0,\n decimals: props.display?.decimals ?? 6,\n token: props.display?.token ?? '',\n contractAddress: props.display?.contractAddress,\n from: props.display?.from,\n to: props.display?.to,\n memo: props.display?.memo,\n value: props.display?.value,\n fee: props.display?.fee,\n latestBlock: props.latestBlock,\n rawData: props.rawData,\n });\n\n const ur = new UrValue(\n 'keystone-sign-request',\n cborEncode(\n cbMap([\n [1, cbBytes(gzipCompress(proto))],\n [2, cbText(props.origin ?? this.context.origin)],\n ]),\n ),\n );\n return makeSignRequest({\n ur,\n requestId,\n replyTypes: REPLY_TYPES,\n context: this.context,\n parse: (reply) => parseTronSignature(reply, requestId),\n });\n }\n\n /**\n * Parse a `keystone-sign-result` standalone. Tron carries the request id\n * INSIDE the protobuf (`signId`); passing `expect.requestId` is what makes\n * the echo check possible here — prefer `SignRequest.scanner().parse()`.\n */\n parseSignature(input: Ur | string, expect?: ExpectedReply): TronSignatureResult {\n return parseTronSignature(\n toUr(input),\n expect?.requestId === undefined ? undefined : normalizeRequestId(expect.requestId),\n );\n }\n}\n\nfunction parseTronSignature(\n ur: Ur,\n expectedRequestId: Uint8Array | undefined,\n): TronSignatureResult {\n requireUrType(ur, [...REPLY_TYPES], 'keystone-sign-result');\n const map = requireReplyMap(ur, 'keystone-sign-result');\n const compressed = asBytes(mapGet(map, 1));\n if (!compressed) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result is missing the payload (key 1)');\n }\n if (compressed.length > MAX_COMPRESSED_BYTES) {\n throw new EraSdkError(\n 'limit-exceeded',\n `keystone-sign-result payload is ${compressed.length} bytes, over the ${MAX_COMPRESSED_BYTES} byte ceiling`,\n );\n }\n const result = decodeSignResultProto(gunzipCapped(compressed, MAX_INFLATED_BYTES));\n\n // The signId echo is the ONLY anti-replay binding on this chain — the\n // device's own bytes are broadcast verbatim, so a stale reply that skipped\n // this check would finalize a payment the user did not approve now.\n if (expectedRequestId !== undefined) {\n const expected = uuidStringify(expectedRequestId).toLowerCase();\n if (result.signId.toLowerCase() !== expected) {\n throw new EraSdkError(\n 'request-id-mismatch',\n result.signId === ''\n ? 'keystone-sign-result does not echo the request id (signId)'\n : 'keystone-sign-result echoes a different request id — it answers another sign request, not this one',\n );\n }\n }\n if (result.rawTx === '') {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result has no signed transaction');\n }\n const signedTx = splitSignedTronTx(result.rawTx);\n const requestId = expectedRequestId ?? signIdToBytes(result.signId);\n return { requestId, txId: result.txId, rawTx: result.rawTx, signedTx };\n}\n\nfunction signIdToBytes(signId: string): Uint8Array {\n try {\n return normalizeRequestId(signId);\n } catch {\n return new Uint8Array(16);\n }\n}\n"],"mappings":";;;;;;;;;;;AAyEA,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB;AAE3B,MAAM,cAAc,CAAC,sBAAsB;;;;;;;AAQ3C,IAAa,YAAb,MAAuB;CAGrB,YAAY,QAA2B;EACrC,KAAK,UAAU,eAAe,MAAM;CACtC;;CAGA,oBAAoB,OAA+D;EACjF,MAAM,YAAY,iBAAiB,KAAK,SAAS,MAAM,SAAS;EAChE,UAAU,MAAM,IAAI;EACpB,MAAM,MAAM,aAAa,MAAM,GAAG;EAClC,IAAI,MAAM,QAAQ,WAAW,GAC3B,MAAM,IAAI,YAAY,iBAAiB,2BAA2B;EAEpE,IAAI,CAAC,oBAAoB,KAAK,MAAM,YAAY,IAAI,GAClD,MAAM,IAAI,YACR,iBACA,8FACF;EAGF,MAAM,QAAQ,uBAAuB;GAKnC,QAAQ,SAAS,GAAG;GACpB,QAAQ,cAAc,SAAS;GAC/B,QAAQ,MAAM;GACd,WAAW,MAAM,aAAa;GAC9B,UAAU,MAAM,SAAS,YAAY;GACrC,OAAO,MAAM,SAAS,SAAS;GAC/B,iBAAiB,MAAM,SAAS;GAChC,MAAM,MAAM,SAAS;GACrB,IAAI,MAAM,SAAS;GACnB,MAAM,MAAM,SAAS;GACrB,OAAO,MAAM,SAAS;GACtB,KAAK,MAAM,SAAS;GACpB,aAAa,MAAM;GACnB,SAAS,MAAM;EACjB,CAAC;EAED,MAAM,KAAK,IAAIA,GACb,yBACA,WACE,MAAM,CACJ,CAAC,GAAG,QAAQ,aAAa,KAAK,CAAC,CAAC,GAChC,CAAC,GAAG,OAAO,MAAM,UAAU,KAAK,QAAQ,MAAM,CAAC,CACjD,CAAC,CACH,CACF;EACA,OAAO,gBAAgB;GACrB;GACA;GACA,YAAY;GACZ,SAAS,KAAK;GACd,QAAQ,UAAU,mBAAmB,OAAO,SAAS;EACvD,CAAC;CACH;;;;;;CAOA,eAAe,OAAoB,QAA6C;EAC9E,OAAO,mBACL,KAAK,KAAK,GACV,QAAQ,cAAc,KAAA,IAAY,KAAA,IAAY,mBAAmB,OAAO,SAAS,CACnF;CACF;AACF;AAEA,SAAS,mBACP,IACA,mBACqB;CACrB,cAAc,IAAI,CAAC,GAAG,WAAW,GAAG,sBAAsB;CAC1D,MAAM,MAAM,gBAAgB,IAAI,sBAAsB;CACtD,MAAM,aAAa,QAAQ,OAAO,KAAK,CAAC,CAAC;CACzC,IAAI,CAAC,YACH,MAAM,IAAI,YAAY,mBAAmB,qDAAqD;CAEhG,IAAI,WAAW,SAAS,sBACtB,MAAM,IAAI,YACR,kBACA,mCAAmC,WAAW,OAAO,mBAAmB,qBAAqB,cAC/F;CAEF,MAAM,SAAS,sBAAsB,aAAa,YAAY,kBAAkB,CAAC;CAKjF,IAAI,sBAAsB,KAAA,GAAW;EACnC,MAAM,WAAW,cAAc,iBAAiB,CAAC,CAAC,YAAY;EAC9D,IAAI,OAAO,OAAO,YAAY,MAAM,UAClC,MAAM,IAAI,YACR,uBACA,OAAO,WAAW,KACd,+DACA,oGACN;CAEJ;CACA,IAAI,OAAO,UAAU,IACnB,MAAM,IAAI,YAAY,mBAAmB,gDAAgD;CAE3F,MAAM,WAAW,kBAAkB,OAAO,KAAK;CAE/C,OAAO;EAAE,WADS,qBAAqB,cAAc,OAAO,MAAM;EAC9C,MAAM,OAAO;EAAM,OAAO,OAAO;EAAO;CAAS;AACvE;AAEA,SAAS,cAAc,QAA4B;CACjD,IAAI;EACF,OAAO,mBAAmB,MAAM;CAClC,QAAQ;EACN,uBAAO,IAAI,WAAW,EAAE;CAC1B;AACF"}
@@ -1,70 +1,7 @@
1
1
  const require_shared = require("./shared-Cu2yynP9.cjs");
2
2
  const require_keypath = require("./keypath-pzm_YIfN.cjs");
3
- const require_messages = require("./messages-BZXDY29v.cjs");
4
- let fflate = require("fflate");
5
- //#region src/tron-proto/gzip.ts
6
- /** Smallest possible gzip stream: 10-byte header + 8-byte trailer. */
7
- const MIN_GZIP_BYTES = 18;
8
- /** Bytes fed to the inflater per step; bounds the overshoot past the cap. */
9
- const SLICE_BYTES = 1024;
10
- /** Deterministic gzip (fixed level, zeroed mtime) for reproducible request bytes. */
11
- function gzipCompress(data) {
12
- return (0, fflate.gzipSync)(data, {
13
- level: 9,
14
- mtime: 0
15
- });
16
- }
17
- /**
18
- * Inflate with a hard output ceiling.
19
- *
20
- * A one-shot gunzip allocates the entire output before anyone can refuse it —
21
- * gzip reaches ~1000:1 in practice, so a few hundred scanned bytes could ask
22
- * for an arbitrary allocation. The decoder is therefore driven in slices and
23
- * the output counted as it arrives; the moment the total would pass the cap,
24
- * feeding stops and the reply is refused.
25
- *
26
- * The trailer's ISIZE (declared inflated length) is used twice: as a cheap
27
- * refusal of honest bombs before any work, and as a truncation check at the
28
- * end — an inflater hands back a partial buffer for a truncated stream
29
- * without erroring, and a genuine device reply always declares honestly.
30
- */
31
- function gunzipCapped(data, maxOutputBytes) {
32
- if (data.length < MIN_GZIP_BYTES) throw new require_shared.EraSdkError("gzip-error", "compressed payload is too short to be a gzip stream");
33
- if (data[0] !== 31 || data[1] !== 139) throw new require_shared.EraSdkError("gzip-error", "compressed payload is not a gzip stream");
34
- const n = data.length;
35
- const isize = (data[n - 4] | data[n - 3] << 8 | data[n - 2] << 16 | data[n - 1] << 24) >>> 0;
36
- if (isize > maxOutputBytes) throw new require_shared.EraSdkError("gzip-error", `compressed payload declares ${isize} bytes, over the ${maxOutputBytes} byte ceiling`);
37
- const chunks = [];
38
- let total = 0;
39
- let overflowed = false;
40
- let malformed = null;
41
- const gunzip = new fflate.Gunzip((chunk) => {
42
- if (overflowed) return;
43
- if (total + chunk.length > maxOutputBytes) {
44
- overflowed = true;
45
- return;
46
- }
47
- chunks.push(chunk);
48
- total += chunk.length;
49
- });
50
- try {
51
- for (let offset = 0; offset < n && !overflowed; offset += SLICE_BYTES) {
52
- const end = Math.min(offset + SLICE_BYTES, n);
53
- const isLast = end === n;
54
- gunzip.push(data.slice(offset, end), isLast);
55
- }
56
- } catch (e) {
57
- malformed = e.message ?? "inflate error";
58
- }
59
- if (overflowed) throw new require_shared.EraSdkError("gzip-error", `compressed payload inflates past the ${maxOutputBytes} byte ceiling`);
60
- if (malformed !== null) throw new require_shared.EraSdkError("gzip-error", `compressed payload is malformed: ${malformed}`);
61
- const out = require_shared.concatBytes(...chunks);
62
- if (out.length !== isize) throw new require_shared.EraSdkError("gzip-error", `compressed payload inflated to ${out.length} bytes but declares ${isize} — truncated or malformed`);
63
- const declaredCrc = (data[n - 8] | data[n - 7] << 8 | data[n - 6] << 16 | data[n - 5] << 24) >>> 0;
64
- if (require_shared.crc32(out) !== declaredCrc) throw new require_shared.EraSdkError("gzip-error", "compressed payload is malformed: CRC mismatch");
65
- return out;
66
- }
67
- //#endregion
3
+ const require_gzip = require("./gzip-CLzDX7AZ.cjs");
4
+ const require_messages = require("./messages-orAQ52CI.cjs");
68
5
  //#region src/chains/tron.ts
69
6
  /**
70
7
  * Ceilings on the gzip blob a `keystone-sign-result` may carry. Tron is the
@@ -108,7 +45,7 @@ var TronChain = class {
108
45
  latestBlock: props.latestBlock,
109
46
  rawData: props.rawData
110
47
  });
111
- const ur = new require_shared.Ur("keystone-sign-request", require_shared.cborEncode(require_shared.cbMap([[1, require_shared.cbBytes(gzipCompress(proto))], [2, require_shared.cbText(props.origin ?? this.context.origin)]])));
48
+ const ur = new require_shared.Ur("keystone-sign-request", require_shared.cborEncode(require_shared.cbMap([[1, require_shared.cbBytes(require_gzip.gzipCompress(proto))], [2, require_shared.cbText(props.origin ?? this.context.origin)]])));
112
49
  return require_shared.makeSignRequest({
113
50
  ur,
114
51
  requestId,
@@ -132,7 +69,7 @@ function parseTronSignature(ur, expectedRequestId) {
132
69
  const compressed = require_shared.asBytes(require_shared.mapGet(map, 1));
133
70
  if (!compressed) throw new require_shared.EraSdkError("malformed-reply", "keystone-sign-result is missing the payload (key 1)");
134
71
  if (compressed.length > MAX_COMPRESSED_BYTES) throw new require_shared.EraSdkError("limit-exceeded", `keystone-sign-result payload is ${compressed.length} bytes, over the ${MAX_COMPRESSED_BYTES} byte ceiling`);
135
- const result = require_messages.decodeSignResultProto(gunzipCapped(compressed, MAX_INFLATED_BYTES));
72
+ const result = require_messages.decodeSignResultProto(require_gzip.gunzipCapped(compressed, MAX_INFLATED_BYTES));
136
73
  if (expectedRequestId !== void 0) {
137
74
  const expected = require_shared.uuidStringify(expectedRequestId).toLowerCase();
138
75
  if (result.signId.toLowerCase() !== expected) throw new require_shared.EraSdkError("request-id-mismatch", result.signId === "" ? "keystone-sign-result does not echo the request id (signId)" : "keystone-sign-result echoes a different request id — it answers another sign request, not this one");
@@ -161,4 +98,4 @@ Object.defineProperty(exports, "TronChain", {
161
98
  }
162
99
  });
163
100
 
164
- //# sourceMappingURL=tron-B9wXdzlA.cjs.map
101
+ //# sourceMappingURL=tron-DYPOg5cm.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"tron-fufBDNuQ.js","names":["UrValue"],"sources":["../src/tron-proto/gzip.ts","../src/chains/tron.ts"],"sourcesContent":["import { Gunzip, gzipSync } from 'fflate';\nimport { concatBytes } from '../core/bytes';\nimport { EraSdkError } from '../core/errors';\nimport { crc32 } from '../ur/crc32';\n\n/** Smallest possible gzip stream: 10-byte header + 8-byte trailer. */\nconst MIN_GZIP_BYTES = 18;\n\n/** Bytes fed to the inflater per step; bounds the overshoot past the cap. */\nconst SLICE_BYTES = 1024;\n\n/** Deterministic gzip (fixed level, zeroed mtime) for reproducible request bytes. */\nexport function gzipCompress(data: Uint8Array): Uint8Array {\n return gzipSync(data, { level: 9, mtime: 0 });\n}\n\n/**\n * Inflate with a hard output ceiling.\n *\n * A one-shot gunzip allocates the entire output before anyone can refuse it —\n * gzip reaches ~1000:1 in practice, so a few hundred scanned bytes could ask\n * for an arbitrary allocation. The decoder is therefore driven in slices and\n * the output counted as it arrives; the moment the total would pass the cap,\n * feeding stops and the reply is refused.\n *\n * The trailer's ISIZE (declared inflated length) is used twice: as a cheap\n * refusal of honest bombs before any work, and as a truncation check at the\n * end — an inflater hands back a partial buffer for a truncated stream\n * without erroring, and a genuine device reply always declares honestly.\n */\nexport function gunzipCapped(data: Uint8Array, maxOutputBytes: number): Uint8Array {\n if (data.length < MIN_GZIP_BYTES) {\n throw new EraSdkError('gzip-error', 'compressed payload is too short to be a gzip stream');\n }\n if (data[0] !== 0x1f || data[1] !== 0x8b) {\n throw new EraSdkError('gzip-error', 'compressed payload is not a gzip stream');\n }\n const n = data.length;\n const isize =\n (data[n - 4]! | (data[n - 3]! << 8) | (data[n - 2]! << 16) | (data[n - 1]! << 24)) >>> 0;\n if (isize > maxOutputBytes) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload declares ${isize} bytes, over the ${maxOutputBytes} byte ceiling`,\n );\n }\n\n const chunks: Uint8Array[] = [];\n let total = 0;\n let overflowed = false;\n let malformed: string | null = null;\n\n const gunzip = new Gunzip((chunk) => {\n if (overflowed) return;\n if (total + chunk.length > maxOutputBytes) {\n overflowed = true;\n return;\n }\n chunks.push(chunk);\n total += chunk.length;\n });\n\n try {\n for (let offset = 0; offset < n && !overflowed; offset += SLICE_BYTES) {\n const end = Math.min(offset + SLICE_BYTES, n);\n const isLast = end === n;\n gunzip.push(data.slice(offset, end), isLast);\n }\n } catch (e) {\n malformed = (e as Error).message ?? 'inflate error';\n }\n\n if (overflowed) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload inflates past the ${maxOutputBytes} byte ceiling`,\n );\n }\n if (malformed !== null) {\n throw new EraSdkError('gzip-error', `compressed payload is malformed: ${malformed}`);\n }\n const out = concatBytes(...chunks);\n if (out.length !== isize) {\n throw new EraSdkError(\n 'gzip-error',\n `compressed payload inflated to ${out.length} bytes but declares ${isize} — truncated or malformed`,\n );\n }\n // The trailer CRC32 (little-endian, bytes n-8..n-5) must cover the inflated\n // output. The streaming inflater does not verify it, and the reference\n // implementation's native decoder does — without this check a corrupted\n // stream, or a CONCATENATED multi-member stream (whose final member's CRC\n // cannot cover the whole output), would be accepted here and refused there.\n const declaredCrc =\n (data[n - 8]! | (data[n - 7]! << 8) | (data[n - 6]! << 16) | (data[n - 5]! << 24)) >>> 0;\n if (crc32(out) !== declaredCrc) {\n throw new EraSdkError('gzip-error', 'compressed payload is malformed: CRC mismatch');\n }\n return out;\n}\n","import { cborEncode } from '../cbor/encode';\nimport { asBytes, cbBytes, cbMap, cbText, mapGet } from '../cbor/model';\nimport { EraSdkError } from '../core/errors';\nimport { normalizeRequestId, uuidStringify } from '../core/rand';\nimport { normalizeXfp, parsePath, xfpToHex } from '../registry/keypath';\nimport { gunzipCapped, gzipCompress } from '../tron-proto/gzip';\nimport type { SignedTronTx, TronLatestBlock } from '../tron-proto/messages';\nimport {\n decodeSignResultProto,\n encodeSignRequestProto,\n splitSignedTronTx,\n} from '../tron-proto/messages';\nimport type { Ur } from '../ur/ur';\nimport { Ur as UrValue } from '../ur/ur';\nimport type { ChainContext, EraConnectConfig, ExpectedReply, SignRequest } from './shared';\nimport {\n makeSignRequest,\n requireReplyMap,\n requireUrType,\n resolveContext,\n resolveRequestId,\n toUr,\n} from './shared';\n\nexport type { TronLatestBlock } from '../tron-proto/messages';\n\nexport interface TronSignRequestProps {\n readonly requestId?: Uint8Array | string;\n /**\n * Serialized `Transaction.raw_data` — THE signing source of truth. The\n * device signs `sha256(rawData) = txID` and returns the transaction with\n * `raw_data` unmodified.\n */\n readonly rawData: Uint8Array;\n /** Full signing path, e.g. `m/44'/195'/0'/0/0`. */\n readonly path: string;\n readonly xfp: string | number;\n /**\n * Reference block context. Source it from a LIVE now-block query and pass\n * the FULL 64-hex block id.\n */\n readonly latestBlock: TronLatestBlock;\n /** On-device display only; safe to omit for opaque dApp transactions. */\n readonly display?: {\n readonly token?: string;\n readonly contractAddress?: string;\n readonly from?: string;\n readonly to?: string;\n readonly value?: string;\n readonly memo?: string;\n readonly fee?: number;\n readonly decimals?: number;\n };\n readonly timestamp?: number;\n readonly origin?: string;\n}\n\nexport interface TronSignatureResult {\n readonly requestId: Uint8Array;\n /** `sha256(raw_data)` hex, as computed by the device. */\n readonly txId: string;\n /** Hex of the fully assembled signed transaction — broadcast as-is. */\n readonly rawTx: string;\n /** The signed frame split into `raw_data` + signatures (65-byte r||s||recovery each). */\n readonly signedTx: SignedTronTx;\n}\n\n/**\n * Ceilings on the gzip blob a `keystone-sign-result` may carry. Tron is the\n * only chain whose reply is compressed, so it is the only one where a few\n * hundred scanned bytes can ask for an arbitrary allocation. Generous\n * multiples of the largest real device reply.\n */\nconst MAX_COMPRESSED_BYTES = 8 * 1024;\nconst MAX_INFLATED_BYTES = 64 * 1024;\n\nconst REPLY_TYPES = ['keystone-sign-result'] as const;\n\n/**\n * Tron signing rides the structured `keystone-sign-request` (6101) envelope —\n * a gzip-compressed protobuf inside CBOR `{1: gzip(protobuf), 2: origin}`.\n * The registry's generic `tron-sign-request` (5101) is NOT accepted by the\n * device and gets no response; do not emit it.\n */\nexport class TronChain {\n private readonly context: ChainContext;\n\n constructor(config?: EraConnectConfig) {\n this.context = resolveContext(config);\n }\n\n /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */\n generateSignRequest(props: TronSignRequestProps): SignRequest<TronSignatureResult> {\n const requestId = resolveRequestId(this.context, props.requestId);\n parsePath(props.path); // validate shape; the wire carries the string form\n const xfp = normalizeXfp(props.xfp);\n if (props.rawData.length === 0) {\n throw new EraSdkError('invalid-props', 'rawData must not be empty');\n }\n if (!/^[0-9a-fA-F]{64}$/.test(props.latestBlock.hash)) {\n throw new EraSdkError(\n 'invalid-props',\n 'latestBlock.hash must be the FULL 64-hex block id (the device slices ref_block_hash from it)',\n );\n }\n\n const proto = encodeSignRequestProto({\n // Zero-padded to eight characters: the firmware parses this string with\n // a hex reader that yields 0 for anything shorter than 4 bytes, and a\n // zero fingerprint fails validation — a wallet whose fingerprint starts\n // with a zero byte (1 in 256) could not sign at all without the pad.\n xfpHex: xfpToHex(xfp),\n signId: uuidStringify(requestId),\n hdPath: props.path,\n timestamp: props.timestamp ?? 0,\n decimals: props.display?.decimals ?? 6,\n token: props.display?.token ?? '',\n contractAddress: props.display?.contractAddress,\n from: props.display?.from,\n to: props.display?.to,\n memo: props.display?.memo,\n value: props.display?.value,\n fee: props.display?.fee,\n latestBlock: props.latestBlock,\n rawData: props.rawData,\n });\n\n const ur = new UrValue(\n 'keystone-sign-request',\n cborEncode(\n cbMap([\n [1, cbBytes(gzipCompress(proto))],\n [2, cbText(props.origin ?? this.context.origin)],\n ]),\n ),\n );\n return makeSignRequest({\n ur,\n requestId,\n replyTypes: REPLY_TYPES,\n context: this.context,\n parse: (reply) => parseTronSignature(reply, requestId),\n });\n }\n\n /**\n * Parse a `keystone-sign-result` standalone. Tron carries the request id\n * INSIDE the protobuf (`signId`); passing `expect.requestId` is what makes\n * the echo check possible here — prefer `SignRequest.scanner().parse()`.\n */\n parseSignature(input: Ur | string, expect?: ExpectedReply): TronSignatureResult {\n return parseTronSignature(\n toUr(input),\n expect?.requestId === undefined ? undefined : normalizeRequestId(expect.requestId),\n );\n }\n}\n\nfunction parseTronSignature(\n ur: Ur,\n expectedRequestId: Uint8Array | undefined,\n): TronSignatureResult {\n requireUrType(ur, [...REPLY_TYPES], 'keystone-sign-result');\n const map = requireReplyMap(ur, 'keystone-sign-result');\n const compressed = asBytes(mapGet(map, 1));\n if (!compressed) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result is missing the payload (key 1)');\n }\n if (compressed.length > MAX_COMPRESSED_BYTES) {\n throw new EraSdkError(\n 'limit-exceeded',\n `keystone-sign-result payload is ${compressed.length} bytes, over the ${MAX_COMPRESSED_BYTES} byte ceiling`,\n );\n }\n const result = decodeSignResultProto(gunzipCapped(compressed, MAX_INFLATED_BYTES));\n\n // The signId echo is the ONLY anti-replay binding on this chain — the\n // device's own bytes are broadcast verbatim, so a stale reply that skipped\n // this check would finalize a payment the user did not approve now.\n if (expectedRequestId !== undefined) {\n const expected = uuidStringify(expectedRequestId).toLowerCase();\n if (result.signId.toLowerCase() !== expected) {\n throw new EraSdkError(\n 'request-id-mismatch',\n result.signId === ''\n ? 'keystone-sign-result does not echo the request id (signId)'\n : 'keystone-sign-result echoes a different request id — it answers another sign request, not this one',\n );\n }\n }\n if (result.rawTx === '') {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result has no signed transaction');\n }\n const signedTx = splitSignedTronTx(result.rawTx);\n const requestId = expectedRequestId ?? signIdToBytes(result.signId);\n return { requestId, txId: result.txId, rawTx: result.rawTx, signedTx };\n}\n\nfunction signIdToBytes(signId: string): Uint8Array {\n try {\n return normalizeRequestId(signId);\n } catch {\n return new Uint8Array(16);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tron-DYPOg5cm.cjs","names":["resolveContext","resolveRequestId","normalizeXfp","EraSdkError","encodeSignRequestProto","xfpToHex","uuidStringify","UrValue","cborEncode","cbMap","cbBytes","gzipCompress","cbText","makeSignRequest","toUr","normalizeRequestId","requireReplyMap","asBytes","mapGet","decodeSignResultProto","gunzipCapped","splitSignedTronTx"],"sources":["../src/chains/tron.ts"],"sourcesContent":["import { cborEncode } from '../cbor/encode';\nimport { asBytes, cbBytes, cbMap, cbText, mapGet } from '../cbor/model';\nimport { EraSdkError } from '../core/errors';\nimport { normalizeRequestId, uuidStringify } from '../core/rand';\nimport { normalizeXfp, parsePath, xfpToHex } from '../registry/keypath';\nimport { gunzipCapped, gzipCompress } from '../tron-proto/gzip';\nimport type { SignedTronTx, TronLatestBlock } from '../tron-proto/messages';\nimport {\n decodeSignResultProto,\n encodeSignRequestProto,\n splitSignedTronTx,\n} from '../tron-proto/messages';\nimport type { Ur } from '../ur/ur';\nimport { Ur as UrValue } from '../ur/ur';\nimport type { ChainContext, EraConnectConfig, ExpectedReply, SignRequest } from './shared';\nimport {\n makeSignRequest,\n requireReplyMap,\n requireUrType,\n resolveContext,\n resolveRequestId,\n toUr,\n} from './shared';\n\nexport type { TronLatestBlock } from '../tron-proto/messages';\n\nexport interface TronSignRequestProps {\n readonly requestId?: Uint8Array | string;\n /**\n * Serialized `Transaction.raw_data` — THE signing source of truth. The\n * device signs `sha256(rawData) = txID` and returns the transaction with\n * `raw_data` unmodified.\n */\n readonly rawData: Uint8Array;\n /** Full signing path, e.g. `m/44'/195'/0'/0/0`. */\n readonly path: string;\n readonly xfp: string | number;\n /**\n * Reference block context. Source it from a LIVE now-block query and pass\n * the FULL 64-hex block id.\n */\n readonly latestBlock: TronLatestBlock;\n /** On-device display only; safe to omit for opaque dApp transactions. */\n readonly display?: {\n readonly token?: string;\n readonly contractAddress?: string;\n readonly from?: string;\n readonly to?: string;\n readonly value?: string;\n readonly memo?: string;\n readonly fee?: number;\n readonly decimals?: number;\n };\n readonly timestamp?: number;\n readonly origin?: string;\n}\n\nexport interface TronSignatureResult {\n readonly requestId: Uint8Array;\n /** `sha256(raw_data)` hex, as computed by the device. */\n readonly txId: string;\n /** Hex of the fully assembled signed transaction — broadcast as-is. */\n readonly rawTx: string;\n /** The signed frame split into `raw_data` + signatures (65-byte r||s||recovery each). */\n readonly signedTx: SignedTronTx;\n}\n\n/**\n * Ceilings on the gzip blob a `keystone-sign-result` may carry. Tron is the\n * only chain whose reply is compressed, so it is the only one where a few\n * hundred scanned bytes can ask for an arbitrary allocation. Generous\n * multiples of the largest real device reply.\n */\nconst MAX_COMPRESSED_BYTES = 8 * 1024;\nconst MAX_INFLATED_BYTES = 64 * 1024;\n\nconst REPLY_TYPES = ['keystone-sign-result'] as const;\n\n/**\n * Tron signing rides the structured `keystone-sign-request` (6101) envelope —\n * a gzip-compressed protobuf inside CBOR `{1: gzip(protobuf), 2: origin}`.\n * The registry's generic `tron-sign-request` (5101) is NOT accepted by the\n * device and gets no response; do not emit it.\n */\nexport class TronChain {\n private readonly context: ChainContext;\n\n constructor(config?: EraConnectConfig) {\n this.context = resolveContext(config);\n }\n\n /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */\n generateSignRequest(props: TronSignRequestProps): SignRequest<TronSignatureResult> {\n const requestId = resolveRequestId(this.context, props.requestId);\n parsePath(props.path); // validate shape; the wire carries the string form\n const xfp = normalizeXfp(props.xfp);\n if (props.rawData.length === 0) {\n throw new EraSdkError('invalid-props', 'rawData must not be empty');\n }\n if (!/^[0-9a-fA-F]{64}$/.test(props.latestBlock.hash)) {\n throw new EraSdkError(\n 'invalid-props',\n 'latestBlock.hash must be the FULL 64-hex block id (the device slices ref_block_hash from it)',\n );\n }\n\n const proto = encodeSignRequestProto({\n // Zero-padded to eight characters: the firmware parses this string with\n // a hex reader that yields 0 for anything shorter than 4 bytes, and a\n // zero fingerprint fails validation — a wallet whose fingerprint starts\n // with a zero byte (1 in 256) could not sign at all without the pad.\n xfpHex: xfpToHex(xfp),\n signId: uuidStringify(requestId),\n hdPath: props.path,\n timestamp: props.timestamp ?? 0,\n decimals: props.display?.decimals ?? 6,\n token: props.display?.token ?? '',\n contractAddress: props.display?.contractAddress,\n from: props.display?.from,\n to: props.display?.to,\n memo: props.display?.memo,\n value: props.display?.value,\n fee: props.display?.fee,\n latestBlock: props.latestBlock,\n rawData: props.rawData,\n });\n\n const ur = new UrValue(\n 'keystone-sign-request',\n cborEncode(\n cbMap([\n [1, cbBytes(gzipCompress(proto))],\n [2, cbText(props.origin ?? this.context.origin)],\n ]),\n ),\n );\n return makeSignRequest({\n ur,\n requestId,\n replyTypes: REPLY_TYPES,\n context: this.context,\n parse: (reply) => parseTronSignature(reply, requestId),\n });\n }\n\n /**\n * Parse a `keystone-sign-result` standalone. Tron carries the request id\n * INSIDE the protobuf (`signId`); passing `expect.requestId` is what makes\n * the echo check possible here — prefer `SignRequest.scanner().parse()`.\n */\n parseSignature(input: Ur | string, expect?: ExpectedReply): TronSignatureResult {\n return parseTronSignature(\n toUr(input),\n expect?.requestId === undefined ? undefined : normalizeRequestId(expect.requestId),\n );\n }\n}\n\nfunction parseTronSignature(\n ur: Ur,\n expectedRequestId: Uint8Array | undefined,\n): TronSignatureResult {\n requireUrType(ur, [...REPLY_TYPES], 'keystone-sign-result');\n const map = requireReplyMap(ur, 'keystone-sign-result');\n const compressed = asBytes(mapGet(map, 1));\n if (!compressed) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result is missing the payload (key 1)');\n }\n if (compressed.length > MAX_COMPRESSED_BYTES) {\n throw new EraSdkError(\n 'limit-exceeded',\n `keystone-sign-result payload is ${compressed.length} bytes, over the ${MAX_COMPRESSED_BYTES} byte ceiling`,\n );\n }\n const result = decodeSignResultProto(gunzipCapped(compressed, MAX_INFLATED_BYTES));\n\n // The signId echo is the ONLY anti-replay binding on this chain — the\n // device's own bytes are broadcast verbatim, so a stale reply that skipped\n // this check would finalize a payment the user did not approve now.\n if (expectedRequestId !== undefined) {\n const expected = uuidStringify(expectedRequestId).toLowerCase();\n if (result.signId.toLowerCase() !== expected) {\n throw new EraSdkError(\n 'request-id-mismatch',\n result.signId === ''\n ? 'keystone-sign-result does not echo the request id (signId)'\n : 'keystone-sign-result echoes a different request id — it answers another sign request, not this one',\n );\n }\n }\n if (result.rawTx === '') {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result has no signed transaction');\n }\n const signedTx = splitSignedTronTx(result.rawTx);\n const requestId = expectedRequestId ?? signIdToBytes(result.signId);\n return { requestId, txId: result.txId, rawTx: result.rawTx, signedTx };\n}\n\nfunction signIdToBytes(signId: string): Uint8Array {\n try {\n return normalizeRequestId(signId);\n } catch {\n return new Uint8Array(16);\n }\n}\n"],"mappings":"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"}
package/dist/tron.cjs CHANGED
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_shared = require("./shared-Cu2yynP9.cjs");
3
- const require_tron = require("./tron-B9wXdzlA.cjs");
4
- const require_messages = require("./messages-BZXDY29v.cjs");
3
+ const require_messages = require("./messages-orAQ52CI.cjs");
4
+ const require_tron = require("./tron-DYPOg5cm.cjs");
5
5
  exports.AnimatedUr = require_shared.AnimatedUr;
6
6
  exports.EraSdkError = require_shared.EraSdkError;
7
7
  exports.TronChain = require_tron.TronChain;
package/dist/tron.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { K as EraSdkError, _ as Ur, d as UrScanner, f as AnimatedUr, u as TypedUrScanner } from "./shared-Bbgejrus.js";
2
- import { t as TronChain } from "./tron-fufBDNuQ.js";
3
- import { r as splitSignedTronTx } from "./messages-DbnXy3dC.js";
2
+ import { i as splitSignedTronTx } from "./messages--XbVef_k.js";
3
+ import { t as TronChain } from "./tron-BpUEYTPT.js";
4
4
  export { AnimatedUr, EraSdkError, TronChain, TypedUrScanner, Ur, UrScanner, splitSignedTronTx };
package/dist/verify.cjs CHANGED
@@ -1,18 +1,237 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_shared = require("./shared-Cu2yynP9.cjs");
3
3
  const require_keypath = require("./keypath-pzm_YIfN.cjs");
4
- const require_derive = require("./derive-BayrpfJk.cjs");
4
+ const require_cashaddr = require("./cashaddr-Ci0_8ITN.cjs");
5
+ const require_derive = require("./derive-BuNsfWlg.cjs");
5
6
  const require_cardano = require("./cardano-CzYswGzK.cjs");
7
+ const require_messages = require("./messages-orAQ52CI.cjs");
6
8
  const require_evm = require("./evm-yfKOc2dy.cjs");
7
9
  const require_sui = require("./sui-CYRn331O.cjs");
8
10
  const require_ton = require("./ton-CI2s9puH.cjs");
9
- const require_messages = require("./messages-BZXDY29v.cjs");
10
11
  let _noble_curves_secp256k1 = require("@noble/curves/secp256k1");
11
12
  let _noble_hashes_blake2b = require("@noble/hashes/blake2b");
13
+ let _noble_hashes_ripemd160 = require("@noble/hashes/ripemd160");
12
14
  let _noble_hashes_sha2 = require("@noble/hashes/sha2");
13
15
  let _noble_hashes_sha3 = require("@noble/hashes/sha3");
14
16
  let _scure_base = require("@scure/base");
15
17
  let _noble_curves_ed25519 = require("@noble/curves/ed25519");
18
+ //#region src/verify/result.ts
19
+ const verified = {
20
+ ok: true,
21
+ checked: true
22
+ };
23
+ const failed = (reason) => ({
24
+ ok: false,
25
+ reason
26
+ });
27
+ const unverifiable = (reason) => ({
28
+ ok: true,
29
+ checked: false,
30
+ reason
31
+ });
32
+ //#endregion
33
+ //#region src/verify/bch.ts
34
+ const SIGHASH_FORKID_ALL = 65;
35
+ const BCH_TX_VERSION = 1;
36
+ const BCH_TX_LOCKTIME = 0;
37
+ const BCH_TX_SEQUENCE = 4294967293;
38
+ /** Hardened reader for the legacy (non-witness) transaction serialization. */
39
+ function decodeBchRawTx(rawTxHex) {
40
+ let bytes;
41
+ try {
42
+ bytes = require_shared.hexToBytes(rawTxHex);
43
+ } catch {
44
+ throw new require_shared.EraSdkError("malformed-reply", "signed transaction is not hex");
45
+ }
46
+ let offset = 0;
47
+ const need = (n) => {
48
+ if (offset + n > bytes.length) throw new require_shared.EraSdkError("malformed-reply", "signed transaction is truncated");
49
+ };
50
+ const readU32 = () => {
51
+ need(4);
52
+ const v = bytes[offset] | bytes[offset + 1] << 8 | bytes[offset + 2] << 16 | bytes[offset + 3] << 24;
53
+ offset += 4;
54
+ return v >>> 0;
55
+ };
56
+ const readU64 = () => {
57
+ need(8);
58
+ let v = 0n;
59
+ for (let i = 7; i >= 0; i--) v = v << 8n | BigInt(bytes[offset + i]);
60
+ offset += 8;
61
+ return v;
62
+ };
63
+ const readVarint = () => {
64
+ need(1);
65
+ const first = bytes[offset++];
66
+ if (first < 253) return first;
67
+ if (first === 253) {
68
+ need(2);
69
+ const v = bytes[offset] | bytes[offset + 1] << 8;
70
+ offset += 2;
71
+ return v;
72
+ }
73
+ throw new require_shared.EraSdkError("malformed-reply", "unreasonable varint in signed transaction");
74
+ };
75
+ const readSlice = (n) => {
76
+ need(n);
77
+ const s = bytes.subarray(offset, offset + n);
78
+ offset += n;
79
+ return s;
80
+ };
81
+ const version = readU32();
82
+ const inputCount = readVarint();
83
+ if (inputCount === 0 || inputCount > 1e3) throw new require_shared.EraSdkError("malformed-reply", "unreasonable input count in signed transaction");
84
+ const inputs = [];
85
+ for (let i = 0; i < inputCount; i++) {
86
+ const txidLE = readSlice(32);
87
+ const index = readU32();
88
+ const scriptSig = readSlice(readVarint());
89
+ const sequence = readU32();
90
+ inputs.push({
91
+ txidLE,
92
+ index,
93
+ scriptSig,
94
+ sequence
95
+ });
96
+ }
97
+ const outputCount = readVarint();
98
+ if (outputCount === 0 || outputCount > 1e3) throw new require_shared.EraSdkError("malformed-reply", "unreasonable output count in signed transaction");
99
+ const outputs = [];
100
+ for (let i = 0; i < outputCount; i++) {
101
+ const value = readU64();
102
+ const script = readSlice(readVarint());
103
+ outputs.push({
104
+ value,
105
+ script
106
+ });
107
+ }
108
+ const locktime = readU32();
109
+ if (offset !== bytes.length) throw new require_shared.EraSdkError("malformed-reply", "trailing bytes after signed transaction");
110
+ return {
111
+ version,
112
+ inputs,
113
+ outputs,
114
+ locktime
115
+ };
116
+ }
117
+ function sha256d(data) {
118
+ return (0, _noble_hashes_sha2.sha256)((0, _noble_hashes_sha2.sha256)(data));
119
+ }
120
+ function hash160(data) {
121
+ return (0, _noble_hashes_ripemd160.ripemd160)((0, _noble_hashes_sha2.sha256)(data));
122
+ }
123
+ function p2pkhScript(pubkeyHash) {
124
+ return require_shared.concatBytes(new Uint8Array([
125
+ 118,
126
+ 169,
127
+ 20
128
+ ]), pubkeyHash, new Uint8Array([136, 172]));
129
+ }
130
+ function p2shScript(scriptHash) {
131
+ return require_shared.concatBytes(new Uint8Array([169, 20]), scriptHash, new Uint8Array([135]));
132
+ }
133
+ function scriptForAddress(address) {
134
+ const decoded = require_cashaddr.decodeCashAddr(address);
135
+ return decoded.type === "p2pkh" ? p2pkhScript(decoded.hash) : p2shScript(decoded.hash);
136
+ }
137
+ function le32(value) {
138
+ return new Uint8Array([
139
+ value & 255,
140
+ value >>> 8 & 255,
141
+ value >>> 16 & 255,
142
+ value >>> 24 & 255
143
+ ]);
144
+ }
145
+ function le64(value) {
146
+ const out = /* @__PURE__ */ new Uint8Array(8);
147
+ let v = value;
148
+ for (let i = 0; i < 8; i++) {
149
+ out[i] = Number(v & 255n);
150
+ v >>= 8n;
151
+ }
152
+ return out;
153
+ }
154
+ function varintBytes(value) {
155
+ if (value < 253) return new Uint8Array([value]);
156
+ return new Uint8Array([
157
+ 253,
158
+ value & 255,
159
+ value >>> 8 & 255
160
+ ]);
161
+ }
162
+ /**
163
+ * BIP-143 sighash preimage with FORKID, exactly as consensus defines it:
164
+ * `version ‖ hashPrevouts ‖ hashSequence ‖ outpoint ‖ scriptCode ‖ value ‖
165
+ * sequence ‖ hashOutputs ‖ locktime ‖ hashType(LE)`, double-SHA256d.
166
+ */
167
+ function computeBchSighash(args) {
168
+ const { tx, inputIndex, scriptCode, value } = args;
169
+ const hashType = args.hashType ?? SIGHASH_FORKID_ALL;
170
+ const input = tx.inputs[inputIndex];
171
+ if (!input) throw new require_shared.EraSdkError("invalid-props", "sighash input index out of range");
172
+ const hashPrevouts = sha256d(require_shared.concatBytes(...tx.inputs.map((i) => require_shared.concatBytes(i.txidLE, le32(i.index)))));
173
+ const hashSequence = sha256d(require_shared.concatBytes(...tx.inputs.map((i) => le32(i.sequence))));
174
+ const hashOutputs = sha256d(require_shared.concatBytes(...tx.outputs.map((o) => require_shared.concatBytes(le64(o.value), varintBytes(o.script.length), o.script))));
175
+ return sha256d(require_shared.concatBytes(le32(tx.version), hashPrevouts, hashSequence, input.txidLE, le32(input.index), varintBytes(scriptCode.length), scriptCode, le64(value), le32(input.sequence), hashOutputs, le32(tx.locktime), le32(hashType)));
176
+ }
177
+ function toBigintValue(value, label) {
178
+ const v = typeof value === "number" ? BigInt(value) : value;
179
+ if (v <= 0n) throw new require_shared.EraSdkError("invalid-props", `${label} must be positive`);
180
+ return v;
181
+ }
182
+ function toPublicKeyBytes(publicKey) {
183
+ return typeof publicKey === "string" ? require_shared.hexToBytes(publicKey) : publicKey;
184
+ }
185
+ function verifyBchSignedTx(args) {
186
+ try {
187
+ const tx = decodeBchRawTx(args.rawTx);
188
+ if (tx.version !== BCH_TX_VERSION || tx.locktime !== BCH_TX_LOCKTIME) return failed(`transaction parameters (version ${tx.version}, locktime ${tx.locktime}) are not the device signer's (1, 0)`);
189
+ for (let i = 0; i < tx.inputs.length; i++) if (tx.inputs[i].sequence !== BCH_TX_SEQUENCE) return failed(`input ${i} sequence is not the device signer's 0xfffffffd`);
190
+ if (tx.inputs.length !== args.inputs.length) return failed(`signed transaction has ${tx.inputs.length} inputs, the request had ${args.inputs.length}`);
191
+ if (tx.outputs.length !== args.outputs.length) return failed(`signed transaction has ${tx.outputs.length} outputs, the request had ${args.outputs.length}`);
192
+ for (let i = 0; i < args.outputs.length; i++) {
193
+ const requested = args.outputs[i];
194
+ const actual = tx.outputs[i];
195
+ if (actual.value !== toBigintValue(requested.value, `output ${i} value`)) return failed(`output ${i} value differs from the request`);
196
+ if (!require_shared.equalBytes(actual.script, scriptForAddress(requested.address))) return failed(`output ${i} does not pay the requested address`);
197
+ }
198
+ for (let i = 0; i < args.inputs.length; i++) {
199
+ const requested = args.inputs[i];
200
+ const actual = tx.inputs[i];
201
+ const txidLE = require_shared.hexToBytes(requested.txid).reverse();
202
+ if (!require_shared.equalBytes(actual.txidLE, txidLE) || actual.index !== requested.index) return failed(`input ${i} spends a different outpoint than the request named`);
203
+ const script = actual.scriptSig;
204
+ if (script.length < 2) return failed(`input ${i} has no signature`);
205
+ const sigLen = script[0];
206
+ if (sigLen < 9 || 1 + sigLen + 1 > script.length) return failed(`input ${i} scriptSig is not a signature push`);
207
+ const sigWithType = script.subarray(1, 1 + sigLen);
208
+ const pubLen = script[1 + sigLen];
209
+ if (pubLen !== 33 || 1 + sigLen + 1 + pubLen !== script.length) return failed(`input ${i} scriptSig does not end with a compressed public key push`);
210
+ const pubkey = script.subarray(1 + sigLen + 1);
211
+ if (!require_shared.equalBytes(pubkey, toPublicKeyBytes(requested.publicKey))) return failed(`input ${i} was signed with a different public key than the request named — the transaction cannot spend the requested UTXO`);
212
+ const hashType = sigWithType[sigWithType.length - 1];
213
+ if (hashType !== SIGHASH_FORKID_ALL) return failed(`input ${i} uses sighash 0x${hashType.toString(16)}, expected SIGHASH_ALL|FORKID (0x41)`);
214
+ const sighash = computeBchSighash({
215
+ tx,
216
+ inputIndex: i,
217
+ scriptCode: p2pkhScript(hash160(pubkey)),
218
+ value: toBigintValue(requested.value, `input ${i} value`)
219
+ });
220
+ const signature = _noble_curves_secp256k1.secp256k1.Signature.fromDER(sigWithType.subarray(0, sigWithType.length - 1)).toCompactRawBytes();
221
+ if (!_noble_curves_secp256k1.secp256k1.verify(signature, sighash, pubkey)) return failed(`input ${i} signature does not verify against the BIP-143 FORKID sighash`);
222
+ }
223
+ if (args.txId !== void 0) {
224
+ if (require_shared.bytesToHex(sha256d(require_shared.hexToBytes(args.rawTx)).reverse()) !== args.txId.toLowerCase()) return failed("reply txId does not match the hash of the signed transaction");
225
+ }
226
+ return verified;
227
+ } catch (e) {
228
+ return failed(message$1(e));
229
+ }
230
+ }
231
+ function message$1(e) {
232
+ return e instanceof require_shared.EraSdkError || e instanceof Error ? e.message : String(e);
233
+ }
234
+ //#endregion
16
235
  //#region src/verify/psbt-reader.ts
17
236
  const MAGIC = [
18
237
  112,
@@ -151,21 +370,6 @@ function inputHas(psbt, index, keyType) {
151
370
  return inputEntries(psbt, index, keyType).length > 0;
152
371
  }
153
372
  //#endregion
154
- //#region src/verify/result.ts
155
- const verified = {
156
- ok: true,
157
- checked: true
158
- };
159
- const failed = (reason) => ({
160
- ok: false,
161
- reason
162
- });
163
- const unverifiable = (reason) => ({
164
- ok: true,
165
- checked: false,
166
- reason
167
- });
168
- //#endregion
169
373
  //#region src/verify/btc.ts
170
374
  /**
171
375
  * The `crypto-psbt` reply carries NO request id — this comparison IS the
@@ -902,7 +1106,10 @@ function readVl(bytes, pos) {
902
1106
  //#endregion
903
1107
  exports.PsbtInputType = PsbtInputType;
904
1108
  exports.bocRootHash = bocRootHash;
1109
+ exports.computeBchSighash = computeBchSighash;
1110
+ exports.decodeBchRawTx = decodeBchRawTx;
905
1111
  exports.parsePsbt = parsePsbt;
1112
+ exports.verifyBchSignedTx = verifyBchSignedTx;
906
1113
  exports.verifyBtcMessageHeader = verifyBtcMessageHeader;
907
1114
  exports.verifyCardanoSignature = verifyCardanoSignature;
908
1115
  exports.verifyCosmosSignature = verifyCosmosSignature;