diff --git a/txscript/data/script_tests.json b/txscript/data/script_tests.json index 0ad02040..92685800 100644 --- a/txscript/data/script_tests.json +++ b/txscript/data/script_tests.json @@ -1492,6 +1492,20 @@ "OK", "BIP66 example 4, with DERSIG, non-null DER-compliant signature" ], +[ + "0", + "0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 CHECKSIG NOT", + "DERSIG,NULLFAIL", + "OK", + "BIP66 example 4, with DERSIG and NULLFAIL" +], +[ + "0x09 0x300602010102010101", + "0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 CHECKSIG NOT", + "DERSIG,NULLFAIL", + "NULLFAIL", + "BIP66 example 4, with DERSIG and NULLFAIL, non-null DER-compliant signature" +], [ "1", "0x21 0x038282263212c609d9ea2a6e3e172de238d8c39cabd5ac1ca10646e23fd5f51508 CHECKSIG", @@ -1844,5 +1858,15 @@ ["4294967296", "CHECKSEQUENCEVERIFY", "CHECKSEQUENCEVERIFY", "UNSATISFIED_LOCKTIME", "CSV fails if stack top bit 1 << 31 is not set, and tx version < 2"], +["NULLFAIL should cover all signatures and signatures only"], +["0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", "DERSIG", "OK", "BIP66 and NULLFAIL-compliant"], +["0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", "DERSIG,NULLFAIL", "OK", "BIP66 and NULLFAIL-compliant"], +["1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", "DERSIG,NULLFAIL", "OK", "BIP66 and NULLFAIL-compliant, not NULLDUMMY-compliant"], +["1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", "DERSIG,NULLFAIL,NULLDUMMY", "SIG_NULLDUMMY", "BIP66 and NULLFAIL-compliant, not NULLDUMMY-compliant"], +["0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0x09 0x300602010102010101", "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", "DERSIG", "OK", "BIP66-compliant but not NULLFAIL-compliant"], +["0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0x09 0x300602010102010101", "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", "DERSIG,NULLFAIL", "NULLFAIL", "BIP66-compliant but not NULLFAIL-compliant"], +["0 0x09 0x300602010102010101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", "DERSIG", "OK", "BIP66-compliant but not NULLFAIL-compliant"], +["0 0x09 0x300602010102010101 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0", "0x01 0x14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0x01 0x14 CHECKMULTISIG NOT", "DERSIG,NULLFAIL", "NULLFAIL", "BIP66-compliant but not NULLFAIL-compliant"], + ["The End"] ] diff --git a/txscript/engine.go b/txscript/engine.go index 09b4dcfb..2b3e4cc0 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -62,6 +62,10 @@ const ( // push operator. This is both rules 3 and 4 of BIP0062. ScriptVerifyMinimalData + // ScriptVerifyNullFail defines that signatures must be empty if + // a CHECKSIG or CHECKMULTISIG operation fails. + ScriptVerifyNullFail + // ScriptVerifySigPushOnly defines that signature scripts must contain // only pushed data. This is rule 2 of BIP0062. ScriptVerifySigPushOnly diff --git a/txscript/error.go b/txscript/error.go index 64bed9df..107a09b8 100644 --- a/txscript/error.go +++ b/txscript/error.go @@ -204,6 +204,11 @@ const ( // single element. ErrCleanStack + // ErrNullFail is returned when the ScriptVerifyNullFail flag is + // set and signatures are not empty on failed checksig or checkmultisig + // operations. + ErrNullFail + // ------------------------------- // Failures related to soft forks. // ------------------------------- @@ -266,6 +271,7 @@ var errorCodeStrings = map[ErrorCode]string{ ErrSigNullDummy: "ErrSigNullDummy", ErrPubKeyType: "ErrPubKeyType", ErrCleanStack: "ErrCleanStack", + ErrNullFail: "ErrNullFail", ErrDiscourageUpgradableNOPs: "ErrDiscourageUpgradableNOPs", ErrNegativeLockTime: "ErrNegativeLockTime", ErrUnsatisfiedLockTime: "ErrUnsatisfiedLockTime", diff --git a/txscript/error_test.go b/txscript/error_test.go index ce8d09c5..65731146 100644 --- a/txscript/error_test.go +++ b/txscript/error_test.go @@ -53,6 +53,7 @@ func TestErrorCodeStringer(t *testing.T) { {ErrSigNullDummy, "ErrSigNullDummy"}, {ErrPubKeyType, "ErrPubKeyType"}, {ErrCleanStack, "ErrCleanStack"}, + {ErrNullFail, "ErrNullFail"}, {ErrDiscourageUpgradableNOPs, "ErrDiscourageUpgradableNOPs"}, {ErrNegativeLockTime, "ErrNegativeLockTime"}, {ErrUnsatisfiedLockTime, "ErrUnsatisfiedLockTime"}, diff --git a/txscript/opcode.go b/txscript/opcode.go index 54392bba..ccbc7d95 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -2084,6 +2084,11 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error { valid = signature.Verify(hash, pubKey) } + if !valid && vm.hasFlag(ScriptVerifyNullFail) && len(sigBytes) > 0 { + str := "signature not empty on failed checksig" + return scriptError(ErrNullFail, str) + } + vm.dstack.PushBool(valid) return nil } @@ -2318,6 +2323,15 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error { } } + if !success && vm.hasFlag(ScriptVerifyNullFail) { + for _, sig := range signatures { + if len(sig.signature) > 0 { + str := "not all signatures empty on failed checkmultisig" + return scriptError(ErrNullFail, str) + } + } + } + vm.dstack.PushBool(success) return nil } diff --git a/txscript/reference_test.go b/txscript/reference_test.go index 09963b92..703de5fc 100644 --- a/txscript/reference_test.go +++ b/txscript/reference_test.go @@ -160,6 +160,8 @@ func parseScriptFlags(flagStr string) (ScriptFlags, error) { // Nothing. case "NULLDUMMY": flags |= ScriptStrictMultiSig + case "NULLFAIL": + flags |= ScriptVerifyNullFail case "P2SH": flags |= ScriptBip16 case "SIGPUSHONLY": @@ -191,7 +193,7 @@ func parseExpectedResult(expected string) ([]ErrorCode, error) { case "EQUALVERIFY": return []ErrorCode{ErrEqualVerify}, nil case "NULLFAIL": - return []ErrorCode{ErrSigNullDummy}, nil + return []ErrorCode{ErrNullFail}, nil case "SIG_HIGH_S": return []ErrorCode{ErrSigHighS}, nil case "SIG_HASHTYPE": diff --git a/txscript/standard.go b/txscript/standard.go index 60a3b907..29472f6c 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -33,6 +33,7 @@ const ( ScriptStrictMultiSig | ScriptDiscourageUpgradableNops | ScriptVerifyCleanStack | + ScriptVerifyNullFail | ScriptVerifyCheckLockTimeVerify | ScriptVerifyCheckSequenceVerify | ScriptVerifyLowS