Jeremy Rubin's Blog

Here you'll find an assorted mix of content from yours truly. I post about a lot of things, but primarily Bitcoin.

categories: Bitcoin, Shenzhen Journey.


Packaging Sapio Applications

Day 21: Rubin's Bitcoin Advent Calendar

Welcome to day 21 of my Bitcoin Advent Calendar. You can see an index of all the posts here or subscribe at judica.org/join to get new posts in your inbox

Today’s a bit of a cheat day for me – not really “new” content, but mostly stuff re-packaged1 from learn.sapio-lang.org.

But it belongs in the series, and is it really plagarism if I wrote it myself?


So you’ve written a Sapio contract and you’re ready to get it out into the world.

How should you release it? How should you use it?

Today’s post covers various ways to deploy and use Sapio contracts.

Note on Open Sourcing:

In general, it is important to make the code available in an open source way, so others can integrate and use your contracts. Rust’s crates system provides a natural place to publish for the time being, although in the future we may build a Sapio specific package manager as smart contracts have some unique differences.

Packaging Contracts via WASM

WASM is “WebAssembly”, or a standard for producing bytecode objects that can be run on any platform. As the name suggests, it was originally designed for use in web browsers as a compiler target for any language to produce code to run safely from untrusted sources.

So what’s it doing in Sapio?

WASM is designed to be cross platform and deterministic, which makes it a great target for smart contracts that we want to be able to be reproduced locally. The determinism also enables our update system. It also makes it relatively safe to run smart contracts provided by untrusted parties as the security of the WASM sandbox prevents bad code from harming or infecting our system.

Sapio Contract objects can be built into WASM binaries very easily. The code required is basically:

/// MyContract must support Deserialize and JsonSchema
#[derive(Deserialize, JsonSchema)]
struct MyContract;
impl Contract for MyContract{\*...*\};
/// binds to the plugin interface -- only one REGISTER macro permitted per project
REGISTER![MyContract];

See the example for more details. The best way to make a new plugin is just to copy that directory and update the Cargo.toml with a new name.

These compiled objects require a special environment to be interacted with. That environment is provided by the Sapio CLI as a standalone binary. It is also possible to use the interface provided by the sapio-wasm-plugin crate to load a plugin from any rust codebase programmatically. Lastly, one could create similar bindings for another platform as long as a WASM interpreter is available.

Cross Module Calls (CMC)

The WASM Plugin Handle architecture permits one WASM plugin to call into another. This is incredibly powerful. What this enables one to do is to package Sapio contracts that are generic and can call one another either by hash (with effective subresource integrity) or by a nickname (providing easy user customizability).

For example, suppose I was writing a standard contract component C which I publish. Then later, I develop a contract B which is designed to work with C. Rather than having to depend on C’s source code (which I may not want to do for various reasons – for example C could be a standard), I could simply hard code C’s hash into B and call create_contract_by_key(key: &[u8; 32], args: Value, amt: Amount) to get the desired code. The plugin management system automatically searches for a contract plugin with that hash, and tries to call it with the provided JSON arguments. Using create_contract(key:&str, args:Value: amt:Amount), a nickname can be provided in which case the appropriate plugin is resolved by the environment. Lastly, it’s possible to use lookup_this_module_name() to resolve the currently executing modules hash for recursive calls. Recursive CMC calls can be helpful when you want to either make a contract generic, or you want a clean JSON argument interface between units. It’s also possible for a contract to detect if a generic argument would result in a recursive CMC and cut-through it locally.

struct C;
const DEPENDS_ON_MODULE : [u8; 32] = [0;32];
impl Contract for C {
    #[then]
    fn demo(self, ctx: Context) {
        let amt = ctx.funds()/2;
        ctx.template()
            .add_output(amt, &create_contract("users_cold_storage", /**/, amt), None)?
            .add_output(amt, &create_contract_by_key(&DEPENDS_ON_MODULE, /**/, amt), None)?
            .add_output(amt, &create_contract_by_key(&lookup_this_module_name().unwrap(), /**/, amt), None)?
            .into()
    }
}

Typed Calls

Using JSONSchemas, plugins have a basic type system that enables run-time checking for compatibility. Plugins can guarantee they implement particular interfaces faithfully. These interfaces currently only support protecting the call, but make no assurances about the returned value or potential errors from the callee’s implementation of the trait.

For example, suppose I want to be able to specify a provided module must statisfy a calling convention for batching. I define the trait BatchingTraitVersion0_1_1 as follows:

/// A payment to a specific address
#[derive(JsonSchema, Serialize, Deserialize, Clone)]
pub struct Payment {
    /// The amount to send in sats
    pub amount: AmountU64,
    /// # Address
    /// The Address to send to
    pub address: Address,
}
#[derive(Serialize, JsonSchema, Deserialize, Clone)]
pub struct BatchingTraitVersion0_1_1 {
    pub payments: Vec<Payment>,
    pub feerate_per_byte: AmountU64,
}

I can then turn this into a SapioJSONTrait by implementing the trait and providing an “example” function.

impl SapioJSONTrait for BatchingTraitVersion0_1_1 {
    /// required to implement
    fn get_example_for_api_checking() -> Value {
        #[derive(Serialize)]
        enum Versions {
            BatchingTraitVersion0_1_1(BatchingTraitVersion0_1_1),
        }
        serde_json::to_value(Versions::BatchingTraitVersion0_1_1(
            BatchingTraitVersion0_1_1 {
                payments: vec![],
                feerate_per_byte: Amount::from_sat(0).into(),
            },
        ))
        .unwrap()
    }

    /// optionally, this method may be overridden directly for more advanced type checking.
    fn check_trait_implemented(api: &dyn SapioAPIHandle) -> bool {
        Self::check_trait_implemented_inner(api).is_ok()
    }
}

If a contract module can receive the example, then it is considered to have implemented the API. We can implement the receivers for a module as follows:

