package input

import (
	"bytes"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"testing"

	"github.com/btcsuite/btcd/btcec"
	"github.com/btcsuite/btcd/chaincfg/chainhash"
	"github.com/btcsuite/btcd/txscript"
	"github.com/btcsuite/btcd/wire"
	"github.com/btcsuite/btcutil"
	"github.com/lightningnetwork/lnd/keychain"
)

// assertEngineExecution executes the VM returned by the newEngine closure,
// asserting the result matches the validity expectation. In the case where it
// doesn't match the expectation, it executes the script step-by-step and
// prints debug information to stdout.
func assertEngineExecution(t *testing.T, testNum int, valid bool,
	newEngine func() (*txscript.Engine, error)) {
	t.Helper()

	// Get a new VM to execute.
	vm, err := newEngine()
	if err != nil {
		t.Fatalf("unable to create engine: %v", err)
	}

	// Execute the VM, only go on to the step-by-step execution if
	// it doesn't validate as expected.
	vmErr := vm.Execute()
	if valid == (vmErr == nil) {
		return
	}

	// Now that the execution didn't match what we expected, fetch a new VM
	// to step through.
	vm, err = newEngine()
	if err != nil {
		t.Fatalf("unable to create engine: %v", err)
	}

	// This buffer will trace execution of the Script, dumping out
	// to stdout.
	var debugBuf bytes.Buffer

	done := false
	for !done {
		dis, err := vm.DisasmPC()
		if err != nil {
			t.Fatalf("stepping (%v)\n", err)
		}
		debugBuf.WriteString(fmt.Sprintf("stepping %v\n", dis))

		done, err = vm.Step()
		if err != nil && valid {
			fmt.Println(debugBuf.String())
			t.Fatalf("spend test case #%v failed, spend "+
				"should be valid: %v", testNum, err)
		} else if err == nil && !valid && done {
			fmt.Println(debugBuf.String())
			t.Fatalf("spend test case #%v succeed, spend "+
				"should be invalid: %v", testNum, err)
		}

		debugBuf.WriteString(fmt.Sprintf("Stack: %v", vm.GetStack()))
		debugBuf.WriteString(fmt.Sprintf("AltStack: %v", vm.GetAltStack()))
	}

	// If we get to this point the unexpected case was not reached
	// during step execution, which happens for some checks, like
	// the clean-stack rule.
	validity := "invalid"
	if valid {
		validity = "valid"
	}

	fmt.Println(debugBuf.String())
	t.Fatalf("%v spend test case #%v execution ended with: %v", validity, testNum, vmErr)
}

// TestRevocationKeyDerivation tests that given a public key, and a revocation
// hash, the homomorphic revocation public and private key derivation work
// properly.
func TestRevocationKeyDerivation(t *testing.T) {
	t.Parallel()

	// First, we'll generate a commitment point, and a commitment secret.
	// These will be used to derive the ultimate revocation keys.
	revocationPreimage := testHdSeed.CloneBytes()
	commitSecret, commitPoint := btcec.PrivKeyFromBytes(btcec.S256(),
		revocationPreimage)

	// With the commitment secrets generated, we'll now create the base
	// keys we'll use to derive the revocation key from.
	basePriv, basePub := btcec.PrivKeyFromBytes(btcec.S256(),
		testWalletPrivKey)

	// With the point and key obtained, we can now derive the revocation
	// key itself.
	revocationPub := DeriveRevocationPubkey(basePub, commitPoint)

	// The revocation public key derived from the original public key, and
	// the one derived from the private key should be identical.
	revocationPriv := DeriveRevocationPrivKey(basePriv, commitSecret)
	if !revocationPub.IsEqual(revocationPriv.PubKey()) {
		t.Fatalf("derived public keys don't match!")
	}
}

// TestTweakKeyDerivation tests that given a public key, and commitment tweak,
// then we're able to properly derive a tweaked private key that corresponds to
// the computed tweak public key. This scenario ensure that our key derivation
// for any of the non revocation keys on the commitment transaction is correct.
func TestTweakKeyDerivation(t *testing.T) {
	t.Parallel()

	// First, we'll generate a base public key that we'll be "tweaking".
	baseSecret := testHdSeed.CloneBytes()
	basePriv, basePub := btcec.PrivKeyFromBytes(btcec.S256(), baseSecret)

	// With the base key create, we'll now create a commitment point, and
	// from that derive the bytes we'll used to tweak the base public key.
	commitPoint := ComputeCommitmentPoint(bobsPrivKey)
	commitTweak := SingleTweakBytes(commitPoint, basePub)

	// Next, we'll modify the public key. When we apply the same operation
	// to the private key we should get a key that matches.
	tweakedPub := TweakPubKey(basePub, commitPoint)

	// Finally, attempt to re-generate the private key that matches the
	// tweaked public key. The derived key should match exactly.
	derivedPriv := TweakPrivKey(basePriv, commitTweak)
	if !derivedPriv.PubKey().IsEqual(tweakedPub) {
		t.Fatalf("pub keys don't match")
	}
}

