Skip to content

Scenario 4: A CTV vault on two chains with different rules

Goal: Run the same covenant on a chain with CTV and on a chain without CTV. Find that the chain without CTV does not enforce the covenant, because there the opcode is still NOP4.

#lang bitcoin/model
;; Scenario 4: a CTV vault on two chains with different rules.

(require rackunit)

(define-consensus ctv-rules #:extends bitcoin
  #:opcodes (upgrade nop4 #:to ctv))

(chain mainnet #:rules bitcoin)
(chain signet #:rules ctv-rules)
(keys alice cold mallory)
(check-equal? (format "~s" (diff-consensus bitcoin ctv-rules)) "((opcode #xb3 nop4 -> ctv))")

(define tmpl
  (template #:outputs (list (output 'to-cold (wpkh cold) (btc 49.98)))))
(contract vault () (ctv tmpl))

(define results
  (for/list ([ch (list mainnet signet)])
    (define cb (first (mine 1 #:on ch #:to alice)))
    (void (mine 100 #:on ch))
    (define-tx lock
      #:inputs  ([cb #:sign alice])
      #:outputs ([vaulted (vault) (btc 49.99)]))
    (confirm lock)
    (list (try (spend vaulted            ; thief ignores the template
                 #:outputs (list (output 'stolen (wpkh mallory) (btc 49.98)))))
          (try (spend vaulted            ; the honest spend follows it
                 #:outputs (list (output 'to-cold (wpkh cold) (btc 49.98))))))))

(define thief (map first results))
(check-pred accepted? (first thief))                                ; covenant unenforced
(check-equal? (rejected-rule (second thief)) 'ctv-template-mismatch)
(check-equal? (rejected-chain (second thief)) 'signet)
(check-true (andmap accepted? (map second results)))

(check-equal? (audit (vault) #:on mainnet)
              '((warning #:rule-unenforced ctv #:chain mainnet #:runs-as nop4)))
(check-equal? (audit (vault) #:on signet) '())

What occurs

  • The rule set. define-consensus makes ctv-rules: bitcoin with NOP4 changed to CTV. diff-consensus shows this one change. See Consensus as a value.
  • The vault. template describes the only transaction that can spend the coin: 49.98 BTC to cold. (contract vault () (ctv tmpl)) locks a coin to the template.
  • Two chains. The same code runs on both chains in a loop.

    • The spend of the thief does not obey the template. mainnet accepts it, because there the ctv of the script runs as NOP4. explain shows (op ctv #:as nop4 …).
    • signet rejects it:

      (rejected #:chain signet #:rule ctv-template-mismatch #:input 0
                #:fields ((outputs all)) #:expected (…) #:got (…) …)
      
    • Both chains accept the honest spend.

  • The audit. audit gives a warning before you trust the vault on mainnet: ((warning #:rule-unenforced ctv #:chain mainnet #:runs-as nop4)). On signet, it returns ().

The same method applies to each soft-fork proposal. CAT, CSFS, TXHASH or a new sighash mode is a proposal opcode or a selector, with a define-consensus.

Against real nodes

tests/replay-4.rkt replays the log two times:

Targets Result
mainnet on Core, signet on Bitcoin Inquisition 14 confirmed. The mempool of Core refuses the NOP4 spend of the thief, because it is not standard. A block can include it, as the model says (#:mempool-only).
Both chains on Core 12 confirmed and 2 unverified. The two signet spends that run CTV return (model-only-rule ctv).

Forms in this scenario: define-consensus, #:extends, upgrade, diff-consensus, template, (ctv t), audit, more than one chain in a session.