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.


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.



Composability in Sapio Contracts

Day 16: Rubin's Bitcoin Advent Calendar

Welcome to day 16 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

Who here has some ERC-20s or 721s1? Anyone? No one? Whatever.

The Punchline is that a lotta fuss goes into Ethereum smart contracts being Turing Complete but guess what? Neither ERC-20 nor 721 really have anything to do with being Turing Complete. What they do have to do with is having a tightly defined interface that can integrate into other applications nicely.

This is great news for Bitcoin. It means that a lot of the cool stuff happening in eth-land isn’t really about Turing Completeness, it’s about just defining really kickass interfaces for the things we’re trying to do.

In the last few posts, we already saw examples of composability. We took a bunch of concepts and were able to nest them inside of each other to make Decentralized Coordination Free Mining Pools. But we can do a lot more with composability than just compose ideas togehter by hand. In this post I’ll give you a little sampler of different types of programmatic composability and interfaces, like the ERC-20 and 721.


Address Composability

Because many Sapio contracts can be made completely noninteractively (with CTV or an Oracle you’ll trust to be online later), if you compile a Sapio contract and get an address you can just plug it in somewhere and it “composes” and you can link it later. We saw this earlier with the ability to make a channel address and send it to an exchange.

However, for Sapio if you just do an Address it won’t necessarily have the understanding of what that address is for so you won’t get any of the Sapio “rich” features.

Pre-Compiled

You can also take not just an address, but an entire (json-serialized?) Compiled object that would include all the relevant metadata.

Rust Generic Types Composability

Well, if you’re a rust programmer this basically boils down to rust types rule! We’ll give a couple examples.

The simplest example is just composing directly in a function:

#[then]
fn some_function(self, ctx: Context) {
    ctx.template()
        .add_output(ctx.funds(), &SomeOtherContract{/**/}, None)?
        .into()
}

What if we want to pass any Contract as an argument for a Contract? Simple:

struct X {
    a : Box<dyn Contract>
}

What if we want to restrict it a little bit more? We can use a trait bound. Now only Y (or anything implementing GoodContract) can be plugged in.

trait GoodContract : Contract {
    decl_then!{some_thing}
}
struct Y {
}
impl GoodContract for Y {
    #[then]
    fn some_thing(self, ctx: Context) {
        empty()
    }
}
impl Contract for Y {
    declare!{then, Self::some_thing}
}
struct X<T: GoodContract> {
    a : Box<dyn GoodContract>,
    // note the inner type of a and b don't have to match
    b : Box<dyn GoodContract>
}

Boxing gives us some power to be Generic at runtime, but we can also do some more “compile time” logic. This can have some advantages, e.g., if we want to guarantee that types are the same.

struct X<T : Contract, U: GoodContract> {
    a : T, 
    b : T
    // a more specific concrete type -- could be a T even
    c: U,
    d: U,
}

Sometimes it can be helpful to wrap things in functions, like we saw in the Vaults post.

struct X<T: Contract>
    // This lets us stub in whatever we want for a function
    a : Box<Fn(Self, Context) -> TxTmplIt>,
    // this lets us get back any contract
    b : Box<Fn(Self, Context) -> Box<dyn Contract>>,
    // this lets us get back a specific contract
    c : Box<Fn(Self, Context) -> T,
}

Clearly there’s a lot to do with the rust type system and making components.

It would even be possible to make certain types of ‘unchecked’ type traits, for example:

trait Reusable {}
struct AlsoReusable<T> {
    a: T,
}
// Only reusable if T Reusable
impl<T> Reusable for AlsoReusable<T> where T: Reusable {}

The Reusable tag could be used to tag contract components that would be “reuse safe”. E.g., an HTLC or HTLC containing component would not be reuse safe since hashes could be revealed. While reusability isn’t “proven” – that’s up to the author to check – these types of traits can help us reason about the properties of compositions of programs more safely. Unfortunately, Rust lacks negative trait bounds (i.e., Not-Reusable), so you can’t reason about certain types of things.

Inheritence

We don’t have a fantastic way to do inheritence in Sapio presently. But stay tuned! For now, then best you get is that you can do traits (like GoodContract).

Cross Module Composability & WASM

One of the goals of Sapio is to be able to create contract modules with a well-defined API Boundary that communicates with JSONs and is “typed” with JSONSchema. This means that the Sapio modules can be running anywhere (e.g., a remote server) and we can treat it like any other component.