struct MockContract;
/// # Different Calling Conventions to create a Treepay
#[derive(Serialize, Deserialize, JsonSchema)]
enum Versions {
    /// # Base
    Base(MockContract),
    /// # Batching Trait API
    BatchingTraitVersion0_1_1(BatchingTraitVersion0_1_1),
}
impl From<BatchingTraitVersion0_1_1> for MockContract {
    fn from(args: BatchingTraitVersion0_1_1) -> Self {
        MockContract
    }
}
impl From<Versions> for TreePay {
    fn from(v: Versions) -> TreePay {
        match v {
            Versions::Base(v) => v,
            Versions::BatchingTraitVersion0_1_1(v) => v.into(),
        }
    }
}
REGISTER![[MockContract, Versions], "logo.png"];

Now MockContract can be called via the BatchingTraitVersion0_1_1 trait interface.

Another module in the future need only have a field SapioHostAPI<BatchingTraitVersion0_1_1>. This type verifies at deserialize time that the provided name or hash key implements the required interface(s).

Future Work on Cross Module Calls

  • Gitian Packaging: Using a gitian signed packaging distribution system would enable a user to set up a web-of-trust setting for their sapio compiler and enable fetching of sub-resources by hash if they’ve been signed by the appropriate parties.
  • NameSpace Registration: A system to allow people to register names unambiguously would aid in ensuring no conflicts. For now, we can handle this using a centralized repo.
  • Remote CMC: In some cases, we may want to make a call to a remote server that will call a given module for us. This might be desirable if the server holds sensitive material that we shouldn’t have.
  • Polymorphic CMC: currently, CMC’s only return the Compiled type. Perhaps future CMC support can return arbitrary types, allowing other types of functionality to be packaged. For example, it would be great if a guard clause could be generated just from a separate WASM module.

What if I don’t want WASM?

Well, ngmi. JK. Kinda.

You do really want WASM. You very much want your contracts to be deterministically compiled. If they are not, then a lot of things are not guaranteed to work correctly and you might lose funds.

We’re very focused on run-in WASM and not focused on other things.

That said, Sapio is just a Rust library, so you can embed your contracts into an application directly, e.g., for an embedded signing device.

If you do this it is paramount that you carefully audit and check that you are able to get consistent deterministic results out, or that you do not need to be able to deterministically recompile (this is true in many cases!) and can save the compilation result.

Another technique you can use is to build a bigger application around a contract and then compile that to a WASM blob. Also works fine if you’re careful not to accidentally add some entropy.


That’s all folks. In sum: Sapio is using WASM, you can choose to not use it at your own peril.

  1. pun certainly intended. 



Oracles, Bonds, and Attestation Chains

Day 20: Rubin's Bitcoin Advent Calendar

Welcome to day 20 of my Bitcoin Advent Calendar. You can see an index of all the posts here or subscribe at judica.org/join to get new posts in your inbox

Today’s post is going to be a bit lighter weight than yesterday’s. We’ll cover some high level concepts around oracles and then look at some Sapio.

The genesis of this line of inquiry was a conversation with Robin Linus that led to a pretty cool whitepaper, so definitely read that if you find this post compelling.


Oracles

Oracles are cool! The most basic form of an useful bitcoin oracle is just a signing key that signs transactions or reveals information that it “should” according to some rule.

Protocols for oracles like discrete log contract oracles produce more generic “key material reveals”, that are more similar to releasing information that allows counterparties to decrypt the relevant signature.

One of the problems with oracles is that they can equivocate, that is, sign multiple conflicting statements. It would be nice if we could esnure that they would be consistent, no?

Bonded Oracles

In order to make the oracles consistent, what we can do is set up our oracles such that if the oracle ever signs two statements they reveal their private key to the world. The common way that this is done is via nonce reuse, which is essentially a way that you can extract a private key from a signature on messages m1 and m2 using the same nonce r1.

While revealing a key might be punishment enough, we can do one better. We can require that if a nonce is leaked, meaning some statement was equivocated, then a some bitcoin protected by that key can be ‘stolen’ by anyone.

But this form is a little problematic, for a few reasons. Reason one is that the oracle could cancel their bond and take it back while there are still contracts settling with their data that they then equivocate on.

The other issue is that the funds in the punishment could be claimed by anyone, including a miner or the oracle themselves, and especially if oracles are also miners!

To fix the first issue, we need to lock up the fund for e.g. 2 weeks and only use the oracle for the first week to permit 1 week gap in closing. This creates a new issue that bonds are always expiring, but maybe that’s OK.

To fix the second issue, we need a way of restricting where the funds go to definitely be out of reach of any bad guys, e.g. burned.

CTV Fixes This.

If you had checktemplateverify, you could stipulate that a bonded oracle must initiate a bond redemption on chain, at which point anyone can challenge it if they know the key and they are guaranteed sufficient time to post a challenge.

The second fix is that CTV can stipulate that the funds must be burned by sending to an OP_RETURN, not released to miners (which would be problematic if a miner was also an oracle).

Now our oracle is ready to sign all sorts of stuff, and we can make sure that for a given Nonce we never sign two conflicting statements.

DLCs?

We can now use this type of oracle for a DLC protocol. We just create the contract and then we sign+reveal using our staking key whatever messages are required. Any cheating, and anyone who detects it can burn our money.

Attestation Chains

One of the other cools things we can do with our Bonded oracle is to sign a chain of attestations.

For example, we could sign message 1, and then sign message 2, and then sign message 3.

We can turn this into a “blockchain” of sorts if when we sign m2 we include a hash of m1, and when we sign m3 we include a hash of m3.

But we can go a step further. If we’re careful, we can set it up so that ‘branching’ on any message in the chain (by equivocating/producing a conflicting statement) leaks the key of the bonded oracle with a trick I (think?) I came up with. Here’s roughly how it works:

message 1: INIT with PK K, nonce R1 for m2, 1 BTC at risk in output X
message 2: SIGN with K, R1 H(m1), nonce R2 for m3
message 3: SIGN with K, R2 H(m1), nonce R3 for m4

If the oracle were to ever branch, it would look like this:

message 1: INIT with PK K, nonce R1, 1 BTC at risk in output X
message 2: SIGN with K, R1 H(m1), nonce R2
message 3: SIGN with K, R2 H(m2), nonce R3
message 3': SIGN with K, R2 H(m3), nonce R3'

The leak would be able to extract K’s secret key via the reuse of R2.

While it might seem that you could ‘get away with it’, because we verify at each step that the last used nonce was from the prior step it cannot be forged. The commitment to H(mi) also makes it more difficult for an invalid signature to float around since from just the top you can know what all the other states should be.

