Blocktrails (v0.1)
Nostr-native output-key commitment chaining on Bitcoin.
Definition
A Blocktrail is a sequence of P2TR key-path outputs where each output key is derived by cumulatively tweaking the base key. Each state transition adds a new tweak, and spending the output advances the trail to the next committed state.
Key Representation
Blocktrails operate on full secp256k1 private keys and public keys internally, as used by Nostr. State transitions are defined as scalar additions to the private key. When committing state to Bitcoin, the corresponding public key is encoded as a Taproot output key by taking its x-coordinate. Verification compares x-coordinates only.
This preserves:
- Scalar arithmetic:
privkey + tjust works - Vanity generation: grind keys without parity concerns
- Nostr compatibility: same key model
- Composability: standard secp256k1 tooling
Parity normalization is an output encoding concern, not a state or semantic concern.
Key encodings such as nsec, npub, or compressed public keys are out of scope and may be used freely by applications.
Mechanism
Blocktrails use chained tweaking: each state transition adds a scalar tweak to the previous key, forming a cryptographic chain that mirrors the spend chain.
For genesis state s₀:
- Compute
t₀ = scalar(s₀) - Private key:
d₀ = d_base + t₀ - Public key:
P₀ = P_base + t₀·G
For transition to state sᵢ:
- Compute
tᵢ = scalar(sᵢ) - Private key:
dᵢ = dᵢ₋₁ + tᵢ - Public key:
Pᵢ = Pᵢ₋₁ + tᵢ·G
Equivalently, the cumulative tweak is the sum of all state scalars:
dₙ = d_base + t₀ + t₁ + ... + tₙ
Pₙ = P_base + (t₀ + t₁ + ... + tₙ)·G
H is SHA-256 unless otherwise specified by the application.
Hash-to-scalar reduction
scalar(s):
h = sha256(serialize(s)) // 32 bytes
t = int(h, big-endian) mod n // n = secp256k1 group order
if t == 0: reject state as invalid // probability ~2^-256
return t
The tweak t MUST be in the range [1, n-1]. Implementations MUST reject states where t = 0. This is an application-layer rule — Bitcoin accepts the output regardless.
Note: Since sha256 output is 256 bits and n ≈ 2256, the modular reduction is nearly uniform. No rehashing or counter-based totality mechanism is required.
All subsequent uses of scalar(state) refer to this function.
serialize(s) MUST be canonical and byte-stable: the same logical state MUST always produce identical bytes. Non-deterministic serialization breaks commitment verification.
The base key d_base is immutable for a given trail. Rotation requires starting a new trail.
Fees are paid from the current head output. When balance runs low, top-up (see Scope) adds funds.
Boundary encoding for P2TR output:
p2tr_xonly(P) → bytes32:
if y(P) is even: return x(P)
else: return x(-P)
The witness program is p2tr_xonly(P). Signing uses the corresponding (possibly negated) private key per BIP-340.
Spending this output advances the trail to the next committed state.
Constraints
Blocktrails use P2TR key-path only and do not require tapscript, script trees, or script-path spends. The only mechanism used is public-key tweaking.
No domain separation is applied; the commitment binds exactly to the serialized state bytes. This is deliberate — simplicity is a goal, mirroring Bitcoin's sighash minimalism philosophy. Applications requiring domain separation can include it in their serialize() function.
Components
| Component | Description |
|---|---|
| Commitment carrier | Tweaked public key (full point) |
| State head | Currently unspent output |
| History | The spend chain |
Design Goals
| Goal | How |
|---|---|
| Minimal on-chain footprint | One P2TR output per state update |
| Standardness-friendly | Key-path spends only |
| External state | State bytes live off-chain; chain anchors integrity |
| Single-writer | One controlling base key (multi-writer requires extra protocol) |
Scope
Blocktrails intentionally specify only the minimal primitive required for interoperable verification: commitment-by-output-key and linear progression by spends.
Many lifecycle behaviors — top-up, third-party payments, transfer of control, explicit closure — are compositions of ordinary Bitcoin spends and do not change the core verification rule. This document treats them as optional extensions.
In practice, top-up is the only liveness-oriented convention that may be widely needed, since trail progression is fee-bounded.
Operations
Genesis
t₀ = scalar(state₀)
d₀ = d_base + t₀
P₀ = d₀·G
output₀ = p2tr_xonly(P₀)
Create P2TR output with witness program output₀. Holder knows d₀.
Transition
tᵢ = scalar(stateᵢ)
dᵢ = dᵢ₋₁ + tᵢ
Pᵢ = Pᵢ₋₁ + tᵢ·G
outputᵢ = p2tr_xonly(Pᵢ)
Sign with dᵢ₋₁ to spend previous output. Create new output with witness program outputᵢ.
Note: The spending key accumulates all previous tweaks. At state n:
dₙ = d_base + t₀ + t₁ + ... + tₙ
Verify
verify(P_base, genesis_outpoint, states[]) → bool:
outpoint = genesis_outpoint
P = P_base
for state in states:
t = scalar(state) // rejects if t = 0
P = P + t·G // chain the tweak
if x(P) ≠ witness_program(outpoint): return false
outpoint = spending_outpoint(outpoint)
return is_unspent(outpoint)
Verification chains the tweaks: each state adds to the running public key. Since x(P) == x(-P), comparison uses x-coordinates only. No parity handling needed.
To locate the spending transaction for a given outpoint, implementations MAY use any standard Bitcoin data source: full node with indexing, Electrum-style servers, or externally published transaction feeds.
SPV compatibility and data dependencies
Blocktrails are SPV-compatible. Verification requires:
- Block headers — to validate proof-of-work chain
- Merkle inclusion proofs — for each transaction in the spend chain
- Genesis outpoint — application must know or discover the starting point
- Spending outpoint lookup — a method to find which transaction spent a given output (e.g., Electrum query, utreexo proof, or trusted indexer)
- Current UTXO status — to determine if the head output is unspent
No full node is necessary, but UTXO status and spend-chain traversal require indexing or proof infrastructure. "SPV-compatible" does not mean "zero external dependencies."
Interface
Applications implement:
serialize(state) → bytes
validate(prev, next) → bool
The primitive handles commitment and chaining. Applications define state format and transition rules.
Guarantees
This primitive provides:
- Integrity of state commitments
- Ordering relative to the spend chain
- Finality — probabilistic via Bitcoin proof-of-work; applications SHOULD wait k confirmations
Non-Guarantees
This primitive does not provide:
- On-chain rule enforcement —
validate(prev, next)is checked client-side - State availability — state distribution requires a separate channel
Security Assumptions
| Assumption | Guarantees |
|---|---|
| Hash preimage resistance | State binding |
| Discrete log hardness | Output key security |
| Bitcoin consensus | Spend ordering, double-spend prevention |
Threat Model
What Blocktrails protect against
| Threat | Mitigation |
|---|---|
| State forgery | Hash commitment — cannot find s' where H(s') = H(s) |
| History rewriting | Bitcoin immutability — confirmed spends are final |
| Double-spending state | UTXO model — each output spendable exactly once |
| Ordering disputes | Blockchain provides canonical order |
What Blocktrails do NOT protect against
| Threat | Why | Mitigation |
|---|---|---|
| Key compromise | Holder of d_base controls all transitions. Compromise means loss of all funds in the head output and full control over all future state transitions. | Secure key storage |
| State withholding | State lives off-chain | Redundant state distribution |
| Invalid state transitions | Validation is client-side | Verifiers must check validate(prev, next) |
| Censorship | Depends on Bitcoin transaction inclusion | Standard Bitcoin censorship resistance |
| Front-running | Miner can see pending state in mempool | Commit-reveal if state must be private |
Trust boundaries
Failure modes
| Failure | Consequence | Recovery |
|---|---|---|
Lost d_base | Cannot advance trail | None — funds/state locked |
| Lost state history | Cannot verify trail | Restore from backup/peers |
| Invalid transition accepted | Verifiers reject; chain forks semantically | Social consensus on valid branch |
| Bitcoin reorg | Recent transitions may revert | Wait for confirmations |
Reference Implementation
blocktrails — JavaScript/Node.js implementation with full test suite.
npm install blocktrails
Related Specifications
- Blocktrails Profiles — Application-layer profiles defining state formats and validation rules (Monochrome, MRC20)