1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
//! `sidestr-wallet` — a wallet for sidestr sidechains, in Rust: the coin
//! set for a script, the reference coin selection, taproot key-path spends
//! and peg-out burns signed behind a signer port, the parent-side peg-in
//! transaction shape, and delivery as a `POST /tx` body or a kind-23500
//! event.
//!
//! A wallet needs a chain id and a relay, and nothing of the producer's
//! (SPEC 11). It learns the chain from a mirror's `chain.json`, held to the
//! signer's announced tip; asks a producer `/coins/<script hex>` for what
//! its script owns, or folds the block file itself with
//! [`sidestr_core::State`] and gets the same list; builds a transaction
//! Bitcoin's rules accept, with the fee at the document's `minFeeRate`; and
//! hands it over as `POST /tx` or as an event on a relay whose key is
//! anyone's, because the transaction authorises itself. A producer includes
//! what validates. A wallet with nothing publishes a kind-23501 request and
//! a faucet may answer it.
//!
//! This crate is a port of **siding**, the reference implementation by
//! Melvin Carvalho (<https://github.com/sidestr/spec>, AGPL-3.0), ported from
//! commit `2de40bdac4cba01be0864156a553d8287c22e279` and brought to SPEC 0.0.4
//! (`@sidestr/spec` 0.0.6) at `fa86dac83d47b8f70195132e91e9dc083e1d9228` (`lib/txsign.mjs`) — `siding/lib/spend.mjs`,
//! `lib/address.mjs`, the construction halves of `lib/parent.mjs` and
//! `lib/checkpoint.mjs`, and the `send` and `faucet` commands of
//! `bin/siding.mjs` — and carries the same licence, AGPL-3.0-only. `SPEC.md`
//! in that repository is the design; section numbers below are its. Where a
//! function ports a siding function its documentation names it. Consensus
//! types, the markers, addresses and the document come from
//! [`sidestr_core`] and are not duplicated here.
//!
//! # What is here
//!
//! | module | what | SPEC | ported from |
//! |---|---|---|---|
//! | [`coins`] | the coin set for a script: `/coins` JSON or a state fold; maturity | 11 | `siding/lib/chain.mjs coins`, `spend.mjs` |
//! | [`select`] | largest-first selection to the amount plus a fee bound | 11 | `spend.mjs buildSpend` |
//! | [`spend`] | a key-path spend to an address or script, fee at `minFeeRate`, signed, as hex | 11 | `spend.mjs buildSpend`, `resolveTo`; `siding send` |
//! | [`burn`] | a peg-out: `OP_RETURN pegout:<parent script hex>`, at least `pegoutMin` | 7 | `spend.mjs` (`--pegout`) |
//! | [`pegin`] | the parent side: peg output + `pegin:<chain id>:<script>` marker; the peg-out payment and checkpoint shapes; scanning | 6, 7, 11 | `parent.mjs`, `checkpoint.mjs` |
//! | [`deliver`] | `POST /tx`, `/coins`, `/tip`, `/chain.json` as data; kind 23500 / 23501 templates; HTTP behind feature `client` | 11 | `spend.mjs deliver`, `bin/siding.mjs` routes |
//! | [`key`] | the [`SpendSigner`] port, a plain key, pubkey → `5120` script → bech32m, ADR-2101 spend-key derivation | 3 | `sign.mjs`, `address.mjs` |
//! | [`policy`] | the [`SpendPolicy`] hook every builder consults; [`Permissive`] | — | (ADR-2100) |
//!
//! # A payment, end to end
//!
//! Against a chain held in memory — the same calls work against a
//! producer's `/coins` and `/tip` (see [`deliver`]).
//!
//! ```
//! use bitcoin::secp256k1::SecretKey;
//! use sidestr_core::block::{challenge_for, pubkey_of};
//! use sidestr_core::document::{ChainDocument, Peg};
//! use sidestr_core::state::{NextBlock, State};
//! use sidestr_wallet::burn::{build_burn, BurnRequest};
//! use sidestr_wallet::spend::{build_spend, SpendRequest};
//! use sidestr_wallet::{coins, key, Error, Permissive, PlainKey, SpendSigner};
//!
//! // the chain's signer seals blocks; the wallet's spend key is a role key (ADR-2101)
//! let signer = SecretKey::from_slice(&[7u8; 32]).unwrap();
//! let root = SecretKey::from_slice(&[0x11u8; 32]).unwrap();
//! let wallet = PlainKey::new(key::derive_spend_key(&root, &"0".repeat(64), 0).unwrap());
//!
//! // a throwaway document whose genesis pegs 0.01 BTC to the wallet's script (SPEC 5)
//! let json = format!(r#"{{"id":"sidestr:example","name":"example","parent":"tbtc4","challenge":"{}",
//! "powLimit":"7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff","addressPrefix":"ex",
//! "genesisTime":1790000000,"signer":"{}","pegs":[]}}"#, challenge_for(&pubkey_of(&signer)).to_hex_string(), pubkey_of(&signer));
//! let mut doc = ChainDocument::from_json(&json).unwrap();
//! doc.pegs.push(Peg { txid: "a".repeat(64), vout: 0, amount: 1_000_000, script: wallet.script().to_hex_string(), extra: Default::default() });
//! let mut chain = State::with_key(doc.clone(), &signer).unwrap();
//!
//! // genesis coins are coinbase outputs: mature at 100 (a wallet sees this as `coinbase: true`)
//! let listed = coins::from_state(&chain, &wallet.script());
//! assert!(listed[0].coinbase && !listed[0].is_mature(chain.height()));
//! for i in 1..=100 { chain.produce(&signer, &NextBlock { time: 1790000000 + i, claims: vec![] }, None).unwrap(); }
//!
//! // pay someone: an address under the chain's prefix, fee at minFeeRate, signed through the port
//! let you = key::address_for(&PlainKey::new(SecretKey::from_slice(&[8u8; 32]).unwrap()).pubkey(), "ex").unwrap();
//! let req = SpendRequest { chain: &doc, coins: &listed, tip_height: chain.height(), to: &you, amount: 250_000, fee: None };
//! let paid = build_spend(&req, &wallet, &Permissive).unwrap();
//! let ok = chain.submit(paid.tx.clone()).unwrap(); // the producer's mempool policy, and the signature
//! assert_eq!((ok.txid, ok.fee), (paid.txid, paid.fee));
//!
//! // once mined, the change is a coin; burn some of it to a parent address: the peg holders owe it on tbtc4 (SPEC 7)
//! chain.produce(&signer, &NextBlock { time: 1790000200, claims: vec![] }, None).unwrap();
//! let coins_now = coins::from_state(&chain, &wallet.script());
//! assert_eq!(coins_now[0].value, paid.change);
//! let burn = build_burn(&BurnRequest { chain: &doc, coins: &coins_now, tip_height: chain.height(), to: "tb1pvts4e2zcrujj9zey3kadyfgh2xs93v8va8ae9ldhukpxy2n3848qyqurhc", amount: 10_000, fee: None }, &wallet, &Permissive).unwrap();
//! assert!(chain.submit(burn.tx.clone()).is_ok());
//!
//! // what the wallet refuses before signing
//! assert!(matches!(build_spend(&SpendRequest { amount: 5_000_000, ..req }, &wallet, &Permissive), Err(Error::Insufficient { .. })));
//! assert!(matches!(build_spend(&SpendRequest { amount: 100, ..req }, &wallet, &Permissive), Err(Error::Dust { .. })));
//! ```
//!
//! # Conventions that matter
//!
//! - **Keys stay behind [`SpendSigner`].** A builder computes the key-path
//! sighash the chain's family requires (BIP 341 beside stock Bitcoin,
//! Knots' unified beside BLAKE2b) and asks the port to sign it; it never
//! holds a secret.
//! [`PlainKey`] is the in-memory implementation; a signer that holds a
//! derived role key and permits named operations only (ADR-2101) fits the
//! same trait. [`key::derive_spend_key`] is the derivation; the identity
//! key never spends.
//! - **The wallet builds and signs; it does not decide.** Every builder
//! consults a [`SpendPolicy`] with the chain, kind, script, amount, fee
//! and input count before signing. [`Permissive`] says yes; the authority
//! gate (ADR-2100) is the caller's implementation.
//! - **The fee is `minFeeRate × vsize`, exactly**, sized with the 65-byte
//! witness the signer will produce, unless the caller fixes one — and a
//! fixed fee under the rate is refused here rather than by the producer.
//! - **No I/O by default.** `deliver` returns URLs, bodies and event
//! templates; feature `client` adds the HTTP calls over `ureq`. Nothing
//! here publishes to a relay: the event's signature is `sidestr-nostr`'s.
//! - **Amounts are sats**, `u64`; txids in markers are display-order hex, as
//! the markers carry them; structural txids are [`bitcoin::Txid`].
//!
//! # Where this port departs from siding
//!
//! - **The signature carries its hash type explicitly.** As `txsign.mjs`
//! does since 0.0.3: `0x01` beside stock Bitcoin, `0x21` beside a BLAKE2b
//! parent, so every witness is 65 bytes and the fee is sized for that.
//! The message is the reference's (`tests/oracle.rs` checks each engine
//! verifies the other's signature); the signature itself differs, since
//! [`PlainKey`] signs with zero auxiliary randomness and siding with
//! fresh randomness, so byte equality of whole transactions is not a goal.
//! - **Dust is refused.** siding will pay 1 sat to a taproot script and
//! return 1 sat of change; this crate refuses an amount under the script's
//! dust threshold ([`spend::dust_threshold`], 330 sats for taproot) and
//! leaves change under it to the fee instead of minting an unspendable
//! coin. A burn's `OP_RETURN` has no dust floor; `pegoutMin` governs it.
//! - **A fixed fee is checked against `minFeeRate`** before signing;
//! siding lets the producer refuse it.
//! - **Zero BIP 340 auxiliary randomness** in [`PlainKey`], as `sidestr-core`
//! seals blocks: a spend is a pure function of its inputs and the key.
//! - **The EVM deposit branch is not carried** (`--evm`; ADR-2096 excludes
//! the `evm` rule), nor are assets (SPEC 12, reserved).
pub use ;
pub use Coin;
pub use ;
pub use ;
pub use ;
pub use ;
pub use ;