Proof of Stake?

Essentially we’ve built a system for proof-of-stake on Bitcoin. Imagine you have 100BTC locked up in these contracts across 127 instances, and you want to run some system based on it.

You can just download the message signed at state Mn and see what the majority of signers voted for that slot.

Any signer who cheats gets their funds burned, and you’d learn to exclude them from consensus.

If you do need to have a ‘rollback’, you can do it by engineering your protocol to allow new updates to the chain of signatures to produce a rollback.

Partial Slashing

You can even implement partial slashing. Suppose you have 10 coins in a contract under key K1. If a cheat is detected, it authorizes a txn which burns 2 and puts the remaining 8 into key K2. The next round of slashing could put 6.4 under K3.

Alternatives to Burning

Burning sats is sad. What if instead of a burn, coins went into an annuity that would be claimable 100 years from now? That way, no economic agents around today can plan to cheat and capture the value of it, but the burned coins can serve a real function. While this is slightly less secure than a full burn, it’s also more secure since it creates an incentive to continue to build the chain.

Or donate to a well known chairty address/developer fund :p


Implementing a Staked Signer

To begin, we’ll define some ‘type tags’. This is a technique in rust where we define empty structs that let us build a little state machine in the type system. You can read more on the technique here.

/// # Operational State
/// State where stakes should be recognized for voting
#[derive(JsonSchema, Deserialize)]
pub struct Operational;
/// # Closing State
/// State where stakes are closing and waiting evidence of misbehavior
#[derive(JsonSchema, Deserialize)]
struct Closing;
/// # Staking States (Operational, Closing)
/// enum trait for states
pub trait StakingState {}
impl StakingState for Operational {}
impl StakingState for Closing {}

Next, we’ll define an interface that an implementation of a Staked Signer should implement:

By default something that is declared is given a default not-present implementation.

/// Functional Interface for Staking Contracts
pub trait StakerInterface
where
    Self: Sized,
{
    decl_guard!(
        /// The key used to sign messages
        staking_key
    );
    decl_guard!(
        /// the clause to begin a close process
        begin_redeem_key
    );
    decl_guard!(
        /// the clause to finish a close process
        finish_redeem_key
    );
    decl_then!(
        /// The transition from Operational to Closing
        begin_redeem
    );

    /// Why would anyone ever cheat!!
    #[then(guarded_by = "[Self::staking_key]")]
    fn cheated(self, ctx: sapio::Context) {
        let f = ctx.funds();
        ctx.template()
            // commit to metadata here for convenience, but really could be anywhere!
            // exercise for reader: what if we plugged in another instance of StakerInterface
            // that:
            // 1. switches to a new, unburned key
            // 2. pays 80% to the new StakerInterface
            // 3. pays 20% to an annuity that pays miners over e.g. 1000 blocks
            //    at some point in the far future.
            .add_output(f, &Compiled::from_op_return(&self.data.as_inner()[..])?, None)?
            .into()
    }
}

/// We can delcare the Contract impl for all valid Staker<T>
impl<T: 'static + StakingState> Contract for Staker<T>
where
    Staker<T>: StakerInterface,
    T: StakingState,
{
    declare! {then, Self::begin_redeem, Self::cheated}
    declare! {finish, Self::finish_redeem_key}
    declare! {non updatable}
}

Next, we’ll define the data required for our staker:

/// # Staker: A Bonded Signing Contract
/// Staker is a contract that proceeds from Operational -> Closing
/// During it's lifetime, many things can be signed with signing_key,
/// but should the key ever leak (e.g., via nonce reuse) the bonded
/// funds can be burned.
///
/// Burning is important v.s. miner fee because otherwise the staker
/// can bribe (or be a miner themselves) to cheat.
#[derive(JsonSchema, Deserialize)]
pub struct Staker<T: StakingState> {
    /// # Timeout
    /// How long to wait for evidence after closing
    timeout: AnyRelTimeLock,
    /// # Signing Key
    /// The key that if leaked can burn funds
    signing_key: PublicKey,
    /// # Redemption Key
    /// The key that will be used to control & return the redeemed funds
    redeeming_key: PublicKey,
    /// # Data
    /// Arbitrary hash of metadata that is needed to start the attestation chain
    data: sha256::Hash,
    /// current contract state.
    #[serde(skip, default)]
    state: PhantomData<T>,
}

Next, we’ll define the StakerInterface when our channel is operational. At this phase, funds can either be burnt or the redeeming key can start the process of withdrawing.

impl StakerInterface for Staker<Operational> {
    /// redeeming key
    #[guard]
    fn begin_redeem_key(self, _ctx: Context) {
        Clause::Key(self.redeeming_key)
    }
    /// begin redemption process
    #[then(guarded_by = "[Self::begin_redeem_key]")]
    fn begin_redeem(self, ctx: sapio::Context) {
        let f = ctx.funds();
        ctx.template()
            .add_output(
                f,
                &Staker::<Closing> {
                    state: Default::default(),
                    timeout: self.timeout,
                    signing_key: self.signing_key,
                    redeeming_key: self.redeeming_key,
                },
                None,
            )?
            .into()
    }
    /// staking key
    #[guard]
    fn staking_key(self, _ctx: Context) {
        Clause::Key(self.signing_key)
    }
}

Lastly, for closing we should not be able to “loop” back into Closing or Operational, so we do not implement the begin_redeem logic.

impl StakerInterface for Staker<Closing> {
    #[guard]
    fn finish_redeem_key(self, _ctx: Context) {
        Clause::And(vec![Clause::Key(self.redeeming_key), self.timeout.into()])
    }
    #[guard]
    fn staking_key(self, _ctx: Context) {
        Clause::Key(self.signing_key)
    }
}

Attestation Chain

In order to start the attestation chain, the data field should be the hash of something like:

struct AttestationStart {
    /// # Nonce
    /// a nonce element
    first_nonce: [0u8; 32],
    /// # Key
    /// the key to sign with (for convenience, should match the StakedSigner's
    /// staking key)
    key: PublicKey,
    /// # Purpose
    /// useful to have some sort of description (machine readable) of what this attestor
    /// is signing for
    purpose: Vec<u8>
}