// makeWitnessTestCase is a helper function used within test cases involving
// the validity of a crafted witness. This function is a wrapper function which
// allows constructing table-driven tests. In the case of an error while
// constructing the witness, the test fails fatally.
func makeWitnessTestCase(t *testing.T,
	f func() (wire.TxWitness, error)) func() wire.TxWitness {

	return func() wire.TxWitness {
		witness, err := f()
		if err != nil {
			t.Fatalf("unable to create witness test case: %v", err)
		}

		return witness
	}
}

// TestHTLCSenderSpendValidation tests all possible valid+invalid redemption
// paths in the script used within the sender's commitment transaction for an
// outgoing HTLC.
//
// The following cases are exercised by this test:
// sender script:
//  * receiver spends
//    * revoke w/ sig
//    * HTLC with invalid preimage size
//    * HTLC with valid preimage size + sig
//  * sender spends
//    * invalid lock-time for CLTV
//    * invalid sequence for CSV
//    * valid lock-time+sequence, valid sig
func TestHTLCSenderSpendValidation(t *testing.T) {
	t.Parallel()

	// We generate a fake output, and the corresponding txin. This output
	// doesn't need to exist, as we'll only be validating spending from the
	// transaction that references this.
	txid, err := chainhash.NewHash(testHdSeed.CloneBytes())
	if err != nil {
		t.Fatalf("unable to create txid: %v", err)
	}
	fundingOut := &wire.OutPoint{
		Hash:  *txid,
		Index: 50,
	}
	fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil)

	// Next we'll the commitment secret for our commitment tx and also the
	// revocation key that we'll use as well.
	revokePreimage := testHdSeed.CloneBytes()
	commitSecret, commitPoint := btcec.PrivKeyFromBytes(btcec.S256(),
		revokePreimage)

	// Generate a payment preimage to be used below.
	paymentPreimage := revokePreimage
	paymentPreimage[0] ^= 1
	paymentHash := sha256.Sum256(paymentPreimage[:])

	// We'll also need some tests keys for alice and bob, and metadata of
	// the HTLC output.
	aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
		testWalletPrivKey)
	bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
		bobsPrivKey)
	paymentAmt := btcutil.Amount(1 * 10e8)

	aliceLocalKey := TweakPubKey(aliceKeyPub, commitPoint)
	bobLocalKey := TweakPubKey(bobKeyPub, commitPoint)

	// As we'll be modeling spends from Alice's commitment transaction,
	// we'll be using Bob's base point for the revocation key.
	revocationKey := DeriveRevocationPubkey(bobKeyPub, commitPoint)

	bobCommitTweak := SingleTweakBytes(commitPoint, bobKeyPub)
	aliceCommitTweak := SingleTweakBytes(commitPoint, aliceKeyPub)

	// Finally, we'll create mock signers for both of them based on their
	// private keys. This test simplifies a bit and uses the same key as
	// the base point for all scripts and derivations.
	bobSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}}
	aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}}

	var (
		htlcWitnessScript, htlcPkScript []byte
		htlcOutput                      *wire.TxOut
		sweepTxSigHashes                *txscript.TxSigHashes
		senderCommitTx, sweepTx         *wire.MsgTx
		bobRecvrSig                     *btcec.Signature
		bobSigHash                      txscript.SigHashType
	)

	// genCommitTx generates a commitment tx where the htlc output requires
	// confirmation to be spent according to 'confirmed'.
	genCommitTx := func(confirmed bool) {
		// Generate the raw HTLC redemption scripts, and its p2wsh
		// counterpart.
		htlcWitnessScript, err = SenderHTLCScript(
			aliceLocalKey, bobLocalKey, revocationKey,
			paymentHash[:], confirmed,
		)
		if err != nil {
			t.Fatalf("unable to create htlc sender script: %v", err)
		}
		htlcPkScript, err = WitnessScriptHash(htlcWitnessScript)
		if err != nil {
			t.Fatalf("unable to create p2wsh htlc script: %v", err)
		}

		// This will be Alice's commitment transaction. In this
		// scenario Alice is sending an HTLC to a node she has a path
		// to (could be Bob, could be multiple hops down, it doesn't
		// really matter).
		htlcOutput = &wire.TxOut{
			Value:    int64(paymentAmt),
			PkScript: htlcPkScript,
		}
		senderCommitTx = wire.NewMsgTx(2)
		senderCommitTx.AddTxIn(fakeFundingTxIn)
		senderCommitTx.AddTxOut(htlcOutput)
	}

	// genSweepTx generates a sweep of the senderCommitTx, and sets the
	// sequence and sighash single|anyonecanspend if confirmed is true.
	genSweepTx := func(confirmed bool) {
		prevOut := &wire.OutPoint{
			Hash:  senderCommitTx.TxHash(),
			Index: 0,
		}

		sweepTx = wire.NewMsgTx(2)

		sweepTx.AddTxIn(wire.NewTxIn(prevOut, nil, nil))
		if confirmed {
			sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 1)
		}

		sweepTx.AddTxOut(
			&wire.TxOut{
				PkScript: []byte("doesn't matter"),
				Value:    1 * 10e8,
			},
		)

		sweepTxSigHashes = txscript.NewTxSigHashes(sweepTx)

		bobSigHash = txscript.SigHashAll
		if confirmed {
			bobSigHash = txscript.SigHashSingle | txscript.SigHashAnyOneCanPay
		}

		// We'll also generate a signature on the sweep transaction above
		// that will act as Bob's signature to Alice for the second level HTLC
		// transaction.
		bobSignDesc := SignDescriptor{
			KeyDesc: keychain.KeyDescriptor{
				PubKey: bobKeyPub,
			},
			SingleTweak:   bobCommitTweak,
			WitnessScript: htlcWitnessScript,
			Output:        htlcOutput,
			HashType:      bobSigHash,
			SigHashes:     sweepTxSigHashes,
			InputIndex:    0,
		}
		bobSig, err := bobSigner.SignOutputRaw(sweepTx, &bobSignDesc)
		if err != nil {
			t.Fatalf("unable to generate alice signature: %v", err)
		}

		bobRecvrSig, err = btcec.ParseDERSignature(
			bobSig.Serialize(), btcec.S256(),
		)
		if err != nil {
			t.Fatalf("unable to parse signature: %v", err)
		}
	}

	testCases := []struct {
		witness func() wire.TxWitness
		valid   bool
	}{
		{
			// revoke w/ sig
			// TODO(roasbeef): test invalid revoke
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				genCommitTx(false)
				genSweepTx(false)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: bobKeyPub,
					},
					DoubleTweak:   commitSecret,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return SenderHtlcSpendRevokeWithKey(bobSigner, signDesc,
					revocationKey, sweepTx)
			}),
			true,
		},
		{
			// HTLC with invalid preimage size
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				genCommitTx(false)
				genSweepTx(false)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: bobKeyPub,
					},
					SingleTweak:   bobCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return SenderHtlcSpendRedeem(bobSigner, signDesc,
					sweepTx,
					// Invalid preimage length
					bytes.Repeat([]byte{1}, 45))
			}),
			false,
		},
		{
			// HTLC with valid preimage size + sig
			// TODO(roasbeef): invalid preimage
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				genCommitTx(false)
				genSweepTx(false)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: bobKeyPub,
					},
					SingleTweak:   bobCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return SenderHtlcSpendRedeem(bobSigner, signDesc,
					sweepTx, paymentPreimage)
			}),
			true,
		},
		{
			// HTLC with valid preimage size + sig, and with
			// enforced locktime in HTLC script.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				// Make a commit tx that needs confirmation for
				// HTLC output to be spent.
				genCommitTx(true)

				// Generate a sweep with the locktime set.
				genSweepTx(true)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: bobKeyPub,
					},
					SingleTweak:   bobCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return SenderHtlcSpendRedeem(bobSigner, signDesc,
					sweepTx, paymentPreimage)
			}),
			true,
		},
		{
			// HTLC with valid preimage size + sig, but trying to
			// spend CSV output without sequence set.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				// Generate commitment tx with 1 CSV locked
				// HTLC.
				genCommitTx(true)

				// Generate sweep tx that doesn't have locktime
				// enabled.
				genSweepTx(false)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: bobKeyPub,
					},
					SingleTweak:   bobCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return SenderHtlcSpendRedeem(bobSigner, signDesc,
					sweepTx, paymentPreimage)
			}),
			false,
		},

		{
			// valid spend to the transition the state of the HTLC
			// output with the second level HTLC timeout
			// transaction.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				genCommitTx(false)
				genSweepTx(false)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: aliceKeyPub,
					},
					SingleTweak:   aliceCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return SenderHtlcSpendTimeout(
					bobRecvrSig, bobSigHash, aliceSigner,
					signDesc, sweepTx,
				)
			}),
			true,
		},
		{
			// valid spend to the transition the state of the HTLC
			// output with the second level HTLC timeout
			// transaction.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				// Make a commit tx that needs confirmation for
				// HTLC output to be spent.
				genCommitTx(true)

				// Generate a sweep with the locktime set.
				genSweepTx(true)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: aliceKeyPub,
					},
					SingleTweak:   aliceCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return SenderHtlcSpendTimeout(
					bobRecvrSig, bobSigHash, aliceSigner,
					signDesc, sweepTx,
				)
			}),
			true,
		},
		{
			// valid spend to the transition the state of the HTLC
			// output with the second level HTLC timeout
			// transaction.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				// Generate commitment tx with 1 CSV locked
				// HTLC.
				genCommitTx(true)

				// Generate sweep tx that doesn't have locktime
				// enabled.
				genSweepTx(false)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: aliceKeyPub,
					},
					SingleTweak:   aliceCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return SenderHtlcSpendTimeout(
					bobRecvrSig, bobSigHash, aliceSigner,
					signDesc, sweepTx,
				)
			}),
			false,
		},
	}

	// TODO(roasbeef): set of cases to ensure able to sign w/ keypath and
	// not

	for i, testCase := range testCases {
		sweepTx.TxIn[0].Witness = testCase.witness()

		newEngine := func() (*txscript.Engine, error) {
			return txscript.NewEngine(htlcPkScript,
				sweepTx, 0, txscript.StandardVerifyFlags, nil,
				nil, int64(paymentAmt))
		}

		assertEngineExecution(t, i, testCase.valid, newEngine)
	}
}

