btcaaron 0.2.2__tar.gz → 0.2.3__tar.gz

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 (43) hide show
  1. {btcaaron-0.2.2 → btcaaron-0.2.3}/PKG-INFO +62 -29
  2. {btcaaron-0.2.2 → btcaaron-0.2.3}/README.md +54 -25
  3. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/__init__.py +39 -3
  4. btcaaron-0.2.3/btcaaron/key.py +296 -0
  5. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/legacy.py +59 -0
  6. btcaaron-0.2.3/btcaaron/node_rpc.py +110 -0
  7. btcaaron-0.2.3/btcaaron/script/__init__.py +26 -0
  8. btcaaron-0.2.3/btcaaron/script/templates.py +239 -0
  9. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/spend/builder.py +15 -0
  10. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/tree/builder.py +50 -0
  11. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/tree/leaf.py +1 -0
  12. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/tree/program.py +14 -0
  13. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron.egg-info/PKG-INFO +62 -29
  14. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron.egg-info/SOURCES.txt +4 -1
  15. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron.egg-info/requires.txt +2 -0
  16. {btcaaron-0.2.2 → btcaaron-0.2.3}/setup.py +17 -5
  17. {btcaaron-0.2.2 → btcaaron-0.2.3}/tests/test_btcaaron_v02.py +266 -2
  18. btcaaron-0.2.3/tests/test_version.py +18 -0
  19. btcaaron-0.2.2/btcaaron/key.py +0 -145
  20. btcaaron-0.2.2/btcaaron/script/__init__.py +0 -7
  21. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/btcaaron.py +0 -0
  22. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/errors.py +0 -0
  23. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/explain/__init__.py +0 -0
  24. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/explain/program.py +0 -0
  25. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/explain/transaction.py +0 -0
  26. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/network/__init__.py +0 -0
  27. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/network/blockstream.py +0 -0
  28. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/network/mempool.py +0 -0
  29. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/network/provider.py +0 -0
  30. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/network/utxo.py +0 -0
  31. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/psbt.py +0 -0
  32. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/script/script.py +0 -0
  33. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/spend/__init__.py +0 -0
  34. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/spend/transaction.py +0 -0
  35. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/tree/__init__.py +0 -0
  36. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron/tree/tapmath.py +0 -0
  37. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron.egg-info/dependency_links.txt +0 -0
  38. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron.egg-info/entry_points.txt +0 -0
  39. {btcaaron-0.2.2 → btcaaron-0.2.3}/btcaaron.egg-info/top_level.txt +0 -0
  40. {btcaaron-0.2.2 → btcaaron-0.2.3}/setup.cfg +0 -0
  41. {btcaaron-0.2.2 → btcaaron-0.2.3}/tests/test_btcaaron_v01.py +0 -0
  42. {btcaaron-0.2.2 → btcaaron-0.2.3}/tests/test_btcaaron_v01_cpfp.py +0 -0
  43. {btcaaron-0.2.2 → btcaaron-0.2.3}/tests/test_rawscript_tapmath.py +0 -0
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: btcaaron
3
- Version: 0.2.2
4
- Summary: A Bitcoin Testnet transaction toolkit supporting Legacy, SegWit, and Taproot
5
- Home-page: https://x.com/aaron_recompile
3
+ Version: 0.2.3
4
+ Summary: Pragmatic Bitcoin Taproot toolkit: Legacy/SegWit/Taproot, PSBT, optional Signet workflows, and Bitcoin Inquisition opcode templates (OP_CAT, CSFS, CTV).
5
+ Home-page: https://github.com/aaron-recompile/btcaaron
6
6
  Author: Aaron Zhang
7
7
  Author-email: aaron.recompile@gmail.com
8
- Keywords: bitcoin,taproot,tapscript,bip341,bip342,schnorr,p2tr,taptree,script-path,miniscript,psbt,regtest,testnet
8
+ Project-URL: X (author), https://x.com/aaron_recompile
9
+ Keywords: bitcoin,taproot,tapscript,bip341,bip342,schnorr,p2tr,taptree,script-path,miniscript,psbt,regtest,testnet,signet,bitcoin-inquisition,checksigfromstack,ctv
9
10
  Classifier: Programming Language :: Python :: 3
10
11
  Classifier: Programming Language :: Python :: 3.10
11
12
  Classifier: Programming Language :: Python :: 3.11