Another goal of Sapio is to make it possible to compile modules into standalone WASM modules. WASM stands for Web Assembly, it’s basically a small deterministic computer emulator program format so we can compile our programs and run them anywhere that the WASM interpreter is available.

Combining these two goals, it’s possible for one Sapio program to dynamically load another as a WASM module. This means we can come up with a component, compile it, and then link to it later from somewhere else. For example, we could have a Payment Pool where we make each person’s leaf node a WASM module of their choice, that could be something like a Channel, a Vault, or anything that satisfies a “Payment Pool Payout Interface”.

For example, suppose we wanted to a generic API for making a batched payment.

Defining the Interface

First, we define a payment that we want to batch.

/// A payment to a specific address
pub struct Payment {
    /// # Amount (btc)
    /// The amount to send
    pub amount: AmountF64,
    /// # Address
    /// The Address to send to
    pub address: bitcoin::Address,
}

Next, we define the full API that we want. Naming and versioning is still a something we need to work on in the Sapio ecosystem, but for now it makes sense to be verbose and include a version.

pub struct BatchingTraitVersion0_1_1 {
    pub payments: Vec<Payment>,
    /// # Feerate (Bitcoin per byte)
    pub feerate_per_byte: AmountF64 
}

Lastly, to finish defining the API, we have to do something really gross looking in order to make it automatically checkable – this is essentially this is what the user defined BatchingTraitVersion0_1_1 is going to verify modules are able to understand. This is going to be improved in Sapio over time for better typechecking!

impl SapioJSONTrait for BatchingTraitVersion0_1_1 {
    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()
    }
}

Implementing the Interface

Let’s say that we want to make a contract like TreePay implement BatchingTraitVersion0_1_1. What do we need to do?

First, let’s get the boring stuff out of the way, we need to make the TreePay module understand that it should support BatchingTraitVersion0_1_1.

/// # Different Calling Conventions to create a Treepay
enum Versions {
    /// # Standard Tree Pay
    TreePay(TreePay),
    /// # Batching Trait API
    BatchingTraitVersion0_1_1(BatchingTraitVersion0_1_1),
}

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

Next, we just need to define logic converting the data provided in BatchingTraitVersion0_1_1 into a TreePay. Since BatchingTraitVersion0_1_1 is really basic, we need to pick values for the other fields.

impl From<BatchingTraitVersion0_1_1> for TreePay {
    fn from(args: BatchingTraitVersion0_1_1) -> Self {
        TreePay {
            participants: args.payments,
            radix: 4,
            // estimate fees to be 4 outputs and 1 input + change
            fee_sats_per_tx: args.feerate_per_byte * ((4 * 41) + 41 + 10),
            timelock_backpressure: None,
        }
    }
}
impl From<Versions> for TreePay {
    fn from(v: Versions) -> TreePay {
        match v {
            Versions::TreePay(v) => v,
            Versions::BatchingTraitVersion0_1_1(v) => v.into(),
        }
    }
}

Using the Interface

To use this BatchingTraitVersion0_1_1, we can just define a struct as follows, and when we deserialize it will be automatically verified to have declared a fitting API.

pub struct RequiresABatch {
    /// # Which Plugin to Use
    /// Specify which contract plugin to call out to.
    handle: SapioHostAPI<BatchingTraitVersion0_1_1>,
}

The SapioHostAPI handle can be either a human readable name (like “user_preferences.batching” or “org.judica.modules.batchpay.latest”) and looked up locally, or it could be an exact hash of the specific module to use.

We can then use the handle to resolve and compile against the third party module. Because the module lives in an entirely separate WASM execution context, we don’t need to worry about it corrupting our module or being able to access information we don’t provide it.

Call to Action

ARE YOU A BIG BRAIN PROGRAMMING LANGUAGE PERSON?

PLEASE HELP ME MAKE THIS SAPIO HAVE A COOL AND USEFUL TYPE SYSTEM I AM A SMALL BRAIN BOI AND THIS STUFF IS HARD AND I NEED FRENZ.

EVEN THE KIND OF “FRENZ” THAT YOU HAVE TO PAY FOR wink.

CLICK HERE


In the posts coming Soon™, we’ll see some more specific examples of contracts that make heavier use of having interfaces and all the cool shit we can get done.

That’s all I have to say. See you tomorrow.

  1. if you’ve been living under a big rock, ICO tokens and NFTs. 


© 2011-2021 Jeremy Rubin. All rights reserved.