@mysten-incubation/hashi 0.0.1

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 (37) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/dist/bitcoin.d.mts +140 -0
  3. package/dist/bitcoin.mjs +272 -0
  4. package/dist/client.d.mts +318 -0
  5. package/dist/client.mjs +522 -0
  6. package/dist/constants.mjs +60 -0
  7. package/dist/contracts/hashi/bitcoin_state.mjs +28 -0
  8. package/dist/contracts/hashi/committee.mjs +40 -0
  9. package/dist/contracts/hashi/committee_set.mjs +34 -0
  10. package/dist/contracts/hashi/config.mjs +28 -0
  11. package/dist/contracts/hashi/config_value.mjs +21 -0
  12. package/dist/contracts/hashi/deposit.mjs +67 -0
  13. package/dist/contracts/hashi/deposit_queue.mjs +33 -0
  14. package/dist/contracts/hashi/deps/sui/bag.mjs +42 -0
  15. package/dist/contracts/hashi/deps/sui/balance.mjs +20 -0
  16. package/dist/contracts/hashi/deps/sui/group_ops.mjs +16 -0
  17. package/dist/contracts/hashi/deps/sui/object_bag.mjs +25 -0
  18. package/dist/contracts/hashi/deps/sui/package.mjs +26 -0
  19. package/dist/contracts/hashi/deps/sui/table.mjs +37 -0
  20. package/dist/contracts/hashi/deps/sui/vec_map.mjs +36 -0
  21. package/dist/contracts/hashi/deps/sui/vec_set.mjs +25 -0
  22. package/dist/contracts/hashi/hashi.mjs +29 -0
  23. package/dist/contracts/hashi/proposals.mjs +18 -0
  24. package/dist/contracts/hashi/treasury.mjs +28 -0
  25. package/dist/contracts/hashi/utxo.mjs +56 -0
  26. package/dist/contracts/hashi/utxo_pool.mjs +35 -0
  27. package/dist/contracts/hashi/withdraw.mjs +91 -0
  28. package/dist/contracts/hashi/withdrawal_queue.mjs +131 -0
  29. package/dist/contracts/utils/index.d.mts +8 -0
  30. package/dist/contracts/utils/index.mjs +118 -0
  31. package/dist/errors.d.mts +118 -0
  32. package/dist/errors.mjs +118 -0
  33. package/dist/index.d.mts +5 -0
  34. package/dist/index.mjs +5 -0
  35. package/dist/types.d.mts +137 -0
  36. package/dist/util.mjs +50 -0
  37. package/package.json +64 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ # @mysten-incubation/hashi
