BINST Pilot

A proof-of-concept for Bitcoin-sovereign institutional processes.

This pilot implements a three-layer architecture for institutional operations where the Bitcoin key is the root of authority, Ordinals inscriptions carry institutional identity, and an EVM-compatible L2 executes operational logic as a portable delegate.

The problem

Current approaches to on-chain institutional operations either:

  • Run everything on an L2, with no Bitcoin anchor — identity is L2-dependent
  • Use Bitcoin only for settlement, with no institutional semantics
  • Lock users into a single L2 with no portability

The pilot's approach

ConcernHow the pilot handles it
Institutional identityInscribed on Bitcoin L1 via Ordinals — permanent, sovereign
MembershipRepresented by Runes tokens on Bitcoin L1
Operational logicRuns on an L2 (Citrea) as a portable delegate
AuthorityThe Bitcoin key holder controls everything; the L2 contract obeys
L2 portabilitySwitching L2s means redeploying contracts bound to the same inscription
UTXO safetyTaproot script tree (NUMS + CSV + multisig) protects inscription sats
Bitcoin verificationTaproot Reader decodes L2 state directly from Bitcoin DA transactions

If the L2 disappears, the inscription remains. If the L2 is replaced, the same Bitcoin identity binds to the new contracts.

What the pilot implements

  • 4 smart contracts deployed and verified on Citrea testnet — factory, institution, process template, process instance
  • 4 Rust crates (Taproot Reader) — decoding BINST data directly from Bitcoin transactions, no_std-compatible, WASM-ready
  • 6 TypeScript scripts — end-to-end protocol flows, Bitcoin inscription tooling, finality monitoring
  • binst metaprotocol JSON schema — formal inscription format for four entity types
  • Taproot vault script tree — NUMS + CSV + multisig UTXO protection for inscription sats

What the pilot proves

  1. Institutional identity can be permanently inscribed on Bitcoin L1
  2. L2 contracts can operate as delegates bound to that Bitcoin identity
  3. The L2 choice is non-permanent — switching L2s preserves the identity
  4. Bitcoin transaction data (DA layer) can be decoded to reconstruct full institutional state without trusting the L2
  5. Inscription UTXOs can be protected with Taproot script trees

Source Code

Architecture

The architecture is built on a three-layer sovereignty model.

┌─────────────────────────────────────────────────────────────┐
│              BITCOIN L1 (Authority)                          │
│                                                             │
│  Ordinals Inscriptions ──── Institutional Identity          │
│  Runes Tokens ───────────── Membership & Roles              │
│  Tapscript Vault ────────── UTXO Safety Layer               │
│  BTC Key ────────────────── Root of All Authority           │
└──────────────────────────┬──────────────────────────────────┘
                           │ anchors
┌──────────────────────────▼──────────────────────────────────┐
│           L2 PROCESSING LAYER (Delegate)                     │
│           Currently: Citrea (Chain 5115)                     │
│                                                             │
│  Institution.sol ─────── On-chain institution definition    │
│  ProcessTemplate.sol ─── Immutable workflow logic           │
│  ProcessInstance.sol ─── Running execution + state          │
│  BINSTDeployer.sol ───── Factory & registry                 │
│                                                             │
│  Cross-chain: LayerZero V2 mirrors identity to other L2s    │
│  Execution state verified trustlessly via Bitcoin DA proofs  │
└──────────────────────────┬──────────────────────────────────┘
                           │ future
┌──────────────────────────▼──────────────────────────────────┐
│         VERIFICATION LAYER (Future)                          │
│                                                             │
│  BitVM ──────── Fraud-proof verification on BTC             │
│  BitVMX ─────── RISC-V execution verification               │
│  Covenants ──── Native BTC spending constraints             │
│  SNARK ──────── ZK proof verification on BTC                │
└─────────────────────────────────────────────────────────────┘

Each layer serves a distinct purpose:

  • Bitcoin L1 — permanent identity, membership, and the root of authority
  • L2 Processing — complex logic execution as a delegate of the Bitcoin key holder
  • Verification (future) — trust-minimized verification between L2 and Bitcoin

The Three-Layer Model

Layer 1: Bitcoin (Authority)

Bitcoin L1 stores three kinds of data:

PrimitiveRoleWhat it represents
Ordinals inscriptionsEntity identity, ownership, metadataInstitutions, process templates, process instances
RunesMembership and fungible roles"Alice is a member of Acme Financial"
ZK batch proofsComputational integrityEvery L2 state transition, ZK-proven
Bitcoin L1
├── Ordinals    → entities EXIST here (identity, ownership — AUTHORITATIVE)
├── Runes       → membership IS here (fungible tokens per institution)
└── ZK proofs   → computation is PROVEN here (L2 batch proofs)

This is the authoritative layer. If there's a conflict between Bitcoin and any L2, Bitcoin wins.

Layer 2: Processing Delegate (Currently Citrea)

Complex institutional logic executes on L2 smart contracts as a delegate of the Bitcoin key holder:

  • Multi-step workflow execution with validation rules
  • Payment processing
  • Cross-contract calls and event emission
  • State management (current step, completion, timestamps)

The L2 is a processing engine — it does not own the identity. The user can redeploy to a different L2 at any time, pointing the new contracts at the same inscription ID. The identity stays on Bitcoin.

Why Citrea?

FeatureWhy it matters
Fully EVM-compatibleSolidity contracts deploy with an RPC endpoint change
Bitcoin Light Client (0x3100…0001)Read Bitcoin block hashes on-chain, verify inclusion proofs
Schnorr precompile (0x…0200)BIP-340 signature verification in Solidity — no other L2 offers this
Clementine Bridge (BitVM2)Trust-minimized BTC ↔ cBTC peg
Testnet uses Bitcoin Testnet4 as DAReal Bitcoin data, not simulated
Three finality levelsSoft confirmation → Committed → ZK-proven on Bitcoin

The L2 choice is explicitly non-permanent. The architecture allows migrating to any EVM-compatible L2 by redeploying contracts and pointing them at the same inscription.

Layer 3: Verification (Future)

As Bitcoin's scripting capabilities evolve, BINST will add a trust-minimized verification layer:

  • BitVM / BitVM2 / BitVM3 — fraud-proof verification of L2 state transitions
  • BitVMX — RISC-V program execution verification on Bitcoin
  • Covenants (OP_CTV, OP_CAT) — native spending constraints for enhanced vaults and trustless bridges
  • SNARK verification — ZK proof verification within Bitcoin Script

This layer doesn't exist yet — it depends on Bitcoin protocol evolution. See the Technology Landscape section for details.

What Each Layer Guarantees

LayerWhat it provesTrust assumptionFailure mode
Ordinal inscriptionEntity exists, admin controls UTXOBitcoin consensusUTXO accidentally spent → lose root authority
Rune balanceThis person is a memberBitcoin consensusToken accidentally sent → membership lost
L2 contractProcessing delegate executes logicBitcoin consensus + ZK mathL2 down → redeploy on another L2
L2 batch proofEvery state transition was correctBitcoin consensus + ZK mathProof missing → state unverifiable until next batch

The Bitcoin key is the single root of authority. L2 contracts are replaceable processing delegates. Losing an L2 is graceful — redeploy elsewhere. Losing the Bitcoin key is catastrophic — the committee multi-sig is the last resort.

Authority Model

The Bitcoin key is sovereign. Everything else is a delegate.

The Hierarchy

Bitcoin secret key (ROOT OF AUTHORITY)
  │
  ├── controls inscription UTXO     → identity, provenance, metadata
  ├── controls Rune distribution    → membership tokens
  └── authorizes L2 contract(s)     → processing delegates
       ├── Citrea     (current)
       ├── BOB        (possible future)
       ├── Rootstock  (possible future)
       └── any L2     (portable)

The user who holds the Bitcoin private key has full control of every element in the protocol. If they decide to use a different L2, they deploy a new contract, point it at the same inscription ID, and pick up where they left off.

What the Key Controls

LayerWhat it controlsCan the user switch it?
Inscription UTXOIdentity, metadata, provenanceNo — this IS the identity
Rune distributionMembership tokensNo — lives on Bitcoin L1
L2 contractProcessing logic (workflows, payments)Yes — redeploy to any L2
Mirror contractsRead-only identity/membership on other L2sYes — add/remove mirrors

L2 Portability

Because the root of authority is the Bitcoin key (not the L2 contract address), the protocol is not locked into any specific L2. A user who starts on Citrea can later move to BOB, Rootstock, or any future Bitcoin L2 without losing their institution's identity, provenance, or membership.

Beyond simple portability, BINST supports multi-chain presence via a dual-channel sync model:

  • LayerZero V2 syncs identity and membership across L2s in real-time
  • Bitcoin DA provides trustless execution state verification via ZK batch proofs

Mirror contracts on other L2s provide read-only identity and membership verification. Process execution stays on the home chain — single-writer per process instance prevents concurrent mutation conflicts across chains.

See Cross-Chain Synchronization for the full model.

Failure Modes

ScenarioSeverityRecovery
L2 goes downGracefulRedeploy contracts on another L2; identity survives on Bitcoin
Inscription UTXO lostSeriousRe-inscribe as child of original + deploy new L2 contract
Bitcoin key lostCatastrophicCommittee 2-of-3 multi-sig recovery (Taproot vault Leaf 1)

The degradation is intentionally hierarchical: losing the L2 is easy to recover from, losing the inscription UTXO is hard but possible, losing the Bitcoin key requires the committee backstop.

Cryptographic Binding: btcPubkey

The Institution contract stores a bytes32 btcPubkey field — the 32-byte x-only public key (BIP-340) of the institution admin's Bitcoin key.

This closes the trust gap between Bitcoin identity and L2 contracts. Without it, the link between an inscription and a contract is informational (a stored string). With it, the binding is verifiable:

  1. Read the institution's btcPubkey from the L2 contract
  2. Read the inscription UTXO's owner from Bitcoin
  3. Verify they match — no oracle, no trust

Citrea's Schnorr precompile (0x0000000000000000000000000000000000000200) enables BIP-340 signature verification on-chain, which is the foundation for this binding. The Rust BitcoinIdentity struct requires bitcoin_pubkey — the L2 contract stores the same key.

Institution Anchoring Lifecycle

An institution progresses through three anchoring states:

The Three States

State 1: UNANCHORED (L2-only)
  → Contract deployed on Citrea (or any L2)
  → Functional on L2: members, processes work
  → Batch proofs reach Bitcoin DA (orphan proofs — see below)
  → No inscription, no rune
  → Status: DRAFT

State 2: BINDING (partially anchored)
  → Contract deployed + inscription created
  → But not yet fully linked (setInscriptionId not called,
    or rune not yet etched)
  → Status: BINDING

State 3: ANCHORED (fully sovereign)
  → Contract deployed + inscription bound + rune etched + rune bound
  → L2 state is provably linked to Bitcoin identity
  → Status: ANCHORED

Design Decision: Progressive Anchoring

Anchoring is not deployment. An institution exists on the L2 from the moment its contract is deployed. It becomes Bitcoin-anchored when its Ordinals inscription is created and bound to the contract.

