@hwlt/era-connect 0.1.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 (81) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +9 -0
  3. package/README.md +101 -0
  4. package/btc/package.json +6 -0
  5. package/dist/btc-DfQ0q79G.d.ts +58 -0
  6. package/dist/btc-Dn6_Y12c.d.cts +58 -0
  7. package/dist/btc-Fr6Jz4Fi.cjs +104 -0
  8. package/dist/btc-Fr6Jz4Fi.cjs.map +1 -0
  9. package/dist/btc-VyJKXq7d.js +99 -0
  10. package/dist/btc-VyJKXq7d.js.map +1 -0
  11. package/dist/btc.cjs +9 -0
  12. package/dist/btc.d.cts +3 -0
  13. package/dist/btc.d.ts +3 -0
  14. package/dist/btc.js +3 -0
  15. package/dist/evm-BHj_2OTG.cjs +134 -0
  16. package/dist/evm-BHj_2OTG.cjs.map +1 -0
  17. package/dist/evm-DJxGGAbD.d.cts +76 -0
  18. package/dist/evm-KrV5wK08.d.ts +76 -0
  19. package/dist/evm-kJb3-oiR.js +117 -0
  20. package/dist/evm-kJb3-oiR.js.map +1 -0
  21. package/dist/evm.cjs +11 -0
  22. package/dist/evm.d.cts +3 -0
  23. package/dist/evm.d.ts +3 -0
  24. package/dist/evm.js +3 -0
  25. package/dist/index.cjs +530 -0
  26. package/dist/index.cjs.map +1 -0
  27. package/dist/index.d.cts +285 -0
  28. package/dist/index.d.ts +285 -0
  29. package/dist/index.js +506 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/messages-17Oup0-Z.js +208 -0
  32. package/dist/messages-17Oup0-Z.js.map +1 -0
  33. package/dist/messages-BzcdWlg1.cjs +243 -0
  34. package/dist/messages-BzcdWlg1.cjs.map +1 -0
  35. package/dist/messages-DsPvW8Oi.d.cts +29 -0
  36. package/dist/messages-DsPvW8Oi.d.ts +29 -0
  37. package/dist/shared-B8Wc7ViU.js +1472 -0
  38. package/dist/shared-B8Wc7ViU.js.map +1 -0
  39. package/dist/shared-CI8zhQiE.d.cts +218 -0
  40. package/dist/shared-CI8zhQiE.d.ts +218 -0
  41. package/dist/shared-nISrEktU.cjs +1777 -0
  42. package/dist/shared-nISrEktU.cjs.map +1 -0
  43. package/dist/solana-8eOwz0gM.d.cts +49 -0
  44. package/dist/solana-C2Xm7w8a.d.ts +49 -0
  45. package/dist/solana-C7JQEAec.js +87 -0
  46. package/dist/solana-C7JQEAec.js.map +1 -0
  47. package/dist/solana-DdEn24X4.cjs +98 -0
  48. package/dist/solana-DdEn24X4.cjs.map +1 -0
  49. package/dist/solana.cjs +10 -0
  50. package/dist/solana.d.cts +3 -0
  51. package/dist/solana.d.ts +3 -0
  52. package/dist/solana.js +3 -0
  53. package/dist/tron-Bo9HnvVE.js +158 -0
  54. package/dist/tron-Bo9HnvVE.js.map +1 -0
  55. package/dist/tron-CQIEqjCA.cjs +163 -0
  56. package/dist/tron-CQIEqjCA.cjs.map +1 -0
  57. package/dist/tron-YllfTmx6.d.ts +63 -0
  58. package/dist/tron-gG51KMnX.d.cts +63 -0
  59. package/dist/tron.cjs +11 -0
  60. package/dist/tron.d.cts +4 -0
  61. package/dist/tron.d.ts +4 -0
  62. package/dist/tron.js +4 -0
  63. package/dist/verify.cjs +445 -0
  64. package/dist/verify.cjs.map +1 -0
  65. package/dist/verify.d.cts +162 -0
  66. package/dist/verify.d.ts +162 -0
  67. package/dist/verify.js +438 -0
  68. package/dist/verify.js.map +1 -0
  69. package/evm/package.json +6 -0
  70. package/package.json +140 -0
  71. package/proto/tron/base.proto +16 -0
  72. package/proto/tron/message.proto +12 -0
  73. package/proto/tron/payload.proto +35 -0
  74. package/proto/tron/sign_transaction_result.proto +10 -0
  75. package/proto/tron/sync.proto +21 -0
  76. package/proto/tron/transaction.proto +17 -0
  77. package/proto/tron/tron_transaction.proto +31 -0
  78. package/proto/tron/verify_address.proto +10 -0
  79. package/solana/package.json +6 -0
  80. package/tron/package.json +6 -0
  81. package/verify/package.json +6 -0