@@ -0,0 +1,140 @@
1
+ import { BitcoinNetwork } from "./types.mjs";
2
+
3
+ //#region src/bitcoin.d.ts
4
+ /**
5
+ * Converts a 33-byte arkworks-compressed secp256k1 point to 33-byte SEC1 compressed format.
6
+ *
7
+ * The on-chain `CommitteeSet.mpc_public_key` is serialised with `ark-serialize`
8
+ * (via `bcs::to_bytes` in the Rust node), which uses a different compressed
9
+ * layout than the SEC1/X9.62 standard that `@noble/curves` expects:
10
+ *
11
+ * | Property | ark-serialize | SEC1 (noble) |
12
+ * |----------------|----------------------------|--------------------------|
13
+ * | Byte order | **little-endian** x | **big-endian** x |
14
+ * | Y-parity | flag in **last** byte | prefix **first** byte |
15
+ * | Parity meaning | "negative" (y > (p-1)/2) | even / odd (y mod 2) |
16
+ *
17
+ * Because the parity conventions differ, we cannot simply remap the flag bit —
18
+ * we must lift the x-coordinate onto the curve to recover y, then check its
19
+ * parity in both systems.
20
+ *
21
+ * @param ark - 33-byte arkworks-compressed point
22
+ * (bytes [0..32] = x in little-endian, byte [32] = flags with bit 7 = y_is_negative)
23
+ * @returns 33-byte SEC1 compressed point (prefix 0x02 | 0x03, then x in big-endian)
24
+ */
25
+ declare function arkworksToSec1Compressed(ark: Uint8Array): Uint8Array;
26
+ /**
27
+ * Derives a child x-only public key from the MPC master key and a Sui address.
28
+ *
29
+ * This is the core key-derivation step that gives each Sui address its own
30
+ * unique Bitcoin public key. The MPC committee can sign for this child key
31
+ * (using threshold Schnorr with additive tweaking), which is what authorises
32
+ * a withdrawal transaction on the Bitcoin side.
33
+ *
34
+ * Replicates the Rust function
35
+ * `fastcrypto_tbls::threshold_schnorr::key_derivation::derive_verifying_key`:
36
+ *
37
+ * ```text
38
+ * tweak = HKDF-SHA3-256(ikm = parent_x ‖ sui_address, len = 64) mod n
39
+ * child = parent + tweak × G
40
+ * ```
41
+ *
42
+ * The tweak is derived deterministically from the master key's x-coordinate
43
+ * concatenated with the depositor's Sui address, using HKDF with SHA3-256 as
44
+ * the underlying hash. The 64-byte output is reduced mod n (the secp256k1
45
+ * group order) to produce a scalar, which is then used as an additive tweak
46
+ * on the master public key.
47
+ *
48
+ * The returned value is the 32-byte x-only form of the child key (the
49
+ * x-coordinate only, without a parity prefix). This is the format expected
50
+ * by BIP-340 Schnorr signatures and BIP-341 taproot constructions.
51
+ *
52
+ * @param mpcKeyCompressed - 33-byte SEC1 compressed secp256k1 public key
53
+ * (the MPC master key, after arkworks-to-SEC1 conversion)
54
+ * @param suiAddress - 32-byte Sui address used as the derivation path
55
+ * @returns 32-byte x-only public key of the derived child
56
+ */
57
+ declare function deriveChildPubkey(mpcKeyCompressed: Uint8Array, suiAddress: Uint8Array): Uint8Array;
58
+ /**
59
+ * Builds a P2TR script-path-only address: `tr(NUMS, pk(pubkey))`.
60
+ *
61
+ * A BIP-341 taproot output has two spending paths: the *key path* (spend with
62
+ * the internal key) and the *script path* (reveal and satisfy a script in the
63
+ * Merkle tree). By using the NUMS (Nothing-Up-My-Sleeve) point as the internal
64
+ * key — a point with no known discrete logarithm — the key path is provably
65
+ * unspendable, forcing every spend through the script path.
66
+ *
67
+ * The script tree contains a single leaf: `<pubkey> OP_CHECKSIG`, meaning the
68
+ * MPC committee must produce a valid Schnorr signature for `pubkey` to spend
69
+ * the output. This is how the committee authorises withdrawals.
70
+ *
71
+ * The taproot output key is computed per BIP-341:
72
+ * ```text
73
+ * leafHash = tagged_hash("TapLeaf", 0xC0 ‖ compact_size(script) ‖ script)
74
+ * tweak = tagged_hash("TapTweak", NUMS ‖ leafHash)
75
+ * outputKey = NUMS + tweak × G
76
+ * ```
77
+ *
78
+ * The resulting output key is encoded as a SegWit v1 witness program in a
79
+ * bech32m address.
80
+ *
81
+ * @param pubkey - 32-byte x-only public key for the script leaf
82
+ * @param network - Bitcoin network for the bech32m human-readable prefix
83
+ * @returns bech32m-encoded P2TR address (e.g. `bc1p…`, `tb1p…`, `bcrt1p…`)
84
+ */
85
+ declare function taprootScriptPathAddress(pubkey: Uint8Array, network: BitcoinNetwork): string;
86
+ /**
87
+ * Generates a Bitcoin P2TR deposit address for a Sui address.
88
+ *
89
+ * This is the main entry point for the address derivation pipeline. Given the
90
+ * MPC master key and a Sui address, it produces the unique Bitcoin deposit
91
+ * address where a user should send BTC in order to receive bridged tokens on
92
+ * Sui.
93
+ *
94
+ * Combines {@link deriveChildPubkey} (child key derivation) and
95
+ * {@link taprootScriptPathAddress} (taproot address construction) into a
96
+ * single call.
97
+ *
98
+ * The current devnet address scheme uses a single-key script path:
99
+ * ```text
100
+ * tr(NUMS, pk(derive(H, d)))
101
+ * ```
102
+ * where `H` is the MPC master key and `d` is the depositor's Sui address.
103
+ *
104
+ * For mainnet the descriptor will include the guardian key:
105
+ * `tr(NUMS, multi_a(2, guardian, derive(H, d)))` — not yet implemented here.
106
+ *
107
+ * @param mpcKeyCompressed - 33-byte SEC1 compressed secp256k1 MPC public key
108
+ * (the MPC master key, after arkworks-to-SEC1 conversion)
109
+ * @param suiAddress - 32-byte Sui address
110
+ * @param network - Bitcoin network (determines the bech32m address prefix)
111
+ * @returns bech32m-encoded P2TR deposit address (e.g. `tb1p…` for signet)
112
+ */
113
+ declare function generateDepositAddress(mpcKeyCompressed: Uint8Array, suiAddress: Uint8Array, network: BitcoinNetwork): string;
114
+ /**
115
+ * Decodes a bech32/bech32m SegWit Bitcoin address into a witness program.
116
+ *
117
+ * Hashi withdrawals send BTC to a witness-program output, so the SDK only
118
+ * accepts the two address types the MPC committee currently supports:
119
+ *
120
+ * - **P2WPKH** — witness version 0, 20-byte program (`bc1q…`, `tb1q…`)
121
+ * - **P2TR** — witness version 1, 32-byte program (`bc1p…`, `tb1p…`)
122
+ *
123
+ * Legacy base58 addresses (`1…`, `3…`) aren't bech32 at all and surface as
124
+ * `"malformed"`. Version-0 32-byte P2WSH is rejected (no committee support).
125
+ *
126
+ * Per BIP-350, v0 must use a bech32 checksum and v1+ must use bech32m. This
127
+ * function enforces that rule strictly — a v0 address encoded as bech32m
128
+ * (or vice versa) fails with `"bad-checksum"`.
129
+ *
130
+ * @param address - User-supplied Bitcoin address string
131
+ * @param network - Expected Bitcoin network; the HRP must match
132
+ * @returns `{ version, program }` — witness version + raw program bytes
133
+ * @throws {@link InvalidBitcoinAddressError} with a structured `code` on any failure
134
+ */
135
+ declare function bitcoinAddressToWitnessProgram(address: string, network: BitcoinNetwork): {
136
+ version: number;
137
+ program: Uint8Array;
138
+ };
139
+ //#endregion
140
+ export { arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress, taprootScriptPathAddress };
@@ -0,0 +1,272 @@
1
+ import { NETWORK_HRP, NUMS_KEY } from "./constants.mjs";
2
+ import { InvalidBitcoinAddressError } from "./errors.mjs";
3
+ import { secp256k1 } from "@noble/curves/secp256k1.js";
4
+ import { sha3_256 } from "@noble/hashes/sha3.js";
5
+ import { hkdf } from "@noble/hashes/hkdf.js";
6
+ import { sha256 } from "@noble/hashes/sha2.js";
7
+ import { concatBytes } from "@noble/hashes/utils.js";
8
+ import { bech32, bech32m } from "@scure/base";
9
+
10
+ //#region src/bitcoin.ts
11
+ /**
12
+ * Bitcoin address derivation for Hashi deposit addresses.
13
+ *
14
+ * Hashi bridges Bitcoin and Sui by assigning each Sui address a unique Bitcoin
15
+ * deposit address. When BTC is sent to that address, the Hashi MPC committee
16
+ * detects the deposit and mints equivalent tokens on Sui.
17
+ *
18
+ * The deposit address is a Pay-to-Taproot (P2TR / BIP-341) script-path address
19
+ * whose spending condition is a single `OP_CHECKSIG` against a child public key
20
+ * derived from the MPC committee's master key and the depositor's Sui address.
21
+ *
22
+ * The full derivation pipeline is:
23
+ *
24
+ * 1. **Fetch** the MPC master key from on-chain (`CommitteeSet.mpc_public_key`).
25
+ * The on-chain bytes use the arkworks compressed format (little-endian x +
26
+ * flag byte), so they must first be converted to SEC1 via
27
+ * {@link arkworksToSec1Compressed} — this is done automatically by the
28
+ * client's `view.mpcPublicKey()` method.
29
+ *
30
+ * 2. **Derive** a child key: `child = masterKey + HKDF-SHA3-256(x ‖ suiAddr) × G`
31
+ * (see {@link deriveChildPubkey}). This replicates the Rust function
32
+ * `fastcrypto_tbls::threshold_schnorr::key_derivation::derive_verifying_key`.
33
+ *
34
+ * 3. **Build** the taproot address: `tr(NUMS, pk(child))` where NUMS is a
35
+ * Nothing-Up-My-Sleeve point with no known private key, forcing all spends
36
+ * through the script path (see {@link taprootScriptPathAddress}).
37
+ *
38
+ * The end-to-end helper {@link generateDepositAddress} combines steps 2–3.
39
+ *
40
+ * @see https://mystenlabs.github.io/hashi/design/address-scheme.html
41
+ */
42
+ const Point = secp256k1.Point;
43
+ const CURVE_ORDER = Point.CURVE().n;
44
+ const NUMS_POINT = Point.fromBytes(concatBytes(new Uint8Array([2]), NUMS_KEY));
45
+ /** BIP-340 tagged hash: SHA256(SHA256(tag) ‖ SHA256(tag) ‖ msg) */
46
+ function taggedHash(tag, ...msgs) {
47
+ const tagHash = sha256(new TextEncoder().encode(tag));
48
+ return sha256(concatBytes(tagHash, tagHash, ...msgs));
49
+ }
50
+ /** Interpret a byte array as a big-endian unsigned integer. */
51
+ function bytesToNumberBE(bytes) {
52
+ let n = 0n;
53
+ for (const byte of bytes) n = n << 8n | BigInt(byte);
54
+ return n;
55
+ }
56
+ /**
57
+ * Converts a 33-byte arkworks-compressed secp256k1 point to 33-byte SEC1 compressed format.
58
+ *
59
+ * The on-chain `CommitteeSet.mpc_public_key` is serialised with `ark-serialize`
60
+ * (via `bcs::to_bytes` in the Rust node), which uses a different compressed
61
+ * layout than the SEC1/X9.62 standard that `@noble/curves` expects:
62
+ *
63
+ * | Property | ark-serialize | SEC1 (noble) |
64
+ * |----------------|----------------------------|--------------------------|
65
+ * | Byte order | **little-endian** x | **big-endian** x |
66
+ * | Y-parity | flag in **last** byte | prefix **first** byte |
67
+ * | Parity meaning | "negative" (y > (p-1)/2) | even / odd (y mod 2) |
68
+ *
69
+ * Because the parity conventions differ, we cannot simply remap the flag bit —
70
+ * we must lift the x-coordinate onto the curve to recover y, then check its
71
+ * parity in both systems.
72
+ *
73
+ * @param ark - 33-byte arkworks-compressed point
74
+ * (bytes [0..32] = x in little-endian, byte [32] = flags with bit 7 = y_is_negative)
75
+ * @returns 33-byte SEC1 compressed point (prefix 0x02 | 0x03, then x in big-endian)
76
+ */
77
+ function arkworksToSec1Compressed(ark) {
78
+ if (ark.length !== 33) throw new Error(`Expected 33-byte arkworks-compressed key, got ${ark.length}`);
79
+ const yIsNegative = ark[32] >> 7 & 1;
80
+ const xBe = new Uint8Array(ark.slice(0, 32)).reverse();
81
+ const trial = new Uint8Array(33);
82
+ trial[0] = 2;
83
+ trial.set(xBe, 1);
84
+ const trialIsNeg = Point.fromBytes(trial).toAffine().y > (Point.CURVE().p - 1n) / 2n;
85
+ const prefix = yIsNegative === 1 !== trialIsNeg ? 3 : 2;
86
+ const sec1 = new Uint8Array(33);
87
+ sec1[0] = prefix;
88
+ sec1.set(xBe, 1);
89
+ return sec1;
90
+ }
91
+ /**
92
+ * Derives a child x-only public key from the MPC master key and a Sui address.
93
+ *
94
+ * This is the core key-derivation step that gives each Sui address its own
95
+ * unique Bitcoin public key. The MPC committee can sign for this child key
96
+ * (using threshold Schnorr with additive tweaking), which is what authorises
97
+ * a withdrawal transaction on the Bitcoin side.
98
+ *
99
+ * Replicates the Rust function
100
+ * `fastcrypto_tbls::threshold_schnorr::key_derivation::derive_verifying_key`:
101
+ *
102
+ * ```text
103
+ * tweak = HKDF-SHA3-256(ikm = parent_x ‖ sui_address, len = 64) mod n
104
+ * child = parent + tweak × G
105
+ * ```
106
+ *
107
+ * The tweak is derived deterministically from the master key's x-coordinate
108
+ * concatenated with the depositor's Sui address, using HKDF with SHA3-256 as
109
+ * the underlying hash. The 64-byte output is reduced mod n (the secp256k1
110
+ * group order) to produce a scalar, which is then used as an additive tweak
111
+ * on the master public key.
112
+ *
113
+ * The returned value is the 32-byte x-only form of the child key (the
114
+ * x-coordinate only, without a parity prefix). This is the format expected
115
+ * by BIP-340 Schnorr signatures and BIP-341 taproot constructions.
116
+ *
117
+ * @param mpcKeyCompressed - 33-byte SEC1 compressed secp256k1 public key
118
+ * (the MPC master key, after arkworks-to-SEC1 conversion)
119
+ * @param suiAddress - 32-byte Sui address used as the derivation path
120
+ * @returns 32-byte x-only public key of the derived child
121
+ */
122
+ function deriveChildPubkey(mpcKeyCompressed, suiAddress) {
123
+ if (mpcKeyCompressed.length !== 33) throw new Error(`Expected 33-byte compressed MPC key, got ${mpcKeyCompressed.length}`);
124
+ if (suiAddress.length !== 32) throw new Error(`Expected 32-byte Sui address, got ${suiAddress.length}`);
125
+ const parentPoint = Point.fromBytes(mpcKeyCompressed);
126
+ const tweakScalar = bytesToNumberBE(hkdf(sha3_256, concatBytes(mpcKeyCompressed.slice(1), suiAddress), void 0, void 0, 64)) % CURVE_ORDER;
127
+ return parentPoint.add(Point.BASE.multiply(tweakScalar)).toBytes(true).slice(1);
128
+ }
129
+ /**
130
+ * Builds a P2TR script-path-only address: `tr(NUMS, pk(pubkey))`.
131
+ *
132
+ * A BIP-341 taproot output has two spending paths: the *key path* (spend with
133
+ * the internal key) and the *script path* (reveal and satisfy a script in the
134
+ * Merkle tree). By using the NUMS (Nothing-Up-My-Sleeve) point as the internal
135
+ * key — a point with no known discrete logarithm — the key path is provably
136
+ * unspendable, forcing every spend through the script path.
137
+ *
138
+ * The script tree contains a single leaf: `<pubkey> OP_CHECKSIG`, meaning the
139
+ * MPC committee must produce a valid Schnorr signature for `pubkey` to spend
140
+ * the output. This is how the committee authorises withdrawals.
141
+ *
142
+ * The taproot output key is computed per BIP-341:
143
+ * ```text
144
+ * leafHash = tagged_hash("TapLeaf", 0xC0 ‖ compact_size(script) ‖ script)
145
+ * tweak = tagged_hash("TapTweak", NUMS ‖ leafHash)
146
+ * outputKey = NUMS + tweak × G
147
+ * ```
148
+ *
149
+ * The resulting output key is encoded as a SegWit v1 witness program in a
150
+ * bech32m address.
151
+ *
152
+ * @param pubkey - 32-byte x-only public key for the script leaf
153
+ * @param network - Bitcoin network for the bech32m human-readable prefix
154
+ * @returns bech32m-encoded P2TR address (e.g. `bc1p…`, `tb1p…`, `bcrt1p…`)
155
+ */
156
+ function taprootScriptPathAddress(pubkey, network) {
157
+ if (pubkey.length !== 32) throw new Error(`Expected 32-byte x-only pubkey, got ${pubkey.length}`);
158
+ const tapscript = new Uint8Array(34);
159
+ tapscript[0] = 32;
160
+ tapscript.set(pubkey, 1);
161
+ tapscript[33] = 172;
162
+ const tweakScalar = bytesToNumberBE(taggedHash("TapTweak", NUMS_KEY, taggedHash("TapLeaf", new Uint8Array([192, tapscript.length]), tapscript))) % CURVE_ORDER;
163
+ const outputKey = NUMS_POINT.add(Point.BASE.multiply(tweakScalar)).toBytes(true).slice(1);
164
+ const words = [1, ...bech32m.toWords(outputKey)];
165
+ return bech32m.encode(NETWORK_HRP[network], words);
166
+ }
167
+ /**
168
+ * Generates a Bitcoin P2TR deposit address for a Sui address.
169
+ *
170
+ * This is the main entry point for the address derivation pipeline. Given the
171
+ * MPC master key and a Sui address, it produces the unique Bitcoin deposit
172
+ * address where a user should send BTC in order to receive bridged tokens on
173
+ * Sui.
174
+ *
175
+ * Combines {@link deriveChildPubkey} (child key derivation) and
176
+ * {@link taprootScriptPathAddress} (taproot address construction) into a
177
+ * single call.
178
+ *
179
+ * The current devnet address scheme uses a single-key script path:
180
+ * ```text
181
+ * tr(NUMS, pk(derive(H, d)))
182
+ * ```
183
+ * where `H` is the MPC master key and `d` is the depositor's Sui address.
184
+ *
185
+ * For mainnet the descriptor will include the guardian key:
186
+ * `tr(NUMS, multi_a(2, guardian, derive(H, d)))` — not yet implemented here.
187
+ *
188
+ * @param mpcKeyCompressed - 33-byte SEC1 compressed secp256k1 MPC public key
189
+ * (the MPC master key, after arkworks-to-SEC1 conversion)
190
+ * @param suiAddress - 32-byte Sui address
191
+ * @param network - Bitcoin network (determines the bech32m address prefix)
192
+ * @returns bech32m-encoded P2TR deposit address (e.g. `tb1p…` for signet)
193
+ */
194
+ function generateDepositAddress(mpcKeyCompressed, suiAddress, network) {
195
+ return taprootScriptPathAddress(deriveChildPubkey(mpcKeyCompressed, suiAddress), network);
196
+ }
197
+ /**
198
+ * Decodes a bech32/bech32m SegWit Bitcoin address into a witness program.
199
+ *
200
+ * Hashi withdrawals send BTC to a witness-program output, so the SDK only
201
+ * accepts the two address types the MPC committee currently supports:
202
+ *
203
+ * - **P2WPKH** — witness version 0, 20-byte program (`bc1q…`, `tb1q…`)
204
+ * - **P2TR** — witness version 1, 32-byte program (`bc1p…`, `tb1p…`)
205
+ *
206
+ * Legacy base58 addresses (`1…`, `3…`) aren't bech32 at all and surface as
207
+ * `"malformed"`. Version-0 32-byte P2WSH is rejected (no committee support).
208
+ *
209
+ * Per BIP-350, v0 must use a bech32 checksum and v1+ must use bech32m. This
210
+ * function enforces that rule strictly — a v0 address encoded as bech32m
211
+ * (or vice versa) fails with `"bad-checksum"`.
212
+ *
213
+ * @param address - User-supplied Bitcoin address string
214
+ * @param network - Expected Bitcoin network; the HRP must match
215
+ * @returns `{ version, program }` — witness version + raw program bytes
216
+ * @throws {@link InvalidBitcoinAddressError} with a structured `code` on any failure
217
+ */
218
+ function bitcoinAddressToWitnessProgram(address, network) {
219
+ const expectedHrp = NETWORK_HRP[network];
220
+ let decoded;
221
+ let variant;
222
+ try {
223
+ decoded = bech32.decode(address);
224
+ variant = "bech32";
225
+ } catch {}
226
+ if (!decoded) try {
227
+ decoded = bech32m.decode(address);
228
+ variant = "bech32m";
229
+ } catch (cause) {
230
+ throw new InvalidBitcoinAddressError({
231
+ address,
232
+ code: "malformed",
233
+ message: `Bitcoin address "${address}" is not valid bech32 or bech32m.`
234
+ }, { cause });
235
+ }
236
+ if (decoded.words.length === 0) throw new InvalidBitcoinAddressError({
237
+ address,
238
+ code: "malformed",
239
+ message: `Bitcoin address "${address}" has no data payload.`
240
+ });
241
+ const version = decoded.words[0];
242
+ const expectedVariant = version === 0 ? "bech32" : "bech32m";
243
+ if (variant !== expectedVariant) throw new InvalidBitcoinAddressError({
244
+ address,
245
+ code: "bad-checksum",
246
+ message: `Bitcoin address "${address}" has witness version ${version} but a ${variant} checksum; BIP-350 requires ${expectedVariant} for this version.`
247
+ });
248
+ if (decoded.prefix !== expectedHrp) throw new InvalidBitcoinAddressError({
249
+ address,
250
+ code: "wrong-network",
251
+ message: `Bitcoin address "${address}" uses HRP "${decoded.prefix}" but the client is configured for ${network} (expected "${expectedHrp}").`
252
+ });
253
+ if (version !== 0 && version !== 1) throw new InvalidBitcoinAddressError({
254
+ address,
255
+ code: "unsupported-version",
256
+ message: `Bitcoin address "${address}" has witness version ${version}; Hashi supports only v0 (P2WPKH) and v1 (P2TR).`
257
+ });
258
+ const program = bech32.fromWords(decoded.words.slice(1));
259
+ const expectedLen = version === 0 ? 20 : 32;
260
+ if (program.length !== expectedLen) throw new InvalidBitcoinAddressError({
261
+ address,
262
+ code: "bad-program-length",
263
+ message: `Bitcoin address "${address}" has a ${program.length}-byte witness program; v${version} (${version === 0 ? "P2WPKH" : "P2TR"}) requires ${expectedLen} bytes.`
264
+ });
265
+ return {
266
+ version,
267
+ program
268
+ };
269
+ }
270
+
271
+ //#endregion
272
+ export { arkworksToSec1Compressed, bitcoinAddressToWitnessProgram, deriveChildPubkey, generateDepositAddress, taprootScriptPathAddress };