// TestHTLCReceiverSpendValidation tests all possible valid+invalid redemption
// paths in the script used within the receiver's commitment transaction for an
// incoming HTLC.
//
// The following cases are exercised by this test:
//  * receiver spends
//     * HTLC redemption w/ invalid preimage size
//     * HTLC redemption w/ invalid sequence
//     * HTLC redemption w/ valid preimage size
//  * sender spends
//     * revoke w/ sig
//     * refund w/ invalid lock time
//     * refund w/ valid lock time
func TestHTLCReceiverSpendValidation(t *testing.T) {
	t.Parallel()

	// We generate a fake output, and the corresponding txin. This output
	// doesn't need to exist, as we'll only be validating spending from the
	// transaction that references this.
	txid, err := chainhash.NewHash(testHdSeed.CloneBytes())
	if err != nil {
		t.Fatalf("unable to create txid: %v", err)
	}
	fundingOut := &wire.OutPoint{
		Hash:  *txid,
		Index: 50,
	}
	fakeFundingTxIn := wire.NewTxIn(fundingOut, nil, nil)

	// Next we'll the commitment secret for our commitment tx and also the
	// revocation key that we'll use as well.
	revokePreimage := testHdSeed.CloneBytes()
	commitSecret, commitPoint := btcec.PrivKeyFromBytes(btcec.S256(),
		revokePreimage)

	// Generate a payment preimage to be used below.
	paymentPreimage := revokePreimage
	paymentPreimage[0] ^= 1
	paymentHash := sha256.Sum256(paymentPreimage[:])

	// We'll also need some tests keys for alice and bob, and metadata of
	// the HTLC output.
	aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
		testWalletPrivKey)
	bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
		bobsPrivKey)
	paymentAmt := btcutil.Amount(1 * 10e8)
	cltvTimeout := uint32(8)

	aliceLocalKey := TweakPubKey(aliceKeyPub, commitPoint)
	bobLocalKey := TweakPubKey(bobKeyPub, commitPoint)

	// As we'll be modeling spends from Bob's commitment transaction, we'll
	// be using Alice's base point for the revocation key.
	revocationKey := DeriveRevocationPubkey(aliceKeyPub, commitPoint)

	bobCommitTweak := SingleTweakBytes(commitPoint, bobKeyPub)
	aliceCommitTweak := SingleTweakBytes(commitPoint, aliceKeyPub)

	// Finally, we'll create mock signers for both of them based on their
	// private keys. This test simplifies a bit and uses the same key as
	// the base point for all scripts and derivations.
	bobSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}}
	aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}}

	var (
		htlcWitnessScript, htlcPkScript []byte
		htlcOutput                      *wire.TxOut
		receiverCommitTx, sweepTx       *wire.MsgTx
		sweepTxSigHashes                *txscript.TxSigHashes
		aliceSenderSig                  *btcec.Signature
		aliceSigHash                    txscript.SigHashType
	)

	genCommitTx := func(confirmed bool) {
		// Generate the raw HTLC redemption scripts, and its p2wsh
		// counterpart.
		htlcWitnessScript, err = ReceiverHTLCScript(
			cltvTimeout, aliceLocalKey, bobLocalKey, revocationKey,
			paymentHash[:], confirmed,
		)
		if err != nil {
			t.Fatalf("unable to create htlc sender script: %v", err)
		}
		htlcPkScript, err = WitnessScriptHash(htlcWitnessScript)
		if err != nil {
			t.Fatalf("unable to create p2wsh htlc script: %v", err)
		}

		// This will be Bob's commitment transaction. In this scenario Alice is
		// sending an HTLC to a node she has a path to (could be Bob, could be
		// multiple hops down, it doesn't really matter).
		htlcOutput = &wire.TxOut{
			Value:    int64(paymentAmt),
			PkScript: htlcWitnessScript,
		}

		receiverCommitTx = wire.NewMsgTx(2)
		receiverCommitTx.AddTxIn(fakeFundingTxIn)
		receiverCommitTx.AddTxOut(htlcOutput)
	}

	genSweepTx := func(confirmed bool) {
		prevOut := &wire.OutPoint{
			Hash:  receiverCommitTx.TxHash(),
			Index: 0,
		}

		sweepTx = wire.NewMsgTx(2)
		sweepTx.AddTxIn(&wire.TxIn{
			PreviousOutPoint: *prevOut,
		})
		if confirmed {
			sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 1)
		}

		sweepTx.AddTxOut(
			&wire.TxOut{
				PkScript: []byte("doesn't matter"),
				Value:    1 * 10e8,
			},
		)
		sweepTxSigHashes = txscript.NewTxSigHashes(sweepTx)

		aliceSigHash = txscript.SigHashAll
		if confirmed {
			aliceSigHash = txscript.SigHashSingle | txscript.SigHashAnyOneCanPay
		}

		// We'll also generate a signature on the sweep transaction above
		// that will act as Alice's signature to Bob for the second level HTLC
		// transaction.
		aliceSignDesc := SignDescriptor{
			KeyDesc: keychain.KeyDescriptor{
				PubKey: aliceKeyPub,
			},
			SingleTweak:   aliceCommitTweak,
			WitnessScript: htlcWitnessScript,
			Output:        htlcOutput,
			HashType:      aliceSigHash,
			SigHashes:     sweepTxSigHashes,
			InputIndex:    0,
		}
		aliceSig, err := aliceSigner.SignOutputRaw(sweepTx, &aliceSignDesc)
		if err != nil {
			t.Fatalf("unable to generate alice signature: %v", err)
		}

		aliceSenderSig, err = btcec.ParseDERSignature(
			aliceSig.Serialize(), btcec.S256(),
		)
		if err != nil {
			t.Fatalf("unable to parse signature: %v", err)
		}
	}

	// TODO(roasbeef): modify valid to check precise script errors?
	testCases := []struct {
		witness func() wire.TxWitness
		valid   bool
	}{
		{
			// HTLC redemption w/ invalid preimage size
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				genCommitTx(false)
				genSweepTx(false)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: bobKeyPub,
					},
					SingleTweak:   bobCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return ReceiverHtlcSpendRedeem(
					aliceSenderSig, aliceSigHash,
					bytes.Repeat([]byte{1}, 45), bobSigner,
					signDesc, sweepTx,
				)

			}),
			false,
		},
		{
			// HTLC redemption w/ valid preimage size
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				genCommitTx(false)
				genSweepTx(false)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: bobKeyPub,
					},
					SingleTweak:   bobCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return ReceiverHtlcSpendRedeem(
					aliceSenderSig, aliceSigHash,
					paymentPreimage, bobSigner,
					signDesc, sweepTx,
				)
			}),
			true,
		},
		{
			// revoke w/ sig
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				genCommitTx(false)
				genSweepTx(false)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: aliceKeyPub,
					},
					DoubleTweak:   commitSecret,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return ReceiverHtlcSpendRevokeWithKey(aliceSigner,
					signDesc, revocationKey, sweepTx)
			}),
			true,
		},
		{
			// HTLC redemption w/ valid preimage size, and with
			// enforced locktime in HTLC scripts.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				// Make a commit tx that needs confirmation for
				// HTLC output to be spent.
				genCommitTx(true)

				// Generate a sweep with the locktime set.
				genSweepTx(true)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: bobKeyPub,
					},
					SingleTweak:   bobCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return ReceiverHtlcSpendRedeem(
					aliceSenderSig, aliceSigHash,
					paymentPreimage, bobSigner,
					signDesc, sweepTx,
				)
			}),
			true,
		},
		{
			// HTLC redemption w/ valid preimage size, but trying
			// to spend CSV output without sequence set.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				// Generate commitment tx with 1 CSV locked
				// HTLC.
				genCommitTx(true)

				// Generate sweep tx that doesn't have locktime
				// enabled.
				genSweepTx(false)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: bobKeyPub,
					},
					SingleTweak:   bobCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return ReceiverHtlcSpendRedeem(
					aliceSenderSig, aliceSigHash,
					paymentPreimage, bobSigner, signDesc,
					sweepTx,
				)
			}),
			false,
		},

		{
			// refund w/ invalid lock time
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				genCommitTx(false)
				genSweepTx(false)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: aliceKeyPub,
					},
					SingleTweak:   aliceCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return ReceiverHtlcSpendTimeout(aliceSigner, signDesc,
					sweepTx, int32(cltvTimeout-2))
			}),
			false,
		},
		{
			// refund w/ valid lock time
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				genCommitTx(false)
				genSweepTx(false)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: aliceKeyPub,
					},
					SingleTweak:   aliceCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return ReceiverHtlcSpendTimeout(aliceSigner, signDesc,
					sweepTx, int32(cltvTimeout))
			}),
			true,
		},
		{
			// refund w/ valid lock time, and enforced locktime in
			// HTLC scripts.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				// Make a commit tx that needs confirmation for
				// HTLC output to be spent.
				genCommitTx(true)

				// Generate a sweep with the locktime set.
				genSweepTx(true)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: aliceKeyPub,
					},
					SingleTweak:   aliceCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return ReceiverHtlcSpendTimeout(aliceSigner, signDesc,
					sweepTx, int32(cltvTimeout))
			}),
			true,
		},
		{
			// refund w/ valid lock time, but no sequence set in
			// sweep tx trying to spend CSV locked HTLC output.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				// Generate commitment tx with 1 CSV locked
				// HTLC.
				genCommitTx(true)

				// Generate sweep tx that doesn't have locktime
				// enabled.
				genSweepTx(false)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: aliceKeyPub,
					},
					SingleTweak:   aliceCommitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return ReceiverHtlcSpendTimeout(aliceSigner, signDesc,
					sweepTx, int32(cltvTimeout))
			}),
			false,
		},
	}

	for i, testCase := range testCases {
		sweepTx.TxIn[0].Witness = testCase.witness()

		newEngine := func() (*txscript.Engine, error) {
			return txscript.NewEngine(htlcPkScript,
				sweepTx, 0, txscript.StandardVerifyFlags, nil,
				nil, int64(paymentAmt))
		}

		assertEngineExecution(t, i, testCase.valid, newEngine)
	}
}

