Skip to content

Instantly share code, notes, and snippets.

@shesek
Created February 16, 2022 13:48
Show Gist options
  • Save shesek/ede9ca921a394580b23d301b8d84deea to your computer and use it in GitHub Desktop.
Save shesek/ede9ca921a394580b23d301b8d84deea to your computer and use it in GitHub Desktop.
// Witness stack elements
// Extra params for call type
// Buy token (0x00)
// New owner key
//<0x5bbab142b0bcf49860583797c35052007310fc4c2e7c78993e60f1af9007a394> $N_owner
// Matching desk SPK: 51201d7ec5f54f094c698c1689755672c1612df511f4b3db36d4774f9467fcefb23b (prefix 0x02)
// Update price (0x01)
// New price
<0x8400000000000000> $N_price
// Signature by current owner
<0x14b15710815ec7ac6576101a0ada6b4f1a33cca39308b3a6b27c94d1cb4722bc5200228e18f3349112bf07a5e500dc7543c5c25bf7d1aa3739c7f593bcdafa50> $sig
// Matching dest SPK: 6d769f7349af3796d0ad28a77112b3b58c15061b8a45a7f92d77805d942261ad (prefix 0x02)
// Destroy (0x02)
// Signature by creator
//<0x59bd7c18e985c23b75582f246c53d1e75bcff07df3e42014c7acb11cb43855f70126740203d1424772e588e42b8502109facef4eabbfc4b3076dee2be43106c5> $sig
// Tweaked output key prefix flag (0x02/0x03)
<0x02> $prefix
// Call type - 0x00 to buy, 0x01 to update price (owner only), 0x02 to destroy (creator only)
<0x01> $call
// State
// Current price in satoshis
<0x6400000000000000> $S_price // 100
// Current owner key
<0x9a8049130e1651fed4ba129960222064cce479c24c552fe7a9e68f91154c1d3c> $S_owner
// Private key: 21e7f0c48e712c772f51576c0cc1421aee06c2b3e67f1368ff3dc07b50bf3949
// Constants (sent to altstack)
// Contract own tapscript for recursive enforcment (excluding state and this constant -- only ops from this point onwards, starting at the push for the next constant)
<0x20499a818545f6bae39fc03b637f2a4e1e64e590cac1bc3a6f6d71aa4443654c14201efc611e1e6feffd661894690e1354ac6269215bd370e9f8f73ed80ea14497050114e0201dae61a4a8f841952be3a511502d4f56e889ffa0685aa0098773ea2d4309f624547a7c6b6b6b6b6bcdcb04FFFFFFFD88527a6351ce696c766b8851d169887651cf698852ce696c8852d1696c886cda697c7552cf69887c08ffffffffffffffff6776547a7c6d7c75527a6c6c6c6d75688258887c8201208800ce69cdc8698800cf69cdc9698801207c7e7e587c7e6c7682014d7c7e7c7e7c7e7e11546170547765616b2f656c656d656e7473a8767e6c766b7e105461704c6561662f656c656d656e7473a8767e01c47e527a8201FD7c7e7c7e7ea87ea87c00d1697e7c6ce4> $C_script
// tL-BTC asset id
<0x499a818545f6bae39fc03b637f2a4e1e64e590cac1bc3a6f6d71aa4443654c14> $LBTC
// Creator pubkey for royalty payments
<0x1efc611e1e6feffd661894690e1354ac6269215bd370e9f8f73ed80ea1449705> $creator
// Royalty amount denominator (royalty = price/N)
<20> $royalty // 5%
// A point with unknown discrete logarithm (script-path-only)
<0x1dae61a4a8f841952be3a511502d4f56e889ffa0685aa0098773ea2d4309f624> $H
<4> OP_ROLL OP_SWAP // re order constants for more convinient access
OP_TOALTSTACK OP_TOALTSTACK OP_TOALTSTACK OP_TOALTSTACK OP_TOALTSTACK
// Alt stack: <script-path-only point> <own tapscript> <royalty> <creator pubkey> <LBTC-id>
// Stack: [call type params] <output key prefix> <call type> -- <current price> <current owner key>
// Sanity checks
// Require RBF to be enabled
OP_PUSHCURRENTINPUTINDEX
OP_INSPECTINPUTSEQUENCE
<0xFFFFFFFD>
OP_EQUALVERIFY
// Enforce CSV to prevent unconfirmed chain?
// TODO Check tx fee
// Bring call type onto the top of the stack
<2> OP_ROLL
// Main call flow
// 0x00: Buy token
OP_DUP <0x00> OP_EQUAL
OP_IF OP_DROP
// Stack: <new owner key> <output key prefix> -- <current price> <current owner key>
// Verify payment output to current owner at index #1
// Is tL-BTC
<1> OP_INSPECTOUTPUTASSET
OP_VERIFY // check explicit asset id prefix
// Get LBTC asset id
OP_FROMALTSTACK OP_DUP OP_TOALTSTACK
OP_EQUALVERIFY
// Pays to current owner key (pops the owner key off the stack)
<1> OP_INSPECTOUTPUTSCRIPTPUBKEY
OP_VERIFY // check witness version 1 (taproot)
OP_EQUALVERIFY
// Duplicate the current price for later use
OP_DUP
// Amount paid matches the current price
<1> OP_INSPECTOUTPUTVALUE
OP_VERIFY // check explicit prefix
OP_EQUALVERIFY
// Verify royalty payment output to creator at index #2
// Is tL-BTC
<2> OP_INSPECTOUTPUTASSET
OP_VERIFY // check explicit asset id prefix
// Get LBTC asset id constant (removing it from the altstack)
OP_FROMALTSTACK
OP_EQUALVERIFY
// Pays to creator key
<2> OP_INSPECTOUTPUTSCRIPTPUBKEY
OP_VERIFY // check witness version 1 (taproot)
// Get creator key constant (removing it from the altstack)
OP_FROMALTSTACK
OP_EQUALVERIFY
// Amount paid matches the <royalty> setting
// Calculate royalty amount (price/<royalty>)
// Get royalty constant (removing it from the altstack)
OP_FROMALTSTACK
OP_SCRIPTNUMTOLE64
OP_DIV64
OP_VERIFY // verify DIV didn't overflow
OP_SWAP OP_DROP // ignore the remainder
// TODO minimum dust amount
<2> OP_INSPECTOUTPUTVALUE
OP_VERIFY // check explicit prefix
OP_EQUALVERIFY
// Bring new owner to top of stack
OP_SWAP
// Set price to MAX (that is, disallow sales until a new price is set by the new owner)
<0xffffffffffffffff> $N_price
// Stack: <output key prefix> <owner key (key)> <price (max)>
// Alt stack: <script-path-only point> <own tapscript>
// 0x01: Update price
OP_ELSE <0x01> OP_EQUAL OP_IF
// Stack: <new price> <owner sig> <output key prefix> -- <current price> <current owner key>
// Keep owner key for later use
OP_DUP
// Bring the sig before the key
<4> OP_ROLL OP_SWAP
// Verify owner signature
OP_2DROP // FIXME OP_CHECKSIGVERIFY -- scriptwiz doesn't support CHECKSIG in Liquid Taproot
// Drop old price
OP_SWAP OP_DROP
// Bring new price to front
<2> OP_ROLL
// Stack: <output key prefix> <owner key (unchanged)> <price (new)>
// Drop unnecessary altstack constants (royalty-related, only needed for Buy calls)
OP_FROMALTSTACK OP_FROMALTSTACK OP_FROMALTSTACK OP_2DROP OP_DROP
// Alt stack: <script-path-only point> <own tapscript>
// 0x02: Destroy token (creator 'backdoor')
OP_ELSE
// Stack: <creator sig> <output key prefix> -- <current price> <current owner key>
// Drop current state, we don't need it
OP_2DROP
// Get creator key constant (removing it and the royalty ratio from the altstack)
OP_FROMALTSTACK OP_DROP OP_FROMALTSTACK
// Bring sig before key
<2> OP_ROLL OP_SWAP
// Verify creator signature
OP_2DROP // FIXME OP_CHECKSIGVERIFY
// Drop unnecessary altstack constant
OP_FROMALTSTACK OP_DROP
// Transition state into an impossible price and impossible pubkey,
// effectively taking the token out of circulation
// Use point with unknown discrete logarithm as the new owner pubkey
OP_FROMALTSTACK OP_FROMALTSTACK OP_DUP OP_TOALTSTACK OP_SWAP OP_TOALTSTACK $N_owner
// Use MAX as the new price (more than all bitcoins to be created)
<0xffffffffffffffff> $N_price
// Stack: <output key prefix> <owner key (unknown discrete logarithm)> <price (max)>
OP_ENDIF OP_ENDIF
// Verify new owner key and price are of right size
OP_SIZE <8> OP_EQUALVERIFY
OP_SWAP
OP_SIZE <32> OP_EQUALVERIFY
// Stack: <output key prefix> <price> <owner key>
// Enforce recursive covenant at output #0
// Matches the input's asset id
<0>
OP_INSPECTOUTPUTASSET
OP_VERIFY // check explicit asset prefix
OP_PUSHCURRENTINPUTINDEX
OP_INSPECTINPUTASSET
OP_VERIFY // check explicit asset prefix
OP_EQUALVERIFY // check the asset id matches
// Same amount as the input (typically 1 token)
<0>
OP_INSPECTOUTPUTVALUE
OP_VERIFY // check explicit amount prefix
OP_PUSHCURRENTINPUTINDEX
OP_INSPECTINPUTVALUE
OP_VERIFY // check explicit amount prefix
OP_EQUALVERIFY
// Verify the output tapscript matches this the contract's tapscript with the updated state
// Build the state blob for the new state - <0x08><price><0x20><owner pubkey>
<0x20> // size of <owner pubkey>
OP_SWAP OP_CAT
OP_CAT
<0x08> // size of <price>
OP_SWAP
OP_CAT $N_state
// Stack: <output key prefix> <new state blob>
// Build the expected destination tapscript (<new state> || <push(own tapscript)> || <own tapscript>)
OP_FROMALTSTACK
OP_DUP
// PUSH for <own tapscript> (without state)
OP_SIZE
<0x4d> // PUSH with two bytes for the size (0x4c for shorter scripts with a size of 1 byte)
OP_SWAP
OP_CAT
OP_SWAP
OP_CAT
// Append <own tapscript> code (actual opcodes, not a data push)
OP_SWAP OP_CAT
// Prepend <new state> blob
OP_CAT $N_script
// Stack: <output key prefix> <expected tapscript>
// Push the taproot tweak key for the expected script into the stack
// TapTweak tag prefix
<'TapTweak/elements'>
OP_SHA256 OP_DUP OP_CAT
// Add internal pubkey to tweak (point with unknown discrete logarithm constant)
OP_FROMALTSTACK OP_DUP OP_TOALTSTACK
OP_CAT
// Add tap leaf to tweak
// TapLeaf tag prefix
<'TapLeaf/elements'>
OP_SHA256 OP_DUP OP_CAT
// Leaf version
<0xc4>
OP_CAT
// Move expected tapscript (including state) to top
<2>
OP_ROLL
// Prefix tapscript with size
OP_SIZE
// Add varint prefix
<0xFD> OP_SWAP OP_CAT
OP_SWAP OP_CAT
// Add size+tapscript to leaf
OP_CAT
OP_SHA256
OP_CAT
OP_SHA256 $N_tweak
// Stack: <output key prefix> <tweak key for expected new tapscript>
// Push the actual tweaked script used by the destination output (prefixed with the 0x02/0x03 prefix)
// Move <output key prefix> to the top
OP_SWAP
// Get the SPK of the destination output
<0> OP_INSPECTOUTPUTSCRIPTPUBKEY
OP_VERIFY // check witness version is 1 (tapscript)
OP_CAT $A_dest
OP_SWAP
// Stack: <actual tweaked destination output script> <tweak key for expected new tapscript>
// Get point with unknown discrete logarithm constant (script-path-only)
OP_FROMALTSTACK
// Verify that the taproot tweak for the destination output matches the expected tapscript
OP_TWEAKVERIFY
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment