package tx import ( "testing" "github.com/btcsuite/btcd/btcec/v2" btcecdsa "github.com/btcsuite/btcd/btcec/v2/ecdsa" "git.smesh.lol/tinywallet/pkg/script" ) // htlcFixture builds an HTLC and a transaction spending it, with the keys and preimage the // claim and refund branches need. func htlcFixture(t *testing.T) (*Tx, []byte, []byte, []byte, uint64) { t.Helper() // The claimer and the refunder are the fixture's key: one key on both branches is enough // for the witness shape, and it means the signature can be checked against a pubkey that // is known to match the secret. claimPub := mustDecode(t, loadFixture(t).Pubkey) hash := mustDecode(t, nodeHash) var secretHash [32]byte copy(secretHash[:], hash) witnessScript, err := script.HTLC(claimPub, claimPub, secretHash, nodeLocktime) if err != nil { t.Fatal(err) } transaction := New() var outpoint [32]byte outpoint[0] = 0x33 transaction.AddInput(Outpoint{Hash: outpoint, Index: 0}, SequenceFinal) transaction.AddOutput(90000, script.P2WSH(witnessScript)) preimage := make([]byte, 32) preimage[31] = 0x01 return transaction, witnessScript, preimage, claimPub, 100000 } // TestHTLCClaimWitnessOrder: the stack is preimage, signature, true selector, script, and the // signature verifies over a digest that is the script's own. func TestHTLCClaimWitnessOrder(t *testing.T) { transaction, witnessScript, preimage, claimer, amount := htlcFixture(t) secret := mustDecode(t, loadFixture(t).Secret) if err := SignHTLCClaim(transaction, 0, secret, witnessScript, preimage, amount); err != nil { t.Fatal(err) } witness := transaction.Inputs[0].Witness if len(witness) != 4 { t.Fatalf("the witness has %d items", len(witness)) } if string(witness[0]) != string(preimage) || len(witness[2]) != 1 || witness[2][0] != 0x01 { t.Fatal("the witness is not preimage, signature, selector, script") } if string(witness[3]) != string(witnessScript) { t.Fatal("the witness does not carry the script it spends") } digest, err := transaction.SighashV0(0, witnessScript, amount, SighashAll) if err != nil { t.Fatal(err) } signature, err := btcecdsa.ParseDERSignature(witness[1][:len(witness[1])-1]) if err != nil { t.Fatal(err) } key, err := btcec.ParsePubKey(claimer) if err != nil { t.Fatal(err) } if !signature.Verify(digest[:], key) { t.Fatal("the claim signature does not verify over the script's digest") } } // TestHTLCRefundWitnessOrder: the refund is signature, an empty selector, the script. func TestHTLCRefundWitnessOrder(t *testing.T) { transaction, witnessScript, _, _, amount := htlcFixture(t) secret := mustDecode(t, loadFixture(t).Secret) if err := SignHTLCRefund(transaction, 0, secret, witnessScript, amount); err != nil { t.Fatal(err) } witness := transaction.Inputs[0].Witness if len(witness) != 3 || len(witness[1]) != 0 { t.Fatalf("the witness has %d items, the selector is %d bytes", len(witness), len(witness[1])) } if string(witness[2]) != string(witnessScript) { t.Fatal("the witness does not carry the script it spends") } } // TestADifferentScriptIsADifferentDigest: the point of committing to the witness script. func TestADifferentScriptIsADifferentDigest(t *testing.T) { transaction, witnessScript, _, _, amount := htlcFixture(t) first, err := transaction.SighashV0(0, witnessScript, amount, SighashAll) if err != nil { t.Fatal(err) } var other [32]byte copy(other[:], mustDecode(t, nodeHash)) other[0] ^= 0xff claimPub := mustDecode(t, loadFixture(t).Pubkey) changed, err := script.HTLC(claimPub, claimPub, other, nodeLocktime) if err != nil { t.Fatal(err) } second, err := transaction.SighashV0(0, changed, amount, SighashAll) if err != nil { t.Fatal(err) } if first == second { t.Fatal("a changed witness script produced the same digest") } } // nodeHash and nodeLocktime are the values pkg/script pins its compiled script against. const ( nodeHash = "0000000000000000000000000000000000000000000000000000000000000001" nodeLocktime = 500 )