// TestSecondLevelHtlcSpends tests all the possible redemption clauses from the
// HTLC success and timeout covenant transactions.
func TestSecondLevelHtlcSpends(t *testing.T) {
	t.Parallel()

	// We'll start be creating a creating a 2BTC HTLC.
	const htlcAmt = btcutil.Amount(2 * 10e8)

	// In all of our scenarios, the CSV timeout to claim a self output will
	// be 5 blocks.
	const claimDelay = 5

	// First we'll set up some initial key state for Alice and Bob that
	// will be used in the scripts we created below.
	aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
		testWalletPrivKey)
	bobKeyPriv, bobKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
		bobsPrivKey)

	revokePreimage := testHdSeed.CloneBytes()
	commitSecret, commitPoint := btcec.PrivKeyFromBytes(
		btcec.S256(), revokePreimage)

	// As we're modeling this as Bob sweeping the HTLC on-chain from his
	// commitment transaction after a period of time, we'll be using a
	// revocation key derived from Alice's base point and his secret.
	revocationKey := DeriveRevocationPubkey(aliceKeyPub, commitPoint)

	// Next, craft a fake HTLC outpoint that we'll use to generate the
	// sweeping transaction using.
	txid, err := chainhash.NewHash(testHdSeed.CloneBytes())
	if err != nil {
		t.Fatalf("unable to create txid: %v", err)
	}
	htlcOutPoint := &wire.OutPoint{
		Hash:  *txid,
		Index: 0,
	}
	sweepTx := wire.NewMsgTx(2)
	sweepTx.AddTxIn(wire.NewTxIn(htlcOutPoint, nil, nil))
	sweepTx.AddTxOut(
		&wire.TxOut{
			PkScript: []byte("doesn't matter"),
			Value:    1 * 10e8,
		},
	)
	sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx)

	// The delay key will be crafted using Bob's public key as the output
	// we created will be spending from Alice's commitment transaction.
	delayKey := TweakPubKey(bobKeyPub, commitPoint)

	// The commit tweak will be required in order for Bob to derive the
	// proper key need to spend the output.
	commitTweak := SingleTweakBytes(commitPoint, bobKeyPub)

	// Finally we'll generate the HTLC script itself that we'll be spending
	// from. The revocation clause can be claimed by Alice, while Bob can
	// sweep the output after a particular delay.
	htlcWitnessScript, err := SecondLevelHtlcScript(revocationKey,
		delayKey, claimDelay)
	if err != nil {
		t.Fatalf("unable to create htlc script: %v", err)
	}
	htlcPkScript, err := WitnessScriptHash(htlcWitnessScript)
	if err != nil {
		t.Fatalf("unable to create htlc output: %v", err)
	}

	htlcOutput := &wire.TxOut{
		PkScript: htlcPkScript,
		Value:    int64(htlcAmt),
	}

	// TODO(roasbeef): make actually use timeout/success txns?

	// Finally, we'll create mock signers for both of them based on their
	// private keys. This test simplifies a bit and uses the same key as
	// the base point for all scripts and derivations.
	bobSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{bobKeyPriv}}
	aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}}

	testCases := []struct {
		witness func() wire.TxWitness
		valid   bool
	}{
		{
			// Sender of the HTLC attempts to activate the
			// revocation clause, but uses the wrong key (fails to
			// use the double tweak in this case).
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: aliceKeyPub,
					},
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return HtlcSpendRevoke(aliceSigner, signDesc,
					sweepTx)
			}),
			false,
		},
		{
			// Sender of HTLC activates the revocation clause.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: aliceKeyPub,
					},
					DoubleTweak:   commitSecret,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return HtlcSpendRevoke(aliceSigner, signDesc,
					sweepTx)
			}),
			true,
		},
		{
			// Receiver of the HTLC attempts to sweep, but tries to
			// do so pre-maturely with a smaller CSV delay (2
			// blocks instead of 5 blocks).
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: bobKeyPub,
					},
					SingleTweak:   commitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return HtlcSpendSuccess(bobSigner, signDesc,
					sweepTx, claimDelay-3)
			}),
			false,
		},
		{
			// Receiver of the HTLC sweeps with the proper CSV
			// delay, but uses the wrong key (leaves off the single
			// tweak).
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: bobKeyPub,
					},
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return HtlcSpendSuccess(bobSigner, signDesc,
					sweepTx, claimDelay)
			}),
			false,
		},
		{
			// Receiver of the HTLC sweeps with the proper CSV
			// delay, and the correct key.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: bobKeyPub,
					},
					SingleTweak:   commitTweak,
					WitnessScript: htlcWitnessScript,
					Output:        htlcOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return HtlcSpendSuccess(bobSigner, signDesc,
					sweepTx, claimDelay)
			}),
			true,
		},
	}

	for i, testCase := range testCases {
		sweepTx.TxIn[0].Witness = testCase.witness()

		newEngine := func() (*txscript.Engine, error) {
			return txscript.NewEngine(htlcPkScript,
				sweepTx, 0, txscript.StandardVerifyFlags, nil,
				nil, int64(htlcAmt))
		}

		assertEngineExecution(t, i, testCase.valid, newEngine)
	}
}

