mirror of
https://github.com/btcsuite/btcd.git
synced 2025-03-10 09:19:28 +01:00
txscript: add more detailed taproot errors
This commit is contained in:
parent
6ab97a3dd8
commit
99e4e00345
7 changed files with 87 additions and 19 deletions
|
@ -178,7 +178,7 @@ func (t *taprootExecutionCtx) tallysigOp() error {
|
||||||
t.sigOpsBudget -= sigOpsDelta
|
t.sigOpsBudget -= sigOpsDelta
|
||||||
|
|
||||||
if t.sigOpsBudget < 0 {
|
if t.sigOpsBudget < 0 {
|
||||||
return fmt.Errorf("max sig ops exceeded")
|
return scriptError(ErrTaprootMaxSigOps, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -367,6 +367,47 @@ const (
|
||||||
// bytes.
|
// bytes.
|
||||||
ErrDiscourageUpgradeablePubKeyType
|
ErrDiscourageUpgradeablePubKeyType
|
||||||
|
|
||||||
|
// ErrTaprootSigInvalid is returned when an invalid taproot key spend
|
||||||
|
// signature is encountered.
|
||||||
|
ErrTaprootSigInvalid
|
||||||
|
|
||||||
|
// ErrTaprootMerkleProofInvalid is returned when the revealed script
|
||||||
|
// merkle proof for a taproot spend is found to be invalid.
|
||||||
|
ErrTaprootMerkleProofInvalid
|
||||||
|
|
||||||
|
// ErrTaprootOutputKeyParityMismatch is returned when the control block
|
||||||
|
// proof is valid, but the parity of the y-coordinate of the derived
|
||||||
|
// key doesn't match the value encoded in the control block.
|
||||||
|
ErrTaprootOutputKeyParityMismatch
|
||||||
|
|
||||||
|
// ErrControlBlockTooSmall is returned when a parsed control block is
|
||||||
|
// less than 33 bytes.
|
||||||
|
ErrControlBlockTooSmall
|
||||||
|
|
||||||
|
// ErrControlBlockTooLarge is returned when the control block is larger
|
||||||
|
// than the largest possible proof for a merkle script tree.
|
||||||
|
ErrControlBlockTooLarge
|
||||||
|
|
||||||
|
// ErrControlBlockInvalidLength is returned when the control block,
|
||||||
|
// without the public key isn't a multiple of 32.
|
||||||
|
ErrControlBlockInvalidLength
|
||||||
|
|
||||||
|
// ErrWitnessHasNoAnnex is returned when a caller attempts to extract
|
||||||
|
// an annex, but the witness has no annex present.
|
||||||
|
ErrWitnessHasNoAnnex
|
||||||
|
|
||||||
|
// ErrInvalidTaprootSigLen is returned when taproot signature isn't 64
|
||||||
|
// or 65 bytes.
|
||||||
|
ErrInvalidTaprootSigLen
|
||||||
|
|
||||||
|
// ErrTaprootPubkeyIsEmpty is returned when a signature checking op
|
||||||
|
// code encounters an empty public key.
|
||||||
|
ErrTaprootPubkeyIsEmpty
|
||||||
|
|
||||||
|
// ErrTaprootMaxSigOps is returned when the number of allotted sig ops
|
||||||
|
// is exceeded during taproot execution.
|
||||||
|
ErrTaprootMaxSigOps
|
||||||
|
|
||||||
// numErrorCodes is the maximum error code number used in tests. This
|
// numErrorCodes is the maximum error code number used in tests. This
|
||||||
// entry MUST be the last entry in the enum.
|
// entry MUST be the last entry in the enum.
|
||||||
numErrorCodes
|
numErrorCodes
|
||||||
|
@ -443,6 +484,16 @@ var errorCodeStrings = map[ErrorCode]string{
|
||||||
ErrDiscourageUpgradeableTaprootVersion: "ErrDiscourageUpgradeableTaprootVersion",
|
ErrDiscourageUpgradeableTaprootVersion: "ErrDiscourageUpgradeableTaprootVersion",
|
||||||
ErrTapscriptCheckMultisig: "ErrTapscriptCheckMultisig",
|
ErrTapscriptCheckMultisig: "ErrTapscriptCheckMultisig",
|
||||||
ErrDiscourageUpgradeablePubKeyType: "ErrDiscourageUpgradeablePubKeyType",
|
ErrDiscourageUpgradeablePubKeyType: "ErrDiscourageUpgradeablePubKeyType",
|
||||||
|
ErrTaprootSigInvalid: "ErrTaprootSigInvalid",
|
||||||
|
ErrTaprootMerkleProofInvalid: "ErrTaprootMerkleProofInvalid",
|
||||||
|
ErrTaprootOutputKeyParityMismatch: "ErrTaprootOutputKeyParityMismatch",
|
||||||
|
ErrControlBlockTooSmall: "ErrControlBlockTooSmall",
|
||||||
|
ErrControlBlockTooLarge: "ErrControlBlockTooLarge",
|
||||||
|
ErrControlBlockInvalidLength: "ErrControlBlockInvalidLength",
|
||||||
|
ErrWitnessHasNoAnnex: "ErrWitnessHasNoAnnex",
|
||||||
|
ErrInvalidTaprootSigLen: "ErrInvalidTaprootSigLen",
|
||||||
|
ErrTaprootPubkeyIsEmpty: "ErrTaprootPubkeyIsEmpty",
|
||||||
|
ErrTaprootMaxSigOps: "ErrTaprootMaxSigOps",
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the ErrorCode as a human-readable name.
|
// String returns the ErrorCode as a human-readable name.
|
||||||
|
|
|
@ -86,6 +86,16 @@ func TestErrorCodeStringer(t *testing.T) {
|
||||||
{ErrTapscriptCheckMultisig, "ErrTapscriptCheckMultisig"},
|
{ErrTapscriptCheckMultisig, "ErrTapscriptCheckMultisig"},
|
||||||
{ErrDiscourageUpgradableWitnessProgram, "ErrDiscourageUpgradableWitnessProgram"},
|
{ErrDiscourageUpgradableWitnessProgram, "ErrDiscourageUpgradableWitnessProgram"},
|
||||||
{ErrDiscourageUpgradeablePubKeyType, "ErrDiscourageUpgradeablePubKeyType"},
|
{ErrDiscourageUpgradeablePubKeyType, "ErrDiscourageUpgradeablePubKeyType"},
|
||||||
|
{ErrTaprootSigInvalid, "ErrTaprootSigInvalid"},
|
||||||
|
{ErrTaprootMerkleProofInvalid, "ErrTaprootMerkleProofInvalid"},
|
||||||
|
{ErrTaprootOutputKeyParityMismatch, "ErrTaprootOutputKeyParityMismatch"},
|
||||||
|
{ErrControlBlockTooSmall, "ErrControlBlockTooSmall"},
|
||||||
|
{ErrControlBlockTooLarge, "ErrControlBlockTooLarge"},
|
||||||
|
{ErrControlBlockInvalidLength, "ErrControlBlockInvalidLength"},
|
||||||
|
{ErrWitnessHasNoAnnex, "ErrWitnessHasNoAnnex"},
|
||||||
|
{ErrInvalidTaprootSigLen, "ErrInvalidTaprootSigLen"},
|
||||||
|
{ErrTaprootPubkeyIsEmpty, "ErrTaprootPubkeyIsEmpty"},
|
||||||
|
{ErrTaprootMaxSigOps, "ErrTaprootMaxSigOps"},
|
||||||
{0xffff, "Unknown ErrorCode (65535)"},
|
{0xffff, "Unknown ErrorCode (65535)"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2038,14 +2038,14 @@ func opcodeCheckSig(op *opcode, data []byte, vm *Engine) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty public keys immeidately cause execution to fail.
|
// Empty public keys immediately cause execution to fail.
|
||||||
if len(pkBytes) == 0 {
|
if len(pkBytes) == 0 {
|
||||||
return fmt.Errorf("nil pub key")
|
return scriptError(ErrTaprootPubkeyIsEmpty, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is tapscript execution, and the signature was
|
// If this is tapscript execution, and the signature was
|
||||||
// actually an empty vector, then we push on an empty vector
|
// actually an empty vector, then we push on an empty vector
|
||||||
// and continue execution from ther, but only if the pubkey
|
// and continue execution from there, but only if the pubkey
|
||||||
// isn't empty.
|
// isn't empty.
|
||||||
if len(fullSigBytes) == 0 {
|
if len(fullSigBytes) == 0 {
|
||||||
vm.dstack.PushByteArray([]byte{})
|
vm.dstack.PushByteArray([]byte{})
|
||||||
|
@ -2143,9 +2143,9 @@ func opcodeCheckSigAdd(op *opcode, data []byte, vm *Engine) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty public keys immeidately cause execution to fail.
|
// Empty public keys immediately cause execution to fail.
|
||||||
if len(pubKeyBytes) == 0 {
|
if len(pubKeyBytes) == 0 {
|
||||||
return fmt.Errorf("nil pubkey")
|
return scriptError(ErrTaprootPubkeyIsEmpty, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the signature is empty, then we'll just push the value N back
|
// If the signature is empty, then we'll just push the value N back
|
||||||
|
|
|
@ -291,8 +291,8 @@ func parseTaprootSigAndPubKey(pkBytes, rawSig []byte,
|
||||||
|
|
||||||
// Otherwise, this is an invalid signature, so we need to bail out.
|
// Otherwise, this is an invalid signature, so we need to bail out.
|
||||||
default:
|
default:
|
||||||
// TODO(roasbeef): do proper error here
|
str := fmt.Sprintf("invalid sig len: %v", len(rawSig))
|
||||||
return nil, nil, 0, fmt.Errorf("invalid sig len: %v", len(rawSig))
|
return nil, nil, 0, scriptError(ErrInvalidTaprootSigLen, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
return pubKey, sig, sigHashType, nil
|
return pubKey, sig, sigHashType, nil
|
||||||
|
@ -402,8 +402,7 @@ func newBaseTapscriptSigVerifier(pkBytes, rawSig []byte,
|
||||||
// If the public key is zero bytes, then this is invalid, and will fail
|
// If the public key is zero bytes, then this is invalid, and will fail
|
||||||
// immediately.
|
// immediately.
|
||||||
case 0:
|
case 0:
|
||||||
// TODO(roasbeef): better erro
|
return nil, scriptError(ErrTaprootPubkeyIsEmpty, "")
|
||||||
return nil, fmt.Errorf("pubkey is zero bytes")
|
|
||||||
|
|
||||||
// If the public key is 32 byte as we expect, then we'll parse things
|
// If the public key is 32 byte as we expect, then we'll parse things
|
||||||
// as normal.
|
// as normal.
|
||||||
|
|
|
@ -482,8 +482,7 @@ func isAnnexedWitness(witness wire.TxWitness) bool {
|
||||||
// witness doesn't contain an annex, then an error is returned.
|
// witness doesn't contain an annex, then an error is returned.
|
||||||
func extractAnnex(witness [][]byte) ([]byte, error) {
|
func extractAnnex(witness [][]byte) ([]byte, error) {
|
||||||
if !isAnnexedWitness(witness) {
|
if !isAnnexedWitness(witness) {
|
||||||
// TODO(roasbeef): make into actual type
|
return nil, scriptError(ErrWitnessHasNoAnnex, "")
|
||||||
return nil, fmt.Errorf("no witness annex")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lastElement := witness[len(witness)-1]
|
lastElement := witness[len(witness)-1]
|
||||||
|
|
|
@ -89,8 +89,7 @@ func VerifyTaprootKeySpend(witnessProgram []byte, rawSig []byte, tx *wire.MsgTx,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(roasbeef): add proper error
|
return scriptError(ErrTaprootSigInvalid, "")
|
||||||
return fmt.Errorf("invalid sig")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ControlBlock houses the structured witness input for a taproot spend. This
|
// ControlBlock houses the structured witness input for a taproot spend. This
|
||||||
|
@ -189,18 +188,24 @@ func ParseControlBlock(ctrlBlock []byte) (*ControlBlock, error) {
|
||||||
// The control block must minimally have 33 bytes for the internal
|
// The control block must minimally have 33 bytes for the internal
|
||||||
// public key and script leaf version.
|
// public key and script leaf version.
|
||||||
case len(ctrlBlock) < ControlBlockBaseSize:
|
case len(ctrlBlock) < ControlBlockBaseSize:
|
||||||
return nil, fmt.Errorf("invalid control block size")
|
str := fmt.Sprintf("min size is %v bytes, control block "+
|
||||||
|
"is %v bytes", ControlBlockBaseSize, len(ctrlBlock))
|
||||||
|
return nil, scriptError(ErrControlBlockTooSmall, str)
|
||||||
|
|
||||||
// The control block can't be larger than a proof for the largest
|
// The control block can't be larger than a proof for the largest
|
||||||
// possible tapscript merkle tree with 2^128 leaves.
|
// possible tapscript merkle tree with 2^128 leaves.
|
||||||
case len(ctrlBlock) > ControlBlockMaxSize:
|
case len(ctrlBlock) > ControlBlockMaxSize:
|
||||||
return nil, fmt.Errorf("invalid max block size")
|
str := fmt.Sprintf("max size is %v, control block is %v bytes",
|
||||||
|
ControlBlockMaxSize, len(ctrlBlock))
|
||||||
|
return nil, scriptError(ErrControlBlockTooLarge, str)
|
||||||
|
|
||||||
// Ignoring the fixed sized portion, we expect the total number of
|
// Ignoring the fixed sized portion, we expect the total number of
|
||||||
// remaining bytes to be a multiple of the node size, which is 32
|
// remaining bytes to be a multiple of the node size, which is 32
|
||||||
// bytes.
|
// bytes.
|
||||||
case (len(ctrlBlock)-ControlBlockBaseSize)%ControlBlockNodeSize != 0:
|
case (len(ctrlBlock)-ControlBlockBaseSize)%ControlBlockNodeSize != 0:
|
||||||
return nil, fmt.Errorf("invalid max block size")
|
str := fmt.Sprintf("control block proof is not a multiple "+
|
||||||
|
"of 32: %v", len(ctrlBlock)-ControlBlockBaseSize)
|
||||||
|
return nil, scriptError(ErrControlBlockInvalidLength, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// With the basic sanity checking complete, we can now parse the
|
// With the basic sanity checking complete, we can now parse the
|
||||||
|
@ -347,7 +352,8 @@ func VerifyTaprootLeafCommitment(controlBlock *ControlBlock,
|
||||||
// program passed in.
|
// program passed in.
|
||||||
expectedWitnessProgram := schnorr.SerializePubKey(taprootKey)
|
expectedWitnessProgram := schnorr.SerializePubKey(taprootKey)
|
||||||
if !bytes.Equal(expectedWitnessProgram, taprootWitnessProgram) {
|
if !bytes.Equal(expectedWitnessProgram, taprootWitnessProgram) {
|
||||||
return fmt.Errorf("invalid witness commitment")
|
|
||||||
|
return scriptError(ErrTaprootMerkleProofInvalid, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, we'll verify that the parity of the y coordinate of the
|
// Finally, we'll verify that the parity of the y coordinate of the
|
||||||
|
@ -355,7 +361,10 @@ func VerifyTaprootLeafCommitment(controlBlock *ControlBlock,
|
||||||
derivedYIsOdd := (taprootKey.SerializeCompressed()[0] ==
|
derivedYIsOdd := (taprootKey.SerializeCompressed()[0] ==
|
||||||
secp.PubKeyFormatCompressedOdd)
|
secp.PubKeyFormatCompressedOdd)
|
||||||
if controlBlock.OutputKeyYIsOdd != derivedYIsOdd {
|
if controlBlock.OutputKeyYIsOdd != derivedYIsOdd {
|
||||||
return fmt.Errorf("invalid witness commitment")
|
str := fmt.Sprintf("control block y is odd: %v, derived "+
|
||||||
|
"parity is odd: %v", controlBlock.OutputKeyYIsOdd,
|
||||||
|
derivedYIsOdd)
|
||||||
|
return scriptError(ErrTaprootOutputKeyParityMismatch, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, if we reach here, the commitment opening is valid and
|
// Otherwise, if we reach here, the commitment opening is valid and
|
||||||
|
|
Loading…
Add table
Reference in a new issue