txscript: add more detailed taproot errors

This commit is contained in:
Olaoluwa Osuntokun 2022-03-15 17:49:45 -07:00
parent 6ab97a3dd8
commit 99e4e00345
No known key found for this signature in database
GPG key ID: 3BBD59E99B280306
7 changed files with 87 additions and 19 deletions

View file

@ -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

View file

@ -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.

View file

@ -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)"},
} }

View file

@ -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

View file

@ -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.

View file

@ -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]

View file

@ -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