To start using the attestation chain, we build a linked list of Attest signatures as described below:

enum Either<T, U> {
    Left(T),
    Right(U)
}
struct Attest {
    /// # Signature
    /// the signature over the below data fields
    sig: Signature,
    /// # Message
    /// whatever info the protocol expects to be signed
    message: Vec<u8>,
    /// # Nonce
    /// a nonce element
    next_nonce: [0u8; 32],
    /// # Height
    /// what # signature is this
    height: u64,
    /// # Previous Attestation
    /// the last attestation. we either keep a hash or the actual value
    prev: Either<Hash, Either<Box<Attest>, AttestationStart>>
}

It would be possible – but perhaps overkill – to instead encode this structure as a Sapio contract with continuation branches. I’ll leave that as an exercise for the reader for now!

Galaxy Brain Time

What if we used this staked signer to coordinate a decentralized mining pool where the stakers sign off on work shares they have seen…

That’s All Folks!



Part One: Implementing NFTs in Sapio

Day 19: Rubin's Bitcoin Advent Calendar

Welcome to day 19 of my Bitcoin Advent Calendar. You can see an index of all the posts here or subscribe at judica.org/join to get new posts in your inbox

For today’s post we’re going to build out some Sapio NFT protocols that are client-side verifiable. Today we’ll focus on code, tomorrow we’ll do more discussion and showing how they work. I was sick last night (minor burrito oriented food poisoning suspected) and so I got behind, hence this post being up late.

As usual, the disclaimer that as I’ve been behind… so we’re less focused today on correctness and more focused on giving you the shape of the idea. In other words, I’m almost positive it won’t work properly, but it can compile! And the general flow looks correct.

There’s also a couple new concepts I want to adopt as I’ve been working on this, so those are things that will have to happen as I refine this idea to be production grade.


Before we start, let’s get an eagle-eye view of the ‘system’ we’re going to be building, because it represents multiple modules and logical components.

By the end, we’ll have 5 separate things:

  1. An Abstract NFT Interface
  2. An Abstract Sellable Interface
  3. An Abstract Sale Interface
  4. A Concrete Sellable NFT (Simple NFT)
  5. A Concrete Sale Interface (Simple NFT Sale)

In words:

Simple NFT implements both NFT and Sellable, and has a sell function that can be called with any Sale module.

Simple NFT Sale implements Sale, and can be used with the sell of anything that implements Sellable and NFT.

We can make other implementations of Sale and NFT and they should be compatible.

How’s it going to ‘work’?

Essentially how this is going to work do is

  1. An artist mint an NFT.
  2. The artist can sell it to anyone whose bids the artist accepts

Normally, in Ethereum NFTs, you could do something for step 2:

  • The artist signs “anyone can buy at this price”

with Bitcoin NFTs, it’s a little different. The artist has to run a server that accepts bids above the owner’s current price threshold and returns signed under-funded transaction that would pay the owner the asking price. Alternatively, the bidder can send an open-bid that the owner can fill immediately.

Because Sapio is super-duper cool, we can make abstract interfaces for this stuff so that NFTs can have lots of neat features like enforcing royalties, dutch auction prices, batch minting, generative art minting, and more. We’ll see a bit more tomorrow.

Client validation is central to this story. A lot of the rules are not enforced by the Bitcoin blockchain. They are, however, enforced by requiring that the ‘auditor’ be able to re-reach the same identical contract state by re-compiling the entire contract from the start. I.e., as long as you generate all your state transitions through Sapio, you can verify that an NFT is ‘authentic’. Of course, anyone can ‘burn’ an NFT if they want by sending e.g. to an unknown key. Client side validation just posits that sending to an unknown key is ‘on the same level’ of error as corrupting an NFT by doing state transitions without having the corresponding ‘witness’ of sapio effects to generate the transfer.

Please re-read this section after you get throught the code (I’ll remind you).


Declaring an NFT Minting Interface

First we are going to declare the basic information for a NFT.

Every NFT should have a owner (PublicKey) and a locator (some url, IPFS hash, etc).

NFTs also should track which Sapio module was used to mint them, to ensure compatibility going forward. If it’s not known, modules can try to fill it in and guess (e.g., a good gues is “this module”).

Let’s put that to code:

/// # Trait for a Mintable NFT
#[derive(Serialize, JsonSchema, Deserialize, Clone)]
pub struct Mint_NFT_Trait_Version_0_1_0 {
    /// # Initial Owner
    /// The key that will own this NFT
    pub owner: bitcoin::PublicKey,
    /// # Locator
    /// A piece of information that will instruct us where the NFT can be
    /// downloaded -- e.g. an IPFs Hash
    pub locator: String,
    /// # Minting Module
    /// If a specific sub-module is to be used / known -- when in doubt, should
    /// be None.
    pub minting_module: Option<SapioHostAPI<Mint_NFT_Trait_Version_0_1_0>>,
}

/// Boilerplate for the Mint trait
pub mod mint_impl {
    use super::*;
    #[derive(Serialize, Deserialize, JsonSchema)]
    pub enum Versions {
        Mint_NFT_Trait_Version_0_1_0(Mint_NFT_Trait_Version_0_1_0),
    }
    /// we must provide an example!
    impl SapioJSONTrait for Mint_NFT_Trait_Version_0_1_0 {
        fn get_example_for_api_checking() -> Value {
            let key = "02996fe4ed5943b281ca8cac92b2d0761f36cc735820579da355b737fb94b828fa";
            let ipfs_hash = "bafkreig7r2tdlwqxzlwnd7aqhkkvzjqv53oyrkfnhksijkvmc6k57uqk6a";
            serde_json::to_value(mint_impl::Versions::Mint_NFT_Trait_Version_0_1_0(
                Mint_NFT_Trait_Version_0_1_0 {
                    owner: bitcoin::PublicKey::from_str(key).unwrap(),
                    locator: ipfs_hash.into(),
                    minting_module: None,
                },
            ))
            .unwrap()
        }
    }
}

Shaweeeeet! We have an NFT Minting Interface!

But you can’t actually use it to Mint yet, since we lack an Implementation.

Before we implement it…

What are NFTs Good For? Selling! (Sales Interface)

If you have an NFT, you probably will want to sell it in the future. Let’s declare a sales interface.