This is a deliberate design choice:

  • Lowers the barrier to entry — users can experiment on L2 for just gas costs
  • Creates a natural funnel — experiment → anchor → grow
  • Matches reality — Citrea batches state regardless of inscription status
  • Permissionless — no gatekeeping on who can create institutions

Orphan ZK Proofs

If a user deploys Institution.sol on Citrea but never inscribes the ordinal identity, the ZK batch proof still reaches Bitcoin DA. This is an orphan proof — valid (it proves the L2 state transition happened) but unanchored (no Bitcoin-native identity to attach it to).

The binst-decoder would see storage slot changes for an Institution contract, but inscriptionId and runeId would be empty strings.

Orphan proofs are not harmful:

  • They are noise the indexer filters with a simple check: if inscriptionId == "" → skip
  • They don't affect anchored institutions
  • They represent experimentation — a healthy signal for protocol adoption

Entity Creation Patterns

PatternBitcoin TX needed?L2 TX needed?Example
Full entity creationYes (inscription + rune)Yes (deploy + bind)Create institution
L2-only creationNoYes (deploy)Unanchored institution, process template
Event registrationNoYes (EVM tx)Execute step, add member
VerificationNoNo (read only)Check membership, verify proof

Read/Write Phase Model

Two Transaction Domains

BINST operations happen in two independent domains:

  1. Bitcoin transactions — deliberate, user-initiated actions that create or transfer identity (inscriptions, runes)
  2. L2 transactions — EVM transactions on the processing delegate for institutional logic

These domains are decoupled. A Bitcoin inscription and an L2 contract deployment are separate operations that can happen in any order. The batch proof that anchors L2 state to Bitcoin happens automatically — the user doesn't trigger it.

USER ACTION           L2 (Citrea)                    Bitcoin
─────────────────────────────────────────────────────────────
Create Institution    → deploy contract               (nothing yet)
                      → state change on L2

                      ... L2 batches state ...     
                      
                      → batch proof inscribed          ← Bitcoin DA write
                                                         (automatic, not 
                                                          user-initiated)

Inscribe Identity     (nothing on L2)                 ← ordinal inscription
                                                         (user-initiated,
                                                          separate Bitcoin tx)

Bind Inscription      → setInscriptionId()            (nothing)
to Contract           → now L2 knows its Bitcoin anchor

Write Phases (User-Initiated Transactions)

ActionWhereWho pays / signs
Inscribe identityBitcoin (ordinal)User, BTC wallet
Etch membership RuneBitcoin (rune)User, BTC wallet
Deploy Institution contractCitrea (EVM)User, EVM wallet
Bind inscription to contractCitrea (EVM)User, EVM wallet
Add memberCitrea (EVM)Admin, EVM wallet
Send Rune to memberBitcoin (rune)Admin, BTC wallet
Create process templateCitrea (EVM)Admin, EVM wallet
Create process instanceCitrea (EVM)Admin, EVM wallet
Execute stepCitrea (EVM)Member, EVM wallet

Automatic (No User Action)

ActionWhereWho pays
ZK batch proofBitcoin DACitrea sequencer (periodic, async)

The user does not create a Bitcoin transaction when they interact with Citrea. The batch proof is automatic — the sequencer batches L2 state changes and inscribes the ZK proof on Bitcoin periodically. The user doesn't trigger it or pay for it.

Read Phases (Free)

ActionWhereCost
Verify membershipCitrea (EVM view call)Free
Check process stateCitrea (EVM view call)Free
Verify inscription existsBitcoin (indexer query)Free
Verify batch proofBitcoin DA (decode)Free
Cross-chain identity queryLayerZero / Bitcoin DARelay fee or free

Wallet UX

Current: Two Wallets

  • Bitcoin wallet (Xverse, Unisat) — for inscriptions and runes
  • EVM wallet (MetaMask) — for L2 transactions

The Schnorr precompile on Citrea (0x5a) means contracts can verify Bitcoin Schnorr signatures, but the current flow requires both wallets.

Future: Single Bitcoin Wallet

Account abstraction or Schnorr-verified sessions will allow the user to sign once with their Bitcoin key, and an AA layer submits to the L2. One wallet, one identity.

Creating an Institution

The full lifecycle from key generation to a fully anchored Bitcoin-sovereign institution.

Step-by-Step Flow

1. Admin generates a Bitcoin key pair (x-only Taproot pubkey)
   → this key IS the institution's identity root
   → everything else derives from this key

2. Admin inscribes institution on Bitcoin (Ordinal)
   → metaprotocol: "binst", body: institution metadata
   → gets inscription ID: abc123...i0
   → inscription lives in a Taproot vault UTXO → admin owns it
   → the inscription is the institution's birth certificate

3. Admin etches membership Rune on Bitcoin
   → INSTITUTION•MEMBER, premine: 1
   → admin holds the initial unit

4. Admin deploys Institution.sol on an L2 (currently Citrea)
   → constructor gets: name, admin address
   → admin calls setInscriptionId() and setRuneId() to bind the contract
   → the L2 contract is now a DELEGATE of the Bitcoin key holder

5. L2 state reaches Bitcoin via batch proof
   → institution is now represented THREE ways on Bitcoin:
      a) Ordinal inscription (identity — AUTHORITATIVE)
      b) Rune (membership token)
      c) State diff in batch proof (computational state)

Note: Step 4 can be repeated on any L2. The inscription ID and Rune ID stay the same. Only the L2 contract address changes.

Note: Steps 2–3 (Bitcoin) and step 4 (L2) are independent — they can happen in any order. If step 4 happens first without steps 2–3, the institution is UNANCHORED (see Institution Anchoring Lifecycle).

Transaction Summary

StepChainTransaction typeCost
Generate keyOfflineNoneFree
Inscribe identityBitcoinOrdinal inscription (~500B)~$2–5
Etch RuneBitcoinRunestone in OP_RETURN~$1–3
Deploy contractCitreaEVM contract creation~cBTC gas
Bind inscriptionCitreaEVM function call~cBTC gas
Bind RuneCitreaEVM function call~cBTC gas
Batch proofBitcoinAutomatic (sequencer)User doesn't pay

The Inscription Envelope

Every BINST inscription uses the Ordinals envelope format:

OP_FALSE OP_IF
  OP_PUSH "ord"
  OP_PUSH 1                              ← content type tag
  OP_PUSH "application/json"             ← MIME type
  OP_PUSH 7                              ← metaprotocol tag
  OP_PUSH "binst"                        ← protocol identifier
  OP_PUSH 5                              ← metadata tag
  OP_PUSH <CBOR-encoded metadata>        ← structured metadata
  OP_PUSH 3                              ← parent tag
  OP_PUSH <binst-root-inscription-id>    ← provenance chain
  OP_PUSH 0                              ← body separator
  OP_PUSH '{
    "type": "institution",
    "name": "Acme Financial",
    "admin_btc_pubkey": "a3f4...x-only-32-bytes",
    "citrea_contract": "0x1234...5678",
    "created_btc_height": 127600,
    "members": ["pubkey1...", "pubkey2..."]
  }'
OP_ENDIF

See Inscription Schema for the full JSON schema specification.

Membership & Runes

How Membership Works

Each institution etches a Rune on Bitcoin that represents membership. The Rune is a fungible token — holding ≥1 unit means "you are a member."

Rune: ACME•MEMBER
  Divisibility: 0  (whole units only — member or not)
  Symbol: 🏛
  Premine: 1  (admin gets the first unit)
  Terms: cap=1000, amount=1 (admin mints and distributes)

Operations

  • Check membership: "Does Alice hold ≥1 ACME•MEMBER?" — standard Rune indexer query. No L2 needed.
  • Add member: Admin sends 1 unit to new member's Bitcoin address.
  • Remove member: Admin burns the token via edict, or member sends it back.
  • Visible: Members see membership in any Rune-aware wallet (Xverse, Unisat).

Adding a Member (Full Flow)

1. Admin sends 1 INSTITUTION•MEMBER rune to new member's address
   → member now holds membership token in their Bitcoin wallet
   → visible in any Rune-aware wallet or indexer

2. Admin calls addMember(memberAddress) on the L2 contract
   → L2 contract updates member list
   → (optional: contract verifies Rune balance via bridge)

3. L2 state diff reaches Bitcoin via batch proof
   → member addition is now ZK-proven on Bitcoin

L1 + L2 Mirroring

Membership exists in two places simultaneously:

LayerHow membership is representedHow to check
Bitcoin L1Rune balance (ACME•MEMBER ≥ 1)Any Rune indexer
L2 (Citrea)isMember[address] mapping in Institution.solEVM view call

Both representations should be kept in sync. The Bitcoin Rune is the authoritative source — if there's a discrepancy, the Rune balance wins.

Future: Governance Tokens

A separate Rune (e.g., ACME•VOTE) with divisibility could represent weighted voting power. Governance becomes a token distribution problem — not a staking competition.

Process Execution

Processes are the core operational primitive of BINST — multi-step workflows that run on L2.

Concepts

  • ProcessTemplate — an immutable blueprint defining a sequence of steps (name, description, action type, configuration)
  • ProcessInstance — a running execution of a template, tracking which steps have been completed, by whom, and when

Executing a Step

1. Member calls executeStep() on L2 ProcessInstance
   → complex validation, payment, state transitions happen on L2
   → event emitted: StepExecuted(who, stepIndex, timestamp)

2. (Optional) Member inscribes step execution as child of instance
   → permanent, discoverable record on Bitcoin
   → not required for protocol correctness (batch proof handles that)
   → makes it human-readable on explorers

3. L2 batch proof writes state diff to Bitcoin
   → step execution is ZK-proven

Entity-to-Primitive Mapping

EntityNatureBitcoin PrimitiveReasoning
InstitutionUnique, one-of-oneOrdinal inscriptionNeeds metadata, provenance, UTXO-based ownership
Process TemplateUnique, immutableOrdinal inscription (child of institution)Unique artifact with structured content
Process InstanceUnique, mutable stateOrdinal inscription (child of template)State updates via batch proofs
Step ExecutionImmutable event recordOrdinal inscription (child of instance)Permanent discoverable record
MembershipFungible relationshipRune balance"Hold ≥1 token = member"
Governance voteFungible weightRune balance (separate)Transferable, weighted voting power

Cross-Chain Execution Safety

A ProcessInstance has exactly one home chain. Steps execute only on that chain. This is enforced by design:

  • Mirror chains provide read-only identity and membership verification
  • Mirror contracts (InstitutionMirror.sol) have no executeStep() function
  • The type system prevents concurrent mutation across chains
  • No rollback/rewind mechanism is needed — conflicts are prevented, not repaired

If a process on one L2 needs to reference a step completed on another L2, it performs a cross-chain read (via Bitcoin DA batch proof or LayerZero query). No mutation, no conflict.

See Cross-Chain Synchronization for the full model.

Switching L2s

One of BINST's core properties: the L2 is replaceable. Here's how migration works.

Flow

1. Admin decides to move from Citrea to another L2 (e.g., BOB)