// TestCommitSpendToRemoteConfirmed checks that the delayed version of the
// to_remote version can only be spent by the owner, and after one
// confirmation.
func TestCommitSpendToRemoteConfirmed(t *testing.T) {
	t.Parallel()

	const outputVal = btcutil.Amount(2 * 10e8)

	aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
		testWalletPrivKey)

	txid, err := chainhash.NewHash(testHdSeed.CloneBytes())
	if err != nil {
		t.Fatalf("unable to create txid: %v", err)
	}
	commitOut := &wire.OutPoint{
		Hash:  *txid,
		Index: 0,
	}
	commitScript, err := CommitScriptToRemoteConfirmed(aliceKeyPub)
	if err != nil {
		t.Fatalf("unable to create htlc script: %v", err)
	}
	commitPkScript, err := WitnessScriptHash(commitScript)
	if err != nil {
		t.Fatalf("unable to create htlc output: %v", err)
	}

	commitOutput := &wire.TxOut{
		PkScript: commitPkScript,
		Value:    int64(outputVal),
	}

	sweepTx := wire.NewMsgTx(2)
	sweepTx.AddTxIn(wire.NewTxIn(commitOut, nil, nil))
	sweepTx.AddTxOut(
		&wire.TxOut{
			PkScript: []byte("doesn't matter"),
			Value:    1 * 10e8,
		},
	)

	aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}}

	testCases := []struct {
		witness func() wire.TxWitness
		valid   bool
	}{
		{
			// Alice can spend after the a CSV delay has passed.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 1)
				sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: aliceKeyPub,
					},
					WitnessScript: commitScript,
					Output:        commitOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return CommitSpendToRemoteConfirmed(aliceSigner, signDesc,
					sweepTx)
			}),
			true,
		},
		{
			// Alice cannot spend output without sequence set.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				sweepTx.TxIn[0].Sequence = wire.MaxTxInSequenceNum
				sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: aliceKeyPub,
					},
					WitnessScript: commitScript,
					Output:        commitOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return CommitSpendToRemoteConfirmed(aliceSigner, signDesc,
					sweepTx)
			}),
			false,
		},
	}

	for i, testCase := range testCases {
		sweepTx.TxIn[0].Witness = testCase.witness()

		newEngine := func() (*txscript.Engine, error) {
			return txscript.NewEngine(commitPkScript,
				sweepTx, 0, txscript.StandardVerifyFlags, nil,
				nil, int64(outputVal))
		}

		assertEngineExecution(t, i, testCase.valid, newEngine)
	}
}