To sell an NFT we need to know:

  1. Who currently owns it
  2. Who is buying it
  3. What they are paying for it
  4. Maybe some extra stuff

/// # NFT Sale Trait
/// A trait for coordinating a sale of an NFT
#[derive(Serialize, JsonSchema, Deserialize, Clone)]
pub struct NFT_Sale_Trait_Version_0_1_0 {
    /// # Owner
    /// The key that will own this NFT
    pub sell_to: bitcoin::PublicKey,
    /// # Price
    /// The price in Sats
    pub price: AmountU64,
    /// # NFT
    /// The NFT's Current Info
    pub data: Mint_NFT_Trait_Version_0_1_0,
    /// # Extra Information
    /// Extra information required by this contract, if any.
    /// Must be Optional for consumer or typechecking will fail.
    /// Usually None unless you know better!
    pub extra: Option<Value>,
}

/// Boilerplate for the Sale trait
pub mod sale_impl {
    use super::*;
    #[derive(Serialize, Deserialize, JsonSchema)]
    pub enum Versions {
        /// # Batching Trait API
        NFT_Sale_Trait_Version_0_1_0(NFT_Sale_Trait_Version_0_1_0),
    }
    impl SapioJSONTrait for NFT_Sale_Trait_Version_0_1_0 {
        fn get_example_for_api_checking() -> Value {
            let key = "02996fe4ed5943b281ca8cac92b2d0761f36cc735820579da355b737fb94b828fa";
            let ipfs_hash = "bafkreig7r2tdlwqxzlwnd7aqhkkvzjqv53oyrkfnhksijkvmc6k57uqk6a";
            serde_json::to_value(sale_impl::Versions::NFT_Sale_Trait_Version_0_1_0(
                NFT_Sale_Trait_Version_0_1_0 {
                    sell_to: bitcoin::PublicKey::from_str(key).unwrap(),
                    price: Amount::from_sat(0).into(),
                    data: Mint_NFT_Trait_Version_0_1_0 {
                        owner: bitcoin::PublicKey::from_str(key).unwrap(),
                        locator: ipfs_hash.into(),
                        minting_module: None,
                    },
                    extra: None,
                },
            ))
            .unwrap()
        }
    }
}

That’s the interface for the contract that sells the NFTs. We also need an interface for NFTs that want to initiate a sale.

To do that, we need to know:

  1. What kind of sale we are doing
  2. The data for that sale

This is really just expressing that we need to bind a NFT Sale Implementation to our contract. We can express the sale interface as follows.



/// # Sellable NFT Function
/// If a NFT should be sellable, it should have this trait implemented.
pub trait SellableNFT: Contract {
    decl_continuation! {<web={}> sell<Sell>}
}
/// # Sell Instructions
#[derive(Serialize, Deserialize, JsonSchema)]
pub enum Sell {
    /// # Hold
    /// Don't transfer this NFT
    Hold,
    /// # MakeSale
    /// Transfer this NFT
    MakeSale {
        /// # Which Sale Contract to use?
        /// Specify a hash/name for a contract to generate the sale with.
        which_sale: SapioHostAPI<NFT_Sale_Trait_Version_0_1_0>,
        /// # The information needed to create the sale
        sale_info: NFT_Sale_Trait_Version_0_1_0,
    },
}
impl Default for Sell {
    fn default() -> Sell {
        Sell::Hold
    }
}
impl StatefulArgumentsTrait for Sell {}

Getting Concrete: Making an NFT

Let’s create a really simple NFT now that implements these interfaces.

There’s a bit of boilerplate, so we’ll go section-by-section.

First, let’s declare the SimpleNFT

/// # SimpleNFT
/// A really simple NFT... not much too it!
#[derive(JsonSchema, Serialize, Deserialize)]
pub struct SimpleNFT {
    /// The minting data, and nothing else.
    data: Mint_NFT_Trait_Version_0_1_0,
}

/// # The SimpleNFT Contract
impl Contract for SimpleNFT {
    // NFTs... only good for selling?
    declare! {updatable<Sell>, Self::sell}
    // embeds metadata
    declare! {then, Self::metadata_txns}
}

First, let’s implement the logic for selling the NFT… You remember our old friend the Sales interface?

impl SimpleNFT {
    /// # signed
    /// Get the current owners signature.
    #[guard]
    fn signed(self, ctx: Context) {
        Clause::Key(self.data.owner.clone())
    }
}
fn default_coerce(k: <SimpleNFT as Contract>::StatefulArguments) -> Result<Sell, CompilationError> {
    Ok(k)
}

impl SellableNFT for SimpleNFT {
    #[continuation(guarded_by = "[Self::signed]", web_api, coerce_args = "default_coerce")]
    fn sell(self, ctx: Context, sale: Sell) {
        if let Sell::MakeSale {
            sale_info,
            which_sale,
        } = sale
        {
            // if we're selling...
            if sale_info.data.owner != self.data.owner {
                // Hmmm... metadata mismatch! the current owner does not
                // matched the sale's claimed owner.
                return Err(CompilationError::TerminateCompilation);
            }
            // create a contract from the sale API passed in
            let compiled = Ok(CreateArgs {
                context: ContextualArguments {
                    amount: ctx.funds(),
                    network: ctx.network,
                    effects: unsafe { ctx.get_effects_internal() }.as_ref().clone(),
                },
                arguments: sale_impl::Versions::NFT_Sale_Trait_Version_0_1_0(sale_info.clone()),
            })
            .map(serde_json::to_value)
            // use the sale API we passed in
            .map(|args| create_contract_by_key(&which_sale.key, args, Amount::from_sat(0)))
            // handle errors...
            .map_err(|_| CompilationError::TerminateCompilation)?
            .ok_or(CompilationError::TerminateCompilation)?;
            // send to this sale!
            let mut builder = ctx.template();
            // todo: we need to cut-through the compiled contract address, but this
            // upgrade to Sapio semantics will come Soon™.
            builder = builder.add_output(compiled.amount_range.max(), &compiled, None)?;
            builder.into()
        } else {
            /// Don't do anything if we're holding!
            empty()
        }
    }
}

Next, let’s implement the metadata logic. There are a million ways to do metadata, so feel free to ‘skip’ this section and just let your mind wander on interesting things you could do here…