2. Admin deploys new Institution contract on the new L2
   → binds it to the SAME inscription ID and rune ID

3. The Bitcoin-layer identity is unchanged:
   → same inscription, same UTXO, same admin key
   → same membership Rune, same member balances
   → provenance chain is intact

4. The old L2 contract becomes historical — its batch proofs
   remain on Bitcoin as a permanent record of past operations

5. New operations flow through the new L2 contract
   → the institution continues seamlessly

What Survives

ElementAfter migration
Inscription ID✅ Unchanged — same identity on Bitcoin
Admin key✅ Unchanged — same UTXO, same authority
Membership Runes✅ Unchanged — live on Bitcoin, not on any L2
Provenance chain✅ Unchanged — parent/child inscriptions intact
Old L2 state✅ Preserved — batch proofs on Bitcoin are permanent
New L2 contract🆕 New address, bound to same inscription

Why This Works

The Bitcoin key is the root of authority, not the L2 contract address. The inscription is the institution's identity, not the Solidity code. When you move L2s, you're changing the processing engine, not the institution itself.

This is analogous to moving a company's operations from one country to another — the company's identity (registration, brand, ownership) doesn't change. Only the operational jurisdiction does.

Admin Transfer

Transferring institutional control from one admin to another.

Flow

1. Current admin transfers the inscription UTXO to new admin's vault
   → on Bitcoin: new admin now controls the UTXO (Leaf 0 spend)
   → the inscription ID stays the same; the controlling key changes

2. New admin calls transferAdmin() on the L2 contract
   → L2 contract updates admin address to match new key holder

3. Both layers now agree: the new admin controls the institution
   on Bitcoin (UTXO) and on the L2 (contract state)

Conflict Resolution

If the L2 contract admin disagrees with the UTXO owner, the UTXO owner is authoritative. The L2 contract is expected to be updated to match.

A future version could enforce this via Bitcoin-key-based signature verification on the L2 (using Citrea's Schnorr precompile).

Security Considerations

The Taproot vault provides safety for admin transfers:

  • Leaf 0 (admin): CSV-delayed (~24 hours / 144 blocks). The delay gives time to abort if the transfer was unauthorized.
  • Leaf 1 (committee): Immediate 2-of-3 multisig. Emergency override if the admin key is compromised during transfer.

See Taproot Vault for the full vault specification.

Cross-Chain Synchronization

BINST institutions can be omnipresent across multiple L2s simultaneously, not just portable between them.

The Dual-Channel Model

Bitcoin (inscription + rune) = AUTHORITY
    ↓
Home L2 (e.g., Citrea) = PRIMARY DELEGATE (read + write)
    ↓ LayerZero V2 (identity)     ↓ Bitcoin DA (execution proof)
Mirror L2s (BOB, Rootstock, etc.) = READ-ONLY MIRRORS

Two sync channels, each optimized for different needs:

ChannelWhat it syncsSpeedTrust model
LayerZero V2Identity: name, admin, members, inscriptionId, runeIdFast (real-time)DVN-configurable
Bitcoin DAExecution: process step states, completion proofsSlow (batch interval)Trustless (ZK-proven)

LayerZero V2 on Citrea

LayerZero V2 has deployed endpoints on Citrea mainnet (Chain ID 4114, Endpoint ID 30403, endpoint address 0x6F475642a6e85809B1c36Fa62763669b1b48DD5B) and supports 8+ Bitcoin L2s:

  • Citrea, BOB, Bitlayer, BEVM, Merlin, Rootstock, Hemi, Corn, Goat

This maps directly to BINST's L2 portability promise.

The Three State Tiers

TierDataSync methodWritable?
IdentityinscriptionId, runeId, admin, nameLayerZero (fast)Home chain only
Membershipmembers[], isMemberLayerZero (fast)Home chain only
ExecutionstepStates[], process progressBitcoin DA (trustless)Home chain only

Single-Writer Rule

Critical invariant: A ProcessInstance lives on exactly one home chain. It is created there, executed there, completed there. Mirror chains can read process state but cannot mutate it.

This prevents the core distributed systems problem: two L2s executing the same step simultaneously and producing conflicting state.

Why not rewind/rollback?

A rollback mechanism would mean:

  1. You allowed the conflict to happen
  2. You detected it after the fact
  3. You unwound one or both executions

This adds enormous complexity and violates the simplicity principle. Instead, architectural prevention eliminates the problem entirely:

  • Mirror contracts (InstitutionMirror.sol) expose only view functions: isMember(), getAdmin(), getInscriptionId()
  • No executeStep(), no createProcess(), no mutating functions
  • The type system enforces the invariant at compile time

Cross-chain process references

If a process on BOB needs to verify a step completed on Citrea (e.g., "KYC must complete before this audit"), it performs a cross-chain read:

  • Query the Citrea mirror via LayerZero (fast, real-time)
  • Or verify the step state from Bitcoin DA batch proof (slower, trustless)

No mutation, no conflict.

Trust Considerations

LayerZero introduces a dependency outside Bitcoin — messages go through DVNs (Decentralized Verifier Networks), not Bitcoin DA. This is acceptable because:

  • The authority remains on Bitcoin (inscription UTXO ownership)
  • Mirrors are convenience, not consensus — any L2 can independently verify the inscription on Bitcoin
  • If LayerZero goes down, institutions still function on their home L2
  • LayerZero's principles align with BINST: permissionless, immutable endpoints, censorship-resistant

Implementation Plan

Phase 3 will introduce:

  • BINSTRelay.sol — an OApp contract that listens for institution events on the home L2 and broadcasts identity state to registered mirror chains
  • InstitutionMirror.sol — a read-only contract on non-home chains that receives and exposes institution identity/membership data

This turns BINST from "portable across L2s" (manual redeploy) into "omnipresent across L2s" (automatic sync).

Inscription Schema

The binst metaprotocol defines a formal JSON schema for Ordinals inscriptions.

Envelope Format

Every BINST inscription uses the Ordinals envelope:

  • Content type (tag 1) = application/json
  • Metaprotocol (tag 7) = binst
  • Parent (tag 3) = parent inscription ID (provenance chain)
  • Metadata (tag 5) = optional CBOR-encoded metadata
  • Body = JSON matching the schema below

Entity Types

TypeParent requirementPurpose
institutionNone (root of its tree)Institution identity and metadata
process_templateInstitution inscriptionImmutable process blueprint
process_instanceProcess template inscriptionRunning execution of a template
step_executionProcess instance inscriptionRecord of a step execution (optional)

Provenance Hierarchy

institution (root — no parent required)
 └─ process_template (child of institution)
     └─ process_instance (child of template)
         └─ step_execution (child of instance)

Schema Version

"v": 0 — pilot / testnet4. Breaking changes increment the version.

Example: Institution

{
  "v": 0,
  "type": "institution",
  "name": "Acme Financial",
  "admin_btc_pubkey": "a3f4b2c1d5e6f7890123456789abcdef0123456789abcdef0123456789abcdef",
  "created": "2026-03-15T12:00:00Z"
}

Example: Process Template

{
  "v": 0,
  "type": "process_template",
  "name": "KYC Onboarding",
  "institution_inscription_id": "abc123...i0",
  "steps": [
    { "name": "Submit Documents", "action_type": "upload" },
    { "name": "Review", "action_type": "approval" },
    { "name": "Final Approval", "action_type": "approval" }
  ]
}

Example: Process Instance

{
  "v": 0,
  "type": "process_instance",
  "template_inscription_id": "def456...i0",
  "created_by": "a3f4b2c1...",
  "status": "in_progress"
}

Example: Step Execution

{
  "v": 0,
  "type": "step_execution",
  "instance_inscription_id": "ghi789...i0",
  "step_index": 0,
  "actor": "b5e6c7d8...",
  "status": "completed",
  "timestamp": "2026-03-15T14:30:00Z"
}

Validation

The full JSON Schema (2020-12) is available at binst-metaprotocol.json.

# Validate with ajv-cli
ajv validate -s binst-metaprotocol.json -d examples/institution.json

Ordinals — Entity Identity

Every BINST entity is a permanent Ordinals inscription on Bitcoin. The inscription is the entity's birth certificate, identity anchor, and metadata carrier.

How It Works

BINST inscriptions use the Ordinals protocol with these conventions:

  • Metaprotocol (tag 7) = "binst" — filterable by any indexer
  • Content type = application/json
  • Metadata (tag 5) = CBOR-encoded structured data
  • Parent (tag 3) = parent inscription ID (provenance chain)

Provenance Hierarchy

Entities form a parent/child tree rooted at the institution inscription:

Institution "Acme Financial" (root inscription)
 ├── Process Template "KYC Onboarding" (child)
 │    ├── Instance #1 (grandchild)
 │    │    ├── Step 1 executed by Alice (event)
 │    │    └── Step 2 executed by Bob (event)
 │    └── Instance #2
 └── Process Template "Loan Approval"

Anyone running ord can verify the full provenance chain — "KYC Onboarding was created by Acme Financial" — without touching any L2.

Each entity in the tree gets its own sat, own UTXO, own vault. The parent-child relationship links them, but each inscription is independently held and independently protected. New information about an institution (a new process, a completed instance) is always a new child inscription — never a modification of the parent.

This keeps the institution's root inscription UTXO locked and safe while the child tree grows as the institution operates.

What gets inscribed vs what gets ZK-proven

Not every level of the tree needs its own Ordinals inscription. ZK batch proofs already carry all L2 state changes to Bitcoin automatically (the sequencer pays, not the user). The practical split:

LevelOrdinals inscription?Rationale
InstitutionYes — alwaysThis IS the identity. Permanent, inscribed once.
Process TemplateYesProves "this process belongs to this institution" on Bitcoin. Inscribed once, never changes.
Process InstanceOptionalCreated frequently. L2 state + ZK batch proof is sufficient.
Step ExecutionNoHigh-frequency L2 operations. Already ZK-proven via batch proofs.

The top two levels justify the inscription cost — infrequent, high-value, permanent identity. The bottom two are operational data better served by the L2 + ZK proof path:

Institution ─────── Ordinals inscription (identity)
Process Template ── Ordinals inscription (blueprint)
Process Instance ── L2 state, verified via ZK batch proofs
Step Execution ──── L2 state, verified via ZK batch proofs

Ownership

The inscription UTXO is controlled by the admin's Bitcoin key. This key is the canonical authority — whoever controls this UTXO controls the institution, its child entities, and any L2 contracts bound to it.

  • Transfer the UTXO = transfer admin rights (on Bitcoin and all L2s)
  • L2 contracts derive their authority from this key, not the other way around
  • A Bitcoin maximalist holds their institution in their Bitcoin wallet

Reinscription Policy

The first inscription is canonical (per Ordinals protocol). Reinscriptions append to the history — they do not overwrite:

  • Inscription 1 (canonical): "Created Acme Financial, admin=pk1"
  • Reinscription 2: "Updated description"
  • Reinscription 3: "Admin transferred to pk2"

Institutions cannot erase their history. The append-only model matches the transparency requirement.

Rule: use child inscriptions for data updates. Reserve reinscription for ownership events only (admin transfer, key rotation). This avoids spending the parent inscription's vault UTXO — the riskiest operation in the protocol — for routine updates. A new process template is a new child, not a reinscription of the institution.

Discovery

BINST inscriptions are discoverable through standard tooling:

  • Ordinals explorers (ordinals.com, ord.io, Hiro) — search by metaprotocol
  • Ordinals wallets (Xverse, Unisat) — shows as an asset
  • Self-hosted ord indexer — trustless, complete access
  • No custom BINST software needed for basic discovery

Runes — Membership Tokens

Each institution etches a Rune that represents membership on Bitcoin L1.

Configuration

Rune: ACME•MEMBER
  Divisibility: 0  (whole units only — member or not)
  Symbol: 🏛
  Premine: 1  (admin gets the first unit)
  Terms: cap=1000, amount=1 (admin mints and distributes)

Operations

ActionHowWho
Check membershipQuery Rune balance ≥ 1Anyone
Add memberSend 1 unit to member's Bitcoin addressAdmin
Remove memberBurn via edict, or member sends backAdmin or member
View membershipAny Rune-aware walletMember

No Custom Software Needed

Membership is a standard Rune balance check — any Rune indexer, any Rune-aware wallet (Xverse, Unisat), or a self-hosted ord can verify it. No BINST-specific tooling required for basic membership queries.

Future: Governance Tokens

A separate Rune (e.g., ACME•VOTE) with divisibility could represent weighted voting power. Governance becomes a token distribution problem — not a staking competition.

Taproot Vault — UTXO Safety

The inscription UTXO is the root of authority. Losing it means losing the institution. The Taproot vault prevents accidental spending at the consensus level.

The Risk

An admin who spends the inscription UTXO in a non-Ordinals-aware wallet loses control of the inscription. The data remains on Bitcoin permanently, but the UTXO tracking it moves to an unknown party.

This is the most critical risk in the protocol. The vault is not optional — it is essential infrastructure.

Miniscript Policy

The vault is defined as a BIP 379 miniscript policy:

or(
  and(pk(admin), older(144)),       ← admin transfer, ~24h CSV delay
  thresh(2, pk(A), pk(B), pk(C))    ← 2-of-3 committee, immediate
)

This policy is compiled to a Taproot descriptor using rust-miniscript:

tr(NUMS, { and(pk(admin),older(144)), thresh(2,pk(A),pk(B),pk(C)) })
  • Internal key = NUMS point (unspendable — disables key-path spend)
  • Leaf 0 = admin single-sig + 144-block CSV delay
  • Leaf 1 = 2-of-3 committee multisig (immediate)

Why Miniscript?

The previous implementation used hand-rolled Tapscript (taproot-vault.ts). Miniscript gives us:

  1. Wallet compatibility — the descriptor is importable into Sparrow, Liana, Nunchuk, and any BIP 379 wallet. Users can sign vault spends with standard software.
  2. Compiler-verified correctness — the miniscript compiler guarantees the spending conditions match the policy. No hand-rolled opcode bugs.
  3. Witness size analysis — the compiler provides worst-case witness sizes for fee estimation.
  4. Extensibility — new policies (timelocked multisig, decay trees) are a one-line policy change.

Script Structure

The compiled descriptor produces the same Taproot structure:

Taproot output:
  Internal key: NUMS point (unspendable — disables key-path spend)

  Script tree:
    Leaf 0 (admin transfer — time-delayed):
      <admin_pubkey> OP_CHECKSIG
      <144> OP_CHECKSEQUENCEVERIFY OP_DROP     ← ~24h delay

    Leaf 1 (committee override — immediate):
      <key_A> OP_CHECKSIG
      <key_B> OP_CHECKSIGADD
      <key_C> OP_CHECKSIGADD
      <2> OP_NUMEQUAL

How It Works

PathWhoDelayPurpose
Key pathNobodyDisabled (NUMS internal key) — no accidental spend possible
Leaf 0Admin (single key)~24 hours (144 blocks CSV)Deliberate admin transfer with safety delay
Leaf 12-of-3 committeeImmediateEmergency override for key loss or compromise

Rust Implementation

The vault module lives in binst-decoder/src/vault.rs:

#![allow(unused)]
fn main() {
use binst_decoder::vault::{VaultPolicy, parse_xonly};

let policy = VaultPolicy::new(
    parse_xonly("79be667e…").unwrap(),
    [parse_xonly("c6047f94…").unwrap(),
     parse_xonly("f9308a01…").unwrap(),
     parse_xonly("e493dbf1…").unwrap()],
);

let desc = policy.compile().unwrap();
println!("{}", desc.descriptor);        // tr(NUMS, {…})
println!("{}", desc.address_testnet);   // tb1p…
println!("{}", desc.address_mainnet);   // bc1p…

for path in &desc.spending_paths {
    println!("{}: {} keys, {:?} CSV, ~{} vbytes",
        path.name, path.required_keys.len(),
        path.timelock_blocks, path.witness_size);
}
}

The module also compiles to WASM (--features wasm) for in-browser vault generation.

Why This Works

  • No accidental spending — the key path is dead. Regular wallets can't spend it.
  • Admin retains control — Leaf 0 allows deliberate moves with a CSV safety net.
  • Committee backstop — Leaf 1 is the "break glass" emergency path.
  • Standard Bitcoin — uses only Taproot (BIP 341/342), OP_CHECKSIG, OP_CSV, OP_CHECKSIGADD. No soft fork needed.
  • Wallet-native — the descriptor imports directly into BIP 379 wallets (Sparrow, Liana, Nunchuk).
  • Ordinals-compatibleord tracks inscriptions regardless of spending script.

Future enhancement: When OP_CTV or OP_CAT activates, the vault can enforce that the UTXO is only spendable to pre-approved addresses (true covenant protection).

See also: Vault Unlock Flows · Sat Isolation · Graceful Degradation

Vault Unlock Flows

The vault locks the inscription UTXO but does not make it permanently frozen.

Path A — Admin Transfer (Leaf 0, ~24h delay)

1. Admin decides to move the inscription (transfer, reinscribe, rotate key)

2. Admin waits until the UTXO is at least 144 blocks old

3. Admin constructs a Bitcoin transaction:
   - Input: the vault UTXO (nSequence = 144)
   - Witness: <admin_signature> <leaf_0_script> <control_block>
   - Output 0: new destination (fresh vault or new owner's vault)

4. Bitcoin consensus validates:
   a) admin_signature valid for admin_pubkey  → OP_CHECKSIG ✓
   b) nSequence ≥ 144 blocks passed           → OP_CSV ✓
   c) Taproot script-path commitment correct   → control_block ✓