// TestSpendAnchor checks that we can spend the anchors using the various spend
// paths.
func TestSpendAnchor(t *testing.T) {
	t.Parallel()

	const anchorSize = 294

	// First we'll set up some initial key state for Alice.
	aliceKeyPriv, aliceKeyPub := btcec.PrivKeyFromBytes(btcec.S256(),
		testWalletPrivKey)

	// Create a fake anchor outpoint that we'll use to generate the
	// sweeping transaction.
	txid, err := chainhash.NewHash(testHdSeed.CloneBytes())
	if err != nil {
		t.Fatalf("unable to create txid: %v", err)
	}
	anchorOutPoint := &wire.OutPoint{
		Hash:  *txid,
		Index: 0,
	}

	sweepTx := wire.NewMsgTx(2)
	sweepTx.AddTxIn(wire.NewTxIn(anchorOutPoint, nil, nil))
	sweepTx.AddTxOut(
		&wire.TxOut{
			PkScript: []byte("doesn't matter"),
			Value:    1 * 10e8,
		},
	)

	// Generate the anchor script that can be spent by Alice immediately,
	// or by anyone after 16 blocks.
	anchorScript, err := CommitScriptAnchor(aliceKeyPub)
	if err != nil {
		t.Fatalf("unable to create htlc script: %v", err)
	}
	anchorPkScript, err := WitnessScriptHash(anchorScript)
	if err != nil {
		t.Fatalf("unable to create htlc output: %v", err)
	}

	anchorOutput := &wire.TxOut{
		PkScript: anchorPkScript,
		Value:    int64(anchorSize),
	}

	// Create mock signer for Alice.
	aliceSigner := &MockSigner{Privkeys: []*btcec.PrivateKey{aliceKeyPriv}}

	testCases := []struct {
		witness func() wire.TxWitness
		valid   bool
	}{
		{
			// Alice can spend immediately.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				sweepTx.TxIn[0].Sequence = wire.MaxTxInSequenceNum
				sweepTxSigHashes := txscript.NewTxSigHashes(sweepTx)

				signDesc := &SignDescriptor{
					KeyDesc: keychain.KeyDescriptor{
						PubKey: aliceKeyPub,
					},
					WitnessScript: anchorScript,
					Output:        anchorOutput,
					HashType:      txscript.SigHashAll,
					SigHashes:     sweepTxSigHashes,
					InputIndex:    0,
				}

				return CommitSpendAnchor(aliceSigner, signDesc,
					sweepTx)
			}),
			true,
		},
		{
			// Anyone can spend after 16 blocks.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 16)
				return CommitSpendAnchorAnyone(anchorScript)
			}),
			true,
		},
		{
			// Anyone cannot spend before 16 blocks.
			makeWitnessTestCase(t, func() (wire.TxWitness, error) {
				sweepTx.TxIn[0].Sequence = LockTimeToSequence(false, 15)
				return CommitSpendAnchorAnyone(anchorScript)
			}),
			false,
		},
	}

	for i, testCase := range testCases {
		sweepTx.TxIn[0].Witness = testCase.witness()

		newEngine := func() (*txscript.Engine, error) {
			return txscript.NewEngine(anchorPkScript,
				sweepTx, 0, txscript.StandardVerifyFlags, nil,
				nil, int64(anchorSize))
		}

		assertEngineExecution(t, i, testCase.valid, newEngine)
	}
}