impl SimpleNFT {
    /// # unspendable
    /// what? This is just a sneaky way of making a provably unspendable branch
    /// (since the preimage of [0u8; 32] hash can never be found). We use that to
    /// help us embed metadata inside of our contract...
    #[guard]
    fn unspendable(self, ctx: Context) {
        Clause::Sha256(sha256::Hash::from_inner([0u8; 32]))
    }
    /// # Metadata TXNs
    /// This metadata TXN is provably unspendable because it is guarded
    /// by `Self::unspendable`. Neat!
    /// Here, we simple embed a OP_RETURN.
    /// But you could imagine tracking (& client side validating)
    /// an entire tree of transactions based on state transitions with these
    /// transactions... in a future post, we'll see more!
    #[then(guarded_by = "[Self::unspendable]")]
    fn metadata_txns(self, ctx: Context) {
        ctx.template()
            .add_output(
                Amount::ZERO,
                &Compiled::from_op_return(
                    &sha256::Hash::hash(&self.data.locator.as_bytes()).as_inner()[..],
                )?,
                None,
            )?
            // note: what if we also comitted to the hash of the wasm module
            // compiling this contract?
            .into()
    }
}

Lastly, some icky boilerplate stuff:

#[derive(Serialize, Deserialize, JsonSchema)]
enum Versions {
    Mint_NFT_Trait_Version_0_1_0(Mint_NFT_Trait_Version_0_1_0),
}

impl TryFrom<Versions> for SimpleNFT {
    type Error = CompilationError;
    fn try_from(v: Versions) -> Result<Self, Self::Error> {
        let Versions::Mint_NFT_Trait_Version_0_1_0(mut data) = v;
        let this = LookupFrom::This
            .try_into()
            .map_err(|_| CompilationError::TerminateCompilation)?;
        match data.minting_module {
            // if no module is provided, it must be this module!
            None => {
                data.minting_module = Some(this);
                Ok(SimpleNFT { data })
            }
            // if a module is provided, we have no idea what to do...
            // unless the module is this module itself!
            Some(ref module) if module.key == this.key => Ok(SimpleNFT { data }),
            _ => Err(CompilationError::TerminateCompilation),
        }
    }
}
REGISTER![[SimpleNFT, Versions], "logo.png"];

Right on! Now we have made a NFT Implementation. We can Mint one, but wait.

How do we sell it?

We need a NFT Sale Implementation

So let’s do it. In today’s post, we’ll implement the most boring lame ass Sale…

Tomorrow we’ll do more fun stuff, I swear.

First, let’s get our boring declarations out of the way:


/// # Simple NFT Sale
/// A Sale which simply transfers the NFT for a fixed price.
#[derive(JsonSchema, Serialize, Deserialize)]
pub struct SimpleNFTSale(NFT_Sale_Trait_Version_0_1_0);

/// # Versions Trait Wrapper
#[derive(Serialize, Deserialize, JsonSchema)]
enum Versions {
    /// # Batching Trait API
    NFT_Sale_Trait_Version_0_1_0(NFT_Sale_Trait_Version_0_1_0),
}
impl Contract for SimpleNFTSale {
    declare! {updatable<()>, Self::transfer}
}
fn default_coerce<T>(_: T) -> Result<(), CompilationError> {
    Ok(())
}
impl From<Versions> for SimpleNFTSale {
    fn from(v: Versions) -> SimpleNFTSale {
        let Versions::NFT_Sale_Trait_Version_0_1_0(x) = v;
        SimpleNFTSale(x)
    }
}

REGISTER![[SimpleNFTSale, Versions], "logo.png"];

Now, onto the logic of a sale!



impl SimpleNFTSale {
    /// # signed
    /// sales must be signed by the current owner
    #[guard]
    fn signed(self, ctx: Context) {
        Clause::Key(self.0.data.owner.clone())
    }
    /// # transfer
    /// transfer exchanges the NFT for cold hard Bitcoinz
    #[continuation(guarded_by = "[Self::signed]", web_api, coerce_args = "default_coerce")]
    fn transfer(self, ctx: Context, u: ()) {
        let amt = ctx.funds();
        // first, let's get the module that should be used to 're-mint' this NFT
        // to the new owner
        let key = self
            .0
            .data
            .minting_module
            .clone()
            .ok_or(CompilationError::TerminateCompilation)?
            .key;
        // let's make a copy of the old nft metadata..
        let mut mint_data = self.0.data.clone();
        // and change the owner to the buyer
        mint_data.owner = self.0.sell_to;
        // let's now compile a new 'mint' of the NFT
        let new_nft_contract = Ok(CreateArgs {
            context: ContextualArguments {
                amount: ctx.funds(),
                network: ctx.network,
                effects: unsafe { ctx.get_effects_internal() }.as_ref().clone(),
            },
            arguments: mint_impl::Versions::Mint_NFT_Trait_Version_0_1_0(mint_data),
        })
        .and_then(serde_json::to_value)
        .map(|args| create_contract_by_key(&key, args, Amount::from_sat(0)))
        .map_err(|_| CompilationError::TerminateCompilation)?
        .ok_or(CompilationError::TerminateCompilation)?;
        // Now for the magic:
        // This is a transaction that creates at output 0 the new nft for the
        // person, and must add another input that pays sufficiently to pay the
        // prior owner an amount.

        // todo: we also could use cut-through here once implemented
        // todo: change seem problematic here? with a bit of work, we could handle it
        // cleanly if the buyer identifys an output they are spending before requesting
        // a purchase.
        ctx.template()
            .add_output(amt, &new_nft_contract, None)?
            .add_amount(self.0.price.into())
            .add_sequence()
            .add_output(self.0.price.into(), &self.0.data.owner, None)?
            // note: what would happen if we had another output that 
            // had a percentage-of-sale royalty to some creator's key?
            .into()
    }
}


And that’s it! Makes sense, right? I hope…

But if not

Re read the part before the code again! Maybe it will be more clear now :)



Sapio Studio Payment Pool Walkthrough

Day 18: Rubin's Bitcoin Advent Calendar

Welcome to day 18 of my Bitcoin Advent Calendar. You can see an index of all the posts here or subscribe at judica.org/join to get new posts in your inbox

