input/size: assert witness size constants

This commit introduces a new test case that asserts all of the witness
size constants currently in the codebase. We also reintroduce the
AcceptedHtlcSuccessWitnessSize and OfferedHtlcTimeoutWitnessSize
constants that were recently removed for the sake of completeness.

In asserting the witnes sizes, there were three uncovered discrepancies:
 * OfferedHtlcSuccessWitnessSize overestimated by about 30% because it
   included an extra signature in the calculation.

 * ToLocalPenaltyWitnessSize was underestimated by one byte, because it
   was missing the length byte for the OP_TRUE. This has implications
   the watchtower protocol since the client and server are assumed to
   share the same weight estimates used for signing. This commit keeps
   the current behavior, with the intention of rolling out negotiation
   for which weight estimate to use for a given session.

 * AcceptedHtlcScriptSize was underestimated by one byte because it was
   missing a length byte for the value 32 pushed on the stack when
   asserting the preimage's length. This affects all AcceptedHtlc*
   witness sizes.
This commit is contained in:
Conner Fromknecht committed 2020-04-10 15:34:27 -07:00
1 parent f2b6e2af04
commit c1b9b272cd
7 files changed
+648 -73

No files matched your search

+565 -8
View File
@@ -1,15 +1,45 @@
package input_test
import (
"math/big"
"testing"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/input"
"github.com/lightningnetwork/lnd/keychain"
)
const (
testCSVDelay = (1 << 31) - 1
testCLTVExpiry = 500000000
// maxDERSignatureSize is the largest possible DER-encoded signature
// without the trailing sighash flag.
maxDERSignatureSize = 72
)
var (
testPubkeyBytes = make([]byte, 33)
testHash160 = make([]byte, 20)
testPreimage = make([]byte, 32)
// testPubkey is a pubkey used in script size calculation.
testPubkey = &btcec.PublicKey{
X: &big.Int{},
Y: &big.Int{},
}
testPrivkey, _ = btcec.PrivKeyFromBytes(btcec.S256(), make([]byte, 32))
testTx = wire.NewMsgTx(2)
)
// TestTxWeightEstimator tests that transaction weight estimates are calculated
@@ -204,7 +234,7 @@ func TestTxWeightEstimator(t *testing.T) {
for j := 0; j < test.numP2PKHInputs; j++ {
weightEstimate.AddP2PKHInput()
signature := make([]byte, 73)
signature := make([]byte, maxDERSignatureSize+1)
compressedPubKey := make([]byte, 33)
scriptSig, err := txscript.NewScriptBuilder().AddData(signature).
AddData(compressedPubKey).Script()
@@ -217,7 +247,7 @@ func TestTxWeightEstimator(t *testing.T) {
for j := 0; j < test.numP2WKHInputs; j++ {
weightEstimate.AddP2WKHInput()
signature := make([]byte, 73)
signature := make([]byte, maxDERSignatureSize+1)
compressedPubKey := make([]byte, 33)
witness := wire.TxWitness{signature, compressedPubKey}
tx.AddTxIn(&wire.TxIn{Witness: witness})
@@ -232,7 +262,7 @@ func TestTxWeightEstimator(t *testing.T) {
for j := 0; j < test.numNestedP2WKHInputs; j++ {
weightEstimate.AddNestedP2WKHInput()
signature := make([]byte, 73)
signature := make([]byte, maxDERSignatureSize+1)
compressedPubKey := make([]byte, 33)
witness := wire.TxWitness{signature, compressedPubKey}
scriptSig, err := txscript.NewScriptBuilder().AddData(p2wkhScript).
@@ -281,10 +311,537 @@ func TestTxWeightEstimator(t *testing.T) {
}
}
// TestSizes guards calculated constants to make sure their values remain
// unchanged.
func TestSizes(t *testing.T) {
if input.AnchorWitnessSize != 116 {
t.Fatal("unexpected anchor witness size")
type maxDERSignature struct{}
func (s *maxDERSignature) Serialize() []byte {
// Always return worst-case signature length, excluding the one byte
// sighash flag.
return make([]byte, maxDERSignatureSize)
}
func (s *maxDERSignature) Verify(_ []byte, _ *btcec.PublicKey) bool {
return true
}
// dummySigner is a fake signer used for size (upper bound) calculations.
type dummySigner struct {
input.Signer
}
// SignOutputRaw generates a signature for the passed transaction according to
// the data within the passed SignDescriptor.
func (s *dummySigner) SignOutputRaw(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (input.Signature, error) {
return &maxDERSignature{}, nil
}
type witnessSizeTest struct {
name string
expSize int
genWitness func(t *testing.T) wire.TxWitness
}
var witnessSizeTests = []witnessSizeTest{
{
name: "funding",
expSize: input.MultiSigWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
witnessScript, _, err := input.GenFundingPkScript(
testPubkeyBytes, testPubkeyBytes, 1,
)
if err != nil {
t.Fatal(err)
}
return input.SpendMultiSig(
witnessScript,
testPubkeyBytes, &maxDERSignature{},
testPubkeyBytes, &maxDERSignature{},
)
},
},
{
name: "to local timeout",
expSize: input.ToLocalTimeoutWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
witnessScript, err := input.CommitScriptToSelf(
testCSVDelay, testPubkey, testPubkey,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witnessScript,
}
witness, err := input.CommitSpendTimeout(
&dummySigner{}, signDesc, testTx,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "to local revoke",
expSize: input.ToLocalPenaltyWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
witnessScript, err := input.CommitScriptToSelf(
testCSVDelay, testPubkey, testPubkey,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witnessScript,
}
witness, err := input.CommitSpendRevoke(
&dummySigner{}, signDesc, testTx,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "to remote confirmed",
expSize: input.ToRemoteConfirmedWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
witScript, err := input.CommitScriptToRemoteConfirmed(
testPubkey,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witScript,
KeyDesc: keychain.KeyDescriptor{
PubKey: testPubkey,
},
}
witness, err := input.CommitSpendToRemoteConfirmed(
&dummySigner{}, signDesc, testTx,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "anchor",
expSize: input.AnchorWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
witScript, err := input.CommitScriptAnchor(
testPubkey,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witScript,
KeyDesc: keychain.KeyDescriptor{
PubKey: testPubkey,
},
}
witness, err := input.CommitSpendAnchor(
&dummySigner{}, signDesc, testTx,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "anchor anyone",
expSize: 43,
genWitness: func(t *testing.T) wire.TxWitness {
witScript, err := input.CommitScriptAnchor(
testPubkey,
)
if err != nil {
t.Fatal(err)
}
witness, _ := input.CommitSpendAnchorAnyone(witScript)
return witness
},
},
{
name: "offered htlc revoke",
expSize: input.OfferedHtlcPenaltyWitnessSize - 3,
genWitness: func(t *testing.T) wire.TxWitness {
witScript, err := input.SenderHTLCScript(
testPubkey, testPubkey, testPubkey,
testHash160, false,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witScript,
KeyDesc: keychain.KeyDescriptor{
PubKey: testPubkey,
},
DoubleTweak: testPrivkey,
}
witness, err := input.SenderHtlcSpendRevoke(
&dummySigner{}, signDesc, testTx,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "offered htlc revoke confirmed",
expSize: input.OfferedHtlcPenaltyWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
hash := make([]byte, 20)
witScript, err := input.SenderHTLCScript(
testPubkey, testPubkey, testPubkey,
hash, true,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witScript,
KeyDesc: keychain.KeyDescriptor{
PubKey: testPubkey,
},
DoubleTweak: testPrivkey,
}
witness, err := input.SenderHtlcSpendRevoke(
&dummySigner{}, signDesc, testTx,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "offered htlc timeout",
expSize: input.OfferedHtlcTimeoutWitnessSize - 3,
genWitness: func(t *testing.T) wire.TxWitness {
witScript, err := input.SenderHTLCScript(
testPubkey, testPubkey, testPubkey,
testHash160, false,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witScript,
}
witness, err := input.SenderHtlcSpendTimeout(
&maxDERSignature{}, txscript.SigHashAll,
&dummySigner{}, signDesc, testTx,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "offered htlc timeout confirmed",
expSize: input.OfferedHtlcTimeoutWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
witScript, err := input.SenderHTLCScript(
testPubkey, testPubkey, testPubkey,
testHash160, true,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witScript,
}
witness, err := input.SenderHtlcSpendTimeout(
&maxDERSignature{}, txscript.SigHashAll,
&dummySigner{}, signDesc, testTx,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "offered htlc success",
expSize: input.OfferedHtlcSuccessWitnessSize - 3,
genWitness: func(t *testing.T) wire.TxWitness {
witScript, err := input.SenderHTLCScript(
testPubkey, testPubkey, testPubkey,
testHash160, false,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witScript,
}
witness, err := input.SenderHtlcSpendRedeem(
&dummySigner{}, signDesc, testTx, testPreimage,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "offered htlc success confirmed",
expSize: input.OfferedHtlcSuccessWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
witScript, err := input.SenderHTLCScript(
testPubkey, testPubkey, testPubkey,
testHash160, true,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witScript,
}
witness, err := input.SenderHtlcSpendRedeem(
&dummySigner{}, signDesc, testTx, testPreimage,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "accepted htlc revoke",
expSize: input.AcceptedHtlcPenaltyWitnessSize - 3,
genWitness: func(t *testing.T) wire.TxWitness {
witScript, err := input.ReceiverHTLCScript(
testCLTVExpiry, testPubkey, testPubkey,
testPubkey, testHash160, false,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witScript,
KeyDesc: keychain.KeyDescriptor{
PubKey: testPubkey,
},
DoubleTweak: testPrivkey,
}
witness, err := input.ReceiverHtlcSpendRevoke(
&dummySigner{}, signDesc, testTx,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "accepted htlc revoke confirmed",
expSize: input.AcceptedHtlcPenaltyWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
witScript, err := input.ReceiverHTLCScript(
testCLTVExpiry, testPubkey, testPubkey,
testPubkey, testHash160, true,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witScript,
KeyDesc: keychain.KeyDescriptor{
PubKey: testPubkey,
},
DoubleTweak: testPrivkey,
}
witness, err := input.ReceiverHtlcSpendRevoke(
&dummySigner{}, signDesc, testTx,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "accepted htlc timeout",
expSize: input.AcceptedHtlcTimeoutWitnessSize - 3,
genWitness: func(t *testing.T) wire.TxWitness {
witScript, err := input.ReceiverHTLCScript(
testCLTVExpiry, testPubkey, testPubkey,
testPubkey, testHash160, false,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witScript,
}
witness, err := input.ReceiverHtlcSpendTimeout(
&dummySigner{}, signDesc, testTx,
testCLTVExpiry,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "accepted htlc timeout confirmed",
expSize: input.AcceptedHtlcTimeoutWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
witScript, err := input.ReceiverHTLCScript(
testCLTVExpiry, testPubkey, testPubkey,
testPubkey, testHash160, true,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witScript,
}
witness, err := input.ReceiverHtlcSpendTimeout(
&dummySigner{}, signDesc, testTx,
testCLTVExpiry,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "accepted htlc success",
expSize: input.AcceptedHtlcSuccessWitnessSize - 3,
genWitness: func(t *testing.T) wire.TxWitness {
witScript, err := input.ReceiverHTLCScript(
testCLTVExpiry, testPubkey, testPubkey,
testPubkey, testHash160, false,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witScript,
KeyDesc: keychain.KeyDescriptor{
PubKey: testPubkey,
},
}
witness, err := input.ReceiverHtlcSpendRedeem(
&maxDERSignature{}, txscript.SigHashAll,
testPreimage, &dummySigner{}, signDesc, testTx,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
{
name: "accepted htlc success confirmed",
expSize: input.AcceptedHtlcSuccessWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
witScript, err := input.ReceiverHTLCScript(
testCLTVExpiry, testPubkey, testPubkey,
testPubkey, testHash160, true,
)
if err != nil {
t.Fatal(err)
}
signDesc := &input.SignDescriptor{
WitnessScript: witScript,
KeyDesc: keychain.KeyDescriptor{
PubKey: testPubkey,
},
}
witness, err := input.ReceiverHtlcSpendRedeem(
&maxDERSignature{}, txscript.SigHashAll,
testPreimage, &dummySigner{}, signDesc, testTx,
)
if err != nil {
t.Fatal(err)
}
return witness
},
},
}
// TestWitnessSizes asserts the correctness of our magic witness constants.
// Witnesses involving signatures will have maxDERSignatures injected so that we
// can determine upper bounds for the witness sizes. These constants are
// predominately used for fee estimation, so we want to be certain that we
// aren't under estimating or our transactions could get stuck.
func TestWitnessSizes(t *testing.T) {
for _, test := range witnessSizeTests {
test := test
t.Run(test.name, func(t *testing.T) {
size := test.genWitness(t).SerializeSize()
if size != test.expSize {
t.Fatalf("size mismatch, want: %v, got: %v",
test.expSize, size)
}
})
}
}