I think I understand at a high level the goals of both CheckTemplateVerify (CTV, BIP 119) and the Sapio language.

Is there anything specific to the design of the Sapio language that makes it well suited to writing covenant scripts? Other than being a high level language like Minsc that eventually compiles down to Miniscript?

To use Sapio for CTV scripts, both Miniscript and Script have to support CTV. So is there a test environment (e.g. signet or sidechain) where CTV is enabled for both Miniscript and Script?

1 Answer 1


excerpted from Designing Bitcoin Contracts with Sapio

How do we think about Smart Contracts and CTV?

Before CTV, in most Bitcoin smart contracts, we think at the key-level. That is, what is a complex set of signers and satisfactions to unlock a specific coin. But once we unlock a coin, the smart contract usually does not encode any further restrictions on how it may be spent.

You could think of this as "a key to a car". If it unlocks the car, you can take the car wherever you want.

With CTV, we hope to encode a bit more information about how coins should move by providing the paths that the coins must move through as well. So rather than just being the key to a car, you could think of it a bit more like the keys to train -- still required to start the engine, but you have to stay on the tracks and there is a finite number of tracks to pick at any juncture.

That's all a bit abstract. Think back to the Hello World example we saw earlier. We created a coin with the following options:

  1. Alice and Bob Agree --> coin goes anywhere
  2. Timeout --> coins go back to Alice and Bob

Now imagine we wanted to change the rules a little. What if instead of rule 2 apply after a timeout, what if we wanted the timeout to be measured from the time that Alice or Bob claimed they wanted to use the escrow.

This puts us in a little bit of a pickle. Sure we could just re-write the rules:

  1. Alice and Bob Agree --> coin goes anywhere
  2. Timeout since Alice or Bob requested --> coins go back to Alice and Bob

But Bitcoin doesn't have a script level notion of "since" a part of a witness was constructed. The CTV way to think of this script is to define a state machine with two states S in {Normal, Closing} and the rules:

  • S <-- Normal:

    1. Alice and Bob Agree --> coin goes anywhere
    2. Alice or Bob Requested --> (S <-- Closing)
  • S <-- Closing:

    1. Alice and Bob Agree --> coin goes anywhere
    2. Timeout since (S <-- Closing) --> coins go back to Alice and Bob.

What drives the transition from Normal to Closing? Just a standard Bitcoin transaction!

So What is Sapio

Sapio is an embedded domain specific language for defining these sorts of state transition rules to build smart contracts for Bitcoin.

CTV is used as the mechanism to enforce that specific state transitions occur.

When we write a program in Sapio, we are designing an arbitrary state machine that can run any program.

When we compile a Sapio program, we run that state machine to completion and merkelize the resultant program states into a fixed graph.

As such, Sapio is a very powerful framework for designing Bitcoin smart contracts, but we're constrained to the set of contracts where we can enumerate all possible end states.

BIP-119 Emulation

Changes to Bitcoin take a long time. The star player in making Sapio work is BIP-119, and that might take a while to get merged. To get around this, Sapio provides some tools to enable similar functionality today by emulating BIP-119 with signatures.

The Default Emulator

Sapio CTV Emulators defines implementations of a local emualator that can be used by sapio compiler library users. To use such an emulator, a user can generate a seed and create a contract. After creating the contract and binding it to a specific UTXO, a user should be able to delete the seed, ensuring that only the compiled logic may be used. Alternatively, they can retain the seed and promise not to improperly use it.

This crate also defines logic for servers that want to offer emulator services to remote compilers. This is convenient since the emulator server must be kept secure, so an organization may want it to be more tightly safeguarded.

The emulator definitions include wrapper types that compose individual instances of an emulator into a federated multisig. This is useful for circumstances where a contract is between e.g. 2 parties and both have a emulator server. Then the contract can be "immutable" unless both collude.

To aid in experimentation, Judica, Inc operates a public emulator server for regtest.


How it works

See the source code for more detailed documentation.

CheckTemplateVerify essentially functions as a self-signed transaction. I.e., imagine you could create a public key that could only ever sign a transaction which matched a certain pattern?

To implement this functionality, we use BIP-32 HD keys with public derivation.

On initialization, a server picks a seed S and generates a root public key K from it, and publishes K.

Users generate a transaction T and extract the CheckTemplateVerify hash H for it. They then take H and convert it into a derivation path D of 8 u32's and 1 u8 for non-hardened derivation (see hash_to_child_vec).

This derivation path is then applied to K to generate a key C. This key is added with a CheckSig(SIGHASH_ALL) to the script in place of a CTV clause.

Then, when a user desires to spend an output with such a key, they create the entire transaction they want to occur and send it to the the emualtor server.

Without even checking to see that the key is used in the transaction, the server generates the template hash H' (which should equal H) and then signs, returning the signature to the client.

Before creating a contract, clients may wish to collect all possible signatures required to prevent an availability fault.

This scheme has the benefit that:

  1. contract specification can occur without any online processes
  2. The server has no intelligent logic, all guarantees are structural.
  3. Server is completely stateless.
  4. Availability/malfeasance can be controlled for with multisig
  5. 1:1 functionality mapping to CTV

The downside of this approach to emulation is that:

  1. It is somewhat inefficient for scripts which have many branched possibilities.
  2. No inherent mechanism to delete keys after use to protect against future exfiltration.

Why BIP-32

We use BIP-32 because it is a well studied primitive and derivation paths are compatible with existing signing hardware. While it is true that a tweak of 32 bytes could be directly applied to the key more efficiently, easier interoperability with existing tools seemed to be the best path.

Customizing Emulator Trait

This emulator trait crate is a base that exports a trait definition and some helper structs that are needed across the sapio ecosystem.

Defining the trait in its own crate allows us to use trait objects in our compiler internals without needing to have the compiler directly depend on e.g. networking primitives.

As a user of the Sapio library, you can define your own custom emulator logic but that's out of scope of this book.

Future Work

There is a plan to make emulation more efficient based on Merkelization, but it is not yet implemented because it messes with the current way the compiler works.

The efficiency issues are also solvable, more or less, with taproot.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.