5. Transaction confirms. Inscription sat moves to new output.

Path B — Committee Override (Leaf 1, immediate)

1. Emergency: admin key lost or compromised

2. Two of three committee members co-sign

3. Committee constructs a transaction:
   - Input: the vault UTXO (nSequence = 0)
   - Witness: <sig_C_or_empty> <sig_B_or_empty> <sig_A> <leaf_1_script> <control_block>
   - Output 0: recovery destination

4. Bitcoin consensus validates:
   a) 2-of-3 via OP_CHECKSIGADD    → accumulated count ≥ 2 ✓
   b) Script-path commitment        → control_block ✓

5. Transaction confirms immediately.

Re-locking After a Spend

Each vault-to-vault transfer resets the CSV timer:

Vault A ──(admin spends after CSV)──▶ Vault B ──(...)──▶ Vault C

When Would the Admin Unlock?

ScenarioPathWhat happens next
Transfer to new adminLeaf 0Send to new admin's vault; call transferAdmin() on L2
Rotate admin keyLeaf 0Send to vault with new admin pubkey
Reinscribe (update metadata)Leaf 0Spend → new reveal TX → re-vault
Admin key compromisedLeaf 1Committee moves to safe address
Admin key lostLeaf 1Committee recovers to new admin's vault
Migrate to covenant vaultLeaf 0Move to OP_CTV vault when available

Atomic Institution Transfer

An institution accumulates vault UTXOs over time — the institution inscription itself plus child inscriptions (process templates). Transferring admin means moving all of them.

Atomicity comes from Bitcoin itself: any transaction with multiple inputs and multiple outputs is all-or-nothing by consensus rules. A single transaction spending every vault UTXO guarantees that either all inscriptions move to the new admin or none do:

