input: add weight estimation + tests for all taproot witness gen types

This commit is contained in:
Olaoluwa Osuntokun 2023-03-01 21:48:28 -08:00
parent d3c7c51f7e
commit 405a435a84
No known key found for this signature in database
GPG key ID: 3BBD59E99B280306
5 changed files with 956 additions and 64 deletions

View file

@ -153,6 +153,12 @@ const (
// - PkScript (P2WSH)
CommitmentDelayOutput = 8 + 1 + P2WSHSize
// TaprootCommitmentOutput 43 bytes
// - Value: 8 bytes
// - VarInt: 1 byte (PkScript length)
// - PkScript (P2TR)
TaprootCommitmentOutput = 8 + 1 + P2TRSize
// CommitmentKeyHashOutput 31 bytes
// - Value: 8 bytes
// - VarInt: 1 byte (PkScript length)
@ -165,6 +171,12 @@ const (
// - PkScript (P2WSH)
CommitmentAnchorOutput = 8 + 1 + P2WSHSize
// TaprootCommitmentAnchorOutput 43 bytes
// - Value: 8 bytes
// - VarInt: 1 byte (PkScript length)
// - PkScript (P2TR)
TaprootCommitmentAnchorOutput = 8 + 1 + P2TRSize
// HTLCSize 43 bytes
// - Value: 8 bytes
// - VarInt: 1 byte (PkScript length)
@ -222,12 +234,32 @@ const (
// BaseAnchorCommitmentTxWeight 900 weight.
BaseAnchorCommitmentTxWeight = witnessScaleFactor * BaseAnchorCommitmentTxSize
// BaseTaprootCommitmentTxWeight 225 + 43 * num-htlc-outputs bytes
// - Version: 4 bytes
// - WitnessHeader <---- part of the witness data
// - CountTxIn: 1 byte
// - TxIn: 41 bytes
// FundingInput
// - CountTxOut: 3 byte
// - TxOut: 172 + 43 * num-htlc-outputs bytes
// OutputPayingToThem,
// OutputPayingToUs,
// ....HTLCOutputs...
// - LockTime: 4 bytes
BaseTaprootCommitmentTxWeight = (4 + 1 + FundingInputSize + 3 +
2*TaprootCommitmentOutput + 2*TaprootCommitmentAnchorOutput + 4) *
witnessScaleFactor
// CommitWeight 724 weight.
CommitWeight = BaseCommitmentTxWeight + WitnessCommitmentTxWeight
// AnchorCommitWeight 1124 weight.
AnchorCommitWeight = BaseAnchorCommitmentTxWeight + WitnessCommitmentTxWeight
// TaprootCommitWeight 968 weight.
TaprootCommitWeight = (BaseTaprootCommitmentTxWeight +
WitnessHeaderSize + TaprootKeyPathWitnessSize)
// HTLCWeight 172 weight.
HTLCWeight = witnessScaleFactor * HTLCSize
@ -236,11 +268,19 @@ const (
// which will transition an outgoing HTLC to the delay-and-claim state.
HtlcTimeoutWeight = 663
// TaprootHtlcTimeoutWeight is the total weight of the taproot HTLC
// timeout transaction.
TaprootHtlcTimeoutWeight = 645
// HtlcSuccessWeight 703 weight
// HtlcSuccessWeight is the weight of the HTLC success transaction
// which will transition an incoming HTLC to the delay-and-claim state.
HtlcSuccessWeight = 703
// TaprootHtlcSuccessWeight is the total weight of the taproot HTLC
// success transaction.
TaprootHtlcSuccessWeight = 705
// HtlcConfirmedScriptOverhead 3 bytes
// HtlcConfirmedScriptOverhead is the extra length of an HTLC script
// that requires confirmation before it can be spent. These extra bytes
@ -556,6 +596,232 @@ const (
// - leafVersionAndParity: 1 byte
// - schnorrPubKey: 32 byte
TaprootBaseControlBlockWitnessSize = 33
// TaprootToLocalScriptSize
// - OP_DATA: 1 byte (pub key len)
// - local_key: 32 bytes
// - OP_CHECKSIG: 1 byte
// - OP_DATA: 1 byte (csv delay)
// - csv_delay: 4 bytes (worst case estimate)
// - OP_CSV: 1 byte
// - OP_DROP: 1 byte
TaprootToLocalScriptSize = 41
// TaprootToLocalWitnessSize: 175 bytes
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - script_len: 1 byte
// - taproot_to_local_script_size: 41 bytes
// - ctrl_block_len: 1 byte
// - base_control_block_size: 33 bytes
// - sibling_merkle_hash: 32 bytes
TaprootToLocalWitnessSize = 1 + 1 + 65 + 1 + TaprootToLocalScriptSize +
1 + TaprootBaseControlBlockWitnessSize + 32
// TaprootToLocalRevokeScriptSize: 68 bytes
// - OP_DATA: 1 byte
// - local key: 32 bytes
// - OP_DROP: 1 byte
// - OP_DATA: 1 byte
// - revocation key: 32 bytes
// - OP_CHECKSIG: 1 byte
TaprootToLocalRevokeScriptSize = 1 + 32 + 1 + 1 + 32 + 1
// TaprootToLocalRevokeWitnessSize: 202 bytes
// - NumberOfWitnessElements: 1 byte
// - sigLength: 1 byte
// - sweep sig: 65 bytes
// - script len: 1 byte
// - revocation script size: 68 bytes
// - ctrl block size: 1 byte
// - base control block: 33 bytes
// - merkle proof: 32
TaprootToLocalRevokeWitnessSize = 1 + 1 + 65 + 1 + TaprootToLocalRevokeScriptSize + 1 + 33 + 32
// TaprootToRemoteScriptSize
// - OP_DATA: 1 byte
// - remote key: 32 bytes
// - OP_CHECKSIG: 1 byte
// - OP_1: 1 byte
// - OP_CHECKSEQUENCEVERIFY: 1 byte
// - OP_DROP: 1 byte
TaprootToRemoteScriptSize = 1 + 32 + 1 + 1 + 1 + 1
// TaprootToRemoteWitnessSize:
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - script_len: 1 byte
// - taproot_to_local_script_size: 36 bytes
// - ctrl_block_len: 1 byte
// - base_control_block_size: 33 bytes
TaprootToRemoteWitnessSize = 1 + 1 + 65 + 1 + TaprootToRemoteScriptSize +
1 + TaprootBaseControlBlockWitnessSize
// TaprootAnchorWitnessSize: 67 bytes
//
// In this case, we use the custom sighash size to give the most
// pessemistic estimate.
TaprootAnchorWitnessSize = TaprootKeyPathCustomSighashWitnessSize
// TaprootSecondLevelHtlcScriptSize: 41 bytes
// - OP_DATA: 1 byte (pub key len)
// - local_key: 32 bytes
// - OP_CHECKSIG: 1 byte
// - OP_DATA: 1 byte (csv delay)
// - csv_delay: 4 bytes (worst case)
// - OP_CSV: 1 byte
// - OP_DROP: 1 byte
TaprootSecondLevelHtlcScriptSize = 1 + 32 + 1 + 1 + 4 + 1 + 1
// TaprootSecondLevelHtlcWitnessSize:
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - script_len: 1 byte
// - taproot_second_level_htlc_script_size: 40 bytes
// - ctrl_block_len: 1 byte
// - base_control_block_size: 33 bytes
TaprootSecondLevelHtlcWitnessSize = 1 + 1 + 65 + 1 +
TaprootSecondLevelHtlcScriptSize + 1 +
TaprootBaseControlBlockWitnessSize
// TaprootSecondLevelRevokeWitnessSize
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
TaprootSecondLevelRevokeWitnessSize = TaprootKeyPathCustomSighashWitnessSize
// TaprootAcceptedRevokeWitnessSize:
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
TaprootAcceptedRevokeWitnessSize = TaprootKeyPathCustomSighashWitnessSize
// TaprootOfferedRevokeWitnessSize:
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
TaprootOfferedRevokeWitnessSize = TaprootKeyPathCustomSighashWitnessSize
// TaprootHtlcOfferedRemoteTimeoutScriptSize: 42 bytes
// - OP_DATA: 1 byte (pub key len)
// - local_key: 32 bytes
// - OP_CHECKSIG: 1 byte
// - OP_1: 1 byte
// - OP_DROP: 1 byte
// - OP_CHECKSEQUENCEVERIFY: 1 byte
// - OP_DATA: 1 byte (cltv_expiry length)
// - cltv_expiry: 4 bytes
// - OP_CHECKLOCKTIMEVERIFY: 1 byte
// - OP_DROP: 1 byte
TaprootHtlcOfferedRemoteTimeoutScriptSize = 1 + 32 + 1 + 1 + 1 + 1 + 1 + 4 + 1 + 1
// TaprootHtlcOfferedRemoteTimeoutwitSize: 176 bytes
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - script_len: 1 byte
// - taproot_offered_htlc_script_size: 42 bytes
// - ctrl_block_len: 1 byte
// - base_control_block_size: 33 bytes
// - sibilng_merkle_proof: 32 bytes
TaprootHtlcOfferedRemoteTimeoutWitnessSize = 1 + 1 + 65 + 1 +
TaprootHtlcOfferedRemoteTimeoutScriptSize + 1 +
TaprootBaseControlBlockWitnessSize + 32
// TaprootHtlcOfferedLocalTmeoutScriptSize:
// - OP_DATA: 1 byte (pub key len)
// - local_key: 32 bytes
// - OP_CHECKSIGVERIFY: 1 byte
// - OP_DATA: 1 byte (pub key len)
// - remote_key: 32 bytes
// - OP_CHECKSIG: 1 byte
TaprootHtlcOfferedLocalTimeoutScriptSize = 1 + 32 + 1 + 1 + 32 + 1
// TaprootOfferedLocalTimeoutWitnessSize
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - script_len: 1 byte
// - taproot_offered_htlc_script_timeout_size:
// - ctrl_block_len: 1 byte
// - base_control_block_size: 33 bytes
// - sibilng_merkle_proof: 32 bytes
TaprootOfferedLocalTimeoutWitnessSize = 1 + 1 + 65 + 1 + 65 + 1 +
TaprootHtlcOfferedLocalTimeoutScriptSize + 1 +
TaprootBaseControlBlockWitnessSize + 32
// TaprootHtlcAcceptedRemoteSuccessScriptSize:
// - OP_SIZE: 1 byte
// - OP_DATA: 1 byte
// - 32: 1 byte
// - OP_EQUALVERIFY: 1 byte
// - OP_HASH160: 1 byte
// - OP_DATA: 1 byte (RIPEMD160(payment_hash) length)
// - RIPEMD160(payment_hash): 20 bytes
// - OP_EQUALVERIFY: 1 byte
// - OP_DATA: 1 byte (pub key len)
// - remote_key: 32 bytes
// - OP_CHECKSIG: 1 byte
// - OP_1: 1 byte
// - OP_CSV: 1 byte
// - OP_DROP: 1 byte
TaprootHtlcAcceptedRemoteSuccessScriptSize = 1 + 1 + 1 + 1 + 1 + 1 +
1 + 20 + 1 + 32 + 1 + 1 + 1 + 1
// TaprootHtlcAcceptedRemoteSuccessScriptSize:
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - payment_preimage_length: 1 byte
// - payment_preimage: 32 bytes
// - script_len: 1 byte
// - taproot_offered_htlc_script_success_size:
// - ctrl_block_len: 1 byte
// - base_control_block_size: 33 bytes
// - sibilng_merkle_proof: 32 bytes
TaprootHtlcAcceptedRemoteSuccessWitnessSize = 1 + 1 + 65 + 1 + 32 + 1 +
TaprootHtlcAcceptedRemoteSuccessScriptSize + 1 +
TaprootBaseControlBlockWitnessSize + 32
// TaprootHtlcAcceptedLocalSuccessScriptSize:
// - OP_SIZE: 1 byte
// - OP_DATA: 1 byte
// - 32: 1 byte
// - OP_EQUALVERIFY: 1 byte
// - OP_HASH160: 1 byte
// - OP_DATA: 1 byte (RIPEMD160(payment_hash) length)
// - RIPEMD160(payment_hash): 20 bytes
// - OP_EQUALVERIFY: 1 byte
// - OP_DATA: 1 byte (pub key len)
// - local_key: 32 bytes
// - OP_CHECKSIGVERIFY: 1 byte
// - OP_DATA: 1 byte (pub key len)
// - remote_key: 32 bytes
// - OP_CHECKSIG: 1 byte
TaprootHtlcAcceptedLocalSuccessScriptSize = 1 + 1 + 1 + 1 + 1 + 1 +
20 + 1 + 1 + 32 + 1 + 1 + 32 + 1
// TaprootHtlcAcceptedLocalSuccessWitnessSize:
// - number_of_witness_elements: 1 byte
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - sig_len: 1 byte
// - sweep_sig: 65 bytes (worst case w/o sighash default)
// - payment_preimage_length: 1 byte
// - payment_preimage: 32 bytes
// - script_len: 1 byte
// - taproot_accepted_htlc_script_success_size:
// - ctrl_block_len: 1 byte
// - base_control_block_size: 33 bytes
// - sibilng_merkle_proof: 32 bytes
TaprootHtlcAcceptedLocalSuccessWitnessSize = 1 + 1 + 65 + 1 + 65 + 1 +
32 + 1 + TaprootHtlcAcceptedLocalSuccessScriptSize + 1 +
TaprootBaseControlBlockWitnessSize + 32
)
// EstimateCommitTxWeight estimate commitment transaction weight depending on
@ -572,6 +838,8 @@ func EstimateCommitTxWeight(count int, prediction bool) int64 {
baseWeight := int64(BaseCommitmentTxWeight)
witnessWeight := int64(WitnessCommitmentTxWeight)
// TODO(roasbeef): need taproot modifier? also no anchor so wrong?
return htlcWeight + baseWeight + witnessWeight
}

View file

@ -26,6 +26,10 @@ const (
// without the trailing sighash flag.
maxDERSignatureSize = 72
// maxSchnorrSignature is the largest possilbe schnorr sig w/o a non
// default sighash.
maxSchnorrSignatureSize = 64
testAmt = btcutil.MaxSatoshi
)
@ -313,6 +317,18 @@ func (s *maxDERSignature) Verify(_ []byte, _ *btcec.PublicKey) bool {
return true
}
type maxSchnorrSignature struct{}
func (s *maxSchnorrSignature) Serialize() []byte {
// Always return worst-case signature length, including a non-default
// sighash type.
return make([]byte, maxSchnorrSignatureSize)
}
func (s *maxSchnorrSignature) Verify(_ []byte, _ *btcec.PublicKey) bool {
return true
}
// dummySigner is a fake signer used for size (upper bound) calculations.
type dummySigner struct {
input.Signer
@ -323,6 +339,15 @@ type dummySigner struct {
func (s *dummySigner) SignOutputRaw(tx *wire.MsgTx,
signDesc *input.SignDescriptor) (input.Signature, error) {
switch signDesc.SignMethod {
case input.TaprootKeySpendBIP0086SignMethod:
fallthrough
case input.TaprootKeySpendSignMethod:
fallthrough
case input.TaprootScriptSpendSignMethod:
return &maxSchnorrSignature{}, nil
}
return &maxDERSignature{}, nil
}
@ -813,6 +838,494 @@ var witnessSizeTests = []witnessSizeTest{
t.Fatal(err)
}
return witness
},
},
{
name: "taproot to local sweep",
expSize: input.TaprootToLocalWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
testKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
signer := &dummySigner{}
commitScriptTree, err := input.NewLocalCommitScriptTree(
testCSVDelay, testKey.PubKey(), testKey.PubKey(),
)
require.NoError(t, err)
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: testKey.PubKey(),
},
WitnessScript: commitScriptTree.SettleLeaf.Script,
HashType: txscript.SigHashAll,
InputIndex: 0,
SignMethod: input.TaprootScriptSpendSignMethod,
}
witness, err := input.TaprootCommitSpendSuccess(
signer, signDesc, testTx,
commitScriptTree.TapscriptTree,
)
require.NoError(t, err)
return witness
},
},
{
name: "taproot to local revocation",
expSize: input.TaprootToLocalRevokeWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
testKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
signer := &dummySigner{}
commitScriptTree, err := input.NewLocalCommitScriptTree(
testCSVDelay, testKey.PubKey(), testKey.PubKey(),
)
require.NoError(t, err)
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: testKey.PubKey(),
},
WitnessScript: commitScriptTree.RevocationLeaf.Script,
HashType: txscript.SigHashAll,
InputIndex: 0,
SignMethod: input.TaprootScriptSpendSignMethod,
}
witness, err := input.TaprootCommitSpendRevoke(
signer, signDesc, testTx,
commitScriptTree.TapscriptTree,
)
require.NoError(t, err)
return witness
},
},
{
name: "taproot to remote sweep",
expSize: input.TaprootToRemoteWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
testKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
signer := &dummySigner{}
commitScriptTree, err := input.NewRemoteCommitScriptTree(
testKey.PubKey(),
)
require.NoError(t, err)
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: testKey.PubKey(),
},
WitnessScript: commitScriptTree.SettleLeaf.Script,
HashType: txscript.SigHashAll,
InputIndex: 0,
SignMethod: input.TaprootScriptSpendSignMethod,
}
witness, err := input.TaprootCommitRemoteSpend(
signer, signDesc, testTx,
commitScriptTree.TapscriptTree,
)
require.NoError(t, err)
return witness
},
},
{
name: "taproot anchor sweep",
expSize: input.TaprootAnchorWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
testKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
signer := &dummySigner{}
anchorScriptTree, err := input.NewAnchorScriptTree(
testKey.PubKey(),
)
require.NoError(t, err)
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: testKey.PubKey(),
},
HashType: txscript.SigHashAll,
InputIndex: 0,
SignMethod: input.TaprootKeySpendSignMethod,
TapTweak: anchorScriptTree.TapscriptRoot,
}
witness, err := input.TaprootAnchorSpend(
signer, signDesc, testTx,
)
require.NoError(t, err)
return witness
},
},
{
name: "taproot second level htlc success+timeout",
expSize: input.TaprootSecondLevelHtlcWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
testKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
signer := &dummySigner{}
scriptTree, err := input.SecondLevelHtlcTapscriptTree(
testKey.PubKey(), testCSVDelay,
)
require.NoError(t, err)
tapScriptRoot := scriptTree.RootNode.TapHash()
revokeKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
tapLeaf := scriptTree.LeafMerkleProofs[0].TapLeaf
witnessScript := tapLeaf.Script
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: revokeKey.PubKey(),
},
WitnessScript: witnessScript,
HashType: txscript.SigHashAll,
InputIndex: 0,
SignMethod: input.TaprootKeySpendSignMethod,
TapTweak: tapScriptRoot[:],
}
witness, err := input.TaprootHtlcSpendSuccess(
signer, signDesc, revokeKey.PubKey(), testTx,
scriptTree,
)
require.NoError(t, err)
return witness
},
},
{
name: "taproot second level htlc revoke",
expSize: input.TaprootSecondLevelRevokeWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
testKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
scriptTree, err := input.SecondLevelHtlcTapscriptTree(
testKey.PubKey(), testCSVDelay,
)
require.NoError(t, err)
revokeKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
signer := &dummySigner{}
tapScriptRoot := scriptTree.RootNode.TapHash()
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: revokeKey.PubKey(),
},
HashType: txscript.SigHashAll,
InputIndex: 0,
SignMethod: input.TaprootKeySpendSignMethod,
TapTweak: tapScriptRoot[:],
}
witness, err := input.TaprootHtlcSpendRevoke(
signer, signDesc, testTx,
)
require.NoError(t, err)
return witness
},
},
{
name: "taproot offered htlc revoke",
expSize: input.TaprootOfferedRevokeWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
senderKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
receiverKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
revokeKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
var payHash [32]byte
signer := &dummySigner{}
htlcScriptTree, err := input.SenderHTLCScriptTaproot(
senderKey.PubKey(), receiverKey.PubKey(),
revokeKey.PubKey(), payHash[:],
)
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: revokeKey.PubKey(),
},
HashType: txscript.SigHashAll,
InputIndex: 0,
SignMethod: input.TaprootKeySpendSignMethod,
TapTweak: htlcScriptTree.TapscriptRoot,
}
witness, err := input.SenderHTLCScriptTaprootRevoke(
signer, signDesc, testTx,
)
require.NoError(t, err)
return witness
},
},
{
name: "taproot accepted htlc revoke",
expSize: input.TaprootAcceptedRevokeWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
senderKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
receiverKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
revokeKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
var payHash [32]byte
signer := &dummySigner{}
htlcScriptTree, err := input.ReceiverHTLCScriptTaproot(
testCLTVExpiry, senderKey.PubKey(),
receiverKey.PubKey(), revokeKey.PubKey(),
payHash[:],
)
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: revokeKey.PubKey(),
},
HashType: txscript.SigHashAll,
InputIndex: 0,
SignMethod: input.TaprootKeySpendSignMethod,
TapTweak: htlcScriptTree.TapscriptRoot,
}
witness, err := input.ReceiverHTLCScriptTaprootRevoke(
signer, signDesc, testTx,
)
require.NoError(t, err)
return witness
},
},
{
name: "taproot offered remote timeout",
expSize: input.TaprootHtlcOfferedRemoteTimeoutWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
senderKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
receiverKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
revokeKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
var payHash [32]byte
signer := &dummySigner{}
htlcScriptTree, err := input.ReceiverHTLCScriptTaproot(
testCLTVExpiry, senderKey.PubKey(),
receiverKey.PubKey(), revokeKey.PubKey(),
payHash[:],
)
timeoutLeaf := htlcScriptTree.TimeoutTapLeaf
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: senderKey.PubKey(),
},
WitnessScript: timeoutLeaf.Script,
HashType: txscript.SigHashAll,
InputIndex: 0,
SignMethod: input.TaprootScriptSpendSignMethod,
}
witness, err := input.ReceiverHTLCScriptTaprootTimeout(
signer, signDesc, testTx, testCLTVExpiry,
revokeKey.PubKey(), htlcScriptTree.TapscriptTree,
)
require.NoError(t, err)
return witness
},
},
{
name: "taproot offered local timeout",
expSize: input.TaprootOfferedLocalTimeoutWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
senderKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
receiverKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
revokeKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
var payHash [32]byte
signer := &dummySigner{}
htlcScriptTree, err := input.SenderHTLCScriptTaproot(
senderKey.PubKey(), receiverKey.PubKey(),
revokeKey.PubKey(), payHash[:],
)
require.NoError(t, err)
timeoutLeaf := htlcScriptTree.TimeoutTapLeaf
scriptTree := htlcScriptTree.TapscriptTree
receiverDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: receiverKey.PubKey(),
},
WitnessScript: timeoutLeaf.Script,
HashType: txscript.SigHashAll,
InputIndex: 0,
SignMethod: input.TaprootScriptSpendSignMethod,
}
receiverSig, err := signer.SignOutputRaw(
testTx, receiverDesc,
)
require.NoError(t, err)
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: senderKey.PubKey(),
},
WitnessScript: timeoutLeaf.Script,
HashType: txscript.SigHashAll,
InputIndex: 0,
SignMethod: input.TaprootScriptSpendSignMethod,
}
witness, err := input.SenderHTLCScriptTaprootTimeout(
receiverSig, txscript.SigHashAll, signer,
signDesc, testTx, revokeKey.PubKey(),
scriptTree,
)
require.NoError(t, err)
return witness
},
},
{
name: "taproot accepted remote success",
expSize: input.TaprootHtlcAcceptedRemoteSuccessWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
senderKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
receiverKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
revokeKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
var payHash [32]byte
signer := &dummySigner{}
htlcScriptTree, err := input.SenderHTLCScriptTaproot(
senderKey.PubKey(), receiverKey.PubKey(),
revokeKey.PubKey(),
payHash[:],
)
require.NoError(t, err)
successLeaf := htlcScriptTree.SuccessTapLeaf
scriptTree := htlcScriptTree.TapscriptTree
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: receiverKey.PubKey(),
},
WitnessScript: successLeaf.Script,
HashType: txscript.SigHashAll,
InputIndex: 0,
SignMethod: input.TaprootScriptSpendSignMethod,
}
witness, err := input.SenderHTLCScriptTaprootRedeem(
signer, signDesc, testTx, testPreimage,
revokeKey.PubKey(), scriptTree,
)
require.NoError(t, err)
return witness
},
},
{
name: "taproot accepted local success",
expSize: input.TaprootHtlcAcceptedLocalSuccessWitnessSize,
genWitness: func(t *testing.T) wire.TxWitness {
senderKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
receiverKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
revokeKey, err := btcec.NewPrivateKey()
require.NoError(t, err)
var payHash [32]byte
signer := &dummySigner{}
htlcScriptTree, err := input.ReceiverHTLCScriptTaproot(
testCLTVExpiry, senderKey.PubKey(),
receiverKey.PubKey(), revokeKey.PubKey(),
payHash[:],
)
successsLeaf := htlcScriptTree.SuccessTapLeaf
scriptTree := htlcScriptTree.TapscriptTree
senderDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: senderKey.PubKey(),
},
WitnessScript: successsLeaf.Script,
HashType: txscript.SigHashAll,
InputIndex: 0,
SignMethod: input.TaprootScriptSpendSignMethod,
}
senderSig, err := signer.SignOutputRaw(
testTx, senderDesc,
)
require.NoError(t, err)
signDesc := &input.SignDescriptor{
KeyDesc: keychain.KeyDescriptor{
PubKey: receiverKey.PubKey(),
},
WitnessScript: successsLeaf.Script,
HashType: txscript.SigHashAll,
InputIndex: 0,
SignMethod: input.TaprootScriptSpendSignMethod,
}
witness, err := input.ReceiverHTLCScriptTaprootRedeem(
senderSig, txscript.SigHashAll, testPreimage,
signer, signDesc, testTx, revokeKey.PubKey(),
scriptTree,
)
require.NoError(t, err)
return witness
},
},
@ -837,24 +1350,40 @@ func TestWitnessSizes(t *testing.T) {
}
// genTimeoutTx creates a signed HTLC second level timeout tx.
func genTimeoutTx(chanType channeldb.ChannelType) (*wire.MsgTx, error) {
func genTimeoutTx(t *testing.T,
chanType channeldb.ChannelType) *wire.MsgTx {
testKeyPriv, err := btcec.NewPrivateKey()
require.NoError(t, err)
testPubkey := testKeyPriv.PubKey()
// Create the unsigned timeout tx.
timeoutTx, err := lnwallet.CreateHtlcTimeoutTx(
chanType, false, testOutPoint, testAmt, testCLTVExpiry,
testCSVDelay, 0, testPubkey, testPubkey,
)
if err != nil {
return nil, err
}
require.NoError(t, err)
// In order to sign the transcation, generate the script for the output
// In order to sign the transaction, generate the script for the output
// it spends.
witScript, err := input.SenderHTLCScript(
testPubkey, testPubkey, testPubkey, testHash160,
chanType.HasAnchors(),
var (
witScript []byte
tapscriptTree *input.HtlcScriptTree
)
if err != nil {
return nil, err
if chanType.IsTaproot() {
tapscriptTree, err = input.SenderHTLCScriptTaproot(
testPubkey, testPubkey, testPubkey, testHash160,
)
require.NoError(t, err)
witScript = tapscriptTree.TimeoutTapLeaf.Script
} else {
witScript, err = input.SenderHTLCScript(
testPubkey, testPubkey, testPubkey, testHash160,
chanType.HasAnchors(),
)
require.NoError(t, err)
}
signDesc := &input.SignDescriptor{
@ -864,39 +1393,66 @@ func genTimeoutTx(chanType channeldb.ChannelType) (*wire.MsgTx, error) {
},
}
// Sign the timeout tx and add the witness.
sigHashType := lnwallet.HtlcSigHashType(chanType)
timeoutWitness, err := input.SenderHtlcSpendTimeout(
&maxDERSignature{}, sigHashType, &dummySigner{},
signDesc, timeoutTx,
)
if err != nil {
return nil, err
// Sign the timeout tx and add the witness.
var timeoutWitness [][]byte
if chanType.IsTaproot() {
signDesc.SignMethod = input.TaprootScriptSpendSignMethod
timeoutWitness, err = input.SenderHTLCScriptTaprootTimeout(
&maxSchnorrSignature{}, sigHashType, &dummySigner{},
signDesc, timeoutTx, testPubkey,
tapscriptTree.TapscriptTree,
)
require.NoError(t, err)
} else {
timeoutWitness, err = input.SenderHtlcSpendTimeout(
&maxDERSignature{}, sigHashType, &dummySigner{},
signDesc, timeoutTx,
)
require.NoError(t, err)
}
timeoutTx.TxIn[0].Witness = timeoutWitness
return timeoutTx, nil
return timeoutTx
}
// genSuccessTx creates a signed HTLC second level success tx.
func genSuccessTx(chanType channeldb.ChannelType) (*wire.MsgTx, error) {
// Create the unisgned success tx.
func genSuccessTx(t *testing.T, chanType channeldb.ChannelType) *wire.MsgTx {
testKeyPriv, err := btcec.NewPrivateKey()
require.NoError(t, err)
testPubkey := testKeyPriv.PubKey()
// Create the unsigned success tx.
successTx, err := lnwallet.CreateHtlcSuccessTx(
chanType, false, testOutPoint, testAmt, testCSVDelay, 0,
testPubkey, testPubkey,
)
if err != nil {
return nil, err
}
require.NoError(t, err)
// In order to sign the transcation, generate the script for the output
// In order to sign the transaction, generate the script for the output
// it spends.
witScript, err := input.ReceiverHTLCScript(
testCLTVExpiry, testPubkey, testPubkey,
testPubkey, testHash160, chanType.HasAnchors(),
var (
witScript []byte
tapscriptTree *input.HtlcScriptTree
)
if err != nil {
return nil, err
if chanType.IsTaproot() {
tapscriptTree, err = input.ReceiverHTLCScriptTaproot(
testCLTVExpiry, testPubkey, testPubkey, testPubkey,
testHash160,
)
require.NoError(t, err)
witScript = tapscriptTree.SuccessTapLeaf.Script
} else {
witScript, err = input.ReceiverHTLCScript(
testCLTVExpiry, testPubkey, testPubkey,
testPubkey, testHash160, chanType.HasAnchors(),
)
require.NoError(t, err)
}
signDesc := &input.SignDescriptor{
@ -906,18 +1462,31 @@ func genSuccessTx(chanType channeldb.ChannelType) (*wire.MsgTx, error) {
},
}
// Sign the success tx and add the witness.
sigHashType := lnwallet.HtlcSigHashType(channeldb.SingleFunderBit)
successWitness, err := input.ReceiverHtlcSpendRedeem(
&maxDERSignature{}, sigHashType, testPreimage,
&dummySigner{}, signDesc, successTx,
)
if err != nil {
return nil, err
var successWitness [][]byte
// Sign the success tx and add the witness.
if chanType.IsTaproot() {
signDesc.SignMethod = input.TaprootScriptSpendSignMethod
successWitness, err = input.ReceiverHTLCScriptTaprootRedeem(
&maxSchnorrSignature{}, sigHashType, testPreimage,
&dummySigner{}, signDesc, successTx, testPubkey,
tapscriptTree.TapscriptTree,
)
require.NoError(t, err)
} else {
successWitness, err = input.ReceiverHtlcSpendRedeem(
&maxDERSignature{}, sigHashType, testPreimage,
&dummySigner{}, signDesc, successTx,
)
require.NoError(t, err)
}
successTx.TxIn[0].Witness = successWitness
return successTx, nil
return successTx
}
type txSizeTest struct {
@ -928,36 +1497,35 @@ type txSizeTest struct {
var txSizeTests = []txSizeTest{
{
name: "htlc timeout regular ",
name: "htlc timeout regular",
expWeight: input.HtlcTimeoutWeight,
genTx: func(t *testing.T) *wire.MsgTx {
tx, err := genTimeoutTx(channeldb.SingleFunderBit)
require.NoError(t, err)
return tx
return genTimeoutTx(t, channeldb.SingleFunderBit)
},
},
{
name: "htlc timeout confirmed",
expWeight: input.HtlcTimeoutWeightConfirmed,
genTx: func(t *testing.T) *wire.MsgTx {
tx, err := genTimeoutTx(channeldb.AnchorOutputsBit)
require.NoError(t, err)
return tx
return genTimeoutTx(t, channeldb.AnchorOutputsBit)
},
},
{
name: "taproot htlc timeout",
expWeight: input.TaprootHtlcTimeoutWeight,
genTx: func(t *testing.T) *wire.MsgTx {
return genTimeoutTx(
t, channeldb.SimpleTaprootFeatureBit,
)
},
},
{
name: "htlc success regular",
// The weight estimate from the spec is off by one, but it's
// okay since we overestimate the weight.
expWeight: input.HtlcSuccessWeight - 1,
genTx: func(t *testing.T) *wire.MsgTx {
tx, err := genSuccessTx(channeldb.SingleFunderBit)
require.NoError(t, err)
return tx
return genSuccessTx(t, channeldb.SingleFunderBit)
},
},
{
@ -966,22 +1534,30 @@ var txSizeTests = []txSizeTest{
// okay since we overestimate the weight.
expWeight: input.HtlcSuccessWeightConfirmed - 1,
genTx: func(t *testing.T) *wire.MsgTx {
tx, err := genSuccessTx(channeldb.AnchorOutputsBit)
require.NoError(t, err)
return tx
return genSuccessTx(t, channeldb.AnchorOutputsBit)
},
},
{
name: "taproot htlc success",
expWeight: input.TaprootHtlcSuccessWeight,
genTx: func(t *testing.T) *wire.MsgTx {
return genSuccessTx(
t, channeldb.SimpleTaprootFeatureBit,
)
},
},
}
// TestWitnessSizes asserts the correctness of our magic tx size constants.
// TestTxSizes asserts the correctness of our magic tx size constants.
func TestTxSizes(t *testing.T) {
for _, test := range txSizeTests {
test := test
t.Run(test.name, func(t *testing.T) {
tx := test.genTx(t)
weight := blockchain.GetTransactionWeight(btcutil.NewTx(tx))
weight := blockchain.GetTransactionWeight(
btcutil.NewTx(tx),
)
if weight != test.expWeight {
t.Fatalf("size mismatch, want: %v, got: %v",
test.expWeight, weight)

View file

@ -224,6 +224,8 @@ func TestTaprootSenderHtlcSpend(t *testing.T) {
testCases := []struct {
name string
// TODO(roasbeef): use sighash slice
witnessGen witnessGen
txInMutator func(txIn *wire.TxIn)
@ -437,7 +439,7 @@ type testReceiverHtlcScriptTree struct {
receiverKey *btcec.PrivateKey
revokeKey *btcec.PrivateKey
revokeKey btcec.PrivateKey
htlcTxOut *wire.TxOut
@ -486,7 +488,7 @@ func newTestReceiverHtlcScriptTree(t *testing.T) *testReceiverHtlcScriptTree {
preImage: preImage,
senderKey: senderKey,
receiverKey: receiverKey,
revokeKey: revokeKey,
revokeKey: *revokeKey,
htlcTxOut: targetTxOut,
htlcAmt: htlcAmt,
rootHash: htlcScriptTree.TapscriptRoot,
@ -542,7 +544,7 @@ func htlcReceiverRevocationWitnessGen(sigHash txscript.SigHashType,
revokeKey := htlcScriptTree.revokeKey
signer := &MockSigner{
Privkeys: []*btcec.PrivateKey{
revokeKey,
&revokeKey,
},
}
@ -637,6 +639,8 @@ func TestTaprootReceiverHtlcSpend(t *testing.T) {
// signing below.
htlcScriptTree := newTestReceiverHtlcScriptTree(t)
// TODO(roasbeef): issue with revoke key??? ctrl block even/odd
spendTx := wire.NewMsgTx(2)
spendTx.AddTxIn(&wire.TxIn{})
spendTx.AddTxOut(&wire.TxOut{

View file

@ -206,7 +206,7 @@ const (
// TaprootHtlcAcceptedSuccessSecondLevel is a witness that allows us to
// sweep an HTLC we accepted on our commitment transaction after we go
// to the second level on chain.
TaprootHtlcAcceptedSuceessSecondLevel StandardWitnessType = 26
TaprootHtlcAcceptedSuccessSecondLevel StandardWitnessType = 26
// TaprootHtlcSecondLevelRevoke is a witness that allows us to sweep an
// HTLC on the revoked transaction of the remote party that goes to the
@ -339,8 +339,8 @@ func (wt StandardWitnessType) String() string {
case TaprootHtlcOfferedTimeoutSecondLevel:
return "TaprootHtlcOfferedTimeoutSecondLevel"
case TaprootHtlcAcceptedSuceessSecondLevel:
return "TaprootHtlcAcceptedSuceessSecondLevel"
case TaprootHtlcAcceptedSuccessSecondLevel:
return "TaprootHtlcAcceptedSuccessSecondLevel"
case TaprootHtlcSecondLevelRevoke:
return "TaprootHtlcSecondLevelRevoke"
@ -621,6 +621,48 @@ func (wt StandardWitnessType) SizeUpperBound() (int, bool, error) {
case TaprootPubKeySpend:
return TaprootKeyPathCustomSighashWitnessSize, false, nil
// Sweeping a self output after a delay for taproot channels.
case TaprootLocalCommitSpend:
return TaprootToLocalWitnessSize, false, nil
// Sweeping a self output after the remote party fro ce closes. Must
// wait 1 CSV.
case TaprootRemoteCommitSpend:
return TaprootToRemoteWitnessSize, false, nil
// Sweeping our anchor output with a key spend witness.
case TaprootAnchorSweepSpend:
return TaprootAnchorWitnessSize, false, nil
case TaprootHtlcOfferedTimeoutSecondLevel,
TaprootHtlcAcceptedSuccessSecondLevel:
return TaprootSecondLevelHtlcWitnessSize, false, nil
case TaprootHtlcSecondLevelRevoke:
return TaprootSecondLevelRevokeWitnessSize, false, nil
case TaprootHtlcAcceptedRevoke:
return TaprootAcceptedRevokeWitnessSize, false, nil
case TaprootHtlcOfferedRevoke:
return TaprootOfferedRevokeWitnessSize, false, nil
case TaprootHtlcOfferedRemoteTimeout:
return TaprootHtlcOfferedRemoteTimeoutWitnessSize, false, nil
case TaprootHtlcLocalOfferedTimeout:
return TaprootOfferedLocalTimeoutWitnessSize, false, nil
case TaprootHtlcAcceptedRemoteSuccess:
return TaprootHtlcAcceptedRemoteSuccessWitnessSize, false, nil
case TaprootHtlcAcceptedLocalSuccess:
return TaprootHtlcAcceptedLocalSuccessWitnessSize, false, nil
case TaprootCommitmentRevoke:
return TaprootToLocalRevokeWitnessSize, false, nil
}
return 0, false, fmt.Errorf("unexpected witness type: %v", wt)

View file

@ -242,7 +242,9 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
// Based on the channel type, we determine the initial commit weight
// and fee.
commitWeight := int64(input.CommitWeight)
if req.CommitType.HasAnchors() {
if req.CommitType.IsTaproot() {
commitWeight = input.TaprootCommitWeight
} else if req.CommitType.HasAnchors() {
commitWeight = int64(input.AnchorCommitWeight)
}
commitFee := req.CommitFeePerKw.FeeForWeight(commitWeight)