htlc_test.go raw

   1  package tx
   2  
   3  import (
   4  	"testing"
   5  
   6  	"github.com/btcsuite/btcd/btcec/v2"
   7  	btcecdsa "github.com/btcsuite/btcd/btcec/v2/ecdsa"
   8  
   9  	"git.smesh.lol/tinywallet/pkg/script"
  10  )
  11  
  12  // htlcFixture builds an HTLC and a transaction spending it, with the keys and preimage the
  13  // claim and refund branches need.
  14  func htlcFixture(t *testing.T) (*Tx, []byte, []byte, []byte, uint64) {
  15  	t.Helper()
  16  
  17  	// The claimer and the refunder are the fixture's key: one key on both branches is enough
  18  	// for the witness shape, and it means the signature can be checked against a pubkey that
  19  	// is known to match the secret.
  20  	claimPub := mustDecode(t, loadFixture(t).Pubkey)
  21  
  22  	hash := mustDecode(t, nodeHash)
  23  
  24  	var secretHash [32]byte
  25  	copy(secretHash[:], hash)
  26  
  27  	witnessScript, err := script.HTLC(claimPub, claimPub, secretHash, nodeLocktime)
  28  
  29  	if err != nil {
  30  		t.Fatal(err)
  31  	}
  32  
  33  	transaction := New()
  34  
  35  	var outpoint [32]byte
  36  	outpoint[0] = 0x33
  37  
  38  	transaction.AddInput(Outpoint{Hash: outpoint, Index: 0}, SequenceFinal)
  39  	transaction.AddOutput(90000, script.P2WSH(witnessScript))
  40  
  41  	preimage := make([]byte, 32)
  42  	preimage[31] = 0x01
  43  
  44  	return transaction, witnessScript, preimage, claimPub, 100000
  45  }
  46  
  47  // TestHTLCClaimWitnessOrder: the stack is preimage, signature, true selector, script, and the
  48  // signature verifies over a digest that is the script's own.
  49  func TestHTLCClaimWitnessOrder(t *testing.T) {
  50  	transaction, witnessScript, preimage, claimer, amount := htlcFixture(t)
  51  
  52  	secret := mustDecode(t, loadFixture(t).Secret)
  53  
  54  	if err := SignHTLCClaim(transaction, 0, secret, witnessScript, preimage, amount); err != nil {
  55  		t.Fatal(err)
  56  	}
  57  
  58  	witness := transaction.Inputs[0].Witness
  59  
  60  	if len(witness) != 4 {
  61  		t.Fatalf("the witness has %d items", len(witness))
  62  	}
  63  
  64  	if string(witness[0]) != string(preimage) || len(witness[2]) != 1 || witness[2][0] != 0x01 {
  65  		t.Fatal("the witness is not preimage, signature, selector, script")
  66  	}
  67  
  68  	if string(witness[3]) != string(witnessScript) {
  69  		t.Fatal("the witness does not carry the script it spends")
  70  	}
  71  
  72  	digest, err := transaction.SighashV0(0, witnessScript, amount, SighashAll)
  73  
  74  	if err != nil {
  75  		t.Fatal(err)
  76  	}
  77  
  78  	signature, err := btcecdsa.ParseDERSignature(witness[1][:len(witness[1])-1])
  79  
  80  	if err != nil {
  81  		t.Fatal(err)
  82  	}
  83  
  84  	key, err := btcec.ParsePubKey(claimer)
  85  
  86  	if err != nil {
  87  		t.Fatal(err)
  88  	}
  89  
  90  	if !signature.Verify(digest[:], key) {
  91  		t.Fatal("the claim signature does not verify over the script's digest")
  92  	}
  93  }
  94  
  95  // TestHTLCRefundWitnessOrder: the refund is signature, an empty selector, the script.
  96  func TestHTLCRefundWitnessOrder(t *testing.T) {
  97  	transaction, witnessScript, _, _, amount := htlcFixture(t)
  98  
  99  	secret := mustDecode(t, loadFixture(t).Secret)
 100  
 101  	if err := SignHTLCRefund(transaction, 0, secret, witnessScript, amount); err != nil {
 102  		t.Fatal(err)
 103  	}
 104  
 105  	witness := transaction.Inputs[0].Witness
 106  
 107  	if len(witness) != 3 || len(witness[1]) != 0 {
 108  		t.Fatalf("the witness has %d items, the selector is %d bytes", len(witness), len(witness[1]))
 109  	}
 110  
 111  	if string(witness[2]) != string(witnessScript) {
 112  		t.Fatal("the witness does not carry the script it spends")
 113  	}
 114  }
 115  
 116  // TestADifferentScriptIsADifferentDigest: the point of committing to the witness script.
 117  func TestADifferentScriptIsADifferentDigest(t *testing.T) {
 118  	transaction, witnessScript, _, _, amount := htlcFixture(t)
 119  
 120  	first, err := transaction.SighashV0(0, witnessScript, amount, SighashAll)
 121  
 122  	if err != nil {
 123  		t.Fatal(err)
 124  	}
 125  
 126  	var other [32]byte
 127  	copy(other[:], mustDecode(t, nodeHash))
 128  	other[0] ^= 0xff
 129  
 130  	claimPub := mustDecode(t, loadFixture(t).Pubkey)
 131  
 132  	changed, err := script.HTLC(claimPub, claimPub, other, nodeLocktime)
 133  
 134  	if err != nil {
 135  		t.Fatal(err)
 136  	}
 137  
 138  	second, err := transaction.SighashV0(0, changed, amount, SighashAll)
 139  
 140  	if err != nil {
 141  		t.Fatal(err)
 142  	}
 143  
 144  	if first == second {
 145  		t.Fatal("a changed witness script produced the same digest")
 146  	}
 147  }
 148  
 149  // nodeHash and nodeLocktime are the values pkg/script pins its compiled script against.
 150  const (
 151  	nodeHash     = "0000000000000000000000000000000000000000000000000000000000000001"
 152  	nodeLocktime = 500
 153  )
 154