psbt_test.go raw

   1  package psbt
   2  
   3  import (
   4  	"bytes"
   5  	"crypto/sha256"
   6  	"encoding/binary"
   7  	"testing"
   8  
   9  	"git.smesh.lol/tinywallet/pkg/script"
  10  	"git.smesh.lol/tinywallet/pkg/tx"
  11  )
  12  
  13  // sha256Sum is the hash, named locally so the test does not depend on the implementation.
  14  func sha256Sum(data []byte) [32]byte { return sha256.Sum256(data) }
  15  
  16  // build makes an HTLC spend and the PSBT that describes it.
  17  func build(t *testing.T) (*tx.Tx, SegwitInput, []byte) {
  18  	t.Helper()
  19  
  20  	preimage := make([]byte, 32)
  21  	preimage[31] = 0x01
  22  
  23  	var secretHash [32]byte
  24  	copy(secretHash[:], sha256Of(preimage))
  25  
  26  	claimant := bytes.Repeat([]byte{0x02}, 33)
  27  	refunder := bytes.Repeat([]byte{0x03}, 33)
  28  
  29  	witnessScript, err := script.HTLC(claimant, refunder, secretHash, 500)
  30  
  31  	if err != nil {
  32  		t.Fatal(err)
  33  	}
  34  
  35  	transaction := tx.New()
  36  
  37  	var outpoint [32]byte
  38  	outpoint[0] = 0x44
  39  
  40  	transaction.AddInput(tx.Outpoint{Hash: outpoint, Index: 1}, tx.SequenceFinal)
  41  	transaction.AddOutput(90000, script.P2WSH(witnessScript))
  42  
  43  	input := SegwitInput{
  44  		Amount:        100000,
  45  		ScriptPubKey:  script.P2WSH(witnessScript),
  46  		WitnessScript: witnessScript,
  47  		Preimage:      preimage,
  48  		SecretHash:    secretHash,
  49  	}
  50  
  51  	return transaction, input, witnessScript
  52  }
  53  
  54  // TestThePSBTIsWellFormed: magic, an unsigned transaction, one input map with the records a
  55  // script-hash spend needs, and an empty output map.
  56  func TestThePSBTIsWellFormed(t *testing.T) {
  57  	transaction, input, witnessScript := build(t)
  58  
  59  	raw, err := Write(transaction, []SegwitInput{input})
  60  
  61  	if err != nil {
  62  		t.Fatal(err)
  63  	}
  64  
  65  	if !bytes.HasPrefix(raw, magic) {
  66  		t.Fatalf("the file starts %x", raw[:5])
  67  	}
  68  
  69  	unsigned, err := transaction.Serialize()
  70  
  71  	if err != nil {
  72  		t.Fatal(err)
  73  	}
  74  
  75  	if !bytes.Contains(raw, unsigned) {
  76  		t.Fatal("the unsigned transaction is not in the psbt")
  77  	}
  78  
  79  	if !bytes.Contains(raw, witnessScript) {
  80  		t.Fatal("the witness script is not in the psbt")
  81  	}
  82  
  83  	if !bytes.Contains(raw, input.Preimage) {
  84  		t.Fatal("the preimage is not in the psbt")
  85  	}
  86  
  87  	// The preimage record's key is the type byte followed by the hash.
  88  	if !bytes.Contains(raw, append([]byte{inputSHA256Preimage}, input.SecretHash[:]...)) {
  89  		t.Fatal("the preimage record is not keyed by its hash")
  90  	}
  91  }
  92  
  93  // TestTheWitnessUTXOCarriesTheAmount: BIP174 and BIP143 both need the value.
  94  func TestTheWitnessUTXOCarriesTheAmount(t *testing.T) {
  95  	transaction, input, _ := build(t)
  96  
  97  	raw, err := Write(transaction, []SegwitInput{input})
  98  
  99  	if err != nil {
 100  		t.Fatal(err)
 101  	}
 102  
 103  	found := false
 104  
 105  	for start := 0; start+9 < len(raw); start++ {
 106  		if raw[start] != 0x01 || binary.LittleEndian.Uint64(raw[start+2:start+10]) != input.Amount {
 107  			continue
 108  		}
 109  
 110  		found = true
 111  
 112  		break
 113  	}
 114  
 115  	if !found {
 116  		t.Fatal("no witness utxo record carries the amount")
 117  	}
 118  }
 119  
 120  // TestAWrongPreimageIsRefused: a preimage that does not open the hash is a mistake, and a
 121  // PSBT carrying it would fail later, on chain, where it costs a fee.
 122  func TestAWrongPreimageIsRefused(t *testing.T) {
 123  	transaction, input, _ := build(t)
 124  
 125  	input.Preimage = make([]byte, 32)
 126  
 127  	if _, err := Write(transaction, []SegwitInput{input}); err != ErrLockedByPreimage {
 128  		t.Fatalf("a wrong preimage was written: %v", err)
 129  	}
 130  }
 131  
 132  // TestInputCountMustMatch: one description per input, or none.
 133  func TestInputCountMustMatch(t *testing.T) {
 134  	transaction, _, _ := build(t)
 135  
 136  	if _, err := Write(transaction, nil); err != ErrNoInputs {
 137  		t.Fatalf("no inputs: %v", err)
 138  	}
 139  }
 140  
 141  // TestRecordNames: the names are what an error would say.
 142  func TestRecordNames(t *testing.T) {
 143  	if recordName(inputSHA256Preimage) != "sha256 preimage" {
 144  		t.Fatal("the preimage record has no name")
 145  	}
 146  }
 147  
 148  // TestWritingAndReadingAgree: what the writer produces, the reader understands, record for
 149  // record — which is the only check that keeps the two directions honest.
 150  func TestWritingAndReadingAgree(t *testing.T) {
 151  	transaction, input, witnessScript := build(t)
 152  
 153  	raw, err := Write(transaction, []SegwitInput{input})
 154  
 155  	if err != nil {
 156  		t.Fatal(err)
 157  	}
 158  
 159  	file, err := Read(raw)
 160  
 161  	if err != nil {
 162  		t.Fatal(err)
 163  	}
 164  
 165  	unsigned, err := transaction.Serialize()
 166  
 167  	if err != nil {
 168  		t.Fatal(err)
 169  	}
 170  
 171  	if !bytes.Equal(file.Unsigned, unsigned) {
 172  		t.Fatal("the unsigned transaction did not survive")
 173  	}
 174  
 175  	if len(file.Inputs) != 1 {
 176  		t.Fatalf("%d input maps", len(file.Inputs))
 177  	}
 178  
 179  	read := file.Inputs[0]
 180  
 181  	if read.Amount != input.Amount || !bytes.Equal(read.ScriptPubKey, input.ScriptPubKey) {
 182  		t.Fatalf("the witness utxo reads as %d %x", read.Amount, read.ScriptPubKey)
 183  	}
 184  
 185  	if !bytes.Equal(read.WitnessScript, witnessScript) {
 186  		t.Fatal("the witness script did not survive")
 187  	}
 188  
 189  	if len(read.Preimages) != 1 || !bytes.Equal(read.Preimages[0].Preimage, input.Preimage) {
 190  		t.Fatal("the preimage did not survive")
 191  	}
 192  
 193  	if read.Preimages[0].SecretHash != input.SecretHash {
 194  		t.Fatal("the preimage is keyed by a different hash")
 195  	}
 196  }
 197  
 198  // TestNotAPSBT: magic first, everything else after.
 199  func TestNotAPSBT(t *testing.T) {
 200  	if _, err := Read([]byte("not a psbt")); err != ErrNotPSBT {
 201  		t.Fatalf("a non-psbt: %v", err)
 202  	}
 203  }
 204  
 205  // TestATruncatedFileIsRefused: a length that runs past the end is an error, not a shorter file.
 206  func TestATruncatedFileIsRefused(t *testing.T) {
 207  	transaction, input, _ := build(t)
 208  
 209  	raw, err := Write(transaction, []SegwitInput{input})
 210  
 211  	if err != nil {
 212  		t.Fatal(err)
 213  	}
 214  
 215  	for _, cut := range []int{6, 12, 40, len(raw) - 1} {
 216  		if _, err := Read(raw[:cut]); err == nil {
 217  			t.Fatalf("a %d-byte psbt parsed", cut)
 218  		}
 219  	}
 220  }
 221