Today’s post will be a pretty different format that usual, it’s basically going to be a pictorial walk through of the Sapio Studio, the frontend tool for Sapio projects. As an example, we’ll go through a Payment Pool contract to familiarize ourselves.

I wanted to put this post here, before we get into some more applications, because I want you to start thinking past “cool one-off concepts we can implement” and to start thinking about reusable components we can build and ship into a common Bitcoin Smart Contract software (Sapio Studio or its successors).


At it’s core, Sapio Studio is just a wallet frontend to Bitcoin Core.

You can make a transaction, just like normal…

And see it show up in the pending transactions…

And even mine some regtest blocks.

But where Sapio Studio is different is that there is also the ability to create contracts. Before we can do that, we need to load a WASM Plugin with a compiled contract. Let’s load the Payment Pool module. You can see the code for it here. And now we can see we have a module! Let’s load a few more so it doesn’t look lonely. Now let’s check out the Payment Pool module. Now let’s check out another one – we can see they each have different types of arguments, auto-generated from the code.

Let’s fill out the form with 10 keys to make a Payment Pool controlled by 10 people, and then submit it. What’s that??? It’s a small bug I am fixing :/. Not to worry… Just click repair layout. And the presentation resets. I’ll fix it soon, but it can be useful if there’s a glitch to reset it.

Now we can see the basic structure of the Payment Pool, and how it splits up. Let’s get a closer look… Let’s zoom out (not helpful!)… Let’s zoom back in. Note how the transactions are square boxes and the outputs are rounded rectangles. Blue lines connect transactions to their outputs. Purple lines connect outputs to their (potential) spends. If we click on a transaction we can learn more about it. We even have some actions that we can take, like sending it to the network. Let’s try it…. Oops! We need to sign it first… And then we can send it. What other buttons do we have? What’s this do? It teleports us to the output we are creating! Notice how the output is marked “Updatable”, and there is also a “DO_TX” button (corresponding to the DO_TX in the Payment Pool). Let’s click that… Ooooh. It prompts us with a form to do the transaction! Ok, let’s fill this sucker out… Click submit, then recompile (separate actions in case we want to make multiple “moves” before recompiling). I really need to fix this bug… Voila!

As you can see, the original graph is intact and we’ve augmented onto it the new state transition. The new part has our 0.1 BTC Spend + the re-creation of the Payment Pool with less funds. Ok, let’s go nuts and do another state transition off-of the first one? This time more payouts! Submit…

And Recompile1

I skipped showing you the bug this time.

Now you can see two state transitions! And because we used more payouts than one, we can see some congestion control2 at work.

It works! It all really, really works!


One more thing I can’t show you with this contract is the timing simulator.

This lets you load up a contract (like our Hanukkiah below) and… Simulate the passing of time (or blocks). Pretty cool!

There are also some settings you can configure for display settings, the node, and for sapio-cli. The first time you run Sapio, you’ll need to get some of these things configured correctly or else it will be broken. Currently, if you look here you can find a template for a script to get everything up and running for a first shot at it, otherwise you’ll have to do it by hand, or just change your preferences.json to be similar to mine in the note3.

before you ask…

OF COURSE THERE IS DARK MODE

configured by your local system theme preference


I hope you enjoyed this! There’s a metric fuckload of work still to do to make Sapio Studio & Sapio anywhere near production grade, but I hope this has helped elucidate how powerful and cool the Sapio approach is, and has inspired you to build something and try it out! I’d also be really eager for feedback on what features should be here/are missing.

Lastly, if you’re just excited about this, it’s definitely a project that could use more experienced react/electron/bitcoin contributors, either yourself or if you’re interested in sponsoring :).

  1. it was here I noticed a small mistake that I named the updates “First Update” instead of “FirstUpdate” so I had to poke in the JSON and fix it in the interest of time… TODO for me to make invalid inputs invalid! 

  2. I used the wrong, wonky version of the TreePay by mistake and was too lazy to fix it, my bad :p it should look more tree-y. I’ll fix the actual code in the repo at some point. 

  3. My config file, if it helps you :)

    {
        "sapio": {
            "binary": "/Users/jr/sapio/target/release/sapio-cli",
            "oracle-local-enabled": true,
            "oracle-remote-enabled": true,
            "oracle-remote-oracles-list": [
                "0.0.0.0:8010 tpubD6NzVbkrYhZ4Y78NbTJtGWzt9a4eeoFJ1phmTVxZNSAiVkVWW5GYixSobuXTQtzFDcSWPoXhtiDUu4n6sChuNKVXZ9UL4LvxnU1WG4Y7pxV"
            ],
            "oracle-remote-threshold": "1",
            "plugin_map": [
            ],
            "configsource": "here",
            "oracle-seed-file": "/Users/jr/sapio/SEED",
            "oracle-netinterface": "0.0.0.0:8010"
        },
        "display": {
            "sats-bound": "9999999",
            "animate-flow": 1594,
            "poll-node-freq": 5
        },
        "bitcoin-config": {
            "rpcuser": "jeremy",
            "rpcpassword": "hunter2"
            "rpcport": "18443",
            "rpchost": "0.0.0.0",
            "network": "regtest"
        }
    }
    



A Defense of Having Fun (and maybe staying poor)

Day 17: Rubin's Bitcoin Advent Calendar

Welcome to day 17 of my Bitcoin Advent Calendar. You can see an index of all the posts here or subscribe at judica.org/join to get new posts in your inbox

A short story. I want to tell first. I recently made friends with Eugene, this really smart 19 year old Cal dropout, when I was visiting Miami for the NFT bachanal around Art Basel.

Eugene just dropped a project he’s been working on, and it’s really freakin’ cool. He basically implemented a human v. chess engine in Solidity that mints beautiful interactive NFTs of representations of the contract’s internal states. You can play the game / mint one for like 0.1 ETH on his site here, and see a permanent record of my embarassingly bad move where I missed a mate-in-one here:

Definitely check out the site and read how it’s implemented. Eugene is very bright, and a talented hacker. The project? It’s not a get-rich-quick project, only cost is gas, so Eugene’s not raking it in (altho I think he should have, but he’ll have many more successes).


So what’s the moral of this story?

