From ab1fba987a817da138ae1c00d00245ba261231df Mon Sep 17 00:00:00 2001
From: Daniel Cousens <github@dcousens.com>
Date: Fri, 1 Dec 2017 13:13:55 +1100
Subject: [PATCH] add BIP68/BIP112 CSV tests

---
 package.json             |   3 +-
 test/integration/cltv.js |  14 ++---
 test/integration/csv.js  | 121 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 127 insertions(+), 11 deletions(-)
 create mode 100644 test/integration/csv.js

diff --git a/package.json b/package.json
index 254289f..62ffd5c 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
     "bigi": "^1.4.0",
     "bip32": "0.0.3",
     "bip66": "^1.1.0",
-    "bitcoin-ops": "^1.3.0",
+    "bitcoin-ops": "^1.4.0",
     "bs58check": "^2.0.0",
     "create-hash": "^1.1.0",
     "create-hmac": "^1.1.3",
@@ -52,6 +52,7 @@
     "bigi": "^1.4.2",
     "bip39": "^2.3.0",
     "bip65": "^1.0.1",
+    "bip68": "^1.0.3",
     "bs58": "^4.0.0",
     "dhttp": "^2.4.2",
     "hoodwink": "^1.0.0",
diff --git a/test/integration/cltv.js b/test/integration/cltv.js
index 547ac79..d58c1a8 100644
--- a/test/integration/cltv.js
+++ b/test/integration/cltv.js
@@ -81,8 +81,8 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () {
     regtestUtils.height(function (err, height) {
       if (err) return done(err)
 
-      // 50 blocks from now
-      var lockTime = bip65.encode({ blocks: height + 50 })
+      // 5 blocks from now
+      var lockTime = bip65.encode({ blocks: height + 5 })
       var redeemScript = cltvCheckSigOutput(alice, bob, lockTime)
       var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
       var address = bitcoin.address.fromOutputScript(scriptPubKey, regtest)
@@ -106,14 +106,9 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () {
         tx.setInputScript(0, redeemScriptSig)
 
         // TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
-//          regtestUtils.broadcast(tx.toHex(), function (err) {
-//            // fails before the expiry
-//            assert.throws(function () {
-//              if (err) throw err
-//            }, /Error: 64: non-final/)
-
+        // ...
         // into the future!
-        regtestUtils.mine(51, function (err) {
+        regtestUtils.mine(5, function (err) {
           if (err) return done(err)
 
           regtestUtils.broadcast(tx.toHex(), function (err) {
@@ -127,7 +122,6 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () {
             }, done)
           })
         })
-//          })
       })
     })
   })
diff --git a/test/integration/csv.js b/test/integration/csv.js
new file mode 100644
index 0000000..5c9dd3b
--- /dev/null
+++ b/test/integration/csv.js
@@ -0,0 +1,121 @@
+/* global describe, it */
+
+let assert = require('assert')
+let bitcoin = require('../../')
+let regtestUtils = require('./_regtest')
+let regtest = regtestUtils.network
+let bip68 = require('bip68')
+
+let alice = bitcoin.ECPair.fromWIF('cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest)
+let bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', regtest)
+
+describe('bitcoinjs-lib (transactions w/ CSV)', function () {
+  let hashType = bitcoin.Transaction.SIGHASH_ALL
+
+  // IF MTP (from when confirmed) > seconds, aQ can redeem
+  function csvCheckSigOutput (aQ, bQ, sequence) {
+    return bitcoin.script.compile([
+      bitcoin.opcodes.OP_IF,
+      bitcoin.script.number.encode(sequence),
+      bitcoin.opcodes.OP_CHECKSEQUENCEVERIFY,
+      bitcoin.opcodes.OP_DROP,
+
+      bitcoin.opcodes.OP_ELSE,
+      bQ.getPublicKeyBuffer(),
+      bitcoin.opcodes.OP_CHECKSIGVERIFY,
+      bitcoin.opcodes.OP_ENDIF,
+
+      aQ.getPublicKeyBuffer(),
+      bitcoin.opcodes.OP_CHECKSIG
+    ])
+  }
+
+  // expiry will pass, {Alice's signature} OP_TRUE
+  it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future)', function (done) {
+    this.timeout(30000)
+
+    regtestUtils.height(function (err, height) {
+      if (err) return done(err)
+
+      // 5 blocks from now
+      let sequence = bip68.encode({ blocks: 5 })
+      let redeemScript = csvCheckSigOutput(alice, bob, sequence)
+      let scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
+      let address = bitcoin.address.fromOutputScript(scriptPubKey, regtest)
+
+      // fund the P2SH(CSV) address
+      regtestUtils.faucet(address, 1e5, function (err, unspent) {
+        if (err) return done(err)
+
+        let txb = new bitcoin.TransactionBuilder(regtest)
+        txb.addInput(unspent.txId, unspent.vout, sequence)
+        txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4)
+
+        // {Alice's signature} OP_TRUE
+        let tx = txb.buildIncomplete()
+        let signatureHash = tx.hashForSignature(0, redeemScript, hashType)
+        let redeemScriptSig = bitcoin.script.scriptHash.input.encode([
+          bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
+          bitcoin.opcodes.OP_TRUE
+        ], redeemScript)
+        tx.setInputScript(0, redeemScriptSig)
+
+        // TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
+        // ...
+        // into the future!
+        regtestUtils.mine(10, function (err) {
+          if (err) return done(err)
+
+          regtestUtils.broadcast(tx.toHex(), function (err) {
+            if (err) return done(err)
+
+            regtestUtils.verify({
+              txId: tx.getId(),
+              address: regtestUtils.RANDOM_ADDRESS,
+              vout: 0,
+              value: 7e4
+            }, done)
+          })
+        })
+      })
+    })
+  })
+
+  // expiry in the future, {Alice's signature} OP_TRUE
+  it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry', function (done) {
+    this.timeout(30000)
+
+    // two hours after confirmation
+    let sequence = bip68.encode({ seconds: 7168 })
+    let redeemScript = csvCheckSigOutput(alice, bob, sequence)
+    let scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
+    let address = bitcoin.address.fromOutputScript(scriptPubKey, regtest)
+
+    // fund the P2SH(CSV) address
+    regtestUtils.faucet(address, 2e4, function (err, unspent) {
+      if (err) return done(err)
+
+      let txb = new bitcoin.TransactionBuilder(regtest)
+      txb.addInput(unspent.txId, unspent.vout, sequence)
+      txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4)
+
+      // {Alice's signature} OP_TRUE
+      let tx = txb.buildIncomplete()
+      let signatureHash = tx.hashForSignature(0, redeemScript, hashType)
+      let redeemScriptSig = bitcoin.script.scriptHash.input.encode([
+        bitcoin.script.signature.encode(alice.sign(signatureHash), hashType),
+        bitcoin.script.signature.encode(bob.sign(signatureHash), hashType),
+        bitcoin.opcodes.OP_TRUE
+      ], redeemScript)
+      tx.setInputScript(0, redeemScriptSig)
+
+      regtestUtils.broadcast(tx.toHex(), function (err) {
+        assert.throws(function () {
+          if (err) throw err
+        }, /Error: 64: non-BIP68-final/)
+
+        done()
+      })
+    })
+  })
+})