@@ -20,6 +21,8 @@ Requires-Python: >=3.10,<3.13
20
21
  Description-Content-Type: text/markdown
21
22
  Requires-Dist: requests<3.0.0,>=2.25.0
22
23
  Requires-Dist: bitcoin-utils<0.8.0,>=0.7.3
24
+ Requires-Dist: bip32>=4.0.0
25
+ Requires-Dist: base58>=2.1.1
23
26
  Dynamic: author
24
27
  Dynamic: author-email
25
28
  Dynamic: classifier
@@ -27,6 +30,7 @@ Dynamic: description
27
30
  Dynamic: description-content-type
28
31
  Dynamic: home-page
29
32
  Dynamic: keywords
33
+ Dynamic: project-url
30
34
  Dynamic: requires-dist
31
35
  Dynamic: requires-python
32
36
  Dynamic: summary
@@ -35,15 +39,17 @@ Dynamic: summary
35
39
 
36
40
  [![Supported by OpenSats](https://img.shields.io/badge/supported%20by-OpenSats-orange?style=flat-square&logo=bitcoin)](https://opensats.org)
37
41
 
38
- A pragmatic Bitcoin toolkit with a clear path toward full Taproot engineering.
42
+ A pragmatic Bitcoin toolkit for Taproot engineering Legacy, SegWit, and Taproot flows, PSBT, and optional **Signet** / **Bitcoin Inquisition** opcode templates (OP_CAT, CSFS, CTV).
39
43
 
40
- Designed for reproducible testnet experiments, educational workflows, and script-path development.
44
+ Designed for reproducible testnet and signet experiments, educational workflows, and script-path development.
41
45
 
42
46
  If you find btcaaron useful, a GitHub star is appreciated.
43
47
 
48
+ 👉 Looking for Bitcoin Inquisition experimental opcode templates (OP_CAT / CSFS / CTV)? See **[INQUISITION.md](INQUISITION.md)**.
49
+
44
50
  ## Current Status
45
51
 
46
- **v0.2.2 (alpha preview)** — Core Taproot spend-path workflows are implemented and testnet/regtest-verified.
52
+ **v0.2.3 (alpha preview)** — Core Taproot spend-path workflows are implemented and testnet/regtest-verified.
47
53
  Current focus is release hardening: broader validation coverage, documentation alignment, and contributor testing feedback.
48
54
 
49
55
  ## Features
@@ -58,7 +64,7 @@ Production-tested on testnet with real transactions:
58
64
  - Broadcast to Blockstream / Mempool endpoints
59
65
  - Developer helpers (`WIFKey`, `quick_transfer`)
60
66
 
61
- ### Available Now (v0.2.2 - Alpha Preview)
67
+ ### Available Now (v0.2.3 - Alpha Preview)
62
68
 
63
69
  Testnet-verified with real transactions (23 tests, all passing):
64
70
 
@@ -118,25 +124,7 @@ python -m pip install -e .
118
124
  btcaaron-doctor
119
125
  ```
120
126
 
121
- ## Quick Start
122
-
123
- ```python
124
- from btcaaron import WIFKey, quick_transfer
125
-
126
- wif = "your_testnet_wif"
127
-
128
- key = WIFKey(wif)
129
- print("Taproot:", key.get_taproot().address)
130
-
131
- balance = key.get_taproot().get_balance()
132
- print("Balance:", balance, "sats")
133
-
134
- if balance > 1000:
135
- txid = quick_transfer(wif, "taproot", "tb1q...", amount=500, fee=300)
136
- print("Broadcasted:", txid)
137
- ```
138
-
139
- ## v0.2.2 API Example
127
+ ## Quick Start (v0.2.3)
140
128
 
141
129
  *Taproot-native API — core features available now.*
142
130
 
@@ -168,6 +156,51 @@ tx = (program.spend("hash")
168
156
  tx.broadcast()
169
157
  ```
170
158
 
159
+ ## Legacy Quick Start (v0.1.x)
160
+
161
+ For backward compatibility with the v0.1.x API:
162
+
163
+ ```python
164
+ from btcaaron import WIFKey, quick_transfer
165
+
166
+ wif = "your_testnet_wif"
167
+
168
+ key = WIFKey(wif)
169
+ print("Taproot:", key.get_taproot().address)
170
+
171
+ balance = key.get_taproot().get_balance()
172
+ print("Balance:", balance, "sats")
173
+
174
+ if balance > 1000:
175
+ txid = quick_transfer(wif, "taproot", "tb1q...", amount=500, fee=300)
176
+ print("Broadcasted:", txid)
177
+ ```
178
+
179
+ ## Descriptor Wallet Quick Start (tprv/xpriv)
180
+
181
+ For descriptor-style HD wallet flows (RGB-friendly), derive directly from `tprv`:
182
+
183
+ ```python
184
+ from btcaaron import taproot_balance_from_tprv, quick_transfer_tprv, taproot_descriptor_from_tprv
185
+
186
+ tprv = "tprv8ZgxMBicQKs..."
187
+ print(taproot_descriptor_from_tprv(tprv, branch=0)) # tr(tprv.../86h/1h/0h/0/*)
188
+
189
+ balance = taproot_balance_from_tprv(tprv, branch=0, index=0)
190
+ print("Balance:", balance, "sats")
191
+
192
+ txid = quick_transfer_tprv(
193
+ tprv,
194
+ "tb1p...",
195
+ amount=10_000,
196
+ fee=800,
197
+ branch=0,
198
+ index=0,
199
+ debug=True,
200
+ )
201
+ print("Broadcasted:", txid)
202
+ ```
203
+
171
204
  Full specification in [DESIGN.md](./DESIGN.md).
172
205
 
173
206
  ## Testing
@@ -181,7 +214,7 @@ python -m pytest tests/
181
214
  Run specific test suites:
182
215
 
183
216
  ```bash
184
- # v0.2.2 comprehensive tests (pytest)
217
+ # v0.2.3 comprehensive tests (pytest)
185
218
  python -m pytest tests/test_btcaaron_v02.py -v
186
219
 
187
220
  # v0.1.x example-based tests
@@ -227,7 +260,7 @@ See [DESIGN.md](./DESIGN.md) for architecture details and development roadmap.
227
260
  - default call blocks mainnet broadcast unless `allow_mainnet=True`
228
261
  - `dry_run=True` is available for no-side-effect routing checks
229
262
  - recommended smoke script: `python3 examples/core_test/scenarios/mainnet_readiness_smoke.py`
230
- - **v0.2.2 Status**: Core Taproot spend-path flows are implemented and testnet-verified; ongoing work focuses on hardening, PSBT, and documentation.
263
+ - **v0.2.3 Status**: Core Taproot spend-path flows are implemented and testnet-verified; ongoing work focuses on hardening, PSBT, and documentation.
231
264
 
232
265
  ## Acknowledgments
233
266
 
@@ -2,15 +2,17 @@
2
2
 
3
3
  [![Supported by OpenSats](https://img.shields.io/badge/supported%20by-OpenSats-orange?style=flat-square&logo=bitcoin)](https://opensats.org)
4
4
 
5
- A pragmatic Bitcoin toolkit with a clear path toward full Taproot engineering.
5
+ A pragmatic Bitcoin toolkit for Taproot engineering Legacy, SegWit, and Taproot flows, PSBT, and optional **Signet** / **Bitcoin Inquisition** opcode templates (OP_CAT, CSFS, CTV).
6
6
 
7
- Designed for reproducible testnet experiments, educational workflows, and script-path development.
7
+ Designed for reproducible testnet and signet experiments, educational workflows, and script-path development.
8
8
 
9
9
  If you find btcaaron useful, a GitHub star is appreciated.
10
10
 
11
+ 👉 Looking for Bitcoin Inquisition experimental opcode templates (OP_CAT / CSFS / CTV)? See **[INQUISITION.md](INQUISITION.md)**.
12
+
11
13
  ## Current Status
12
14
 
13
- **v0.2.2 (alpha preview)** — Core Taproot spend-path workflows are implemented and testnet/regtest-verified.
15
+ **v0.2.3 (alpha preview)** — Core Taproot spend-path workflows are implemented and testnet/regtest-verified.
14
16
  Current focus is release hardening: broader validation coverage, documentation alignment, and contributor testing feedback.
15
17
 
16
18
  ## Features
@@ -25,7 +27,7 @@ Production-tested on testnet with real transactions:
25
27
  - Broadcast to Blockstream / Mempool endpoints
26
28
  - Developer helpers (`WIFKey`, `quick_transfer`)
27
29
 
28
- ### Available Now (v0.2.2 - Alpha Preview)
30
+ ### Available Now (v0.2.3 - Alpha Preview)
29
31
 
30
32
  Testnet-verified with real transactions (23 tests, all passing):
31
33
 
@@ -85,25 +87,7 @@ python -m pip install -e .
85
87
  btcaaron-doctor
86
88
  ```
87
89
 
88
- ## Quick Start
89
-
90
- ```python
91
- from btcaaron import WIFKey, quick_transfer
92
-
93
- wif = "your_testnet_wif"
94
-
95
- key = WIFKey(wif)
96
- print("Taproot:", key.get_taproot().address)
97
-
98
- balance = key.get_taproot().get_balance()
99
- print("Balance:", balance, "sats")
100
-
101
- if balance > 1000:
102
- txid = quick_transfer(wif, "taproot", "tb1q...", amount=500, fee=300)
103
- print("Broadcasted:", txid)
104
- ```
105
-
106
- ## v0.2.2 API Example
90
+ ## Quick Start (v0.2.3)
107
91
 
108
92
  *Taproot-native API — core features available now.*
109
93
 
@@ -135,6 +119,51 @@ tx = (program.spend("hash")
135
119
  tx.broadcast()
136
120
  ```
137
121
 
122
+ ## Legacy Quick Start (v0.1.x)
123
+
124
+ For backward compatibility with the v0.1.x API:
125
+
126
+ ```python
127
+ from btcaaron import WIFKey, quick_transfer
128
+
129
+ wif = "your_testnet_wif"
130
+
131
+ key = WIFKey(wif)
132
+ print("Taproot:", key.get_taproot().address)
133
+
134
+ balance = key.get_taproot().get_balance()
135
+ print("Balance:", balance, "sats")
136
+
137
+ if balance > 1000:
138
+ txid = quick_transfer(wif, "taproot", "tb1q...", amount=500, fee=300)
139
+ print("Broadcasted:", txid)
140
+ ```
141
+
142
+ ## Descriptor Wallet Quick Start (tprv/xpriv)
143
+
144
+ For descriptor-style HD wallet flows (RGB-friendly), derive directly from `tprv`:
145
+
146
+ ```python
147
+ from btcaaron import taproot_balance_from_tprv, quick_transfer_tprv, taproot_descriptor_from_tprv
148
+
149
+ tprv = "tprv8ZgxMBicQKs..."
150
+ print(taproot_descriptor_from_tprv(tprv, branch=0)) # tr(tprv.../86h/1h/0h/0/*)
151
+
152
+ balance = taproot_balance_from_tprv(tprv, branch=0, index=0)
153
+ print("Balance:", balance, "sats")
154
+
155
+ txid = quick_transfer_tprv(
156
+ tprv,
157
+ "tb1p...",
158
+ amount=10_000,
159
+ fee=800,
160
+ branch=0,
161
+ index=0,
162
+ debug=True,
163
+ )
164
+ print("Broadcasted:", txid)
165
+ ```
166
+
138
167
  Full specification in [DESIGN.md](./DESIGN.md).
139
168
 
140
169
  ## Testing
@@ -148,7 +177,7 @@ python -m pytest tests/
148
177
  Run specific test suites:
149
178
 
150
179
  ```bash
151
- # v0.2.2 comprehensive tests (pytest)
180
+ # v0.2.3 comprehensive tests (pytest)
152
181
  python -m pytest tests/test_btcaaron_v02.py -v
153
182
 
154
183
  # v0.1.x example-based tests
@@ -194,7 +223,7 @@ See [DESIGN.md](./DESIGN.md) for architecture details and development roadmap.
194
223
  - default call blocks mainnet broadcast unless `allow_mainnet=True`
195
224
  - `dry_run=True` is available for no-side-effect routing checks
196
225
  - recommended smoke script: `python3 examples/core_test/scenarios/mainnet_readiness_smoke.py`
197
- - **v0.2.2 Status**: Core Taproot spend-path flows are implemented and testnet-verified; ongoing work focuses on hardening, PSBT, and documentation.
226
+ - **v0.2.3 Status**: Core Taproot spend-path flows are implemented and testnet-verified; ongoing work focuses on hardening, PSBT, and documentation.
198
227
 
199
228
  ## Acknowledgments
200
229
 
@@ -8,7 +8,7 @@ v0.2.x API (new):
8
8
  from btcaaron import Key, TapTree
9
9
  """
10
10
 
11
- __version__ = "0.2.2"
11
+ __version__ = "0.2.3"
12
12
 
13
13
  # ══════════════════════════════════════════════════════════════════
14
14
  # Legacy API (v0.1.x) - backward compatible
@@ -19,16 +19,35 @@ from .legacy import (
19
19
  BTCTransaction,
20
20
  wif_to_addresses,
21
21
  quick_transfer,
22
+ quick_transfer_tprv,
23
+ taproot_balance_from_tprv,
22
24
  fund_program,
23
25
  )
24
26
 
25
27
  # ══════════════════════════════════════════════════════════════════
26
28
  # New API (v0.2.x)
27
29
  # ══════════════════════════════════════════════════════════════════
28
- from .key import Key
30
+ from .key import Key, derive_wif_from_tprv, taproot_descriptor_from_tprv, wif_secret_bytes
29
31
  from .tree import TapTree, TaprootProgram, LeafDescriptor
30
32
  from .spend import SpendBuilder, Transaction
31
- from .script import Script, RawScript
33
+ from .script import (
34
+ Script,
35
+ RawScript,
36
+ ord_inscription_script,
37
+ brc20_mint_json,
38
+ inq_cat_hashlock_script,
39
+ inq_csfs_script,
40
+ inq_ctv_script,
41
+ inq_ctv_template_hash_for_output,
42
+ inq_ctv_program_for_output,
43
+ )
44
+ from .node_rpc import (
45
+ broadcast_tx_hex,
46
+ find_utxo_for_address,
47
+ sats_from_rpc_amount,
48
+ wallet_change_address,
49
+ wallet_send_sats,
50
+ )
32
51
  from .psbt import Psbt, PsbtInput, PsbtV2, PsbtV2Input
33
52
  from .errors import (
34
53
  BtcAaronError,
@@ -47,10 +66,15 @@ __all__ = [
47
66
  "BTCTransaction",
48
67
  "wif_to_addresses",
49
68
  "quick_transfer",
69
+ "quick_transfer_tprv",
70
+ "taproot_balance_from_tprv",
50
71
  "fund_program",
51
72
 
52
73
  # New API
53
74
  "Key",
75
+ "derive_wif_from_tprv",
76
+ "taproot_descriptor_from_tprv",
77
+ "wif_secret_bytes",
54
78
  "TapTree",
55
79
  "TaprootProgram",
56
80
  "LeafDescriptor",
@@ -58,6 +82,18 @@ __all__ = [
58
82
  "Transaction",
59
83
  "Script",
60
84
  "RawScript",
85
+ "ord_inscription_script",
86
+ "brc20_mint_json",
87
+ "inq_cat_hashlock_script",
88
+ "inq_csfs_script",
89
+ "inq_ctv_script",
90
+ "inq_ctv_template_hash_for_output",
91
+ "inq_ctv_program_for_output",
92
+ "broadcast_tx_hex",
93
+ "find_utxo_for_address",
94
+ "sats_from_rpc_amount",
95
+ "wallet_change_address",
96
+ "wallet_send_sats",
61
97
  "Psbt",
62
98
  "PsbtInput",
63
99
  "PsbtV2",
@@ -0,0 +1,296 @@
1
+ """
2
+ btcaaron.key - Key Management
3
+
4
+ The Key class provides a clean interface for Bitcoin key operations.
5
+ """
6
+
7
+ import hashlib
8
+ from typing import Optional
9
+ from bitcoinutils.setup import setup
10
+ from bitcoinutils.keys import PrivateKey
11
+
12
+
13
+ def _normalize_network(network: str) -> str:
14
+ """
15
+ Normalize app-level network labels to bitcoinutils setup labels.
16
+
17
+ bitcoinutils distinguishes mainnet/testnet/regtest. For signet-family
18
+ networks we use testnet address/version rules.
19
+ """
20
+ n = (network or "testnet").lower()
21
+ if n == "mainnet":
22
+ return "mainnet"
23
+ if n == "regtest":
24
+ return "regtest"
25
+ return "testnet"
26
+
27
+
28
+ def set_network(network: str) -> str:
29
+ """
30
+ Set bitcoinutils global network context and return normalized value.
31
+ """
32
+ normalized = _normalize_network(network)
33
+ setup(normalized)
34
+ return normalized
35
+
36
+
37
+ def _default_coin_type(network: str) -> int:
38
+ """
39
+ Default BIP44/86 coin type by network.
40
+
41
+ mainnet -> 0, testnet/regtest/signet-family -> 1
42
+ """
43
+ return 0 if _normalize_network(network) == "mainnet" else 1
44
+
45
+
46
+ def wif_secret_bytes(wif: str) -> bytes:
47
+ """
48
+ Decode WIF and return 32-byte raw private key bytes.
49
+
50
+ Supports both compressed and uncompressed WIF.
51
+ """
52
+ try:
53
+ import base58
54
+ except ImportError as exc:
55
+ raise ValueError("Missing dep: pip install base58") from exc
56
+
57
+ try:
58
+ decoded = base58.b58decode(wif)
59
+ except Exception as exc:
60
+ raise ValueError(f"Invalid WIF encoding: {exc}") from exc
61
+
62
+ if len(decoded) not in (37, 38):
63
+ raise ValueError("Invalid WIF length")
64
+
65
+ payload, checksum = decoded[:-4], decoded[-4:]
66
+ expected = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4]
67
+ if checksum != expected:
68
+ raise ValueError("Invalid WIF checksum")
69
+
70
+ prefix = payload[0]
71
+ if prefix not in (0x80, 0xEF):
72
+ raise ValueError("Invalid WIF network/version byte")
73
+
74
+ if len(payload) == 34:
75
+ # Compressed key marker must be present.
76
+ if payload[-1] != 0x01:
77
+ raise ValueError("Invalid compressed WIF marker")
78
+ secret = payload[1:-1]
79
+ else:
80
+ secret = payload[1:]
81
+
82
+ if len(secret) != 32:
83
+ raise ValueError("Invalid private key length in WIF")
84
+ return secret
85
+
86
+
87
+ def derive_wif_from_tprv(
88
+ tprv: str,
89
+ *,
90
+ branch: int = 0,
91
+ index: int = 0,
92
+ purpose: int = 86,
93
+ coin_type: Optional[int] = None,
94
+ account: int = 0,
95
+ network: str = "testnet",
96
+ ) -> str:
97
+ """
98
+ Derive a compressed child WIF from an account-level xpriv/tprv.
99
+
100
+ Default path follows BIP86:
101
+ m / 86' / coin_type' / account' / branch / index
102
+ """
103
+ try:
104
+ from bip32 import BIP32, HARDENED_INDEX
105
+ import base58
106
+ except ImportError as exc:
107
+ raise ValueError("Missing deps: pip install bip32 base58") from exc
108
+
109
+ if min(branch, index, purpose, account) < 0:
110
+ raise ValueError("branch/index/purpose/account must be non-negative")
111
+
112
+ ct = _default_coin_type(network) if coin_type is None else coin_type
113
+ if ct < 0:
114
+ raise ValueError("coin_type must be non-negative")
115
+
116
+ path = [
117
+ purpose | HARDENED_INDEX,
118
+ ct | HARDENED_INDEX,
119
+ account | HARDENED_INDEX,
120
+ branch,
121
+ index,
122
+ ]
123
+ try:
124
+ bip32_obj = BIP32.from_xpriv(tprv)
125
+ private_key = bip32_obj.get_privkey_from_path(path)
126
+ except Exception as exc:
127
+ raise ValueError(f"Invalid tprv/xpriv or derivation path: {exc}") from exc
128
+
129
+ prefix = b"\x80" if _normalize_network(network) == "mainnet" else b"\xEF"
130
+ payload = prefix + private_key + b"\x01"
131
+ checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4]
132
+ return base58.b58encode(payload + checksum).decode()
133
+
134
+
135
+ def taproot_descriptor_from_tprv(
136
+ tprv: str,
137
+ *,
138
+ branch: int = 0,
139
+ purpose: int = 86,
140
+ coin_type: Optional[int] = None,
141
+ account: int = 0,
142
+ network: str = "testnet",
143
+ wildcard: bool = True,
144
+ index: int = 0,
145
+ ) -> str:
146
+ """
147
+ Build a BIP86-style taproot descriptor string from xpriv/tprv.
148
+
149
+ Example:
150
+ tr(tprv.../86h/1h/0h/0/*)
151
+ """
152
+ ct = _default_coin_type(network) if coin_type is None else coin_type
153
+ suffix = "*" if wildcard else str(index)
154
+ return f"tr({tprv}/{purpose}h/{ct}h/{account}h/{branch}/{suffix})"
155
+
156
+
157
+ # Default module context remains testnet-first.
158
+ set_network("testnet")
159
+
160
+
161
+ class Key:
162
+ """
163
+ Bitcoin Key Manager (Immutable)
164
+
165
+ Handles private key operations, signing, and public key derivation.
166
+
167
+ Example:
168
+ alice = Key.from_wif("cRxebG1hY6vVgS9CSLNaEbEJaXkpZvc6nFeqqGT7v6gcW7MbzKNT")
169
+ print(alice.xonly) # x-only pubkey for Taproot
170
+ """
171
+
172
+ def __init__(self, private_key: PrivateKey):
173
+ """Internal constructor. Use from_wif() or generate() instead."""
174
+ self._private_key = private_key
175
+ self._public_key = private_key.get_public_key()
176
+
177
+ @classmethod
178
+ def from_wif(cls, wif: str) -> "Key":
179
+ """
180
+ Create Key from WIF (Wallet Import Format) string.
181
+
182
+ Args:
183
+ wif: WIF-encoded private key
184
+
185
+ Returns:
186
+ Key instance
187
+
188
+ Raises:
189
+ ValueError: If WIF format is invalid
190
+ """
191
+ try:
192
+ private_key = PrivateKey(wif)
193
+ return cls(private_key)
194
+ except Exception as e:
195
+ raise ValueError(f"Invalid WIF private key: {e}")
196
+
197
+ @classmethod
198
+ def from_hex(cls, hex_privkey: str) -> "Key":
199
+ """
200
+ Create Key from hex-encoded private key.
201
+
202
+ Args:
203
+ hex_privkey: 32-byte hex string
204
+
205
+ Returns:
206
+ Key instance
207
+ """
208
+ try:
209
+ private_key = PrivateKey(secret_exponent=int(hex_privkey, 16))
210
+ return cls(private_key)
211
+ except Exception as e:
212
+ raise ValueError(f"Invalid hex private key: {e}")
213
+
214
+ @classmethod
215
+ def from_tprv(
216
+ cls,
217
+ tprv: str,
218
+ *,
219
+ branch: int = 0,
220
+ index: int = 0,
221
+ purpose: int = 86,
222
+ coin_type: Optional[int] = None,
223
+ account: int = 0,
224
+ network: str = "testnet",
225
+ ) -> "Key":
226
+ """
227
+ Create Key by deriving child private key from xpriv/tprv.
228
+
229
+ Default derivation path:
230
+ m/86'/coin_type'/account'/branch/index
231
+ """
232
+ set_network(network)
233
+ wif = derive_wif_from_tprv(
234
+ tprv,
235
+ branch=branch,
236
+ index=index,
237
+ purpose=purpose,
238
+ coin_type=coin_type,
239
+ account=account,
240
+ network=network,
241
+ )
242
+ return cls.from_wif(wif)
243
+
244
+ @classmethod
245
+ def generate(cls, network: str = "testnet") -> "Key":
246
+ """
247
+ Generate a new random key.
248
+
249
+ Uses the underlying bitcoin-utils PrivateKey() which generates
250
+ a cryptographically secure random private key via os.urandom().
251
+
252
+ Args:
253
+ network: "testnet" | "mainnet" (default: testnet)
254
+
255
+ Returns:
256
+ Key instance
257
+ """
258
+ set_network(network)
259
+ private_key = PrivateKey()
260
+ return cls(private_key)
261
+
262
+ @property
263
+ def wif(self) -> str:
264
+ """WIF-encoded private key"""
265
+ return self._private_key.to_wif()
266
+
267
+ @property
268
+ def pubkey(self) -> str:
269
+ """33-byte compressed public key (hex)"""
270
+ return self._public_key.to_hex()
271
+
272
+ @property
273
+ def xonly(self) -> str:
274
+ """32-byte x-only public key for Taproot (hex)"""
275
+ return self._public_key.to_x_only_hex()
276
+
277
+ @property
278
+ def _internal(self) -> PrivateKey:
279
+ """Access to underlying bitcoinutils PrivateKey (internal use)"""
280
+ return self._private_key
281
+
282
+ @property
283
+ def _internal_pub(self):
284
+ """Access to underlying bitcoinutils PublicKey (internal use)"""
285
+ return self._public_key
286
+
287
+ def __repr__(self) -> str:
288
+ return f"Key(xonly={self.xonly[:16]}...)"
289
+
290
+ def __eq__(self, other) -> bool:
291
+ if not isinstance(other, Key):
292
+ return False
293
+ return self.xonly == other.xonly
294
+
295
+ def __hash__(self) -> int:
296
+ return hash(self.xonly)