Why isn’t Eugene working on Bitcoin? People say that “eth is a scam” and that “everyone working on it are scammers”. But I don’t see that. I see people, like Eugene, wanting to build cool shit every day. And wanting to ship cool stuff. It’s fun. And I like to be friends with creative and curious people.

Working on Bitcoin is can be fun. But mostly it’s not. My post yesterday? The one describing new techniques to make Bitcoin more decentralized? I had a lot of fun writing it. And then someone claimed that my work is “very dangerous” to Bitcoin.

I get it, I truly do. Bitcoin is money for enemies. Don’t trust, verify. In the IRC channels, twitter spaces, and other forums we hear rants like the below all the time:

Working on Bitcoin isn’t just a stupid fucking game of chess you idiot, it’s solving literally every geoscale challenge humanity has ever faced and your work on that is a bajillion fold more worthwhile. You are an asshole thinking you deserve to have any fun in your life when you could be a miserable piece of shit with people shouting at you all the time about how you suck and struggling to ship small features and hoping how you might ship one bigger project this decade. Fun? Fun??? You fucking asshole. If I don’t kill you, someone else will.

But developers are people too, and good developers like to build cool shit. If you haven’t noticed, Bitcoin development has a bit of a burnout problem, with multiple contributors stepping down their engagement recently. A likely cause is the struggle it takes to ship even the smallest features, not to mention the monumental effort it takes to ship a single large project. But the death threats certainly don’t help either.

It doesn’t have to be this hard

It’s hard to tell people, especially younger folk just entering the space, to work on Bitcoin full-time. What I say is as follows:

If you have a strong ideological calling to the mission of Bitcoin and sound money, it’s absolutely the most meaningful project for you to work on. But if that’s not you, and you want to explore crypto, you should probably play around with Ethereum or something else. Bitcoin is really tough to get funded for and the community, while amazing, can be very hostile.

If I were more selfish about the mission, I’d glaze over these details. But I want folks to decide for themselves and find something that makes them truly happy. For most “at-heart” Bitcoiners that won’t be a disincentive to taking the orange-pill, but for some it might. Note: I’m Jewish, so that probably influence my views on converting people to things (Jews believe in discouraging converts to find the pure of heart).

Despite my best efforts to convince myself otherwise, I’ve been an audit-the-fed libertarian since elementary school or something and I have photos of myself with Ron Paul a decade apart. So I am one of those ideologically drawn to Bitcoin and not other projects with weaker foundations.

But it doesn’t mean I don’t turn an envious eye to the activity and research happening in other communities, or get excited about on-chain chess engines even if they’re impractical for now. And there’s also the magic of a supportive communtiy that doesn’t threaten to have you beaten up when they disagree with you about the minutae of a soft fork rollout.

Cool technologies attract nerds like moths to a lamp at night. Smart nerds trying to solve interesting problems create solutions. Experiments that strain the limits of a platform expose problems and create demand for solutions. These solutions often have major positive externalities.

I don’t think I’m going to convince you here to care about NFTs. But I am – hopefully – going to convince you to care about NFTs the phenomenon.

For example, scaling challenge in Ethereum have led to the development of Zero Knowledge Roll-Ups, privacy issues things like Tornado Cash, and more. While as a project Eth might be #ngmi, Bitcoiners have traditionally said that if anything is worth having we’ll just be able to implement it ourselves in Bitcoin. But there are certain things that have network effects where it will be hard for us to replicate. And by the time we do go to replicate, the tooling that’s been developed for doing these things on Eth might be like a decade ahead of what we’ve got for Bitcoin. And all of the smart kids are going to become adults who are bought in technically and socially on things other than Bitcoin. And that really freakin’ matters.

I’m not advocating that Bitcoiners should embrace full-blown degeneracy. But also it’s not in particular our job to prevent it technically. And the tools that are produced when people have fun can lead to major innvoations.

For example, right now I am working on a system for building NFTs for Bitcoin on Sapio.

Why? It’s fun. It touches on almost all of the infrastructure problems I’m working on for Sapio right now. And it is low enough risk – in terms of risk of losing Bitcoin – that I feel comfortable building and experimenting with these NFT test-subjects. Worst-come-to-worst, the artists can always re-issue anything corrupted via a software flaw. And then as the software matures with low-risk but still fun applications, we can apply those learnings to managing Bitcoin as well.

I also want to note that I really like artists. Artists as a community are incredible. Artists use NFTs, so I like NFTs. Artists are the voice of the people. Art can tear down the establishment. Art can change the world. And so for Bitcoin, whose use is an inherently political message, what better community to engage than the art world?

Bitcoiners are really fixated on the technical nonsense backing NFTs – yeah, it’s not ‘literally’ the artwork. But then again, what literally is the artwork? If you want a photograph and you want to pay the photographer for it, do you? Do you get a receipt for it? Can you use that bill of sale to sell the photo later? NFTs are just a better that. And if you don’t like the art that’s sold as NFTs right now, why not find artists you do like? Why not get them onboarded onto Bitcoin and fully orange-pilled? Hint: the HRF has a whole Art in Protest section on their website. Bitcoin NFTs could enable Bitcoin holders to pay dissident artists for their art and avoid being shut down by authorities. Why not embrace that culturally? Isn’t that the group that Bitcoin is for? And if you don’t like that, maybe you just don’t like art. That’s ok, don’t buy it.

But NFTs are Stupid JPEGs Man

Ok sure. Whatever.

But there are all these contracts (as I showed you in previous posts) that Bitcoin would benefit from, like inheritence schemes, vaults, decentralized mining, payment pools, and more. Believe it or not, there tools needed to make NFTs work well are the exact same tools required to make these work seamlessly.

So why not have a little fun, let people experiment with new ideas, get excited, grow the community, and convert the big innovations into stable and mature tooling for the critical infrastructure applications? And maybe we’ll uncover some upside for brand new things that never occured possible to us before.


Throught the end of this series I’ll have some more posts detailing how to build NFTs, Derivatives, DAOs, and Bonded Oracles. I hope that you can view them with an open mind and appreciate how – even if you don’t think they are core to what Bitcoin has to do – these innovations will fuel development of tools to support the projects you do like without turning Bitcoin into a shitcoin. Who knows, maybe you’ll find a new application you like.


© 2011-2021 Jeremy Rubin. All rights reserved.