// TestSpecificationKeyDerivation implements the test vectors provided in
// BOLT-03, Appendix E.
func TestSpecificationKeyDerivation(t *testing.T) {
	const (
		baseSecretHex          = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
		perCommitmentSecretHex = "1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100"
		basePointHex           = "036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2"
		perCommitmentPointHex  = "025f7117a78150fe2ef97db7cfc83bd57b2e2c0d0dd25eaf467a4a1c2a45ce1486"
	)

	baseSecret, err := privkeyFromHex(baseSecretHex)
	if err != nil {
		t.Fatalf("Failed to parse serialized privkey: %v", err)
	}
	perCommitmentSecret, err := privkeyFromHex(perCommitmentSecretHex)
	if err != nil {
		t.Fatalf("Failed to parse serialized privkey: %v", err)
	}
	basePoint, err := pubkeyFromHex(basePointHex)
	if err != nil {
		t.Fatalf("Failed to parse serialized pubkey: %v", err)
	}
	perCommitmentPoint, err := pubkeyFromHex(perCommitmentPointHex)
	if err != nil {
		t.Fatalf("Failed to parse serialized pubkey: %v", err)
	}

	// name: derivation of key from basepoint and per_commitment_point
	const expectedLocalKeyHex = "0235f2dbfaa89b57ec7b055afe29849ef7ddfeb1cefdb9ebdc43f5494984db29e5"
	actualLocalKey := TweakPubKey(basePoint, perCommitmentPoint)
	actualLocalKeyHex := pubkeyToHex(actualLocalKey)
	if actualLocalKeyHex != expectedLocalKeyHex {
		t.Errorf("Incorrect derivation of local public key: "+
			"expected %v, got %v", expectedLocalKeyHex, actualLocalKeyHex)
	}

	// name: derivation of secret key from basepoint secret and per_commitment_secret
	const expectedLocalPrivKeyHex = "cbced912d3b21bf196a766651e436aff192362621ce317704ea2f75d87e7be0f"
	tweak := SingleTweakBytes(perCommitmentPoint, basePoint)
	actualLocalPrivKey := TweakPrivKey(baseSecret, tweak)
	actualLocalPrivKeyHex := privkeyToHex(actualLocalPrivKey)
	if actualLocalPrivKeyHex != expectedLocalPrivKeyHex {
		t.Errorf("Incorrect derivation of local private key: "+
			"expected %v, got %v, %v", expectedLocalPrivKeyHex,
			actualLocalPrivKeyHex, hex.EncodeToString(tweak))
	}

	// name: derivation of revocation key from basepoint and per_commitment_point
	const expectedRevocationKeyHex = "02916e326636d19c33f13e8c0c3a03dd157f332f3e99c317c141dd865eb01f8ff0"
	actualRevocationKey := DeriveRevocationPubkey(basePoint, perCommitmentPoint)
	actualRevocationKeyHex := pubkeyToHex(actualRevocationKey)
	if actualRevocationKeyHex != expectedRevocationKeyHex {
		t.Errorf("Incorrect derivation of revocation public key: "+
			"expected %v, got %v", expectedRevocationKeyHex,
			actualRevocationKeyHex)
	}

	// name: derivation of revocation secret from basepoint_secret and per_commitment_secret
	const expectedRevocationPrivKeyHex = "d09ffff62ddb2297ab000cc85bcb4283fdeb6aa052affbc9dddcf33b61078110"
	actualRevocationPrivKey := DeriveRevocationPrivKey(baseSecret,
		perCommitmentSecret)
	actualRevocationPrivKeyHex := privkeyToHex(actualRevocationPrivKey)
	if actualRevocationPrivKeyHex != expectedRevocationPrivKeyHex {
		t.Errorf("Incorrect derivation of revocation private key: "+
			"expected %v, got %v", expectedRevocationPrivKeyHex,
			actualRevocationPrivKeyHex)
	}
}
