Bitcoin support in Simplex: how I think it should work

Proposal 1: the chain is picked in Simplex.toml

[build]
target = "bitcoin"   # default is "elements"

This decides the jet set, the tapleaf and address format, the transaction environment, the sighash, and which nodes simplex regtest / simplex test start.

--target CLI flag would override [build] target.

Proposal 2: libraries are compiled with the consumer’s target

A dependency’s own [build.target] only applies when the library builds itself, so simplex install would ignore it.

Library authors keep chain-specific functions in their own modules (std already does) and check both chains in CI:

simplex build --target elements

simplex build --target bitcoin

Proposal 3: a Chain trait with Elements and Bitcoin impls

Chain-specific behaviour goes behind a small trait:

pub trait Chain {
    type Network;
    type Tx;
    type Pst;
    type Env: JetEnvironment;
    type Address;

    fn jet_hinter() -> Box<dyn JetHinter>;
    fn leaf_version() -> LeafVersion;
    fn env(program: &Program, pst: &Self::Pst, input: usize, network: &Self::Network)
        -> Result<Self::Env, ProgramError>;
}

pub struct Elements;
pub struct Bitcoin;

Where each attribute is used:

- Chain::jet_hinter(): wherever .simf is compiled, i.e. simplex build codegen, include_simf!, Program::load(), and the pruning logger. The hinter decides which jet:: names exist and what they compile to, so it determines the CMR. These four examples have hardcoded ElementsJetHinter now, generated bindings will pass the project’s hinter into Program::new instead.

- Chain::leaf_version(): the taproot builder behind Program::get_tr_address and the control block. Elements uses 0xbe; Bitcoin would use the value Inquisition defines.

- Chain::env(): Program::execute, which hands it to prune_with_tracker and BitMachine::exec, and Signer::sign_program, which reads sighash_all from it.

- Chain::Network, Chain::Address: get_tr_address / get_script_pubkey.

- Chain::Tx, Chain::Pst: transaction builder, signer and providers.

Code that does not touch chain types stays as it is and serves both chains: compilation cache, ABI, storage tap tree, SigMessage, witness injection, fee estimation, key derivation.

The transaction builder, signer and providers do touch chain types (PSET vs PSBT, elements::TxOut vs bitcoin::TxOut), so each would have one implementation per chain: simplex::elements::{FinalTransaction, Signer, EsploraProvider, ...} and simplex::bitcoin::{...}. The project’s `target` decides which one is used:

  • codegen imports types from simplex::elements or simplex::bitcoin in the generated bindings
  • simplex test builds TestContext with that chain’s signer and provider
  • simplex regtest starts elementsd + electrs or bitcoind + electrs-bitcoin

Proposal 4: simplexup ships the Bitcoin node pair in the default install

Today simplexup installs one signed .tar per Simplex version (simplex, elementsd, electrs, smplx-nextest) and simplex regtest finds the node binaries on PATH by name. I propose:

  • From new version the same .tar also would contain bitcoind (Bitcoin Inquisition with Simplicity, pinned to one tag) and electrs-bitcoin (Blockstream electrs without liquid).
  • simplex regtest / simplex test pick the pair from target.
  • [regtest] bitcoind = "..." and electrs = "..." (plus env vars) override the paths for people who build their own node.
2 Likes

I personally tend to like proposal 1 because it feels least surprising to me. However, maybe there are some use cases that the other proposals help with.