Single transaction (all-or-nothing):

  Inputs:                              Outputs:
  ─────────                            ────────
  vault UTXO 1 (Institution sat)  →   new vault 1 → new admin's key
  vault UTXO 2 (Template A sat)   →   new vault 2 → new admin's key
  vault UTXO 3 (Template B sat)   →   new vault 3 → new admin's key
  fee input (admin's spending)    →   change → admin

Each input uses the Leaf 0 script-path spend. The new admin verifies that every inscription is routed to the correct new vault before the transaction is broadcast.

CSV maturity constraint

Every vault UTXO must be ≥ 144 blocks old for Leaf 0 spends. If a child inscription was created recently (e.g., a new process template), its vault may not have matured yet. In that case, the transfer is split into two transactions:

  • TX 1: all matured vault UTXOs (transfers immediately)
  • TX 2: remaining UTXOs (transfers once CSV matures)

Committee recovery

If the admin is uncooperative, the committee (Leaf 1, 2-of-3 multisig) can construct the same multi-input transaction. Leaf 1 has no CSV delay — committee transfers are immediate.

PSBT as a signing tool

A PSBT (BIP-174/371) is not required for atomicity — any Bitcoin transaction is inherently atomic. PSBT is a workflow format: an ephemeral container for building, inspecting, and co-signing a transaction before broadcast. It is recommended when:

  • Cold keys — the admin signs offline and passes the file to a watch-only node for broadcast.
  • Committee spends — two members sign independently, combine signatures, then finalize — without ever being online at the same time.
  • Audit before broadcast — any party can decode the PSBT and verify inputs, outputs, and script paths before a signature is added.

Once broadcast, the PSBT ceases to exist. The vaults and their Tapscript leaves are what protect the UTXOs — PSBT is just the recommended tool for spending from them.

Sat Isolation

The inscribed satoshi lives on its own dedicated, minimal UTXO — separate from spending funds.

Reveal Transaction Layout

Reveal TX:
  Input 0:  commit UTXO (inscription envelope in witness)

  Output 0: 546 sats → Taproot vault address (script-guarded)
             ↑ the inscribed sat lives HERE, alone
             ├── NUMS internal key (no key-path spend)
             ├── Leaf 0: admin + 144-block CSV delay
             └── Leaf 1: 2-of-3 committee multisig

  Output 1: change → admin's regular spending wallet
             Normal sats, freely spendable, no inscription.

The pointer tag (Ordinals tag 2) explicitly binds the inscription to the first satoshi of output 0.

Why 546 sats?

546 sats is Bitcoin's dust limit — the minimum UTXO value that Bitcoin Core nodes will relay. A 1-sat UTXO would be rejected by the mempool as "dust." The inscription lives on exactly one sat (the first sat of the output, per Ordinals ordinal theory), but the UTXO must hold ≥ 546 sats to exist on the network. The other 545 sats are padding — locked in the vault, recoverable when the UTXO is spent.

Two Layers of Protection

  1. Economic — dust-limit UTXO (546 sats) has no spending value to attract
  2. Consensus — Taproot vault script prevents spending even if attempted

Ordinals vs Runes: UTXO Sharing

A single UTXO can carry multiple ordinals (one per sat) and multiple Rune types simultaneously. The risk profile differs:

OrdinalsRunes
GranularityIndividual satsFungible balances per UTXO
Multiple per UTXOYes (one per sat)Yes (multiple Rune types)
Risk of co-locationHigh — sat ordering is complex, accidental transferLow — Runestone edicts are explicit
BINST approachOne inscription per isolated UTXOMultiple Runes per member UTXO is fine

Merging inscribed UTXOs is dangerous because spending scatters sats across outputs unpredictably (ordinal numbering follows first-in-first-out rules). The pilot isolates each inscription in its own vault UTXO to prevent accidental loss.

Graceful Degradation

The protocol degrades hierarchically — the closer to Bitcoin you lose, the harder the recovery.

Losing the L2 Contract (Graceful)

If Citrea goes down or the user wants to switch L2:

  1. Inscription data is permanent and readable on Bitcoin forever
  2. Membership Runes continue to function on Bitcoin L1
  3. Admin deploys a new contract on another L2
  4. New contract references the same inscription ID and rune ID
  5. Institution continues with full identity and membership intact

Losing the Inscription UTXO (Serious)

If the admin accidentally spends the inscription UTXO despite vault protection:

  1. Inscription data is permanent and readable forever
  2. L2 contracts continue to function short-term
  3. Admin re-inscribes a recovery record (child of original)
  4. L2 contracts are updated to reference the new inscription
  5. Original provenance chain is preserved

The vault script exists specifically to make this scenario extremely unlikely.

Losing the Bitcoin Key (Catastrophic)

  1. Committee (Leaf 1, 2-of-3 multisig) recovers the inscription to a new key's vault
  2. Admin deploys new L2 contracts from the new key
  3. This is the hardest recovery — the committee backstop is the last resort

The Hierarchy

L2 contract lost     → redeploy elsewhere, identity survives on Bitcoin
Inscription UTXO lost → re-inscribe + update L2 (harder, but recoverable)
Bitcoin key lost      → committee recovery (hardest, requires multi-sig)

Taproot Coverage Audit

BINST was designed to live as close to Bitcoin L1 as possible. This page cross-references every feature introduced by the Taproot upgrade (BIPs 340, 341, 342) against what the pilot actually uses, deliberately skips, or defers to production.

Features We Use

Taproot FeatureBIPWhere We Use It
P2TR output (SegWit v1)341Vault address (tb1p... / bc1p...) — every inscription UTXO is locked to a Pay-to-Taproot output
x-only public keys (32 bytes)340Admin key, committee keys, NUMS internal key — all stored and transmitted as 32-byte x-only keys, saving 1 byte vs compressed ECDSA
Schnorr signatures (64 bytes)340Admin single-sig (Leaf 0) and committee multi-sig (Leaf 1) — 64-byte Schnorr sigs replace 71–72-byte ECDSA sigs
MAST (Merkelized Alternative Script Tree)3412-leaf script tree: Leaf 0 (admin + CSV) and Leaf 1 (committee override). Only the exercised leaf is revealed on-chain — the other stays hidden
Tagged hashes341TapLeaf, TapBranch, TapTweak — domain-separated SHA-256 used for tree construction and output key derivation
Taptweak (Q = P + t·G)341NUMS internal key + Merkle root → tweaked output key. This is the core of BIP 341 key commitment
NUMS internal key341Provably unspendable point — disables key-path spend entirely, forcing all spends through the script tree
Script-path spend341Both vault unlock paths (admin and committee) use Taproot script-path spending with control blocks
Control blocks341(parity | leaf_version) || internal_key || sibling_hash — built for both leaves, enabling Taproot proof-of-inclusion
Leaf version 0xc0342All leaf scripts use the BIP 342 Tapscript leaf version
OP_CHECKSIG (Schnorr variant)342Admin leaf — single-key Schnorr signature check
OP_CHECKSIGADD342Committee leaf — the BIP 342 replacement for OP_CHECKMULTISIG (which is disabled in Tapscript). Accumulates a counter across multiple signature checks
OP_CHECKSEQUENCEVERIFY (CSV)112Admin leaf — enforces 144-block (~24h) relative timelock before the admin can move the inscription UTXO
Schnorr precompile on Citrea340Citrea's precompile at 0x…0200 can verify BIP-340 Schnorr signatures in Solidity — used for Bitcoin-key-based L2 authorization
Ordinals inscription in witness341The inscription envelope lives inside Tapscript witness data. Taproot's witness discount makes inscriptions economically viable
Bech32m address encoding341All P2TR addresses use Bech32m (BIP 350), distinct from SegWit v0's Bech32

Features We Deliberately Skip

Taproot FeatureBIPWhy We Skip It
Key-path spend341We kill it with the NUMS internal key. The entire purpose of the vault is to prevent accidental spending — a live key-path would let any Taproot-aware wallet move the inscription UTXO.
Key aggregation / MuSig2340MuSig2 aggregates N public keys into a single key for key-path spend. Since we disable the key-path, there is no aggregated key to spend with. For the committee, we use OP_CHECKSIGADD in a script leaf instead — this is simpler, independently auditable, and requires no interactive MuSig signing rounds between committee members.
Batch signature verification340Batch verification is a node-level optimization: Bitcoin Core can verify N Schnorr signatures faster than verifying them individually. This is transparent to script authors — our transactions benefit automatically when nodes use batch validation. There is nothing to implement.
Annex field341The annex is a reserved witness field (identified by the 0x50 prefix) with no current consensus meaning. It is reserved for future soft-fork extensions. No use case today.
OP_SUCCESS opcodes342These are upgrade placeholders that make unrecognized opcodes succeed unconditionally, allowing future soft forks to assign them new semantics. They exist precisely so that new opcodes can be added without a hard fork. Nothing to use today.

Design Rationale

Why kill the key-path?

In a normal P2TR workflow, the key-path is the happy path — it looks like a regular single-sig spend and provides maximum privacy. BINST deliberately sacrifices this because the vault's primary job is to prevent spending:

  • The inscription UTXO is the root of authority
  • Accidental spending = loss of the institution
  • The NUMS point makes the key-path provably dead
  • All spends must go through a script leaf with explicit conditions

This is the correct tradeoff for a protocol where the UTXO represents identity, not money.

Why OP_CHECKSIGADD instead of MuSig2?

MuSig2 would produce a cleaner on-chain footprint (single key, single sig), but it requires:

  1. Interactive signing rounds between committee members
  2. Nonce commitment coordination (two rounds minimum)
  3. Specialized MuSig2 software on each signer's machine
  4. All parties online at roughly the same time

OP_CHECKSIGADD in a Tapscript leaf is:

  1. Non-interactive — each member signs independently
  2. Standard tooling — any BIP-340 Schnorr signer works
  3. Auditable — the script is human-readable and each signature is individually verifiable
  4. PSBT-compatible — members sign via PSBT (BIP 174/371) without being online simultaneously

For an emergency recovery mechanism (which the committee path is), simplicity and independence matter more than on-chain size.

Potential Future Enhancements

These Taproot-adjacent features are not in the pilot but could be adopted in production:

EnhancementBasisWhat It EnablesComplexity
MuSig2 aggregated key-path (separate vault variant)BIP 340Aggregate committee keys into one key for key-path spend. Looks like a regular P2TR on-chain → maximum privacy. Script tree remains as fallback.High — requires MuSig2 signing infrastructure
Deeper MAST trees (3+ leaves)BIP 341Add a third leaf: e.g., a dead-man switch that becomes spendable after 1 year of inactivity, or a dedicated "migrate to covenant vault" leafMedium — straightforward script extension
Schnorr-signed L2 actions (single-wallet UX)BIP 340Admin signs L2 transactions with their Bitcoin Schnorr key via Citrea's precompile → one wallet, one identity, both layersMedium — needs account abstraction on L2
Covenants (OP_CTV / OP_CAT)ProposedRestrict the output of a vault spend — the UTXO can only move to a pre-approved address. True on-chain spending constraints.Requires soft fork (not yet activated)
Batch inscriptions in one treeBIP 341Embed multiple inscription commitments in different MAST leaves of a single transaction, reducing on-chain cost for multi-template institutionsLow–Medium

Summary

BINST uses every active Taproot feature relevant to its design goal of UTXO-locked institutional identity:

  • ✅ P2TR outputs with Bech32m
  • ✅ MAST script tree (2 leaves, hidden until spent)
  • ✅ Schnorr signatures (64 bytes, provably secure)
  • ✅ OP_CHECKSIGADD (BIP 342 multisig)
  • ✅ Tagged hashes and taptweak for key commitment
  • ✅ NUMS point to disable key-path
  • ✅ Control blocks for script-path proofs
  • ✅ CSV timelocks in Tapscript
  • ✅ Schnorr precompile on L2

The features we skip (key-path, MuSig2, annex, OP_SUCCESS) are either deliberately disabled for security, irrelevant to script authors, or reserved for future upgrades. No Taproot capability is left on the table without a documented reason.


Post-Quantum Considerations

See the dedicated Post-Quantum Analysis page for a full assessment of how Taproot's P2PK structure affects BINST and what mitigations are already in place.

References

Post-Quantum Analysis

The Taproot P2PK Problem

On April 2026, Bitcoin developers @niftynei and @JeremyRubin highlighted a structural property of Taproot that is relevant to every protocol built on top of it:

Every P2TR output is fundamentally a Pay-to-Public-Key (P2PK).

The 32-byte x-only public key is committed directly in the output script. A sufficiently powerful quantum computer could, in theory, derive the private key from this exposed curve point before the owner spends it.

This was a deliberate 2018/2019 design choice. The Taproot authors traded quantum resistance for on-chain privacy: all Taproot outputs look identical regardless of the complexity of the underlying script tree. At the time, post-quantum (PQ) cryptography was considered too immature and too unpredictable to constrain the design.

@niftynei summarised the rationale:

"Taproot accomplished its design goals to increase privacy. Being quantum safe wasn't on the list of design goals. Now we're in the era where PQ is a design goal."

Jeremy Rubin added that OP_CTV (his covenant proposal) can be used in a quantum-proof way, while OP_TEMPLATEHASH (a competing Taproot- only proposal) cannot — precisely because OP_TEMPLATEHASH inherits the P2PK exposure of Taproot.

How This Affects BINST

Attack Surface Map

BINST ComponentP2PK Exposed?RiskNotes
Institution inscription UTXOYes — tb1p… outputLowNUMS internal key — no private key exists to derive
ProcessTemplate inscription UTXOYes — same P2TRLowSame NUMS mitigation
Taproot vault (admin leaf)Yes — admin pubkey in scriptMediumKey revealed only at spend time, not before
Taproot vault (committee leaf)Yes — 3 pubkeys in OP_CHECKSIGADDMediumSame: revealed only when the committee path is exercised
Citrea DA inscriptionsYes — sequencer's Taproot outputNot oursCitrea controls this key
Inscription content (JSON body)No — pure dataNoneData is not a curve point
State digest merkle rootsNo — SHA-256 hashesNoneHash-based commitments are quantum-resistant
L2 contract stateNo — EVM/ECDSASeparate concernCitrea's EVM would need its own PQ migration
Bitcoin DA batch dataNo — Merkle commitmentsNoneHash-based, quantum-resistant

Why BINST Is Better Positioned Than Most

1. NUMS key kills the key-path attack.

The primary P2PK risk is that a quantum attacker sees the output key on-chain and derives the private key. In BINST, the internal key is the provably unspendable NUMS point:

H = lift_x(SHA256("BINST/nums"))

There is no discrete logarithm relationship between this point and any known generator. A quantum computer running Shor's algorithm needs a known group structure to exploit — NUMS provides none. The tweaked output key Q = H + t·G inherits this property: knowing Q does not help derive a private key because H has no known private key to start with.

2. Script-path keys are only revealed at spend time.

The admin and committee public keys live inside the Taptree leaves, not in the output. They are only revealed in the witness when the script-path is actually exercised. An attacker would need to:

  1. Wait for a vault spend transaction to appear in the mempool
  2. Extract the public key from the witness
  3. Run Shor's algorithm before the transaction is mined

With current block times (~10 minutes) and projected quantum timelines, this is not a practical attack vector in the near term.

3. Our covenant roadmap uses OP_CTV.

The BINST roadmap (Phase 5) plans to use OP_CTV for covenant-locked vaults. Per Jeremy Rubin's analysis, OP_CTV can be constructed in a quantum-proof way because it commits to the transaction template hash rather than requiring a public key. This means our planned upgrade path does not inherit the P2PK vulnerability.

4. DA proofs use hash commitments, not keys.

The state_digest inscriptions commit to Merkle roots over instance states. SHA-256 Merkle trees are quantum-resistant (Grover's algorithm provides only a quadratic speedup, requiring ~2¹²⁸ operations to break SHA-256, which is still infeasible). The entire DA verification chain — from state diffs to batch proofs to digest roots — is hash-based.

What Would Need to Change

If Bitcoin ships a post-quantum address format (e.g. a new SegWit version using hash-based or lattice-based signatures), BINST would need to:

Migration Steps

  1. Move inscription UTXOs to PQ-safe addresses. This requires spending the current Taproot outputs (which reveals the script-path keys) and sending the sats to new PQ outputs. The inscription content transfers with the sat.

  2. Update vault scripts to use PQ signature verification opcodes (if available). The MAST structure would remain; only the leaf scripts change from OP_CHECKSIG (Schnorr) to whatever PQ opcode Bitcoin adopts.

  3. Rotate btcPubkey bindings on the L2 contracts. The setBtcPubkey() function is currently set-once. A PQ migration would require either a new setter or a contract upgrade to rebind to a PQ public key.

  4. Update the metaprotocol schema to support PQ key formats in the admin field of inscription bodies (currently a 32-byte x-only Schnorr key).

What We Can Do Now

ActionComplexityWhen
Document the migration path (this page)DoneNow
Avoid key reuse — each vault uses fresh keysAlready in placeNow
Don't rely on key-path spend — NUMS is already defaultAlready in placeNow
Design setBtcPubkey v2 — allow admin-initiated key rotation with timelocked delayLowPhase 4
Monitor BIPs for PQ address proposalsOngoing
Prototype PQ inscription transfer when a PQ address format is available on signetMediumWhen available

Timeline Assessment

MilestoneEstimatedSource
Cryptographically relevant quantum computer2030–2040NIST, IBM roadmaps
Bitcoin PQ address soft fork proposal2026–2028Community discussion active
Bitcoin PQ soft fork activation2029+Typical activation timelines
BINST production deployment2026–2027Project roadmap

The gap between BINST deployment and quantum threat is at least 4–5 years, and Bitcoin itself will need to solve this problem for all Taproot users. BINST's migration path is no harder than any other Taproot-based protocol, and easier than most because:

  • NUMS removes the key-path attack surface
  • OP_CTV covenants (roadmap) are quantum-compatible
  • DA verification is entirely hash-based
  • Inscription UTXOs can be migrated by simple spend-and-resend

References

ZK Batch Proofs — Computational Integrity

The L2 (Citrea) periodically writes ZK batch proofs to Bitcoin — mathematical guarantees that every state transition was computed correctly.

How It Works

  1. Every BINST transaction lives in a Citrea L2 block
  2. The sequencer inscribes Sequencer Commitments (Merkle roots) on Bitcoin — pins ordering
  3. The batch prover inscribes ZK proofs (Groth16 via RISC Zero) on Bitcoin with state diffs — proves correctness
  4. Anyone with a Bitcoin node can reconstruct the entire L2 state including all BINST data

Finality Levels

LevelWhat happensHow to verify
Soft ConfirmationSequencer signs the L2 blockTransaction receipt
CommittedSequencer commitment inscribed on Bitcoincitrea_getLastCommittedL2Height
ZK-ProvenZK batch proof inscribed on Bitcoincitrea_getLastProvenL2Height

Why This Matters for BINST

Process instance state reaches Bitcoin via batch proof. This means:

  • A second L2 can verify process execution by reading Bitcoin DA — no messaging protocol needed
  • The taproot-reader can reconstruct which steps were executed, by whom, at what timestamp
  • Cross-chain execution verification is trustless — it goes through Bitcoin, not through any relay

See: Citrea DA Format · Decoding Procedure

Citrea DA Transaction Format

Citrea serializes a Borsh enum called DataOnDa inside taproot script-path reveals on Bitcoin.

The Five Variants

VariantIDPayloadPurpose
Complete0Vec<u8> (compressed proof)Full ZK batch proof in one tx
Aggregate1Vec<[u8;32]> txids + wtxidsReferences chunk txs for large proofs
Chunk2Vec<u8> (fragment)Fragment of a large proof
BatchProofMethodId3Method ID + signatures + pubkeysSecurity council metadata
SequencerCommitment4Merkle root + index + L2 end blockMost common — anchors L2 batch to Bitcoin

Tapscript Layout

PUSH32 <x_only_pubkey>           32 bytes — Citrea DA pubkey
OP_CHECKSIGVERIFY
PUSH2  <kind_bytes_le>           2 bytes LE — transaction kind (u16)
OP_FALSE
OP_IF
  PUSH <schnorr_signature>       64 bytes
  PUSH <signer_pubkey>           33 bytes compressed
  PUSH <body_chunk> ...          one or many pushdata entries
OP_ENDIF
PUSH8  <nonce_le>                8 bytes LE — wtxid mining nonce
OP_NIP

Identification

Citrea reveal transactions are identified by their wtxid prefix — the sequencer picks a nonce so the wtxid starts with 0x0202 (production). This allows fast filtering without full parsing.

Implementation Notes

  • Borsh discriminant is 1 byte (not 4)
  • Body may be split into ≤520-byte pushdata chunks — concatenate before deserializing
  • Always compute wtxid (witness hash), not txid
  • Support OP_PUSHBYTES_N, OP_PUSHDATA1, and OP_PUSHDATA2 encodings

Decoding Procedure

Step-by-step process for finding and decoding Citrea DA inscriptions from Bitcoin.

1. Choose a Scanning Strategy

  • Tip-based (continuous): scan from last seen block to current tip
  • Range scan (investigation): specific block range with --from/--to
  • Targeted: only blocks with nTx > 1 (many testnet blocks are coinbase-only)

2. Fetch the Block

Use getblockhash(height) then getblock(hash) to get the full block with witness data.

3. Filter by wtxid Prefix

For each non-coinbase transaction, compute wtxid = SHA256d(serialized_tx_with_witness). Check if it starts with 0x0202.

4. Extract Tapscript

For script-path spends, the tapscript is the second-to-last witness element: witness[witness.len() - 2].

5. Parse Structured Tapscript

Walk pushdata opcodes in order: pubkey → signature → signer → body chunks. Concatenate body chunks.

6. Borsh Deserialize

First byte = variant discriminant (0–4). Deserialize to typed DataOnDa value.

7. Map to Protocol Events

VariantWhat it means
SequencerCommitmentNew L2 batch finalized up to l2_end_block_number
CompleteFull ZK batch proof — verify to confirm correctness
Aggregate + ChunkLarge proof assembly instructions
BatchProofMethodIdSecurity council / method-ID update

CLI Usage

Bitcoin Core mode (local full node)

# Scan a single block (uses cookie auth by default)
cargo run --bin citrea-scanner -- --block 127600

# Scan a range with explicit auth, JSON output
cargo run --bin citrea-scanner -- --from 127600 --to 127761 --format json \
  --rpc-user <user> --rpc-pass <pass>

Citrea RPC mode (no Bitcoin node required)

# Query batch proofs via Citrea RPC with auto-discovery of BINST contracts
cargo run --bin citrea-scanner -- \
  --citrea-rpc https://rpc.testnet.citrea.xyz \
  --discover \
  --deployer 0xd0abca83bd52949fcf741d6da0289c5ec7235aaf \
  --block 127848

# Manual contract addresses (skip discovery)
cargo run --bin citrea-scanner -- \
  --citrea-rpc https://rpc.testnet.citrea.xyz \
  --deployer 0x... --template 0x... --instance 0x... \
  --from 127840 --to 127850

The --discover flag crawls the deployer contract on-chain: deployer.getInstitutions()institution.getProcesses()template.getAllInstances().

8. Human-Readable Value Decoding

Once BINST storage slot changes are identified, raw hex values are automatically decoded into human-readable form. Example output from block 127848:

ProcessTemplate.name = "KYC Verification"
ProcessInstance.creator = 0x8cf6fe5cd0905b6bfb81643b0dcda64af32fd762
ProcessInstance.stepStates[0] = Completed by 0x8cf6fe5cd0905b6bfb81643b0dcda64af32fd762
ProcessInstance.currentStepIndex = 4
ProcessInstance.completed = true
ProcessTemplate.steps.length = 4
BINSTDeployer.institutions[0] = 0x3a6a07c5d2c420331f68dd407aafff92f3275a86

Supported Solidity types

Solidity typeDecoded form
address0x8cf6fe5c…d762
uint2564, 1774750572
booltrue / false
string (short ≤31 bytes)"KYC Verification"
string (long >31 bytes)<string, 62 bytes>
StepState (packed struct)Completed by 0x8cf6…d762
bytes320x… hex

Citrea little-endian word order

Key discovery: Citrea / Sovereign SDK stores EVM storage values in little-endian word order — the entire 32-byte slot is byte-reversed compared to standard Solidity ABI encoding.

uint256 value 1:
  Standard Solidity (BE): 0x00000000…00000001
  Citrea state trie (LE): 0x01000000…00000000

The decoder pads to 32 bytes (state diffs may trim trailing LE zeros) and reverses before interpreting according to the field's Solidity type.

Packed struct layout (StepState)

Solidity packs uint8 status + address actor right-aligned in one slot:

BE word (after LE→BE reversal):
  [00 × 11 bytes][actor × 20 bytes][status × 1 byte]
   ↑ padding      ↑ bytes 11..31    ↑ byte 31

Status: 0 = Pending, 1 = Completed, 2 = Rejected.

See the taproot-reader/crates/cli/ directory in the pilot repository.

Discovery

How to find and verify BINST entities — from casual browsing to full trustless verification.

Who Needs What

What you want to knowWhere to lookFull node needed?
Does institution X exist?Ordinals explorer❌ No
Who is the admin?Inscription UTXO owner❌ No
Am I a member?Rune balance in wallet❌ No
Who are all members?Rune indexer query❌ No
What processes exist?Child inscriptions❌ No
What step is instance Y on?L2 RPC (Citrea)❌ No
Is institution X anchored?L2 view call (inscriptionId != "")❌ No
Was step execution valid?ZK batch proof decode✅ Yes
Full trustless verification?taproot-reader✅ Yes
Which L2 is processing?Inscription metadata❌ No
Is this person a member? (cross-chain)InstitutionMirror on any L2❌ No
Did step X complete? (cross-chain)Bitcoin DA batch proof✅ Yes

Two Tiers

Tier 1 (standard tooling):  Ordinals explorer + Rune wallet
  → identity, ownership, membership — no full node needed

Tier 2 (L2 RPC):  Citrea RPC + citrea-scanner --discover
  → process state, step execution, BINST contract discovery — no full node needed

Tier 3 (verification):  Bitcoin full node + taproot-reader
  → ZK proof verification, state diff decoding — trustless

Basic discovery requires no custom software and no full node. Standard Ordinals and Rune tooling covers identity, ownership, membership, and provenance. Tier 2 adds on-chain contract discovery via Citrea RPC. The full node is only needed for the strongest verification tier.

Auto-discovery with --discover

The citrea-scanner CLI can automatically crawl the deployer contract to find all BINST contracts registered on-chain:

cargo run --bin citrea-scanner -- \
  --citrea-rpc https://rpc.testnet.citrea.xyz \
  --discover \
  --deployer 0xd0abca83bd52949fcf741d6da0289c5ec7235aaf \
  --block 127848

Discovery chain:

  1. deployer.getInstitutions() → all institution addresses
  2. institution.getProcesses() → all template addresses
  3. deployer.getDeployedProcesses() → standalone templates
  4. template.getAllInstances() → all instance addresses

All discovered addresses are merged into the BINST registry, which pre-computes storage slot hashes for matching against state diffs in ZK batch proofs.

Matched state diff values are automatically decoded into human-readable form — addresses, integers, booleans, short Solidity strings, and packed StepState structs. See Decoding Procedure § Human-Readable Value Decoding.

Critically: none of this depends on any specific L2 being online.

Cost Analysis

Approximate costs for BINST operations at 10 sat/vB fee rate.

OperationMechanismApprox. cost
Create institutionOrdinal inscription (~500B text)~$2–5
Create process templateChild inscription (~300B)~$1–3
Record step executionChild inscription (~200B)~$0.50–2
Etch membership RuneRunestone in OP_RETURN~$1–3
Mint membership for 1 userRunestone transaction~$0.50–1
Transfer institution adminSend UTXO (standard tx)~$0.30–1
Total: institution + template + 10 members~$15–30

Testnet4 is free during development. Mainnet costs scale with fee rates but remain reasonable for institutional operations that happen infrequently.

Locked Sats at Scale

Each entity UTXO locks 546 sats (the dust limit). These sats are not burned — they are recoverable when the vault UTXO is spent (admin transfer, reinscription, etc.).

ScaleSats lockedBTC lockedAt $100K/BTC
1 institution5460.00000546$0.55
100 institutions54,6000.00054600$54.60
10,000 institutions5,460,0000.05460000$5,460

Transaction fees (commit + reveal) dwarf the dust padding by 5–10×. The real cost optimization target is batching and fee rate timing, not the locked sats.

Future Optimization: Batching

Bitcoin-side operations (inscribe + etch + distribute runes) can be grouped into fewer transactions:

Single Bitcoin TX:
  Input:  admin's UTXO (from taproot vault)
  Output 0: inscription envelope (institution identity)
  Output 1: rune etch (INSTITUTION•MEMBER)
  Output 2: rune transfer to member_1
  Output 3: rune transfer to member_2
  Output 4: change back to vault

This is planned for Phase 3 — a BINST-aware transaction builder that composes Bitcoin-side operations into minimal transactions.

Smart Contracts

Four Solidity contracts deployed and verified on Citrea Testnet (Chain 5115, Shanghai EVM, Solidity 0.8.24).

Contract Overview

ContractPurpose
BINSTDeployerFactory/registry — creates institutions and deploys process templates
InstitutionInstitution entity — members, admin, Bitcoin identity (inscriptionId, runeId)
ProcessTemplateImmutable workflow blueprint with named steps
ProcessInstanceRunning execution with step-by-step state tracking

BINSTDeployer

The factory and registry for all BINST entities. Creates institutions and process templates, maintains a global registry.

Key functions:

  • createInstitution(name, admin) → deploys a new Institution contract
  • createProcessTemplate(institutionAddr, steps[]) → deploys an immutable template
  • createProcessInstance(templateAddr) → creates a running instance

Institution

Defines an institution with Bitcoin anchoring.

State:

  • name — institution name
  • admin — current admin address (EVM)
  • inscriptionId — Ordinals inscription ID (Bitcoin anchor)
  • runeId — Rune ID for membership
  • members[] — member addresses
  • isMember mapping — O(1) membership check
  • processes[] — deployed process contracts

Key functions:

  • setInscriptionId(id) / setRuneId(id) — bind to Bitcoin identity
  • addMember(addr) / removeMember(addr) — manage membership
  • transferAdmin(newAdmin) — transfer control

ProcessTemplate

Immutable blueprint defining a sequence of steps.

Step struct:

struct Step {
    string name;
    string description;
    string actionType;
    string config;
}

Steps are set at deployment and cannot be modified — the template is a permanent record.

ProcessInstance

A running execution of a template, tracking step-by-step progress.

StepState struct:

struct StepState {
    StepStatus status;   // Pending, Completed, Skipped
    address actor;       // who executed it
    string data;         // execution data
    uint256 timestamp;   // when it was executed
}

Key function:

  • executeStep(stepIndex, data) — complete a step (only callable once per step)

EVM Configuration

Citrea does not support Cancun. Target Shanghai:

solidity: {
  version: "0.8.24",
  settings: { evmVersion: "shanghai", optimizer: { enabled: true, runs: 200 } }
}

Taproot Reader (Rust)

A Rust workspace that decodes BINST data directly from Bitcoin. Four crates, 79 tests.

Crate Architecture

taproot-reader/
  crates/
    citrea-decoder/    ← Citrea DA inscription parser (no_std, WASM-ready)
    binst-decoder/     ← Storage slots → protocol entities + miniscript vault module
    binst-inscription/ ← Ordinals envelope parser for binst metaprotocol
    cli/               ← citrea-scanner binary (Bitcoin Core RPC + Citrea RPC)

citrea-decoder

Parses Citrea DA inscriptions from raw tapscript witness data. Handles all five DataOnDa variants, pushdata chunking, and wtxid prefix filtering. Also decodes batch proof output (Brotli decompression, journal extraction, state diff parsing).

  • no_std compatible, WASM-ready
  • 7 tests

binst-decoder

Maps L2 storage slot diffs to BINST entities. Given a state diff from a batch proof, reconstructs InstitutionState, ProcessTemplateState, etc. Parses Citrea JMT keys and builds forward-hash lookup tables for matching state diff entries to known BINST contracts.

  • Computes Solidity storage slot positions (keccak256-based)
  • JMT key parsing: E/s/ (storage), E/H/ (headers), E/a/ (accounts)
  • Forward-hash lookup: SHA-256 precomputation of all known (address, slot) pairs
  • Human-readable value decoding (value module): decodes raw Citrea LE storage values to addresses, uints, bools, Solidity strings, and packed StepState structs
  • Key discovery: Citrea stores EVM slot values in little-endian word order (entire 32-byte word byte-reversed vs. standard Solidity ABI)
  • Carries BitcoinIdentity struct linking entities across layers
  • Miniscript vault module (vault module): compiles BIP 379 spending policies to Taproot descriptors using rust-miniscript. Generates wallet-compatible descriptors, derives addresses, and analyzes spending paths. WASM-exportable for in-browser vault generation.
  • 52 tests (27 unit + 5 e2e + 9 value decoding + 11 vault)

binst-inscription

Parses Ordinals envelopes for binst metaprotocol inscriptions. Extracts typed entity bodies (institution, template, instance, step execution).

  • Validates metaprotocol field, content type, parent chain
  • 10 tests

cli (citrea-scanner)

Binary that scans for Citrea DA transactions. Supports two modes:

  • Bitcoin Core mode: connects to a local full node via RPC
  • Citrea RPC mode: queries batch proofs directly from a Citrea node (no Bitcoin node required)

The --discover flag auto-discovers all BINST contracts by crawling the deployer on-chain.

# Bitcoin Core mode (uses cookie auth by default)
cargo run --bin citrea-scanner -- --block 127600

# Citrea RPC mode with auto-discovery
cargo run --bin citrea-scanner -- \
  --citrea-rpc https://rpc.testnet.citrea.xyz \
  --discover \
  --deployer 0xd0abca83bd52949fcf741d6da0289c5ec7235aaf \
  --block 127848
  • 5 tests

See also: BitcoinIdentity Type

BitcoinIdentity Type

Every BINST entity in the decoder carries a BitcoinIdentity struct linking it across all reachability layers.

#![allow(unused)]
fn main() {
pub struct BitcoinIdentity {
    /// Taproot x-only public key (32 bytes) — ROOT OF AUTHORITY.
    /// Controls the inscription UTXO and is the canonical identity.
    pub bitcoin_pubkey: [u8; 32],

    /// Ordinals inscription ID (e.g., "abc123...i0")
    pub inscription_id: Option<String>,

    /// Rune ID for membership token (e.g., "840000:20")
    pub membership_rune_id: Option<String>,

    /// EVM address on the current L2 (derived from or authorized by the BTC key)
    pub evm_address: Option<[u8; 20]>,

    /// HD derivation path hint (e.g., "m/86'/0'/0'/0/0")
    pub derivation_hint: Option<String>,
}
}

Field Ordering = Authority Hierarchy

  1. bitcoin_pubkey — the root of authority (controls inscription UTXO) — required
  2. inscription_id — permanent identity on Bitcoin
  3. membership_rune_id — membership token on Bitcoin
  4. evm_address — current L2 delegate (optional — changes if L2 changes)
  5. derivation_hint — wallet recovery aid

The required/optional distinction encodes the sovereignty model: Bitcoin identity is mandatory, L2 address is a transient delegate reference.

Scripts & Tooling

Six TypeScript scripts demonstrate the protocol end-to-end.

ScriptPurposeStatus
demo-flow.tsEnd-to-end: deploy → institution → members → process → execute all stepsActive
inscribe-binst.tsGenerate ord commands to inscribe BINST entities on Bitcoin testnet4Active
taproot-vault.tsBuild Taproot leaf scripts for inscription UTXO safety (NUMS + CSV + multisig)Deprecated — replaced by binst-decoder::vault (Rust miniscript)
psbt-transfer.tsGenerate PSBT commands for atomic vault transfersDeprecated — replaced by wallet-native descriptor signing
bitcoin-awareness.tsRead Bitcoin Light Client, query finality RPCsActive
finality-monitor.tsPoll Citrea RPCs until a watched L2 block is committed / ZK-provenActive
test-protocol.tsQuery live deployed contracts on Citrea testnetActive

Note: taproot-vault.ts and psbt-transfer.ts are kept as reference implementations. The Rust vault module (binst-decoder/src/vault.rs) uses BIP 379 miniscript to produce wallet-compatible Taproot descriptors, replacing the hand-rolled scripts. See Taproot Vault.

Usage Examples

# Run end-to-end demo on local Hardhat
npx hardhat run scripts/demo-flow.ts

# Deploy to Citrea Testnet
npx hardhat run scripts/demo-flow.ts --network citreaTestnet

# Generate inscription command
npx ts-node scripts/inscribe-binst.ts institution "Acme Financial" <admin_pubkey>

# Generate vault descriptor (Rust — replaces taproot-vault.ts)
cd taproot-reader && cargo test -p binst-decoder vault

# Bitcoin awareness (reads Light Client)
npx tsx scripts/bitcoin-awareness.ts

# Monitor finality for a specific L2 block
WATCH_L2=23972426 npx tsx scripts/finality-monitor.ts

Infrastructure & L2 Config

Development Environment

ComponentDetails
Dev machine (macOS)Node.js 22+, Rust 1.94, Hardhat 3.2, ord 0.27
Bitcoin Core testnet4Remote node via SSH tunnel, rpc:48332, fully synced
ord indexLocal, syncing against testnet4 node
Citrea testnetPublic RPC https://rpc.testnet.citrea.xyz, chain 5115

Citrea Testnet Configuration

SettingValue
RPChttps://rpc.testnet.citrea.xyz
Chain ID5115
EVMShanghai (no Cancun)
CurrencycBTC
FaucetCitrea Discord #faucet
Explorerexplorer.testnet.citrea.xyz

Citrea System Contracts

ContractAddress
Bitcoin Light Client0x3100000000000000000000000000000000000001
Clementine Bridge0x3100000000000000000000000000000000000002
Schnorr Precompile (BIP-340)0x0000000000000000000000000000000000000200

Clementine Bridge

  • Peg-in (BTC → cBTC): send BTC to deposit address → bridge validates via Light Client → mints cBTC
  • Peg-out (cBTC → BTC): burn cBTC → operator pays BTC → dispute via BitVM if needed
  • Testnet: 100-confirmation depth, non-trivial minimum deposit. Use Discord faucet for cBTC.

Quick Start

npm install
npx hardhat compile
npx hardhat test                        # 14 Solidity tests
cd taproot-reader && cargo test         # 68 Rust tests
npx hardhat run scripts/demo-flow.ts    # local demo

Test Suite

Solidity Tests (Hardhat 3, node:test)

14 tests in test/BINSTPilot.ts covering the full contract lifecycle:

#TestWhat it verifies
1Deploy BINSTDeployerFactory deploys, owner set correctly
2Create InstitutioncreateInstitution() emits event, stores name/admin
3Set Inscription IDAdmin can bind Bitcoin inscription
4Set Rune IDAdmin can bind Rune for membership
5Add MemberAdmin adds member, isMember returns true
6Remove MemberAdmin removes member, isMember returns false
7Create Process TemplateTemplate created with correct step names
8Create Process InstanceInstance linked to template and institution
9Execute StepFirst step executed, state updated
10Execute All StepsSequential execution through all steps
11Complete InstanceFinal step marks instance as completed
12Access ControlNon-admin calls revert with correct error
13Set btcPubkeyAdmin can set Bitcoin x-only pubkey
14btcPubkey validationRejects zero pubkey and non-admin calls
npx hardhat test          # runs all 14

Rust Tests (cargo test)

79 tests across 4 crates in taproot-reader/:

binst-inscription (10 tests)

TestWhat it verifies
extract_binst_inscriptionParses binst metaprotocol envelope from witness
extract_with_parent_tagHandles parent inscription tag
no_envelope_in_random_dataRejects non-envelope witness data
ignore_non_binst_inscriptionSkips non-binst metaprotocol
handle_pushdata1Handles OP_PUSHDATA1 encoding
parse_institutionParses institution JSON body
parse_process_templateParses template JSON body
parse_step_executionParses step execution JSON body
parse_state_digestParses state digest body
reject_unknown_typeRejects unknown entity type

binst-decoder (27 unit + 14 value + 11 vault + 5 e2e = 57 tests)

TestWhat it verifies
registry_build_lookup_populates_tableForward-hash lookup table is populated
registry_lookupRegistry resolves address to contract type
registry_resolves_known_slotKnown slot hash maps to BINST field
map_state_diff_finds_binst_entriesState diff entries matched to BINST
map_state_diff_array_elementArray slot elements decoded correctly
decode_deployer_elementsDeployer storage slots decoded
decode_institution_simple_slotsInstitution simple fields decoded
decode_institution_members_array_elementInstitution members array decoded
decode_instance_completedInstance completion flag decoded
decode_instance_step_stateInstance step state decoded
slot_to_u64_simple/largeU256 → u64 conversion
sub_words_basic/underflowWord subtraction arithmetic
evm_storage_hash_*JMT key computation
parse_evm_storage/header/account/indexJMT key prefix parsing
summarize_mixed_diffJMT diff categorization
keccak256_known_vectorKeccak256 matches known output
array_base/element_slotArray storage layout
mapping_slot_addressMapping storage layout
add_word_offsetU256 word offset addition
full_pipeline_* (5 e2e)End-to-end: proof → registry → BINST changes

Vault module (vault — 11 tests)

TestWhat it verifies
compile_produces_descriptorPolicy compiles to tr(NUMS, {…}) descriptor
testnet_address_starts_with_tb1pTestnet address is valid Taproot bech32m
mainnet_address_starts_with_bc1pMainnet address is valid Taproot bech32m
csv_delay_one_compilesMinimum valid CSV delay compiles
csv_delay_zero_rejectedCSV delay = 0 rejected (miniscript minimum is 1)
analyze_returns_two_pathsTwo spending paths: admin + committee
admin_path_has_timelockAdmin path has CSV = 144, requires 1 key
committee_path_is_immediateCommittee path has no timelock, requires 3 keys
witness_sizes_are_reasonableAll paths < 200 vbytes
descriptor_round_tripsparse(format(descriptor)) == descriptor
address_accessor_worksNetwork-specific address accessor returns correct prefix

Value decoding (value module — 14 tests)

TestWhat it verifies
decode_address_from_wordAddress extracted from last 20 bytes of BE word
decode_uint256_smallSmall integer decoded from big-endian bytes
decode_uint256_zeroZero value decoded correctly
decode_uint256_timestampTimestamp-sized integer decoded
decode_bool_trueNon-zero byte → true
decode_bool_falseZero bytes → false
decode_bytes32_pubkeyFull 32-byte hex preserved
decode_short_stringInline Solidity string (≤31 chars) decoded
decode_short_string_emptyEmpty string slot decoded
decode_long_stringLong string length marker detected
decode_step_state_completedPacked StepState: status + actor extracted
decode_value_deletedNone raw value → DELETED
decode_value_from_hexFull pipeline: Citrea LE hex → BE → decoded address
field_type_coverageEvery FieldChange variant has a type mapping

citrea-decoder (7 tests)

TestWhat it verifies
parse_real_sequencer_commitmentParses real commitment from witness data
reject_too_shortRejects truncated input
batch_proof_output_roundtripProof output serialize/deserialize
heuristic_finds_embedded_journalHeuristic journal extraction
decompress_empty_failsBrotli rejects empty input
decompress_garbage_failsBrotli rejects random data
brotli_roundtripBrotli compress → decompress roundtrip

cli (5 tests)

TestWhat it verifies
state_diff_from_rpc_basicRPC state diff hex map conversion
state_diff_from_rpc_no_prefixHandles keys without 0x prefix
decode_address_array_emptyABI decodes empty address[]
decode_address_array_two_addrsABI decodes two-element address[]
decode_address_array_too_shortRejects truncated ABI data
cd taproot-reader && cargo test    # runs all 79

Running Everything

# From project root
npx hardhat test && cd taproot-reader && cargo test
# 14 Solidity + 79 Rust = 93 total tests

No external services required — all tests run against local state.

Use Cases

The pilot's architecture — institution + process template + process instance — is generic. Any multi-step workflow that benefits from on-chain auditability and Bitcoin-anchored identity can be modeled.


What the pilot demonstrates

The demo-flow.ts script runs a complete lifecycle:

  1. Deploy an institution with a named admin
  2. Bind a Bitcoin inscription ID to the institution
  3. Add members to the institution
  4. Create a process template with defined steps
  5. Instantiate the process
  6. Execute each step sequentially, recording actor and timestamp
  7. Complete the process instance

This generic flow maps to any domain where who did what, when, and in what order matters.

Example mappings

DomainInstitutionProcess TemplateSteps
Public adminMunicipal officePermit applicationSubmit → Review → Approve/Reject
Private sectorCompany HRHiring workflowPost → Screen → Interview → Offer
LegalArbitration bodyDispute resolutionFile → Assign mediator → Hear → Decide
GovernanceCommunity orgProposal lifecycleDraft → Discuss → Vote → Execute
Supply chainManufacturerQuality inspectionSample → Test → Report → Certify

Each row is a different parameterization of the same four contracts. The pilot doesn't implement domain-specific logic — it provides the framework that any of these domains can plug into.

Contributing

BINST is an open-source project under the Bitcoin-Institutions organization.

Repository

Areas of contribution

AreaStackDescription
Smart contractsSolidity 0.8.24, Hardhat 3Extend or improve the four core contracts
Taproot ReaderRust, no_stdImprove Bitcoin transaction parsing, add crates
Scripts & toolingTypeScript, ViemNew protocol demonstrations, CLI tools
Inscription toolingRust, ordImprove binst metaprotocol parsing and indexing
DocumentationMarkdown, mdbookImprove or translate this book
TestingTypeScript, RustExpand test coverage across all layers

Development setup

# Clone the pilot
git clone https://github.com/Bitcoin-Institutions/binst-pilot.git
cd binst-pilot

# Solidity
npm install
npx hardhat compile
npx hardhat test

# Rust
cd taproot-reader
cargo build
cargo test

License

MIT — see LICENSE.

References