# sidestr-core
> Part of [sidestr-rs](https://github.com/DreamLab-AI/sidestr-rs). Rust port of Melvin Carvalho's sidestr sidechains, AGPL-3.0-only: the economic engine for did:nostr agents. A did:nostr key is a sidechain wallet.
[sidestr](https://github.com/sidestr/spec) user-activated sidechains beside a
Bitcoin-family parent, in Rust: the chain document, the parents table, signed
blocks in either header family (a BIP 325 challenge, no subsidy), the peg-in
claim and peg-out burn rules, the block file, and an in-memory validating
chain with a producer's mempool.
A sidestr chain runs beside a Bitcoin-family chain with Bitcoin's transaction
rules, blocks that are valid because they are *signed* rather than mined, no
subsidy, and every coin on it a coin locked on the parent. Signers decide the
order of blocks; they do not decide the rules.
```toml
[dependencies]
sidestr-core = "0.3"
# and, for a chain beside a BLAKE2b parent (xbt, txbt4):
sidestr-header = "0.3"
```
The rules, state and chain are generic over the header family
(`sidestr_core::block::HeaderFamily`): `State` / `Chain` are the stock
instantiation (`btc`, `tbtc4`), and `StateOf<Blake2bV2>` / `ChainOf<Blake2bV2>`
with `sidestr_header::Blake2bV2` validate a chain beside Knots' BLAKE2b fork.
The dependency edge runs from `sidestr-header` to this crate, never back.
## Attribution
This crate is a port of **siding**, the reference implementation of sidestr by
Melvin Carvalho — [github.com/sidestr/spec](https://github.com/sidestr/spec),
AGPL-3.0 — ported from commit `2de40bdac4cba01be0864156a553d8287c22e279` and
brought to SPEC 0.0.3 at `722ad42d3271efccfdfaf57c3c6943f58fc168f8`
(`siding/lib/{parents,block,chain,overlay,marker,records,address,checkpoint}.mjs`,
`bin/siding.mjs`, and the tests in `siding/test/`). Two parts come from the
engine siding loads, by the same author and under the same licence:
- the block, header and spending checks, and Knots' unified sighash, from
[bitcoin-desktop/schema](https://github.com/bitcoin-desktop/schema)
(`codec/blocks.js`, `codec/headers.js`, `codec/interpreter.js`
`sighashUnified`, `schema/validate.jsonld`);
- the block file format and the chain state machine from
[bitcoin-blake/blaketestnode](https://github.com/bitcoin-blake/blaketestnode)
(`lib/blockfile.mjs`, `lib/node.mjs`).
`SPEC.md` in the sidestr repository is the design; the crate documentation
cites its sections, and every ported function names its original.
## What changed in the port
- Bitcoin's consensus serialisation, hashes, merkle roots, the taproot sighash
and BIP-340 Schnorr come from [`rust-bitcoin`](https://crates.io/crates/bitcoin)
and its `secp256k1`; bech32 from the [`bech32`](https://crates.io/crates/bech32)
crate. Nothing cryptographic is hand-rolled.
- Script verification fails closed: taproot key-path spends are verified, every
other script type is refused rather than skipped. The reference lets a witness
version it cannot verify through.
- The solution's witness decoder refuses truncation, trailing bytes, non-minimal
sizes and more than 256 items; the reference reads what it can.
- The mempool judges a spend's sighash type by the family's block rule (unified
only beside a BLAKE2b parent); the reference mempool accepts unified on every
family while its block rule does not.
- Claim and burn records commit only when a block is applied, not while it is
being judged.
- Blocks are a pure function of their inputs and the key: BIP 340 auxiliary
randomness is zero for every block, as siding sets it for the genesis.
- A document naming the `assets`, `pool` or `evm` rules is refused, because
this version does not carry them.
- Level 2 carries the pure parts of `federation.mjs` and not the round (that
is [`sidestr-round`](https://crates.io/crates/sidestr-round)): the script
path is verified for exactly the `multi_a(k, …)` leaf, an unknown leaf
version is refused rather than skipped, and `template_id` names what
signers authorise separately from the sealed hash (ADR-2101 review).
- No I/O in the rules: the filesystem and the clock are behind the `std`
feature (`blockfile`, `chain`).
- The genesis is judged, not trusted: `from_genesis` runs every rule that
applies at height 0 (the family's header rules, the signature against the
challenge, the pegs as the one subsidy, `sidestr:rule-genesis-document`)
before the hash is held to the document's `genesisHash`; siding applies
block 0 on the hash alone. There is no trusted import.
- A stock header with version bit 31 set is refused at decode and, on the
typed path, by `btc:rule-header-version` — the rule the kernel names, which
reads a stock version as `i32le`.
- A mirror's `blocks.dat` record framing (`[u32 height][u32 size]`) is held to
`blocks.json` and to the file's length on every read (`Error::BlockFile`).
- Markers are written with a canonical push (`OP_PUSHDATA1` above 75 bytes)
and read exactly as siding's `opReturnData` reads them — a bare length byte
or an `OP_PUSHDATA1` prefix, minimal or not — because that is the burn
rule's grammar and a burn a reference wallet wrote must be paid. Their
text is decoded as siding's `TextDecoder` decodes it, one leading UTF-8
byte-order mark dropped, at exactly the readers that text-decode there
(burns, claims, the peg-in remainder's hex-form decision, records) and
not at the two compared as bytes (the parent peg-out record, the
checkpoint) — so a BOM-led burn is recorded, or refused, alike.
`tests/audit_regressions_records.rs` holds every marker case and every
block of an audit corpus to identical derived lists in both engines.
## Status — 0.3.0
Level 1 (one signer), both header families, end to end: genesis from the
document, block production, validation, the mempool policy, the block file.
SPEC 0.0.3 (reference `722ad42`): the peg output is the taproot output the
peg holders own, at any position (`parent::find_pegin` with a `PegOwner`;
`owned_by_peg_wallet` asks the node as the reference does), and
`sighash::key_path_sighash` signs by the parent's family, the rule the
mempool and the block both check. Verified independently before release (GPT-6 Astra, 2026-09-23). The pass
re-ran the gates and checked each 0.0.3 change at its layer against the
reference. It found every block with the same claims, burns and UTXO set on
both engines, for both families. Its probes and its four findings, now
fixed, are kept as `tests/audit_regressions_0_0_3.rs`.
Proven against the reference:
- the genesis of a throwaway chain rebuilt from its document and key is
byte-identical to the one siding wrote (`tests/oracle.rs`);
- the estate's sealed `sidestr:dreamlab` genesis replays to its documented hash;
- blocks produced here are accepted by siding and blocks siding produces are
accepted here (`tests/interop.rs`, needs the reference checkouts);
- with `sidestr-header`'s `Blake2bV2`, Melvin Carvalho's live
`sidestr:txbt4-siding` chain replays from its genesis to its tip with every
rule on, spends signed with Knots' unified sighash included
(`sidestr-header/tests/core_family.rs`);
- a 2-of-3 federation's challenge, its genesis and two blocks sealed by three
different pairs are byte-identical to siding's (`tests/federation.rs`,
`fixtures/fedtest`), every subset and both parities are property-tested
(`tests/federation_prop.rs`), and Bitcoin Core's interpreter agrees with the
`multi_a` verifier on 161 differential cases
(`cargo test --features consensus-oracle`);
- the five counter-examples of the independent 0.2 audit (an unsigned genesis
accepted on its hash, a stock block with version bit 31 replayed, a corrupt
record prefix replayed, a `u32` overflow in `claimable`, and the typed v2
genesis bypassing the family rules) are fixed and pinned as regressions
(`tests/audit_regressions.rs`, both crates).
0.2 made the rules, state and chain generic over `HeaderFamily` (with an
associated header and block type), added Knots' unified sighash and the
family's own rules, tightened the witness decoder, and added level 2's pure
parts (`federation`: NUMS key, leaf, partial signatures, witness assembly,
sealing, the `multi_a` verifier, `template_id`, `Chain::open_sealed`).
`State`, `Chain` and every 0.1 name keep their meaning as the stock
instantiation. The parent view (`parent`) is behind two traits — the chain
read-only, the peg wallet — with every decision pure (peg-ins found in
decoded blocks, what to claim and lock, the burn payment and the checkpoint
as `send` outputs, reconciliation) and Bitcoin Core's JSON-RPC as the one
implementation behind the `rpc` feature; `tests/parent_live.rs` (ignored,
`SIDESTR_PARENT_RPC`) finds the estate's peg-wallet funding on a testnet4
node without sending anything.
Elsewhere in the stack: the level-2 co-signing round is `sidestr-round`,
tips and transactions over Nostr are `sidestr-nostr`, spending is
`sidestr-wallet`. Not yet: the assets and pool rules, a full script
interpreter, and the Byzantine-tolerant consensus protocol above the
signature (ADR-2101 review), which is a later crate.
## Running the checks
```sh
cargo test # unit, ported siding suites, fixtures, doctests
cargo test --features consensus-oracle # plus Bitcoin Core's interpreter as a differential oracle (needs a C++ toolchain)
SIDESTR_PARENT_RPC=http://<node>:48332/ cargo test --features rpc --test parent_live -- --ignored # a testnet4 node, read-only
cargo run --example siding -- replay --chain fixtures/dreamlab/chain.json --dir <dir with blocks.dat>
cargo run --example siding -- genesis --chain chain.json --dir state --key-file signer.key
# the JS interop test, with the reference checkouts:
SIDESTR_SIDING=<sidestr/spec>/siding SCHEMA=<bitcoin-desktop/schema> BLAKETESTNODE=<bitcoin-blake/blaketestnode> \
cargo test --test interop
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps
cargo clippy --all-targets -- -D warnings && cargo fmt --check
```
`fixtures/trial` is a throwaway chain with its disposable signer key, carried
for the byte-for-byte test. `fixtures/dreamlab` is a sealed genesis without its
key. Keys are files, never arguments; nothing here prints one.
## Licence
AGPL-3.0-only, as a derivative work of siding. See [LICENSE](LICENSE). This
crate is **not dual-licensed**: a crate that links it is AGPL-3.0 in effect and
should say so.