package/dist/verify.js ADDED
@@ -0,0 +1,438 @@
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";
2
+ import { n as EvmDataType, r as foldRecoveryId } from "./evm-kJb3-oiR.js";
3
+ import { a as firstVarint, i as firstBytes, o as readFields, r as splitSignedTronTx } from "./messages-17Oup0-Z.js";
4
+ import { secp256k1 } from "@noble/curves/secp256k1";
5
+ import { sha256 } from "@noble/hashes/sha2";
6
+ import { keccak_256 } from "@noble/hashes/sha3";
7
+ import { createBase58check } from "@scure/base";
8
+ import { ed25519 } from "@noble/curves/ed25519";
9
+ //#region src/verify/psbt-reader.ts
10
+ const MAGIC = [
11
+ 112,
12
+ 115,
13
+ 98,
14
+ 116,
15
+ 255
16
+ ];
17
+ const PsbtInputType = {
18
+ partialSig: 2,
19
+ finalScriptSig: 7,
20
+ finalScriptWitness: 8,
21
+ taprootKeySpendSignature: 19,
22
+ taprootScriptSpendSignature: 20
23
+ };
24
+ function err(message) {
25
+ return new EraSdkError("malformed-reply", `psbt: ${message}`);
26
+ }
27
+ var Reader = class {
28
+ constructor(bytes) {
29
+ this.bytes = bytes;
30
+ this.offset = 0;
31
+ }
32
+ get remaining() {
33
+ return this.bytes.length - this.offset;
34
+ }
35
+ u8() {
36
+ const b = this.bytes[this.offset];
37
+ if (b === void 0) throw err("truncated");
38
+ this.offset += 1;
39
+ return b;
40
+ }
41
+ /** Bitcoin compact-size integer, MINIMAL encoding required (as consensus does). */
42
+ compactSize() {
43
+ const first = this.u8();
44
+ if (first < 253) return first;
45
+ let width;
46
+ let minimum;
47
+ if (first === 253) {
48
+ width = 2;
49
+ minimum = 253n;
50
+ } else if (first === 254) {
51
+ width = 4;
52
+ minimum = 65536n;
53
+ } else {
54
+ width = 8;
55
+ minimum = 4294967296n;
56
+ }
57
+ let value = 0n;
58
+ 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");
61
+ return Number(value);
62
+ }
63
+ take(length) {
64
+ if (length > this.remaining) throw err("length exceeds input");
65
+ const out = this.bytes.slice(this.offset, this.offset + length);
66
+ this.offset += length;
67
+ return out;
68
+ }
69
+ };
70
+ /** Read one key/value map (ends at the 0x00 separator). */
71
+ function readMap(reader) {
72
+ const entries = [];
73
+ const seen = /* @__PURE__ */ new Set();
74
+ for (;;) {
75
+ const keyLength = reader.compactSize();
76
+ if (keyLength === 0) return entries;
77
+ const key = reader.take(keyLength);
78
+ const value = reader.take(reader.compactSize());
79
+ 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");
81
+ seen.add(keyId);
82
+ entries.push({
83
+ keyType: key[0],
84
+ keyData: key.slice(1),
85
+ value
86
+ });
87
+ }
88
+ }
89
+ /** Count of inputs/outputs in a (non-witness) unsigned transaction. */
90
+ function countTxInputsOutputs(tx) {
91
+ const reader = new Reader(tx);
92
+ reader.take(4);
93
+ const inputs = reader.compactSize();
94
+ if (inputs === 0) throw err("unsigned transaction has zero inputs (or carries witness data)");
95
+ for (let i = 0; i < inputs; i++) {
96
+ reader.take(36);
97
+ reader.take(reader.compactSize());
98
+ reader.take(4);
99
+ }
100
+ const outputs = reader.compactSize();
101
+ for (let i = 0; i < outputs; i++) {
102
+ reader.take(8);
103
+ reader.take(reader.compactSize());
104
+ }
105
+ reader.take(4);
106
+ if (reader.remaining !== 0) throw err("trailing bytes after the unsigned transaction");
107
+ return {
108
+ inputs,
109
+ outputs
110
+ };
111
+ }
112
+ function parsePsbt(bytes) {
113
+ const reader = new Reader(bytes);
114
+ for (const expected of MAGIC) if (reader.u8() !== expected) throw err("bad magic");
115
+ const globalMap = readMap(reader);
116
+ let unsignedTx = null;
117
+ let version = 0;
118
+ for (const entry of globalMap) {
119
+ if (entry.keyType === 0 && entry.keyData.length === 0) unsignedTx = entry.value;
120
+ if (entry.keyType === 251 && entry.keyData.length === 0) {
121
+ if (entry.value.length !== 4) throw err("bad version field");
122
+ version = (entry.value[0] | entry.value[1] << 8 | entry.value[2] << 16 | entry.value[3] << 24) >>> 0;
123
+ }
124
+ }
125
+ if (unsignedTx === null) throw err("no global unsigned transaction — not a PSBT v0");
126
+ if (version !== 0) throw err(`unsupported PSBT version ${version}`);
127
+ const counts = countTxInputsOutputs(unsignedTx);
128
+ const inputs = [];
129
+ for (let i = 0; i < counts.inputs; i++) inputs.push(readMap(reader));
130
+ const outputs = [];
131
+ 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");
133
+ return {
134
+ unsignedTx,
135
+ version,
136
+ inputs,
137
+ outputs
138
+ };
139
+ }
140
+ function inputEntries(psbt, index, keyType) {
141
+ return (psbt.inputs[index] ?? []).filter((e) => e.keyType === keyType);
142
+ }
143
+ function inputHas(psbt, index, keyType) {
144
+ return inputEntries(psbt, index, keyType).length > 0;
145
+ }
146
+ //#endregion
147
+ //#region src/verify/result.ts
148
+ const verified = {
149
+ ok: true,
150
+ checked: true
151
+ };
152
+ const failed = (reason) => ({
153
+ ok: false,
154
+ reason
155
+ });
156
+ const unverifiable = (reason) => ({
157
+ ok: true,
158
+ checked: false,
159
+ reason
160
+ });
161
+ //#endregion
162
+ //#region src/verify/btc.ts
163
+ /**
164
+ * The `crypto-psbt` reply carries NO request id — this comparison IS the
165
+ * anti-replay binding for Bitcoin. It is not optional.
166
+ *
167
+ * The unsigned transaction is compared byte for byte, which pins the input
168
+ * set and order, the outputs, their amounts, the version and the locktime in
169
+ * one shot — and therefore the txid. The device only ADDS per-input
170
+ * signature fields, so a legitimate reply always matches.
171
+ */
172
+ function verifySignedPsbt(args) {
173
+ let sent;
174
+ let signed;
175
+ try {
176
+ sent = parsePsbt(args.sentPsbt);
177
+ } catch (e) {
178
+ return failed(`the PSBT we sent is not readable: ${message(e)}`);
179
+ }
180
+ try {
181
+ signed = parsePsbt(args.signedPsbt);
182
+ } catch (e) {
183
+ return failed(`the PSBT the device returned is not readable: ${message(e)}`);
184
+ }
185
+ if (!equalBytes(sent.unsignedTx, signed.unsignedTx)) return failed("the returned PSBT is a different transaction from the one approved");
186
+ const finalizedTypes = [PsbtInputType.finalScriptSig, PsbtInputType.finalScriptWitness];
187
+ for (let i = 0; i < signed.inputs.length; i++) for (const type of finalizedTypes) {
188
+ if (!inputHas(signed, i, type)) continue;
189
+ if (!inputHas(sent, i, type)) return failed(`input ${i} came back finalized (type 0x${type.toString(16)}) and was not sent that way — the script it would broadcast is not ours`);
190
+ const a = inputEntries(sent, i, type);
191
+ const b = inputEntries(signed, i, type);
192
+ if (a.length !== b.length || !a.every((entry, k) => equalBytes(entry.value, b[k].value))) return failed(`input ${i} came back with a different finalized script than the one we sent`);
193
+ }
194
+ const isSigned = (i) => inputHas(signed, i, PsbtInputType.partialSig) || inputHas(signed, i, PsbtInputType.taprootKeySpendSignature) || inputHas(signed, i, PsbtInputType.taprootScriptSpendSignature);
195
+ const indexes = signed.inputs.map((_, i) => i);
196
+ if (args.requireEveryInputSigned ?? true) {
197
+ if (!indexes.every(isSigned)) return failed("the device signed only part of the transaction");
198
+ } else if (!indexes.some(isSigned)) return failed("the returned PSBT carries no signature");
199
+ return verified;
200
+ }
201
+ /**
202
+ * BIP-137: the recovery header names the address type a verifier derives
203
+ * before comparing. A header of the wrong range produces a signature that
204
+ * LOOKS fine (65 bytes, valid base64) but fails every verifier downstream —
205
+ * this check is the only place that difference is visible.
206
+ */
207
+ function verifyBtcMessageHeader(args) {
208
+ if (args.signature.length === 0) return failed("empty signature");
209
+ const header = args.signature[0];
210
+ const range = headerRangeFor(args.address);
211
+ if (range === null) return {
212
+ ok: true,
213
+ checked: false,
214
+ reason: "address kind has no BIP-137 header range to check against"
215
+ };
216
+ if (header >= range.low && header <= range.high) return verified;
217
+ return failed(`recovery header ${header} does not match a ${range.label} address (BIP-137 expects ${range.low}..${range.high}); this signature would not verify against the address it was asked to sign for`);
218
+ }
219
+ function headerRangeFor(address) {
220
+ const a = address.toLowerCase();
221
+ if (a.startsWith("bc1q") || a.startsWith("tb1q") || a.startsWith("bcrt1q")) return {
222
+ low: 39,
223
+ high: 42,
224
+ label: "native segwit (P2WPKH)"
225
+ };
226
+ if (a.startsWith("bc1p") || a.startsWith("tb1p") || a.startsWith("bcrt1p")) return null;
227
+ if (looksBase58(address)) {
228
+ if (address.startsWith("3") || address.startsWith("2")) return {
229
+ low: 35,
230
+ high: 38,
231
+ label: "P2SH (nested segwit)"
232
+ };
233
+ if (address.startsWith("1") || address.startsWith("m") || address.startsWith("n")) return {
234
+ low: 27,
235
+ high: 34,
236
+ label: "legacy P2PKH"
237
+ };
238
+ }
239
+ return null;
240
+ }
241
+ function looksBase58(address) {
242
+ return address.length >= 26 && address.length <= 35 && /^[1-9A-HJ-NP-Za-km-z]+$/.test(address);
243
+ }
244
+ function message(e) {
245
+ return e instanceof EraSdkError || e instanceof Error ? e.message : String(e);
246
+ }
247
+ //#endregion
248
+ //#region src/verify/evm.ts
249
+ /** EIP-191: 0x19 || "Ethereum Signed Message:\n" || len(message). */
250
+ function personalSignDigest(message) {
251
+ const prefix = utf8Encode(`Ethereum Signed Message:\n${message.length}`);
252
+ return keccak_256(concatBytes(new Uint8Array([25]), prefix, message));
253
+ }
254
+ /**
255
+ * "Did the device sign exactly what I sent, with the key I expected?"
256
+ * keccak digest + public-key recovery; the recovered address must equal the
257
+ * request's. Run it before broadcasting.
258
+ */
259
+ function verifyEvmSignature(args) {
260
+ if (args.signature.length < 65) return failed("signature is shorter than 65 bytes");
261
+ if (args.reEncodedSignData !== void 0 && !equalBytes(args.reEncodedSignData, args.signData)) return failed("the transaction to broadcast is not the one the device signed");
262
+ let digest;
263
+ switch (args.dataType) {
264
+ case EvmDataType.transaction:
265
+ case EvmDataType.typedTransaction:
266
+ digest = keccak_256(args.signData);
267
+ break;
268
+ case EvmDataType.personalMessage:
269
+ digest = personalSignDigest(args.signData);
270
+ break;
271
+ case EvmDataType.typedData: return unverifiable("EIP-712: the digest is the hash of the structure, computed only on the device");
272
+ default: return failed(`unknown dataType ${args.dataType}`);
273
+ }
274
+ let vBig = 0n;
275
+ for (const b of args.signature.slice(64)) vBig = vBig << 8n | BigInt(b);
276
+ const recoveryId = foldRecoveryId(vBig);
277
+ if (recoveryId !== 0 && recoveryId !== 1) return failed("implausible recovery value");
278
+ let recovered;
279
+ try {
280
+ const point = secp256k1.Signature.fromCompact(args.signature.slice(0, 64)).addRecoveryBit(recoveryId).recoverPublicKey(digest);
281
+ recovered = keccak_256(point.toRawBytes(false).slice(1)).slice(12);
282
+ } catch (e) {
283
+ return failed(`signature could not be checked: ${e.message}`);
284
+ }
285
+ const expected = args.address instanceof Uint8Array ? args.address : hexToBytes(args.address);
286
+ if (expected.length !== 20) return failed(`expected address must be 20 bytes, got ${expected.length}`);
287
+ if (!equalBytes(recovered, expected)) return failed(`the signature does not belong to this account (recovered 0x${bytesToHex(recovered)})`);
288
+ return verified;
289
+ }
290
+ //#endregion
291
+ //#region src/verify/solana.ts
292
+ function verifySolanaSignature(args) {
293
+ if (args.broadcastMessageBytes !== void 0 && !equalBytes(args.broadcastMessageBytes, args.signData)) return failed("the message to broadcast is not the one the device signed");
294
+ let ok;
295
+ try {
296
+ ok = ed25519.verify(args.signature, args.signData, args.publicKey);
297
+ } catch (e) {
298
+ return failed(`Solana signature could not be checked: ${e.message}`);
299
+ }
300
+ return ok ? verified : failed("the signature does not belong to this account");
301
+ }
302
+ //#endregion
303
+ //#region src/verify/tron.ts
304
+ const base58check = createBase58check(sha256);
305
+ /**
306
+ * The Tron reply is a fully signed transaction broadcast VERBATIM, so it is
307
+ * checked on both counts: every signature must recover to the owner address,
308
+ * and the transaction must move what the user approved.
309
+ *
310
+ * Byte equality with the request's `rawData` is the strong form. When the
311
+ * bytes differ (a firmware that rebuilds `raw_data` from the semantic
312
+ * fields), the fallback compares the fields that decide where the money goes,
313
+ * plus the validity window against the reference block.
314
+ */
315
+ function verifyTronSignature(args) {
316
+ let signedTx;
317
+ try {
318
+ signedTx = typeof args.signedTx === "string" ? splitSignedTronTx(args.signedTx) : args.signedTx;
319
+ } catch (e) {
320
+ return failed(`the returned Tron transaction is not readable: ${e.message}`);
321
+ }
322
+ if (signedTx.signatures.length === 0) return failed("the returned Tron transaction carries no signature");
323
+ if (!args.from) return failed("no owner address to check the signature against");
324
+ const digest = sha256(signedTx.rawData);
325
+ for (const signature of signedTx.signatures) {
326
+ const recovered = recoverTronAddress(digest, signature);
327
+ if (recovered === null) return failed("signature could not be checked");
328
+ if (recovered !== args.from) return failed("the signature does not belong to this account");
329
+ }
330
+ if (equalBytes(signedTx.rawData, args.rawData)) return verified;
331
+ const contractResult = compareContracts(args.rawData, signedTx.rawData);
332
+ if (contractResult !== null) return contractResult;
333
+ if (args.latestBlock === void 0) return failed("the returned raw_data differs from the request and no latestBlock was provided to check the validity window");
334
+ return compareWindow(signedTx.rawData, args.latestBlock);
335
+ }
336
+ function recoverTronAddress(digest, signature) {
337
+ if (signature.length < 65) return null;
338
+ const recovery = signature[64];
339
+ const recoveryId = recovery >= 27 ? recovery - 27 & 1 : recovery & 1;
340
+ try {
341
+ const point = secp256k1.Signature.fromCompact(signature.slice(0, 64)).addRecoveryBit(recoveryId).recoverPublicKey(digest);
342
+ const hash = keccak_256(point.toRawBytes(false).slice(1));
343
+ return base58check.encode(concatBytes(new Uint8Array([65]), hash.slice(12)));
344
+ } catch {
345
+ return null;
346
+ }
347
+ }
348
+ function parseRawData(bytes) {
349
+ try {
350
+ const fields = readFields(bytes);
351
+ const contracts = [];
352
+ for (const f of fields) if (f.field === 11 && f.wireType === 2) {
353
+ const c = readFields(f.bytes);
354
+ const anyBytes = firstBytes(c, 2);
355
+ const any = anyBytes ? readFields(anyBytes) : [];
356
+ const typeUrlBytes = anyBytes ? firstBytes(any, 1) : null;
357
+ contracts.push({
358
+ typeUrl: typeUrlBytes ? utf8Decode(typeUrlBytes) : "",
359
+ type: firstVarint(c, 1) ?? 0n,
360
+ parameter: anyBytes && firstBytes(any, 2) || /* @__PURE__ */ new Uint8Array(0)
361
+ });
362
+ }
363
+ return {
364
+ contracts,
365
+ expiration: firstVarint(fields, 8) ?? 0n,
366
+ timestamp: firstVarint(fields, 14) ?? 0n
367
+ };
368
+ } catch {
369
+ return null;
370
+ }
371
+ }
372
+ /** null = contracts match (continue to the window check); a result = verdict. */
373
+ function compareContracts(askedBytes, repliedBytes) {
374
+ const asked = parseRawData(askedBytes);
375
+ const replied = parseRawData(repliedBytes);
376
+ if (!asked) return failed("the Tron transaction built by the app could not be read");
377
+ if (!replied) return failed("the Tron transaction built by the device could not be read");
378
+ if (asked.contracts.length !== 1 || replied.contracts.length !== 1) return failed("the Tron transaction does not carry exactly one contract");
379
+ const a = asked.contracts[0];
380
+ const b = replied.contracts[0];
381
+ const kindA = contractKind(a);
382
+ if (kindA !== contractKind(b)) return failed("the returned Tron transaction is a different kind of operation");
383
+ const pa = readFieldsSafe(a.parameter);
384
+ const pb = readFieldsSafe(b.parameter);
385
+ if (!pa || !pb) return failed("the Tron contract parameters could not be read");
386
+ switch (kindA) {
387
+ case "transfer":
388
+ if (sameBytesField(pa, pb, 1) && sameBytesField(pa, pb, 2) && (firstVarint(pa, 3) ?? 0n) === (firstVarint(pb, 3) ?? 0n)) return null;
389
+ break;
390
+ case "transferAsset":
391
+ if (sameBytesField(pa, pb, 1) && sameBytesField(pa, pb, 2) && sameBytesField(pa, pb, 3) && (firstVarint(pa, 4) ?? 0n) === (firstVarint(pb, 4) ?? 0n)) return null;
392
+ break;
393
+ case "triggerSmartContract":
394
+ if (sameBytesField(pa, pb, 1) && sameBytesField(pa, pb, 2) && (firstVarint(pa, 3) ?? 0n) === (firstVarint(pb, 3) ?? 0n) && sameBytesField(pa, pb, 4)) return null;
395
+ break;
396
+ default: return failed("the returned Tron transaction carries an operation this check cannot compare");
397
+ }
398
+ return failed("the returned Tron transaction does not match the one approved");
399
+ }
400
+ function contractKind(contract) {
401
+ if (contract.typeUrl.endsWith(".TransferContract") || contract.type === 1n) return "transfer";
402
+ if (contract.typeUrl.endsWith(".TransferAssetContract") || contract.type === 2n) return "transferAsset";
403
+ if (contract.typeUrl.endsWith(".TriggerSmartContract") || contract.type === 31n) return "triggerSmartContract";
404
+ return "other";
405
+ }
406
+ function readFieldsSafe(bytes) {
407
+ try {
408
+ return readFields(bytes);
409
+ } catch {
410
+ return null;
411
+ }
412
+ }
413
+ function sameBytesField(a, b, field) {
414
+ const x = firstBytes(a, field) ?? /* @__PURE__ */ new Uint8Array(0);
415
+ const y = firstBytes(b, field) ?? /* @__PURE__ */ new Uint8Array(0);
416
+ return equalBytes(x, y);
417
+ }
418
+ /**
419
+ * Firmware formulas, not policy: the two device-side transaction builders
420
+ * stamp `timestamp` with the request's reference-block timestamp verbatim and
421
+ * set `expiration` to +10 minutes (one builder) or +10 hours (the other). A
422
+ * range spanning both cannot refuse a reply the live fleet produces.
423
+ */
424
+ const MIN_EXPIRY_MS = 10n * 60n * 1000n;
425
+ const MAX_EXPIRY_MS = 10n * 60n * 60n * 1000n;
426
+ function compareWindow(repliedBytes, latestBlock) {
427
+ const replied = parseRawData(repliedBytes);
428
+ if (!replied) return failed("the Tron transaction built by the device could not be read");
429
+ const block = BigInt(latestBlock.timestamp);
430
+ if (replied.timestamp !== block) return failed("the returned Tron transaction is stamped against a different reference block than the one we sent");
431
+ const validFor = replied.expiration - block;
432
+ if (validFor < MIN_EXPIRY_MS || validFor > MAX_EXPIRY_MS) return failed(`the returned Tron transaction is valid for ${validFor} ms after the reference block, outside the firmware's ${MIN_EXPIRY_MS}-${MAX_EXPIRY_MS} ms window`);
433
+ return verified;
434
+ }
435
+ //#endregion
436
+ export { PsbtInputType, parsePsbt, verifyBtcMessageHeader, verifyEvmSignature, verifySignedPsbt, verifySolanaSignature, verifyTronSignature };
437
+
438
+ //# sourceMappingURL=verify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verify.js","names":[],"sources":["../src/verify/psbt-reader.ts","../src/verify/result.ts","../src/verify/btc.ts","../src/verify/evm.ts","../src/verify/solana.ts","../src/verify/tron.ts"],"sourcesContent":["import { EraSdkError } from '../core/errors';\n\n/**\n * Minimal PSBT v0 (BIP-174) reader — just enough structure for the\n * verification guard: the global unsigned transaction (verbatim slice, never\n * re-serialized), the PSBT version, and per-input key/value maps.\n *\n * Hardened: compact-size lengths bounds-checked before slicing, duplicate\n * keys within one map refused (a hostile PSBT carrying two final scriptSigs\n * for one input must not survive parsing).\n */\n\nexport interface PsbtKeyValue {\n readonly keyType: number;\n readonly keyData: Uint8Array;\n readonly value: Uint8Array;\n}\n\nexport interface ParsedPsbt {\n /** The global UNSIGNED_TX value, verbatim. */\n readonly unsignedTx: Uint8Array;\n /** Global PSBT_GLOBAL_VERSION (0xFB) if present; v0 files normally omit it. */\n readonly version: number;\n readonly inputs: readonly (readonly PsbtKeyValue[])[];\n readonly outputs: readonly (readonly PsbtKeyValue[])[];\n}\n\nconst MAGIC = [0x70, 0x73, 0x62, 0x74, 0xff]; // \"psbt\\xff\"\n\nexport const PsbtInputType = {\n partialSig: 0x02,\n finalScriptSig: 0x07,\n finalScriptWitness: 0x08,\n taprootKeySpendSignature: 0x13,\n taprootScriptSpendSignature: 0x14,\n} as const;\n\nfunction err(message: string): EraSdkError {\n return new EraSdkError('malformed-reply', `psbt: ${message}`);\n}\n\nclass Reader {\n offset = 0;\n constructor(readonly bytes: Uint8Array) {}\n\n get remaining(): number {\n return this.bytes.length - this.offset;\n }\n\n u8(): number {\n const b = this.bytes[this.offset];\n if (b === undefined) throw err('truncated');\n this.offset += 1;\n return b;\n }\n\n /** Bitcoin compact-size integer, MINIMAL encoding required (as consensus does). */\n compactSize(): number {\n const first = this.u8();\n if (first < 0xfd) return first;\n let width: number;\n let minimum: bigint;\n if (first === 0xfd) {\n width = 2;\n minimum = 0xfdn;\n } else if (first === 0xfe) {\n width = 4;\n minimum = 0x10000n;\n } else {\n width = 8;\n minimum = 0x100000000n;\n }\n let value = 0n;\n for (let i = 0; i < width; i++) value |= BigInt(this.u8()) << BigInt(8 * i);\n if (value < minimum) throw err('non-minimal compact-size encoding');\n if (value > BigInt(Number.MAX_SAFE_INTEGER)) throw err('length exceeds safe range');\n return Number(value);\n }\n\n take(length: number): Uint8Array {\n if (length > this.remaining) throw err('length exceeds input');\n const out = this.bytes.slice(this.offset, this.offset + length);\n this.offset += length;\n return out;\n }\n}\n\n/** Read one key/value map (ends at the 0x00 separator). */\nfunction readMap(reader: Reader): PsbtKeyValue[] {\n const entries: PsbtKeyValue[] = [];\n const seen = new Set<string>();\n for (;;) {\n const keyLength = reader.compactSize();\n if (keyLength === 0) return entries;\n const key = reader.take(keyLength);\n const value = reader.take(reader.compactSize());\n const keyId = Array.from(key)\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n if (seen.has(keyId)) throw err('duplicate key within one map');\n seen.add(keyId);\n entries.push({ keyType: key[0]!, keyData: key.slice(1), value });\n }\n}\n\n/** Count of inputs/outputs in a (non-witness) unsigned transaction. */\nfunction countTxInputsOutputs(tx: Uint8Array): { inputs: number; outputs: number } {\n const reader = new Reader(tx);\n reader.take(4); // version\n const inputs = reader.compactSize();\n if (inputs === 0) {\n // A zero here would be a segwit marker — the PSBT unsigned tx must not\n // carry witness data, so this is not a transaction we can count.\n throw err('unsigned transaction has zero inputs (or carries witness data)');\n }\n for (let i = 0; i < inputs; i++) {\n reader.take(32 + 4); // prevout\n reader.take(reader.compactSize()); // scriptSig (empty in a PSBT)\n reader.take(4); // sequence\n }\n const outputs = reader.compactSize();\n for (let i = 0; i < outputs; i++) {\n reader.take(8); // amount\n reader.take(reader.compactSize()); // scriptPubKey\n }\n reader.take(4); // locktime\n if (reader.remaining !== 0) throw err('trailing bytes after the unsigned transaction');\n return { inputs, outputs };\n}\n\nexport function parsePsbt(bytes: Uint8Array): ParsedPsbt {\n const reader = new Reader(bytes);\n for (const expected of MAGIC) {\n if (reader.u8() !== expected) throw err('bad magic');\n }\n const globalMap = readMap(reader);\n\n let unsignedTx: Uint8Array | null = null;\n let version = 0;\n for (const entry of globalMap) {\n if (entry.keyType === 0x00 && entry.keyData.length === 0) unsignedTx = entry.value;\n if (entry.keyType === 0xfb && entry.keyData.length === 0) {\n if (entry.value.length !== 4) throw err('bad version field');\n version =\n (entry.value[0]! |\n (entry.value[1]! << 8) |\n (entry.value[2]! << 16) |\n (entry.value[3]! << 24)) >>>\n 0;\n }\n }\n if (unsignedTx === null) {\n // The device's signer relies on the global UNSIGNED_TX that only PSBT v0\n // carries; its absence means v2 (or not a PSBT at all).\n throw err('no global unsigned transaction — not a PSBT v0');\n }\n if (version !== 0) throw err(`unsupported PSBT version ${version}`);\n\n const counts = countTxInputsOutputs(unsignedTx);\n const inputs: PsbtKeyValue[][] = [];\n for (let i = 0; i < counts.inputs; i++) inputs.push(readMap(reader));\n const outputs: PsbtKeyValue[][] = [];\n for (let i = 0; i < counts.outputs; i++) outputs.push(readMap(reader));\n if (reader.remaining !== 0) throw err('trailing bytes after the output maps');\n\n return { unsignedTx, version, inputs, outputs };\n}\n\nexport function inputEntries(\n psbt: ParsedPsbt,\n index: number,\n keyType: number,\n): readonly PsbtKeyValue[] {\n return (psbt.inputs[index] ?? []).filter((e) => e.keyType === keyType);\n}\n\nexport function inputHas(psbt: ParsedPsbt, index: number, keyType: number): boolean {\n return inputEntries(psbt, index, keyType).length > 0;\n}\n","/** Outcome of a verification helper. Helpers return, never throw, on mismatches. */\nexport type VerifyResult =\n /** Verified cryptographically / byte-for-byte. */\n | { readonly ok: true; readonly checked: true }\n /**\n * Nothing client-side is verifiable for this input (EIP-712 typed data:\n * the digest is the hash of the structure, which only the device computes).\n * The UR-type pin and the request-id echo are the whole binding.\n */\n | { readonly ok: true; readonly checked: false; readonly reason: string }\n | { readonly ok: false; readonly reason: string };\n\nexport const verified: VerifyResult = { ok: true, checked: true };\nexport const failed = (reason: string): VerifyResult => ({ ok: false, reason });\nexport const unverifiable = (reason: string): VerifyResult => ({\n ok: true,\n checked: false,\n reason,\n});\n","import { equalBytes } from '../core/bytes';\nimport { EraSdkError } from '../core/errors';\nimport type { ParsedPsbt } from './psbt-reader';\nimport { inputEntries, inputHas, PsbtInputType, parsePsbt } from './psbt-reader';\nimport type { VerifyResult } from './result';\nimport { failed, verified } from './result';\n\nexport interface VerifySignedPsbtArgs {\n /** The PSBT you sent to the device. */\n readonly sentPsbt: Uint8Array;\n /** The PSBT the device returned. */\n readonly signedPsbt: Uint8Array;\n /**\n * true (default) on flows where every input is yours (a plain send): a\n * reply that signed only part of the transaction is refused here with a\n * reason instead of failing later inside a finalizer. Set false for dApp\n * `signPsbt` hand-backs, where a PSBT legitimately carries inputs you\n * cannot sign.\n */\n readonly requireEveryInputSigned?: boolean;\n}\n\n/**\n * The `crypto-psbt` reply carries NO request id — this comparison IS the\n * anti-replay binding for Bitcoin. It is not optional.\n *\n * The unsigned transaction is compared byte for byte, which pins the input\n * set and order, the outputs, their amounts, the version and the locktime in\n * one shot — and therefore the txid. The device only ADDS per-input\n * signature fields, so a legitimate reply always matches.\n */\nexport function verifySignedPsbt(args: VerifySignedPsbtArgs): VerifyResult {\n let sent: ParsedPsbt;\n let signed: ParsedPsbt;\n try {\n sent = parsePsbt(args.sentPsbt);\n } catch (e) {\n return failed(`the PSBT we sent is not readable: ${message(e)}`);\n }\n try {\n signed = parsePsbt(args.signedPsbt);\n } catch (e) {\n return failed(`the PSBT the device returned is not readable: ${message(e)}`);\n }\n\n if (!equalBytes(sent.unsignedTx, signed.unsignedTx)) {\n return failed('the returned PSBT is a different transaction from the one approved');\n }\n\n // A finalized field carries the COMPLETE scriptSig/witness that will be\n // broadcast, and the unsigned-tx comparison above does not cover it (it\n // lives per input, the unsigned tx in the global map). An input that comes\n // back finalized must have been SENT that way, with byte-identical\n // values — the device echoes these fields, it never authors them.\n const finalizedTypes = [PsbtInputType.finalScriptSig, PsbtInputType.finalScriptWitness];\n for (let i = 0; i < signed.inputs.length; i++) {\n for (const type of finalizedTypes) {\n if (!inputHas(signed, i, type)) continue;\n if (!inputHas(sent, i, type)) {\n return failed(\n `input ${i} came back finalized (type 0x${type.toString(16)}) and was not sent that way — the script it would broadcast is not ours`,\n );\n }\n const a = inputEntries(sent, i, type);\n const b = inputEntries(signed, i, type);\n if (a.length !== b.length || !a.every((entry, k) => equalBytes(entry.value, b[k]!.value))) {\n return failed(\n `input ${i} came back with a different finalized script than the one we sent`,\n );\n }\n }\n }\n\n const isSigned = (i: number): boolean =>\n inputHas(signed, i, PsbtInputType.partialSig) ||\n inputHas(signed, i, PsbtInputType.taprootKeySpendSignature) ||\n inputHas(signed, i, PsbtInputType.taprootScriptSpendSignature);\n\n const indexes = signed.inputs.map((_, i) => i);\n if (args.requireEveryInputSigned ?? true) {\n if (!indexes.every(isSigned)) {\n return failed('the device signed only part of the transaction');\n }\n } else if (!indexes.some(isSigned)) {\n return failed('the returned PSBT carries no signature');\n }\n return verified;\n}\n\nexport interface VerifyBtcMessageHeaderArgs {\n /** The address the request asked the device to sign with. */\n readonly address: string;\n /** The raw 65-byte BIP-137 signature. */\n readonly signature: Uint8Array;\n}\n\n/**\n * BIP-137: the recovery header names the address type a verifier derives\n * before comparing. A header of the wrong range produces a signature that\n * LOOKS fine (65 bytes, valid base64) but fails every verifier downstream —\n * this check is the only place that difference is visible.\n */\nexport function verifyBtcMessageHeader(args: VerifyBtcMessageHeaderArgs): VerifyResult {\n if (args.signature.length === 0) {\n return failed('empty signature');\n }\n const header = args.signature[0]!;\n const range = headerRangeFor(args.address);\n if (range === null) {\n return {\n ok: true,\n checked: false,\n reason: 'address kind has no BIP-137 header range to check against',\n };\n }\n if (header >= range.low && header <= range.high) return verified;\n return failed(\n `recovery header ${header} does not match a ${range.label} address ` +\n `(BIP-137 expects ${range.low}..${range.high}); this signature would not verify against the address it was asked to sign for`,\n );\n}\n\ninterface HeaderRange {\n readonly low: number;\n readonly high: number;\n readonly label: string;\n}\n\nfunction headerRangeFor(address: string): HeaderRange | null {\n const a = address.toLowerCase();\n if (a.startsWith('bc1q') || a.startsWith('tb1q') || a.startsWith('bcrt1q')) {\n return { low: 39, high: 42, label: 'native segwit (P2WPKH)' };\n }\n if (a.startsWith('bc1p') || a.startsWith('tb1p') || a.startsWith('bcrt1p')) {\n return null; // Taproot: BIP-137 does not cover it (BIP-322 is the scheme).\n }\n // Base58 kinds matched on SHAPE, not first character alone — a guard that\n // invents a range for a string it does not understand is worse than one\n // that declines to judge.\n if (looksBase58(address)) {\n if (address.startsWith('3') || address.startsWith('2')) {\n return { low: 35, high: 38, label: 'P2SH (nested segwit)' };\n }\n if (address.startsWith('1') || address.startsWith('m') || address.startsWith('n')) {\n // Both the uncompressed and compressed P2PKH ranges — which one is\n // right depends on the key the device used, which we do not know.\n return { low: 27, high: 34, label: 'legacy P2PKH' };\n }\n }\n return null;\n}\n\nfunction looksBase58(address: string): boolean {\n return address.length >= 26 && address.length <= 35 && /^[1-9A-HJ-NP-Za-km-z]+$/.test(address);\n}\n\nfunction message(e: unknown): string {\n return e instanceof EraSdkError || e instanceof Error ? e.message : String(e);\n}\n","import { secp256k1 } from '@noble/curves/secp256k1';\nimport { keccak_256 } from '@noble/hashes/sha3';\nimport { EvmDataType, foldRecoveryId } from '../chains/evm';\nimport { bytesToHex, concatBytes, equalBytes, hexToBytes, utf8Encode } from '../core/bytes';\nimport type { VerifyResult } from './result';\nimport { failed, unverifiable, verified } from './result';\n\nexport interface VerifyEvmSignatureArgs {\n /** The exact bytes the request carried in `signData`. */\n readonly signData: Uint8Array;\n readonly dataType: EvmDataType;\n /** Raw `r || s || v` from the reply (65+ bytes; multi-byte legacy `v` handled). */\n readonly signature: Uint8Array;\n /** The signer address the request was built for. */\n readonly address: Uint8Array | `0x${string}`;\n /**\n * Optional: the signing payload re-derived from the transaction you are\n * ABOUT TO BROADCAST. Recovering against `signData` alone proves the device\n * signed something you asked for — this closes the second half: that it is\n * the transaction still in your hands (payloads can legitimately change\n * between build and send, e.g. a blockhash refresh in your own state).\n */\n readonly reEncodedSignData?: Uint8Array;\n}\n\n/** EIP-191: 0x19 || \"Ethereum Signed Message:\\n\" || len(message). */\nfunction personalSignDigest(message: Uint8Array): Uint8Array {\n const prefix = utf8Encode(`Ethereum Signed Message:\\n${message.length}`);\n return keccak_256(concatBytes(new Uint8Array([0x19]), prefix, message));\n}\n\n/**\n * \"Did the device sign exactly what I sent, with the key I expected?\"\n * keccak digest + public-key recovery; the recovered address must equal the\n * request's. Run it before broadcasting.\n */\nexport function verifyEvmSignature(args: VerifyEvmSignatureArgs): VerifyResult {\n if (args.signature.length < 65) {\n return failed('signature is shorter than 65 bytes');\n }\n if (args.reEncodedSignData !== undefined && !equalBytes(args.reEncodedSignData, args.signData)) {\n return failed('the transaction to broadcast is not the one the device signed');\n }\n\n let digest: Uint8Array;\n switch (args.dataType) {\n case EvmDataType.transaction:\n case EvmDataType.typedTransaction:\n digest = keccak_256(args.signData);\n break;\n case EvmDataType.personalMessage:\n digest = personalSignDigest(args.signData);\n break;\n case EvmDataType.typedData:\n return unverifiable(\n 'EIP-712: the digest is the hash of the structure, computed only on the device',\n );\n default:\n return failed(`unknown dataType ${args.dataType satisfies never}`);\n }\n\n let vBig = 0n;\n for (const b of args.signature.slice(64)) vBig = (vBig << 8n) | BigInt(b);\n const recoveryId = foldRecoveryId(vBig);\n if (recoveryId !== 0 && recoveryId !== 1) {\n return failed('implausible recovery value');\n }\n\n let recovered: Uint8Array;\n try {\n const point = secp256k1.Signature.fromCompact(args.signature.slice(0, 64))\n .addRecoveryBit(recoveryId)\n .recoverPublicKey(digest);\n recovered = keccak_256(point.toRawBytes(false).slice(1)).slice(12);\n } catch (e) {\n return failed(`signature could not be checked: ${(e as Error).message}`);\n }\n\n const expected = args.address instanceof Uint8Array ? args.address : hexToBytes(args.address);\n if (expected.length !== 20) {\n return failed(`expected address must be 20 bytes, got ${expected.length}`);\n }\n if (!equalBytes(recovered, expected)) {\n return failed(\n `the signature does not belong to this account (recovered 0x${bytesToHex(recovered)})`,\n );\n }\n return verified;\n}\n","import { ed25519 } from '@noble/curves/ed25519';\nimport { equalBytes } from '../core/bytes';\nimport type { VerifyResult } from './result';\nimport { failed, verified } from './result';\n\nexport interface VerifySolanaSignatureArgs {\n /** The exact bytes the request carried in `signData` (the compiled message). */\n readonly signData: Uint8Array;\n /** 64-byte Ed25519 signature from the reply. */\n readonly signature: Uint8Array;\n /** The 32-byte signer public key the request was built for. */\n readonly publicKey: Uint8Array;\n /**\n * Optional: the message bytes you are ABOUT TO BROADCAST. Matters most on\n * Solana — a blockhash refresh between build and send makes \"what was\n * signed\" and \"what will be sent\" two different objects that must agree.\n */\n readonly broadcastMessageBytes?: Uint8Array;\n}\n\nexport function verifySolanaSignature(args: VerifySolanaSignatureArgs): VerifyResult {\n if (\n args.broadcastMessageBytes !== undefined &&\n !equalBytes(args.broadcastMessageBytes, args.signData)\n ) {\n return failed('the message to broadcast is not the one the device signed');\n }\n let ok: boolean;\n try {\n ok = ed25519.verify(args.signature, args.signData, args.publicKey);\n } catch (e) {\n return failed(`Solana signature could not be checked: ${(e as Error).message}`);\n }\n return ok ? verified : failed('the signature does not belong to this account');\n}\n","import { secp256k1 } from '@noble/curves/secp256k1';\nimport { sha256 } from '@noble/hashes/sha2';\nimport { keccak_256 } from '@noble/hashes/sha3';\nimport { createBase58check } from '@scure/base';\nimport { concatBytes, equalBytes, utf8Decode } from '../core/bytes';\nimport type { SignedTronTx, TronLatestBlock } from '../tron-proto/messages';\nimport { splitSignedTronTx } from '../tron-proto/messages';\nimport { firstBytes, firstVarint, readFields } from '../tron-proto/wire';\nimport type { VerifyResult } from './result';\nimport { failed, verified } from './result';\n\nconst base58check = createBase58check(sha256);\n\nexport interface VerifyTronSignatureArgs {\n /** The `raw_data` bytes the request carried. */\n readonly rawData: Uint8Array;\n /** The base58 owner address the transaction spends from. */\n readonly from: string;\n /** The reference block the request carried (enables the rebuild-path window check). */\n readonly latestBlock?: TronLatestBlock;\n /** The reply: either the split frame from `TronSignatureResult.signedTx`, or the raw hex. */\n readonly signedTx: SignedTronTx | string;\n}\n\n/**\n * The Tron reply is a fully signed transaction broadcast VERBATIM, so it is\n * checked on both counts: every signature must recover to the owner address,\n * and the transaction must move what the user approved.\n *\n * Byte equality with the request's `rawData` is the strong form. When the\n * bytes differ (a firmware that rebuilds `raw_data` from the semantic\n * fields), the fallback compares the fields that decide where the money goes,\n * plus the validity window against the reference block.\n */\nexport function verifyTronSignature(args: VerifyTronSignatureArgs): VerifyResult {\n let signedTx: SignedTronTx;\n try {\n signedTx = typeof args.signedTx === 'string' ? splitSignedTronTx(args.signedTx) : args.signedTx;\n } catch (e) {\n return failed(`the returned Tron transaction is not readable: ${(e as Error).message}`);\n }\n if (signedTx.signatures.length === 0) {\n return failed('the returned Tron transaction carries no signature');\n }\n if (!args.from) {\n return failed('no owner address to check the signature against');\n }\n\n const digest = sha256(signedTx.rawData);\n for (const signature of signedTx.signatures) {\n const recovered = recoverTronAddress(digest, signature);\n if (recovered === null) return failed('signature could not be checked');\n if (recovered !== args.from) {\n return failed('the signature does not belong to this account');\n }\n }\n\n if (equalBytes(signedTx.rawData, args.rawData)) return verified;\n\n // Rebuild path: the firmware built its own raw_data from the semantic\n // fields. Compare the operation, then the validity window.\n const contractResult = compareContracts(args.rawData, signedTx.rawData);\n if (contractResult !== null) return contractResult;\n if (args.latestBlock === undefined) {\n return failed(\n 'the returned raw_data differs from the request and no latestBlock was provided to check the validity window',\n );\n }\n return compareWindow(signedTx.rawData, args.latestBlock);\n}\n\nfunction recoverTronAddress(digest: Uint8Array, signature: Uint8Array): string | null {\n if (signature.length < 65) return null;\n const recovery = signature[64]!;\n const recoveryId = recovery >= 27 ? (recovery - 27) & 1 : recovery & 1;\n try {\n const point = secp256k1.Signature.fromCompact(signature.slice(0, 64))\n .addRecoveryBit(recoveryId)\n .recoverPublicKey(digest);\n const hash = keccak_256(point.toRawBytes(false).slice(1));\n return base58check.encode(concatBytes(new Uint8Array([0x41]), hash.slice(12)));\n } catch {\n return null;\n }\n}\n\n// --- raw_data structural comparison ---------------------------------------\n\ninterface TronContract {\n readonly typeUrl: string;\n readonly type: bigint;\n readonly parameter: Uint8Array;\n}\n\ninterface TronRawData {\n readonly contracts: readonly TronContract[];\n readonly expiration: bigint;\n readonly timestamp: bigint;\n}\n\nfunction parseRawData(bytes: Uint8Array): TronRawData | null {\n try {\n const fields = readFields(bytes);\n const contracts: TronContract[] = [];\n for (const f of fields) {\n if (f.field === 11 && f.wireType === 2) {\n const c = readFields(f.bytes);\n const anyBytes = firstBytes(c, 2);\n const any = anyBytes ? readFields(anyBytes) : [];\n const typeUrlBytes = anyBytes ? firstBytes(any, 1) : null;\n contracts.push({\n typeUrl: typeUrlBytes ? utf8Decode(typeUrlBytes) : '',\n type: firstVarint(c, 1) ?? 0n,\n parameter: (anyBytes && firstBytes(any, 2)) || new Uint8Array(0),\n });\n }\n }\n return {\n contracts,\n expiration: firstVarint(fields, 8) ?? 0n,\n timestamp: firstVarint(fields, 14) ?? 0n,\n };\n } catch {\n return null;\n }\n}\n\n/** null = contracts match (continue to the window check); a result = verdict. */\nfunction compareContracts(askedBytes: Uint8Array, repliedBytes: Uint8Array): VerifyResult | null {\n const asked = parseRawData(askedBytes);\n const replied = parseRawData(repliedBytes);\n if (!asked) return failed('the Tron transaction built by the app could not be read');\n if (!replied) return failed('the Tron transaction built by the device could not be read');\n if (asked.contracts.length !== 1 || replied.contracts.length !== 1) {\n return failed('the Tron transaction does not carry exactly one contract');\n }\n const a = asked.contracts[0]!;\n const b = replied.contracts[0]!;\n const kindA = contractKind(a);\n const kindB = contractKind(b);\n if (kindA !== kindB) {\n return failed('the returned Tron transaction is a different kind of operation');\n }\n\n const pa = readFieldsSafe(a.parameter);\n const pb = readFieldsSafe(b.parameter);\n if (!pa || !pb) return failed('the Tron contract parameters could not be read');\n\n switch (kindA) {\n case 'transfer':\n // TransferContract {1: owner, 2: to, 3: amount}\n if (\n sameBytesField(pa, pb, 1) &&\n sameBytesField(pa, pb, 2) &&\n (firstVarint(pa, 3) ?? 0n) === (firstVarint(pb, 3) ?? 0n)\n ) {\n return null;\n }\n break;\n case 'transferAsset':\n // TransferAssetContract {1: asset, 2: owner, 3: to, 4: amount}\n if (\n sameBytesField(pa, pb, 1) &&\n sameBytesField(pa, pb, 2) &&\n sameBytesField(pa, pb, 3) &&\n (firstVarint(pa, 4) ?? 0n) === (firstVarint(pb, 4) ?? 0n)\n ) {\n return null;\n }\n break;\n case 'triggerSmartContract':\n // TriggerSmartContract {1: owner, 2: contract, 3: call_value, 4: data}.\n // call_value: zero and absent are the same thing — the two sides are\n // serialized by different protobuf writers that disagree on whether a\n // zero scalar is written, and every TRC-20 transfer carries call_value 0.\n if (\n sameBytesField(pa, pb, 1) &&\n sameBytesField(pa, pb, 2) &&\n (firstVarint(pa, 3) ?? 0n) === (firstVarint(pb, 3) ?? 0n) &&\n sameBytesField(pa, pb, 4)\n ) {\n return null;\n }\n break;\n default:\n // An operation this gate cannot compare field by field is not waved\n // through: without byte equality there is nothing left to bind it to\n // what the user approved.\n return failed('the returned Tron transaction carries an operation this check cannot compare');\n }\n return failed('the returned Tron transaction does not match the one approved');\n}\n\ntype ContractKind = 'transfer' | 'transferAsset' | 'triggerSmartContract' | 'other';\n\nfunction contractKind(contract: TronContract): ContractKind {\n if (contract.typeUrl.endsWith('.TransferContract') || contract.type === 1n) return 'transfer';\n if (contract.typeUrl.endsWith('.TransferAssetContract') || contract.type === 2n) {\n return 'transferAsset';\n }\n if (contract.typeUrl.endsWith('.TriggerSmartContract') || contract.type === 31n) {\n return 'triggerSmartContract';\n }\n return 'other';\n}\n\nfunction readFieldsSafe(bytes: Uint8Array): ReturnType<typeof readFields> | null {\n try {\n return readFields(bytes);\n } catch {\n return null;\n }\n}\n\nfunction sameBytesField(\n a: ReturnType<typeof readFields>,\n b: ReturnType<typeof readFields>,\n field: number,\n): boolean {\n const x = firstBytes(a, field) ?? new Uint8Array(0);\n const y = firstBytes(b, field) ?? new Uint8Array(0);\n return equalBytes(x, y);\n}\n\n/**\n * Firmware formulas, not policy: the two device-side transaction builders\n * stamp `timestamp` with the request's reference-block timestamp verbatim and\n * set `expiration` to +10 minutes (one builder) or +10 hours (the other). A\n * range spanning both cannot refuse a reply the live fleet produces.\n */\nconst MIN_EXPIRY_MS = 10n * 60n * 1000n;\nconst MAX_EXPIRY_MS = 10n * 60n * 60n * 1000n;\n\nfunction compareWindow(repliedBytes: Uint8Array, latestBlock: TronLatestBlock): VerifyResult {\n const replied = parseRawData(repliedBytes);\n if (!replied) return failed('the Tron transaction built by the device could not be read');\n const block = BigInt(latestBlock.timestamp);\n if (replied.timestamp !== block) {\n return failed(\n 'the returned Tron transaction is stamped against a different reference block than the one we sent',\n );\n }\n const validFor = replied.expiration - block;\n if (validFor < MIN_EXPIRY_MS || validFor > MAX_EXPIRY_MS) {\n return failed(\n `the returned Tron transaction is valid for ${validFor} ms after the reference block, outside the firmware's ${MIN_EXPIRY_MS}-${MAX_EXPIRY_MS} ms window`,\n );\n }\n return verified;\n}\n"],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ {
2
+ "main": "../dist/evm.cjs",
3
+ "module": "../dist/evm.js",
4
+ "types": "../dist/evm.d.ts",
5
+ "react-native": "../dist/evm.js"
6
+ }
package/package.json ADDED
@@ -0,0 +1,140 @@
1
+ {
2
+ "name": "@hwlt/era-connect",
3
+ "version": "0.1.0",
4
+ "description": "ERA hardware wallet SDK: air-gapped UR/QR account linking and transaction signing for EVM, Bitcoin, Solana and Tron",
5
+ "keywords": [
6
+ "era-wallet",
7
+ "hardware-wallet",
8
+ "air-gap",
9
+ "ur",
10
+ "bc-ur",
11
+ "qr",
12
+ "keystone",
13
+ "react-native",
14
+ "bitcoin",
15
+ "ethereum",
16
+ "solana",
17
+ "tron"
18
+ ],
19
+ "license": "Apache-2.0",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/ERAWLT/era-connect-sdk.git",
23
+ "directory": "packages/connect"
24
+ },
25
+ "homepage": "https://github.com/ERAWLT/era-connect-sdk#readme",
26
+ "bugs": "https://github.com/ERAWLT/era-connect-sdk/issues",
27
+ "type": "module",
28
+ "main": "./dist/index.cjs",
29
+ "module": "./dist/index.js",
30
+ "types": "./dist/index.d.ts",
31
+ "react-native": "./dist/index.js",
32
+ "sideEffects": false,
33
+ "files": [
34
+ "dist",
35
+ "proto",
36
+ "evm",
37
+ "btc",
38
+ "solana",
39
+ "tron",
40
+ "verify",
41
+ "NOTICE"
42
+ ],
43
+ "engines": {
44
+ "node": ">=18"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public"
48
+ },
49
+ "exports": {
50
+ ".": {
51
+ "react-native": "./dist/index.js",
52
+ "import": {
53
+ "types": "./dist/index.d.ts",
54
+ "default": "./dist/index.js"
55
+ },
56
+ "require": {
57
+ "types": "./dist/index.d.cts",
58
+ "default": "./dist/index.cjs"
59
+ }
60
+ },
61
+ "./evm": {
62
+ "react-native": "./dist/evm.js",
63
+ "import": {
64
+ "types": "./dist/evm.d.ts",
65
+ "default": "./dist/evm.js"
66
+ },
67
+ "require": {
68
+ "types": "./dist/evm.d.cts",
69
+ "default": "./dist/evm.cjs"
70
+ }
71
+ },
72
+ "./btc": {
73
+ "react-native": "./dist/btc.js",
74
+ "import": {
75
+ "types": "./dist/btc.d.ts",
76
+ "default": "./dist/btc.js"
77
+ },
78
+ "require": {
79
+ "types": "./dist/btc.d.cts",
80
+ "default": "./dist/btc.cjs"
81
+ }
82
+ },
83
+ "./solana": {
84
+ "react-native": "./dist/solana.js",
85
+ "import": {
86
+ "types": "./dist/solana.d.ts",
87
+ "default": "./dist/solana.js"
88
+ },
89
+ "require": {
90
+ "types": "./dist/solana.d.cts",
91
+ "default": "./dist/solana.cjs"
92
+ }
93
+ },
94
+ "./tron": {
95
+ "react-native": "./dist/tron.js",
96
+ "import": {
97
+ "types": "./dist/tron.d.ts",
98
+ "default": "./dist/tron.js"
99
+ },
100
+ "require": {
101
+ "types": "./dist/tron.d.cts",
102
+ "default": "./dist/tron.cjs"
103
+ }
104
+ },
105
+ "./verify": {
106
+ "react-native": "./dist/verify.js",
107
+ "import": {
108
+ "types": "./dist/verify.d.ts",
109
+ "default": "./dist/verify.js"
110
+ },
111
+ "require": {
112
+ "types": "./dist/verify.d.cts",
113
+ "default": "./dist/verify.cjs"
114
+ }
115
+ },
116
+ "./package.json": "./package.json"
117
+ },
118
+ "scripts": {
119
+ "build": "tsdown",
120
+ "test": "vitest run",
121
+ "test:watch": "vitest",
122
+ "typecheck": "tsc --noEmit"
123
+ },
124
+ "dependencies": {
125
+ "@noble/curves": "^1.8.1",
126
+ "@noble/hashes": "^1.7.1",
127
+ "@scure/base": "^1.2.4",
128
+ "@scure/bip32": "^1.6.2",
129
+ "fflate": "^0.8.2"
130
+ },
131
+ "devDependencies": {
132
+ "@ngraveio/bc-ur": "1.1.13",
133
+ "@types/node": "^26.4.0",
134
+ "cbor2": "^1.7.0",
135
+ "fast-check": "^3.23.2",
136
+ "tsdown": "^0.22.14",
137
+ "typescript": "^5.7.3",
138
+ "vitest": "^3.0.5"
139
+ }
140
+ }