@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
package/README.md CHANGED
@@ -83,16 +83,10 @@ today and speaks the rest through the same primitives.
83
83
  | Cosmos (~35 networks incl. Ethermint) | `cosmos-sign-request` / `evm-sign-request` | ADR-036 | `@hwlt/era-connect/cosmos` |
84
84
  | XRP | `ur:bytes` (XRP Toolkit convention) | — | `@hwlt/era-connect/xrp` |
85
85
  | Litecoin, Dogecoin, Dash | `crypto-psbt-extend` (same flow as Bitcoin) | — | `@hwlt/era-connect/btc` |
86
+ | Bitcoin Cash | structured envelope (FORKID signing, CashAddr) | — | `@hwlt/era-connect/bch` |
86
87
 
87
- **Also supported by the device** usable today via `era.raw` +
88
- `era.scanner({expectedTypes})`, dedicated modules on the roadmap (order driven
89
- by integrator demand):
90
-
91
- | Chain | Wire type | Module status |
92
- |---|---|---|
93
- | Bitcoin Cash | structured envelope (FORKID signing) | planned |
94
-
95
- Two things make the gap workable now: **linking already surfaces every account
88
+ Every chain the current device firmware ships now has a dedicated module.
89
+ Two things keep future additions workable: **linking already surfaces every account
96
90
  the device exports** (unknown chains still carry their path, fingerprint and
97
91
  public key), and the scanner/fountain layer is chain-agnostic. The device's
98
92
  chain list keeps growing — confirm the current set with the ERA team.
@@ -0,0 +1,6 @@
1
+ {
2
+ "main": "../dist/bch.cjs",
3
+ "module": "../dist/bch.js",
4
+ "types": "../dist/bch.d.ts",
5
+ "react-native": "../dist/bch.js"
6
+ }
@@ -0,0 +1,74 @@
1
+ import { _ as Ur, a as SignRequest, i as ExpectedReply, r as EraConnectConfig } from "./shared-CI8zhQiE.js";
2
+ //#region src/chains/bch.d.ts
3
+ /** One UTXO the transaction spends. P2PKH only — that is what the device signs. */
4
+ interface BchTxInput {
5
+ /** Display-order (big-endian) txid of the UTXO, 64 hex chars. */
6
+ readonly txid: string;
7
+ /** Output index of the UTXO. */
8
+ readonly index: number;
9
+ /** UTXO value in satoshis — part of the BIP-143 sighash, so it MUST be exact. */
10
+ readonly value: number | bigint;
11
+ /** The compressed (33-byte) public key that owns the UTXO. */
12
+ readonly publicKey: Uint8Array | string;
13
+ /** Full derivation path of that key, e.g. `m/44'/145'/0'/0/0`. */
14
+ readonly path: string;
15
+ }
16
+ interface BchTxOutput {
17
+ /** CashAddr (P2PKH or P2SH), with or without the `bitcoincash:` prefix. */
18
+ readonly address: string;
19
+ /** Output value in satoshis. */
20
+ readonly value: number | bigint;
21
+ /** Marks the output as change on the device screen. Display only. */
22
+ readonly isChange?: boolean;
23
+ /** Shown with the change output; the address above is still what is paid. */
24
+ readonly changeAddressPath?: string;
25
+ }
26
+ interface BchSignRequestProps {
27
+ readonly requestId?: Uint8Array | string;
28
+ readonly inputs: readonly BchTxInput[];
29
+ /** Every output — change included — carries a real CashAddr. */
30
+ readonly outputs: readonly BchTxOutput[];
31
+ /** Fee in satoshis. Must equal `sum(inputs) - sum(outputs)` exactly. */
32
+ readonly fee: number | bigint;
33
+ /** Dust threshold shown on the device; defaults to 546. */
34
+ readonly dustThreshold?: number;
35
+ readonly memo?: string;
36
+ readonly xfp: string | number;
37
+ /** Milliseconds timestamp shown in the device log; 0 omits it. */
38
+ readonly timestamp?: number;
39
+ readonly origin?: string;
40
+ }
41
+ interface BchSignatureResult {
42
+ readonly requestId: Uint8Array;
43
+ /** Display-order txid of the signed transaction, as computed by the device. */
44
+ readonly txId: string;
45
+ /** Hex of the fully signed transaction — broadcast as-is. */
46
+ readonly rawTx: string;
47
+ }
48
+ /**
49
+ * Bitcoin Cash signing rides the structured `keystone-sign-request` (6101)
50
+ * envelope, NOT the PSBT path: the device's PSBT signer cannot apply the
51
+ * `SIGHASH_FORKID` (0x41) sighash BCH consensus requires, so a dedicated
52
+ * FORKID signer sits behind this envelope instead. The SDK therefore builds
53
+ * the transaction container from structured inputs/outputs here — the one
54
+ * chain where it is more than a transport.
55
+ *
56
+ * The device derives each input's signing key from its `path`, computes the
57
+ * BIP-143 sighash with FORKID over version-1/locktime-0/sequence-0xfffffffd
58
+ * legacy serialization, and returns the COMPLETE signed transaction.
59
+ */
60
+ declare class BchChain {
61
+ private readonly context;
62
+ constructor(config?: EraConnectConfig);
63
+ /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */
64
+ generateSignRequest(props: BchSignRequestProps): SignRequest<BchSignatureResult>;
65
+ /**
66
+ * Parse a `keystone-sign-result` standalone. The request id lives INSIDE
67
+ * the protobuf (`signId`); pass `expect.requestId` to enable the echo
68
+ * check — prefer `SignRequest.scanner().parse()`.
69
+ */
70
+ parseSignature(input: Ur | string, expect?: ExpectedReply): BchSignatureResult;
71
+ }
72
+ //#endregion
73
+ export { BchTxOutput as a, BchTxInput as i, BchSignRequestProps as n, BchSignatureResult as r, BchChain as t };
74
+ //# sourceMappingURL=bch-CK6dKGQA.d.ts.map
@@ -0,0 +1,139 @@
1
+ const require_shared = require("./shared-Cu2yynP9.cjs");
2
+ const require_keypath = require("./keypath-pzm_YIfN.cjs");
3
+ const require_cashaddr = require("./cashaddr-Ci0_8ITN.cjs");
4
+ const require_gzip = require("./gzip-CLzDX7AZ.cjs");
5
+ const require_messages = require("./messages-orAQ52CI.cjs");
6
+ //#region src/chains/bch.ts
7
+ const MAX_COMPRESSED_BYTES = 8192;
8
+ const MAX_INFLATED_BYTES = 65536;
9
+ const REPLY_TYPES = ["keystone-sign-result"];
10
+ /** Satoshi amounts must stay exact in a double; anything above is refused. */
11
+ const MAX_SATOSHI = 2100000000000000n;
12
+ function toSatoshi(value, label) {
13
+ if (typeof value === "number" && !Number.isSafeInteger(value)) throw new require_shared.EraSdkError("invalid-props", `${label} must be an integer satoshi amount`);
14
+ const v = typeof value === "number" ? BigInt(value) : value;
15
+ if (v <= 0n || v > MAX_SATOSHI) throw new require_shared.EraSdkError("invalid-props", `${label} must be a positive satoshi amount`);
16
+ return v;
17
+ }
18
+ function toPublicKeyHex(publicKey, label) {
19
+ const hex = typeof publicKey === "string" ? publicKey.toLowerCase() : require_shared.bytesToHex(publicKey);
20
+ if (!/^0[23][0-9a-f]{64}$/.test(hex)) throw new require_shared.EraSdkError("invalid-props", `${label} must be a 33-byte compressed public key`);
21
+ return hex;
22
+ }
23
+ /**
24
+ * Bitcoin Cash signing rides the structured `keystone-sign-request` (6101)
25
+ * envelope, NOT the PSBT path: the device's PSBT signer cannot apply the
26
+ * `SIGHASH_FORKID` (0x41) sighash BCH consensus requires, so a dedicated
27
+ * FORKID signer sits behind this envelope instead. The SDK therefore builds
28
+ * the transaction container from structured inputs/outputs here — the one
29
+ * chain where it is more than a transport.
30
+ *
31
+ * The device derives each input's signing key from its `path`, computes the
32
+ * BIP-143 sighash with FORKID over version-1/locktime-0/sequence-0xfffffffd
33
+ * legacy serialization, and returns the COMPLETE signed transaction.
34
+ */
35
+ var BchChain = class {
36
+ constructor(config) {
37
+ this.context = require_shared.resolveContext(config);
38
+ }
39
+ /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */
40
+ generateSignRequest(props) {
41
+ const requestId = require_shared.resolveRequestId(this.context, props.requestId);
42
+ const xfp = require_keypath.normalizeXfp(props.xfp);
43
+ if (props.inputs.length === 0) throw new require_shared.EraSdkError("invalid-props", "at least one input is required");
44
+ if (props.outputs.length === 0) throw new require_shared.EraSdkError("invalid-props", "at least one output is required");
45
+ let inputSum = 0n;
46
+ const inputs = props.inputs.map((input, i) => {
47
+ if (!/^[0-9a-fA-F]{64}$/.test(input.txid)) throw new require_shared.EraSdkError("invalid-props", `input ${i}: txid must be 64 hex chars`);
48
+ if (!Number.isInteger(input.index) || input.index < 0) throw new require_shared.EraSdkError("invalid-props", `input ${i}: index must be a non-negative integer`);
49
+ require_keypath.parsePath(input.path);
50
+ const value = toSatoshi(input.value, `input ${i} value`);
51
+ inputSum += value;
52
+ return {
53
+ txidHex: input.txid.toLowerCase(),
54
+ index: input.index,
55
+ value,
56
+ publicKeyHex: toPublicKeyHex(input.publicKey, `input ${i} publicKey`),
57
+ ownerKeyPath: input.path
58
+ };
59
+ });
60
+ let outputSum = 0n;
61
+ const outputs = props.outputs.map((output, i) => {
62
+ const decoded = require_cashaddr.decodeCashAddr(output.address);
63
+ const value = toSatoshi(output.value, `output ${i} value`);
64
+ outputSum += value;
65
+ if (output.changeAddressPath !== void 0) require_keypath.parsePath(output.changeAddressPath);
66
+ return {
67
+ address: require_cashaddr.encodeCashAddr(decoded.type, decoded.hash, { withPrefix: output.address.includes(":") }),
68
+ value,
69
+ isChange: output.isChange ?? false,
70
+ changeAddressPath: output.changeAddressPath
71
+ };
72
+ });
73
+ const fee = toSatoshi(props.fee, "fee");
74
+ if (inputSum !== outputSum + fee) throw new require_shared.EraSdkError("invalid-props", `fee mismatch: inputs (${inputSum}) minus outputs (${outputSum}) is ${inputSum - outputSum}, but fee says ${fee}`);
75
+ const dustThreshold = props.dustThreshold ?? 546;
76
+ if (!Number.isInteger(dustThreshold) || dustThreshold < 0 || dustThreshold > 2147483647) throw new require_shared.EraSdkError("invalid-props", "dustThreshold must fit a non-negative int32");
77
+ const proto = require_messages.encodeBchSignRequestProto({
78
+ xfpHex: require_keypath.xfpToHex(xfp),
79
+ signId: require_shared.uuidStringify(requestId),
80
+ timestamp: props.timestamp ?? 0,
81
+ fee,
82
+ dustThreshold,
83
+ memo: props.memo,
84
+ inputs,
85
+ outputs
86
+ });
87
+ 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)]])));
88
+ return require_shared.makeSignRequest({
89
+ ur,
90
+ requestId,
91
+ replyTypes: REPLY_TYPES,
92
+ context: this.context,
93
+ parse: (reply) => parseBchSignature(reply, requestId)
94
+ });
95
+ }
96
+ /**
97
+ * Parse a `keystone-sign-result` standalone. The request id lives INSIDE
98
+ * the protobuf (`signId`); pass `expect.requestId` to enable the echo
99
+ * check — prefer `SignRequest.scanner().parse()`.
100
+ */
101
+ parseSignature(input, expect) {
102
+ return parseBchSignature(require_shared.toUr(input), expect?.requestId === void 0 ? void 0 : require_shared.normalizeRequestId(expect.requestId));
103
+ }
104
+ };
105
+ function parseBchSignature(ur, expectedRequestId) {
106
+ require_shared.requireUrType(ur, [...REPLY_TYPES], "keystone-sign-result");
107
+ const map = require_shared.requireReplyMap(ur, "keystone-sign-result");
108
+ const compressed = require_shared.asBytes(require_shared.mapGet(map, 1));
109
+ if (!compressed) throw new require_shared.EraSdkError("malformed-reply", "keystone-sign-result is missing the payload (key 1)");
110
+ 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`);
111
+ const result = require_messages.decodeSignResultProto(require_gzip.gunzipCapped(compressed, MAX_INFLATED_BYTES));
112
+ if (expectedRequestId !== void 0) {
113
+ const expected = require_shared.uuidStringify(expectedRequestId).toLowerCase();
114
+ 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");
115
+ }
116
+ if (result.rawTx === "") throw new require_shared.EraSdkError("malformed-reply", "keystone-sign-result has no signed transaction");
117
+ if (!/^[0-9a-fA-F]+$/.test(result.rawTx) || result.rawTx.length % 2 !== 0) throw new require_shared.EraSdkError("malformed-reply", "keystone-sign-result rawTx is not hex");
118
+ return {
119
+ requestId: expectedRequestId ?? signIdToBytes(result.signId),
120
+ txId: result.txId,
121
+ rawTx: result.rawTx
122
+ };
123
+ }
124
+ function signIdToBytes(signId) {
125
+ try {
126
+ return require_shared.normalizeRequestId(signId);
127
+ } catch {
128
+ return /* @__PURE__ */ new Uint8Array(16);
129
+ }
130
+ }
131
+ //#endregion
132
+ Object.defineProperty(exports, "BchChain", {
133
+ enumerable: true,
134
+ get: function() {
135
+ return BchChain;
136
+ }
137
+ });
138
+
139
+ //# sourceMappingURL=bch-CpLACx0j.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bch-CpLACx0j.cjs","names":["EraSdkError","bytesToHex","resolveContext","resolveRequestId","normalizeXfp","decodeCashAddr","encodeCashAddr","encodeBchSignRequestProto","xfpToHex","uuidStringify","UrValue","cborEncode","cbMap","cbBytes","gzipCompress","cbText","makeSignRequest","toUr","normalizeRequestId","requireReplyMap","asBytes","mapGet","decodeSignResultProto","gunzipCapped"],"sources":["../src/chains/bch.ts"],"sourcesContent":["import { cborEncode } from '../cbor/encode';\nimport { asBytes, cbBytes, cbMap, cbText, mapGet } from '../cbor/model';\nimport { bytesToHex } from '../core/bytes';\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 { BchProtoInput, BchProtoOutput } from '../tron-proto/messages';\nimport { decodeSignResultProto, encodeBchSignRequestProto } from '../tron-proto/messages';\nimport type { Ur } from '../ur/ur';\nimport { Ur as UrValue } from '../ur/ur';\nimport { decodeCashAddr, encodeCashAddr } from './cashaddr';\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 { CashAddrPayload, CashAddrType } from './cashaddr';\nexport { CASHADDR_PREFIX, decodeCashAddr, encodeCashAddr } from './cashaddr';\n\n/** One UTXO the transaction spends. P2PKH only — that is what the device signs. */\nexport interface BchTxInput {\n /** Display-order (big-endian) txid of the UTXO, 64 hex chars. */\n readonly txid: string;\n /** Output index of the UTXO. */\n readonly index: number;\n /** UTXO value in satoshis — part of the BIP-143 sighash, so it MUST be exact. */\n readonly value: number | bigint;\n /** The compressed (33-byte) public key that owns the UTXO. */\n readonly publicKey: Uint8Array | string;\n /** Full derivation path of that key, e.g. `m/44'/145'/0'/0/0`. */\n readonly path: string;\n}\n\nexport interface BchTxOutput {\n /** CashAddr (P2PKH or P2SH), with or without the `bitcoincash:` prefix. */\n readonly address: string;\n /** Output value in satoshis. */\n readonly value: number | bigint;\n /** Marks the output as change on the device screen. Display only. */\n readonly isChange?: boolean;\n /** Shown with the change output; the address above is still what is paid. */\n readonly changeAddressPath?: string;\n}\n\nexport interface BchSignRequestProps {\n readonly requestId?: Uint8Array | string;\n readonly inputs: readonly BchTxInput[];\n /** Every output — change included — carries a real CashAddr. */\n readonly outputs: readonly BchTxOutput[];\n /** Fee in satoshis. Must equal `sum(inputs) - sum(outputs)` exactly. */\n readonly fee: number | bigint;\n /** Dust threshold shown on the device; defaults to 546. */\n readonly dustThreshold?: number;\n readonly memo?: string;\n readonly xfp: string | number;\n /** Milliseconds timestamp shown in the device log; 0 omits it. */\n readonly timestamp?: number;\n readonly origin?: string;\n}\n\nexport interface BchSignatureResult {\n readonly requestId: Uint8Array;\n /** Display-order txid of the signed transaction, as computed by the device. */\n readonly txId: string;\n /** Hex of the fully signed transaction — broadcast as-is. */\n readonly rawTx: string;\n}\n\nconst MAX_COMPRESSED_BYTES = 8 * 1024;\nconst MAX_INFLATED_BYTES = 64 * 1024;\n\nconst REPLY_TYPES = ['keystone-sign-result'] as const;\n\n/** Satoshi amounts must stay exact in a double; anything above is refused. */\nconst MAX_SATOSHI = 2_100_000_000_000_000n; // 21M coins\n\nfunction toSatoshi(value: number | bigint, label: string): bigint {\n // Refuse non-integers BEFORE BigInt() — BigInt(NaN) throws a raw RangeError.\n if (typeof value === 'number' && !Number.isSafeInteger(value)) {\n throw new EraSdkError('invalid-props', `${label} must be an integer satoshi amount`);\n }\n const v = typeof value === 'number' ? BigInt(value) : value;\n if (v <= 0n || v > MAX_SATOSHI) {\n throw new EraSdkError('invalid-props', `${label} must be a positive satoshi amount`);\n }\n return v;\n}\n\nfunction toPublicKeyHex(publicKey: Uint8Array | string, label: string): string {\n const hex = typeof publicKey === 'string' ? publicKey.toLowerCase() : bytesToHex(publicKey);\n if (!/^0[23][0-9a-f]{64}$/.test(hex)) {\n throw new EraSdkError('invalid-props', `${label} must be a 33-byte compressed public key`);\n }\n return hex;\n}\n\n/**\n * Bitcoin Cash signing rides the structured `keystone-sign-request` (6101)\n * envelope, NOT the PSBT path: the device's PSBT signer cannot apply the\n * `SIGHASH_FORKID` (0x41) sighash BCH consensus requires, so a dedicated\n * FORKID signer sits behind this envelope instead. The SDK therefore builds\n * the transaction container from structured inputs/outputs here — the one\n * chain where it is more than a transport.\n *\n * The device derives each input's signing key from its `path`, computes the\n * BIP-143 sighash with FORKID over version-1/locktime-0/sequence-0xfffffffd\n * legacy serialization, and returns the COMPLETE signed transaction.\n */\nexport class BchChain {\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: BchSignRequestProps): SignRequest<BchSignatureResult> {\n const requestId = resolveRequestId(this.context, props.requestId);\n const xfp = normalizeXfp(props.xfp);\n if (props.inputs.length === 0) {\n throw new EraSdkError('invalid-props', 'at least one input is required');\n }\n if (props.outputs.length === 0) {\n throw new EraSdkError('invalid-props', 'at least one output is required');\n }\n\n let inputSum = 0n;\n const inputs: BchProtoInput[] = props.inputs.map((input, i) => {\n if (!/^[0-9a-fA-F]{64}$/.test(input.txid)) {\n throw new EraSdkError('invalid-props', `input ${i}: txid must be 64 hex chars`);\n }\n if (!Number.isInteger(input.index) || input.index < 0) {\n throw new EraSdkError('invalid-props', `input ${i}: index must be a non-negative integer`);\n }\n parsePath(input.path); // validate shape; the wire carries the string form\n const value = toSatoshi(input.value, `input ${i} value`);\n inputSum += value;\n return {\n txidHex: input.txid.toLowerCase(),\n index: input.index,\n value,\n publicKeyHex: toPublicKeyHex(input.publicKey, `input ${i} publicKey`),\n ownerKeyPath: input.path,\n };\n });\n\n let outputSum = 0n;\n const outputs: BchProtoOutput[] = props.outputs.map((output, i) => {\n // Decode AND re-encode: the wire must carry the canonical lowercase\n // form. The device's own parser prepends a lowercase prefix before\n // decoding, so the spec's all-uppercase (QR alphanumeric) spelling\n // turns mixed-case there, is rejected — and the rejection FAILS OPEN\n // into a zero pubkey hash, i.e. a signed burn output. Never forward\n // the caller's spelling.\n const decoded = decodeCashAddr(output.address);\n const value = toSatoshi(output.value, `output ${i} value`);\n outputSum += value;\n if (output.changeAddressPath !== undefined) parsePath(output.changeAddressPath);\n return {\n address: encodeCashAddr(decoded.type, decoded.hash, {\n withPrefix: output.address.includes(':'),\n }),\n value,\n isChange: output.isChange ?? false,\n changeAddressPath: output.changeAddressPath,\n };\n });\n\n // The fee field is what the device SHOWS the user, but the fee the network\n // takes is inputs minus outputs — an inconsistent pair would put a lie on\n // the confirmation screen, so it is refused here.\n const fee = toSatoshi(props.fee, 'fee');\n if (inputSum !== outputSum + fee) {\n throw new EraSdkError(\n 'invalid-props',\n `fee mismatch: inputs (${inputSum}) minus outputs (${outputSum}) is ${\n inputSum - outputSum\n }, but fee says ${fee}`,\n );\n }\n\n const dustThreshold = props.dustThreshold ?? 546;\n if (!Number.isInteger(dustThreshold) || dustThreshold < 0 || dustThreshold > 0x7fffffff) {\n throw new EraSdkError('invalid-props', 'dustThreshold must fit a non-negative int32');\n }\n\n const proto = encodeBchSignRequestProto({\n // Zero-padded to eight characters — same firmware hex reader as Tron.\n xfpHex: xfpToHex(xfp),\n signId: uuidStringify(requestId),\n timestamp: props.timestamp ?? 0,\n fee,\n dustThreshold,\n memo: props.memo,\n inputs,\n outputs,\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) => parseBchSignature(reply, requestId),\n });\n }\n\n /**\n * Parse a `keystone-sign-result` standalone. The request id lives INSIDE\n * the protobuf (`signId`); pass `expect.requestId` to enable the echo\n * check — prefer `SignRequest.scanner().parse()`.\n */\n parseSignature(input: Ur | string, expect?: ExpectedReply): BchSignatureResult {\n return parseBchSignature(\n toUr(input),\n expect?.requestId === undefined ? undefined : normalizeRequestId(expect.requestId),\n );\n }\n}\n\nfunction parseBchSignature(ur: Ur, expectedRequestId: Uint8Array | undefined): BchSignatureResult {\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 envelope. After\n // it, ALWAYS run `verifyBchSignedTx` from `@hwlt/era-connect/verify` — the\n // reply is a complete broadcastable transaction, and the echo alone does\n // not prove its inputs and outputs are the ones that were requested.\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 if (!/^[0-9a-fA-F]+$/.test(result.rawTx) || result.rawTx.length % 2 !== 0) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result rawTx is not hex');\n }\n const requestId = expectedRequestId ?? signIdToBytes(result.signId);\n return { requestId, txId: result.txId, rawTx: result.rawTx };\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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"}
@@ -0,0 +1,74 @@
1
+ import { _ as Ur, a as SignRequest, i as ExpectedReply, r as EraConnectConfig } from "./shared-CI8zhQiE.cjs";
2
+ //#region src/chains/bch.d.ts
3
+ /** One UTXO the transaction spends. P2PKH only — that is what the device signs. */
4
+ interface BchTxInput {
5
+ /** Display-order (big-endian) txid of the UTXO, 64 hex chars. */
6
+ readonly txid: string;
7
+ /** Output index of the UTXO. */
8
+ readonly index: number;
9
+ /** UTXO value in satoshis — part of the BIP-143 sighash, so it MUST be exact. */
10
+ readonly value: number | bigint;
11
+ /** The compressed (33-byte) public key that owns the UTXO. */
12
+ readonly publicKey: Uint8Array | string;
13
+ /** Full derivation path of that key, e.g. `m/44'/145'/0'/0/0`. */
14
+ readonly path: string;
15
+ }
16
+ interface BchTxOutput {
17
+ /** CashAddr (P2PKH or P2SH), with or without the `bitcoincash:` prefix. */
18
+ readonly address: string;
19
+ /** Output value in satoshis. */
20
+ readonly value: number | bigint;
21
+ /** Marks the output as change on the device screen. Display only. */
22
+ readonly isChange?: boolean;
23
+ /** Shown with the change output; the address above is still what is paid. */
24
+ readonly changeAddressPath?: string;
25
+ }
26
+ interface BchSignRequestProps {
27
+ readonly requestId?: Uint8Array | string;
28
+ readonly inputs: readonly BchTxInput[];
29
+ /** Every output — change included — carries a real CashAddr. */
30
+ readonly outputs: readonly BchTxOutput[];
31
+ /** Fee in satoshis. Must equal `sum(inputs) - sum(outputs)` exactly. */
32
+ readonly fee: number | bigint;
33
+ /** Dust threshold shown on the device; defaults to 546. */
34
+ readonly dustThreshold?: number;
35
+ readonly memo?: string;
36
+ readonly xfp: string | number;
37
+ /** Milliseconds timestamp shown in the device log; 0 omits it. */
38
+ readonly timestamp?: number;
39
+ readonly origin?: string;
40
+ }
41
+ interface BchSignatureResult {
42
+ readonly requestId: Uint8Array;
43
+ /** Display-order txid of the signed transaction, as computed by the device. */
44
+ readonly txId: string;
45
+ /** Hex of the fully signed transaction — broadcast as-is. */
46
+ readonly rawTx: string;
47
+ }
48
+ /**
49
+ * Bitcoin Cash signing rides the structured `keystone-sign-request` (6101)
50
+ * envelope, NOT the PSBT path: the device's PSBT signer cannot apply the
51
+ * `SIGHASH_FORKID` (0x41) sighash BCH consensus requires, so a dedicated
52
+ * FORKID signer sits behind this envelope instead. The SDK therefore builds
53
+ * the transaction container from structured inputs/outputs here — the one
54
+ * chain where it is more than a transport.
55
+ *
56
+ * The device derives each input's signing key from its `path`, computes the
57
+ * BIP-143 sighash with FORKID over version-1/locktime-0/sequence-0xfffffffd
58
+ * legacy serialization, and returns the COMPLETE signed transaction.
59
+ */
60
+ declare class BchChain {
61
+ private readonly context;
62
+ constructor(config?: EraConnectConfig);
63
+ /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */
64
+ generateSignRequest(props: BchSignRequestProps): SignRequest<BchSignatureResult>;
65
+ /**
66
+ * Parse a `keystone-sign-result` standalone. The request id lives INSIDE
67
+ * the protobuf (`signId`); pass `expect.requestId` to enable the echo
68
+ * check — prefer `SignRequest.scanner().parse()`.
69
+ */
70
+ parseSignature(input: Ur | string, expect?: ExpectedReply): BchSignatureResult;
71
+ }
72
+ //#endregion
73
+ export { BchTxOutput as a, BchTxInput as i, BchSignRequestProps as n, BchSignatureResult as r, BchChain as t };
74
+ //# sourceMappingURL=bch-DHsIVJqK.d.cts.map
@@ -0,0 +1,134 @@
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, w as bytesToHex, x as cborEncode, z as cbBytes } from "./shared-Bbgejrus.js";
2
+ import { i as parsePath, r as normalizeXfp, s as xfpToHex } from "./keypath-Bvv_cfHt.js";
3
+ import { n as decodeCashAddr, r as encodeCashAddr } from "./cashaddr-BIzawizX.js";
4
+ import { n as gzipCompress, t as gunzipCapped } from "./gzip-DyZLtgJJ.js";
5
+ import { n as encodeBchSignRequestProto, t as decodeSignResultProto } from "./messages--XbVef_k.js";
6
+ //#region src/chains/bch.ts
7
+ const MAX_COMPRESSED_BYTES = 8192;
8
+ const MAX_INFLATED_BYTES = 65536;
9
+ const REPLY_TYPES = ["keystone-sign-result"];
10
+ /** Satoshi amounts must stay exact in a double; anything above is refused. */
11
+ const MAX_SATOSHI = 2100000000000000n;
12
+ function toSatoshi(value, label) {
13
+ if (typeof value === "number" && !Number.isSafeInteger(value)) throw new EraSdkError("invalid-props", `${label} must be an integer satoshi amount`);
14
+ const v = typeof value === "number" ? BigInt(value) : value;
15
+ if (v <= 0n || v > MAX_SATOSHI) throw new EraSdkError("invalid-props", `${label} must be a positive satoshi amount`);
16
+ return v;
17
+ }
18
+ function toPublicKeyHex(publicKey, label) {
19
+ const hex = typeof publicKey === "string" ? publicKey.toLowerCase() : bytesToHex(publicKey);
20
+ if (!/^0[23][0-9a-f]{64}$/.test(hex)) throw new EraSdkError("invalid-props", `${label} must be a 33-byte compressed public key`);
21
+ return hex;
22
+ }
23
+ /**
24
+ * Bitcoin Cash signing rides the structured `keystone-sign-request` (6101)
25
+ * envelope, NOT the PSBT path: the device's PSBT signer cannot apply the
26
+ * `SIGHASH_FORKID` (0x41) sighash BCH consensus requires, so a dedicated
27
+ * FORKID signer sits behind this envelope instead. The SDK therefore builds
28
+ * the transaction container from structured inputs/outputs here — the one
29
+ * chain where it is more than a transport.
30
+ *
31
+ * The device derives each input's signing key from its `path`, computes the
32
+ * BIP-143 sighash with FORKID over version-1/locktime-0/sequence-0xfffffffd
33
+ * legacy serialization, and returns the COMPLETE signed transaction.
34
+ */
35
+ var BchChain = class {
36
+ constructor(config) {
37
+ this.context = resolveContext(config);
38
+ }
39
+ /** Build a `keystone-sign-request` (6101). Reply: `keystone-sign-result` (6102). */
40
+ generateSignRequest(props) {
41
+ const requestId = resolveRequestId(this.context, props.requestId);
42
+ const xfp = normalizeXfp(props.xfp);
43
+ if (props.inputs.length === 0) throw new EraSdkError("invalid-props", "at least one input is required");
44
+ if (props.outputs.length === 0) throw new EraSdkError("invalid-props", "at least one output is required");
45
+ let inputSum = 0n;
46
+ const inputs = props.inputs.map((input, i) => {
47
+ if (!/^[0-9a-fA-F]{64}$/.test(input.txid)) throw new EraSdkError("invalid-props", `input ${i}: txid must be 64 hex chars`);
48
+ if (!Number.isInteger(input.index) || input.index < 0) throw new EraSdkError("invalid-props", `input ${i}: index must be a non-negative integer`);
49
+ parsePath(input.path);
50
+ const value = toSatoshi(input.value, `input ${i} value`);
51
+ inputSum += value;
52
+ return {
53
+ txidHex: input.txid.toLowerCase(),
54
+ index: input.index,
55
+ value,
56
+ publicKeyHex: toPublicKeyHex(input.publicKey, `input ${i} publicKey`),
57
+ ownerKeyPath: input.path
58
+ };
59
+ });
60
+ let outputSum = 0n;
61
+ const outputs = props.outputs.map((output, i) => {
62
+ const decoded = decodeCashAddr(output.address);
63
+ const value = toSatoshi(output.value, `output ${i} value`);
64
+ outputSum += value;
65
+ if (output.changeAddressPath !== void 0) parsePath(output.changeAddressPath);
66
+ return {
67
+ address: encodeCashAddr(decoded.type, decoded.hash, { withPrefix: output.address.includes(":") }),
68
+ value,
69
+ isChange: output.isChange ?? false,
70
+ changeAddressPath: output.changeAddressPath
71
+ };
72
+ });
73
+ const fee = toSatoshi(props.fee, "fee");
74
+ if (inputSum !== outputSum + fee) throw new EraSdkError("invalid-props", `fee mismatch: inputs (${inputSum}) minus outputs (${outputSum}) is ${inputSum - outputSum}, but fee says ${fee}`);
75
+ const dustThreshold = props.dustThreshold ?? 546;
76
+ if (!Number.isInteger(dustThreshold) || dustThreshold < 0 || dustThreshold > 2147483647) throw new EraSdkError("invalid-props", "dustThreshold must fit a non-negative int32");
77
+ const proto = encodeBchSignRequestProto({
78
+ xfpHex: xfpToHex(xfp),
79
+ signId: uuidStringify(requestId),
80
+ timestamp: props.timestamp ?? 0,
81
+ fee,
82
+ dustThreshold,
83
+ memo: props.memo,
84
+ inputs,
85
+ outputs
86
+ });
87
+ const ur = new Ur("keystone-sign-request", cborEncode(cbMap([[1, cbBytes(gzipCompress(proto))], [2, cbText(props.origin ?? this.context.origin)]])));
88
+ return makeSignRequest({
89
+ ur,
90
+ requestId,
91
+ replyTypes: REPLY_TYPES,
92
+ context: this.context,
93
+ parse: (reply) => parseBchSignature(reply, requestId)
94
+ });
95
+ }
96
+ /**
97
+ * Parse a `keystone-sign-result` standalone. The request id lives INSIDE
98
+ * the protobuf (`signId`); pass `expect.requestId` to enable the echo
99
+ * check — prefer `SignRequest.scanner().parse()`.
100
+ */
101
+ parseSignature(input, expect) {
102
+ return parseBchSignature(toUr(input), expect?.requestId === void 0 ? void 0 : normalizeRequestId(expect.requestId));
103
+ }
104
+ };
105
+ function parseBchSignature(ur, expectedRequestId) {
106
+ requireUrType(ur, [...REPLY_TYPES], "keystone-sign-result");
107
+ const map = requireReplyMap(ur, "keystone-sign-result");
108
+ const compressed = asBytes(mapGet(map, 1));
109
+ if (!compressed) throw new EraSdkError("malformed-reply", "keystone-sign-result is missing the payload (key 1)");
110
+ if (compressed.length > MAX_COMPRESSED_BYTES) throw new EraSdkError("limit-exceeded", `keystone-sign-result payload is ${compressed.length} bytes, over the ${MAX_COMPRESSED_BYTES} byte ceiling`);
111
+ const result = decodeSignResultProto(gunzipCapped(compressed, MAX_INFLATED_BYTES));
112
+ if (expectedRequestId !== void 0) {
113
+ const expected = uuidStringify(expectedRequestId).toLowerCase();
114
+ if (result.signId.toLowerCase() !== expected) throw new 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");
115
+ }
116
+ if (result.rawTx === "") throw new EraSdkError("malformed-reply", "keystone-sign-result has no signed transaction");
117
+ if (!/^[0-9a-fA-F]+$/.test(result.rawTx) || result.rawTx.length % 2 !== 0) throw new EraSdkError("malformed-reply", "keystone-sign-result rawTx is not hex");
118
+ return {
119
+ requestId: expectedRequestId ?? signIdToBytes(result.signId),
120
+ txId: result.txId,
121
+ rawTx: result.rawTx
122
+ };
123
+ }
124
+ function signIdToBytes(signId) {
125
+ try {
126
+ return normalizeRequestId(signId);
127
+ } catch {
128
+ return /* @__PURE__ */ new Uint8Array(16);
129
+ }
130
+ }
131
+ //#endregion
132
+ export { BchChain as t };
133
+
134
+ //# sourceMappingURL=bch-dMESlQfv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bch-dMESlQfv.js","names":["UrValue"],"sources":["../src/chains/bch.ts"],"sourcesContent":["import { cborEncode } from '../cbor/encode';\nimport { asBytes, cbBytes, cbMap, cbText, mapGet } from '../cbor/model';\nimport { bytesToHex } from '../core/bytes';\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 { BchProtoInput, BchProtoOutput } from '../tron-proto/messages';\nimport { decodeSignResultProto, encodeBchSignRequestProto } from '../tron-proto/messages';\nimport type { Ur } from '../ur/ur';\nimport { Ur as UrValue } from '../ur/ur';\nimport { decodeCashAddr, encodeCashAddr } from './cashaddr';\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 { CashAddrPayload, CashAddrType } from './cashaddr';\nexport { CASHADDR_PREFIX, decodeCashAddr, encodeCashAddr } from './cashaddr';\n\n/** One UTXO the transaction spends. P2PKH only — that is what the device signs. */\nexport interface BchTxInput {\n /** Display-order (big-endian) txid of the UTXO, 64 hex chars. */\n readonly txid: string;\n /** Output index of the UTXO. */\n readonly index: number;\n /** UTXO value in satoshis — part of the BIP-143 sighash, so it MUST be exact. */\n readonly value: number | bigint;\n /** The compressed (33-byte) public key that owns the UTXO. */\n readonly publicKey: Uint8Array | string;\n /** Full derivation path of that key, e.g. `m/44'/145'/0'/0/0`. */\n readonly path: string;\n}\n\nexport interface BchTxOutput {\n /** CashAddr (P2PKH or P2SH), with or without the `bitcoincash:` prefix. */\n readonly address: string;\n /** Output value in satoshis. */\n readonly value: number | bigint;\n /** Marks the output as change on the device screen. Display only. */\n readonly isChange?: boolean;\n /** Shown with the change output; the address above is still what is paid. */\n readonly changeAddressPath?: string;\n}\n\nexport interface BchSignRequestProps {\n readonly requestId?: Uint8Array | string;\n readonly inputs: readonly BchTxInput[];\n /** Every output — change included — carries a real CashAddr. */\n readonly outputs: readonly BchTxOutput[];\n /** Fee in satoshis. Must equal `sum(inputs) - sum(outputs)` exactly. */\n readonly fee: number | bigint;\n /** Dust threshold shown on the device; defaults to 546. */\n readonly dustThreshold?: number;\n readonly memo?: string;\n readonly xfp: string | number;\n /** Milliseconds timestamp shown in the device log; 0 omits it. */\n readonly timestamp?: number;\n readonly origin?: string;\n}\n\nexport interface BchSignatureResult {\n readonly requestId: Uint8Array;\n /** Display-order txid of the signed transaction, as computed by the device. */\n readonly txId: string;\n /** Hex of the fully signed transaction — broadcast as-is. */\n readonly rawTx: string;\n}\n\nconst MAX_COMPRESSED_BYTES = 8 * 1024;\nconst MAX_INFLATED_BYTES = 64 * 1024;\n\nconst REPLY_TYPES = ['keystone-sign-result'] as const;\n\n/** Satoshi amounts must stay exact in a double; anything above is refused. */\nconst MAX_SATOSHI = 2_100_000_000_000_000n; // 21M coins\n\nfunction toSatoshi(value: number | bigint, label: string): bigint {\n // Refuse non-integers BEFORE BigInt() — BigInt(NaN) throws a raw RangeError.\n if (typeof value === 'number' && !Number.isSafeInteger(value)) {\n throw new EraSdkError('invalid-props', `${label} must be an integer satoshi amount`);\n }\n const v = typeof value === 'number' ? BigInt(value) : value;\n if (v <= 0n || v > MAX_SATOSHI) {\n throw new EraSdkError('invalid-props', `${label} must be a positive satoshi amount`);\n }\n return v;\n}\n\nfunction toPublicKeyHex(publicKey: Uint8Array | string, label: string): string {\n const hex = typeof publicKey === 'string' ? publicKey.toLowerCase() : bytesToHex(publicKey);\n if (!/^0[23][0-9a-f]{64}$/.test(hex)) {\n throw new EraSdkError('invalid-props', `${label} must be a 33-byte compressed public key`);\n }\n return hex;\n}\n\n/**\n * Bitcoin Cash signing rides the structured `keystone-sign-request` (6101)\n * envelope, NOT the PSBT path: the device's PSBT signer cannot apply the\n * `SIGHASH_FORKID` (0x41) sighash BCH consensus requires, so a dedicated\n * FORKID signer sits behind this envelope instead. The SDK therefore builds\n * the transaction container from structured inputs/outputs here — the one\n * chain where it is more than a transport.\n *\n * The device derives each input's signing key from its `path`, computes the\n * BIP-143 sighash with FORKID over version-1/locktime-0/sequence-0xfffffffd\n * legacy serialization, and returns the COMPLETE signed transaction.\n */\nexport class BchChain {\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: BchSignRequestProps): SignRequest<BchSignatureResult> {\n const requestId = resolveRequestId(this.context, props.requestId);\n const xfp = normalizeXfp(props.xfp);\n if (props.inputs.length === 0) {\n throw new EraSdkError('invalid-props', 'at least one input is required');\n }\n if (props.outputs.length === 0) {\n throw new EraSdkError('invalid-props', 'at least one output is required');\n }\n\n let inputSum = 0n;\n const inputs: BchProtoInput[] = props.inputs.map((input, i) => {\n if (!/^[0-9a-fA-F]{64}$/.test(input.txid)) {\n throw new EraSdkError('invalid-props', `input ${i}: txid must be 64 hex chars`);\n }\n if (!Number.isInteger(input.index) || input.index < 0) {\n throw new EraSdkError('invalid-props', `input ${i}: index must be a non-negative integer`);\n }\n parsePath(input.path); // validate shape; the wire carries the string form\n const value = toSatoshi(input.value, `input ${i} value`);\n inputSum += value;\n return {\n txidHex: input.txid.toLowerCase(),\n index: input.index,\n value,\n publicKeyHex: toPublicKeyHex(input.publicKey, `input ${i} publicKey`),\n ownerKeyPath: input.path,\n };\n });\n\n let outputSum = 0n;\n const outputs: BchProtoOutput[] = props.outputs.map((output, i) => {\n // Decode AND re-encode: the wire must carry the canonical lowercase\n // form. The device's own parser prepends a lowercase prefix before\n // decoding, so the spec's all-uppercase (QR alphanumeric) spelling\n // turns mixed-case there, is rejected — and the rejection FAILS OPEN\n // into a zero pubkey hash, i.e. a signed burn output. Never forward\n // the caller's spelling.\n const decoded = decodeCashAddr(output.address);\n const value = toSatoshi(output.value, `output ${i} value`);\n outputSum += value;\n if (output.changeAddressPath !== undefined) parsePath(output.changeAddressPath);\n return {\n address: encodeCashAddr(decoded.type, decoded.hash, {\n withPrefix: output.address.includes(':'),\n }),\n value,\n isChange: output.isChange ?? false,\n changeAddressPath: output.changeAddressPath,\n };\n });\n\n // The fee field is what the device SHOWS the user, but the fee the network\n // takes is inputs minus outputs — an inconsistent pair would put a lie on\n // the confirmation screen, so it is refused here.\n const fee = toSatoshi(props.fee, 'fee');\n if (inputSum !== outputSum + fee) {\n throw new EraSdkError(\n 'invalid-props',\n `fee mismatch: inputs (${inputSum}) minus outputs (${outputSum}) is ${\n inputSum - outputSum\n }, but fee says ${fee}`,\n );\n }\n\n const dustThreshold = props.dustThreshold ?? 546;\n if (!Number.isInteger(dustThreshold) || dustThreshold < 0 || dustThreshold > 0x7fffffff) {\n throw new EraSdkError('invalid-props', 'dustThreshold must fit a non-negative int32');\n }\n\n const proto = encodeBchSignRequestProto({\n // Zero-padded to eight characters — same firmware hex reader as Tron.\n xfpHex: xfpToHex(xfp),\n signId: uuidStringify(requestId),\n timestamp: props.timestamp ?? 0,\n fee,\n dustThreshold,\n memo: props.memo,\n inputs,\n outputs,\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) => parseBchSignature(reply, requestId),\n });\n }\n\n /**\n * Parse a `keystone-sign-result` standalone. The request id lives INSIDE\n * the protobuf (`signId`); pass `expect.requestId` to enable the echo\n * check — prefer `SignRequest.scanner().parse()`.\n */\n parseSignature(input: Ur | string, expect?: ExpectedReply): BchSignatureResult {\n return parseBchSignature(\n toUr(input),\n expect?.requestId === undefined ? undefined : normalizeRequestId(expect.requestId),\n );\n }\n}\n\nfunction parseBchSignature(ur: Ur, expectedRequestId: Uint8Array | undefined): BchSignatureResult {\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 envelope. After\n // it, ALWAYS run `verifyBchSignedTx` from `@hwlt/era-connect/verify` — the\n // reply is a complete broadcastable transaction, and the echo alone does\n // not prove its inputs and outputs are the ones that were requested.\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 if (!/^[0-9a-fA-F]+$/.test(result.rawTx) || result.rawTx.length % 2 !== 0) {\n throw new EraSdkError('malformed-reply', 'keystone-sign-result rawTx is not hex');\n }\n const requestId = expectedRequestId ?? signIdToBytes(result.signId);\n return { requestId, txId: result.txId, rawTx: result.rawTx };\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/bch.cjs ADDED
@@ -0,0 +1,13 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_shared = require("./shared-Cu2yynP9.cjs");
3
+ const require_cashaddr = require("./cashaddr-Ci0_8ITN.cjs");
4
+ const require_bch = require("./bch-CpLACx0j.cjs");
5
+ exports.AnimatedUr = require_shared.AnimatedUr;
6
+ exports.BchChain = require_bch.BchChain;
7
+ exports.CASHADDR_PREFIX = require_cashaddr.CASHADDR_PREFIX;
8
+ exports.EraSdkError = require_shared.EraSdkError;
9
+ exports.TypedUrScanner = require_shared.TypedUrScanner;
10
+ exports.Ur = require_shared.Ur;
11
+ exports.UrScanner = require_shared.UrScanner;
12
+ exports.decodeCashAddr = require_cashaddr.decodeCashAddr;
13
+ exports.encodeCashAddr = require_cashaddr.encodeCashAddr;
package/dist/bch.d.cts ADDED
@@ -0,0 +1,26 @@
1
+ import { _ as Ur, a as SignRequest, c as TypedUrScanner, d as EraErrorCode, f as EraSdkError, i as ExpectedReply, l as UrScanner, p as AnimatedUr, r as EraConnectConfig } from "./shared-CI8zhQiE.cjs";
2
+ import { a as BchTxOutput, i as BchTxInput, n as BchSignRequestProps, r as BchSignatureResult, t as BchChain } from "./bch-DHsIVJqK.cjs";
3
+ //#region src/chains/cashaddr.d.ts
4
+ /** The default (mainnet) human-readable prefix. */
5
+ declare const CASHADDR_PREFIX = "bitcoincash";
6
+ type CashAddrType = 'p2pkh' | 'p2sh';
7
+ interface CashAddrPayload {
8
+ readonly type: CashAddrType;
9
+ /** The 20-byte hash160. */
10
+ readonly hash: Uint8Array;
11
+ readonly prefix: string;
12
+ }
13
+ /**
14
+ * Decode a CashAddr, with or without its `prefix:`. Only 20-byte P2PKH and
15
+ * P2SH payloads are accepted — those are the two script kinds the device
16
+ * builds outputs for.
17
+ */
18
+ declare function decodeCashAddr(address: string, expectedPrefix?: string): CashAddrPayload;
19
+ /** Encode a 20-byte hash160 as a CashAddr. Returns the bare form by default. */
20
+ declare function encodeCashAddr(type: CashAddrType, hash: Uint8Array, options?: {
21
+ prefix?: string | undefined;
22
+ withPrefix?: boolean | undefined;
23
+ }): string;
24
+ //#endregion
25
+ export { AnimatedUr, BchChain, type BchSignRequestProps, type BchSignatureResult, type BchTxInput, type BchTxOutput, CASHADDR_PREFIX, type CashAddrPayload, type CashAddrType, type EraConnectConfig, type EraErrorCode, EraSdkError, type ExpectedReply, type SignRequest, TypedUrScanner, Ur, UrScanner, decodeCashAddr, encodeCashAddr };
26
+ //# sourceMappingURL=bch.d.cts.map
package/dist/bch.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ import { _ as Ur, a as SignRequest, c as TypedUrScanner, d as EraErrorCode, f as EraSdkError, i as ExpectedReply, l as UrScanner, p as AnimatedUr, r as EraConnectConfig } from "./shared-CI8zhQiE.js";
2
+ import { a as BchTxOutput, i as BchTxInput, n as BchSignRequestProps, r as BchSignatureResult, t as BchChain } from "./bch-CK6dKGQA.js";
3
+ //#region src/chains/cashaddr.d.ts
4
+ /** The default (mainnet) human-readable prefix. */
5
+ declare const CASHADDR_PREFIX = "bitcoincash";
6
+ type CashAddrType = 'p2pkh' | 'p2sh';
7
+ interface CashAddrPayload {
8
+ readonly type: CashAddrType;
9
+ /** The 20-byte hash160. */
10
+ readonly hash: Uint8Array;
11
+ readonly prefix: string;
12
+ }
13
+ /**
14
+ * Decode a CashAddr, with or without its `prefix:`. Only 20-byte P2PKH and
15
+ * P2SH payloads are accepted — those are the two script kinds the device
16
+ * builds outputs for.
17
+ */
18
+ declare function decodeCashAddr(address: string, expectedPrefix?: string): CashAddrPayload;
19
+ /** Encode a 20-byte hash160 as a CashAddr. Returns the bare form by default. */
20
+ declare function encodeCashAddr(type: CashAddrType, hash: Uint8Array, options?: {
21
+ prefix?: string | undefined;
22
+ withPrefix?: boolean | undefined;
23
+ }): string;
24
+ //#endregion
25
+ export { AnimatedUr, BchChain, type BchSignRequestProps, type BchSignatureResult, type BchTxInput, type BchTxOutput, CASHADDR_PREFIX, type CashAddrPayload, type CashAddrType, type EraConnectConfig, type EraErrorCode, EraSdkError, type ExpectedReply, type SignRequest, TypedUrScanner, Ur, UrScanner, decodeCashAddr, encodeCashAddr };
26
+ //# sourceMappingURL=bch.d.ts.map
package/dist/bch.js ADDED
@@ -0,0 +1,4 @@
1
+ import { K as EraSdkError, _ as Ur, d as UrScanner, f as AnimatedUr, u as TypedUrScanner } from "./shared-Bbgejrus.js";
2
+ import { n as decodeCashAddr, r as encodeCashAddr, t as CASHADDR_PREFIX } from "./cashaddr-BIzawizX.js";
3
+ import { t as BchChain } from "./bch-dMESlQfv.js";
4
+ export { AnimatedUr, BchChain, CASHADDR_PREFIX, EraSdkError, TypedUrScanner, Ur, UrScanner, decodeCashAddr, encodeCashAddr };
@@ -84,7 +84,7 @@ function parseMessageSignature(ur, expectedRequestId) {
84
84
  const requestId = requireRequestIdEcho(map, 1, expectedRequestId, "btc-signature");
85
85
  const sigValue = asBytes(mapGet(map, 2));
86
86
  if (sigValue === void 0) throw new EraSdkError("malformed-reply", "btc-signature is missing the signature (key 2)");
87
- if (sigValue.length === 0) throw new EraSdkError("empty-signature", "the device returned an empty signature — Bitcoin message signing supports legacy P2PKH (1...) addresses only");
87
+ if (sigValue.length === 0) throw new EraSdkError("empty-signature", "the device returned an empty signature — the address kind is not message-signable on this firmware (older firmware: legacy P2PKH only; 2.1.0+: everything but Taproot)");
88
88
  let signature;
89
89
  try {
90
90
  signature = base64.decode(asciiDecode(sigValue));
@@ -105,4 +105,4 @@ function parseMessageSignature(ur, expectedRequestId) {
105
105
  //#endregion
106
106
  export { BtcChain as t };
107
107
 
108
- //# sourceMappingURL=btc-B_Nq2H4T.js.map
108
+ //# sourceMappingURL=btc-BmVgvvBd.js.map