From 7186f83095619fabac93cb7f71cf1347f745d609 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Tue, 6 Apr 2021 16:29:16 +0300 Subject: [PATCH] Add OpCheckMultiSigECDSA (#1663) Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com> --- domain/consensus/utils/txscript/opcode.go | 179 +++++++++++++++++- .../consensus/utils/txscript/opcode_test.go | 4 +- 2 files changed, 179 insertions(+), 4 deletions(-) diff --git a/domain/consensus/utils/txscript/opcode.go b/domain/consensus/utils/txscript/opcode.go index ad46e4e49..d4ba6d114 100644 --- a/domain/consensus/utils/txscript/opcode.go +++ b/domain/consensus/utils/txscript/opcode.go @@ -204,7 +204,7 @@ const ( OpUnknown166 = 0xa6 // 166 OpUnknown167 = 0xa7 // 167 OpSHA256 = 0xa8 // 168 - OpUnknown169 = 0xa9 // 169 + OpCheckMultiSigECDSA = 0xa9 // 169 OpBlake2b = 0xaa // 170 OpCheckSigECDSA = 0xab // 171 OpCheckSig = 0xac // 172 @@ -485,6 +485,7 @@ var opcodeArray = [256]opcode{ OpWithin: {OpWithin, "OP_WITHIN", 1, opcodeWithin}, // Crypto opcodes. + OpCheckMultiSigECDSA: {OpCheckMultiSigECDSA, "OP_CHECKMULTISIGECDSA", 1, opcodeCheckMultiSigECDSA}, OpSHA256: {OpSHA256, "OP_SHA256", 1, opcodeSha256}, OpBlake2b: {OpBlake2b, "OP_BLAKE2B", 1, opcodeBlake2b}, OpCheckSigECDSA: {OpCheckSigECDSA, "OP_CHECKSIGECDSA", 1, opcodeCheckSigECDSA}, @@ -508,7 +509,6 @@ var opcodeArray = [256]opcode{ // Undefined opcodes. OpUnknown166: {OpUnknown166, "OP_UNKNOWN166", 1, opcodeInvalid}, OpUnknown167: {OpUnknown167, "OP_UNKNOWN167", 1, opcodeInvalid}, - OpUnknown169: {OpUnknown169, "OP_UNKNOWN169", 1, opcodeInvalid}, OpUnknown188: {OpUnknown188, "OP_UNKNOWN188", 1, opcodeInvalid}, OpUnknown189: {OpUnknown189, "OP_UNKNOWN189", 1, opcodeInvalid}, OpUnknown190: {OpUnknown190, "OP_UNKNOWN190", 1, opcodeInvalid}, @@ -2132,6 +2132,12 @@ type parsedSigInfo struct { parsed bool } +type parsedSigInfoECDSA struct { + signature []byte + parsedSignature *secp256k1.ECDSASignature + parsed bool +} + // opcodeCheckMultiSig treats the top item on the stack as an integer number of // public keys, followed by that many entries as raw data representing the public // keys, followed by the integer number of signatures, followed by that many @@ -2314,6 +2320,175 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error { return nil } +func opcodeCheckMultiSigECDSA(op *parsedOpcode, vm *Engine) error { + numKeys, err := vm.dstack.PopInt() + if err != nil { + return err + } + + numPubKeys := int(numKeys.Int32()) + if numPubKeys < 0 { + str := fmt.Sprintf("number of pubkeys %d is negative", + numPubKeys) + return scriptError(ErrInvalidPubKeyCount, str) + } + if numPubKeys > MaxPubKeysPerMultiSig { + str := fmt.Sprintf("too many pubkeys: %d > %d", + numPubKeys, MaxPubKeysPerMultiSig) + return scriptError(ErrInvalidPubKeyCount, str) + } + vm.numOps += numPubKeys + if vm.numOps > MaxOpsPerScript { + str := fmt.Sprintf("exceeded max operation limit of %d", + MaxOpsPerScript) + return scriptError(ErrTooManyOperations, str) + } + + pubKeys := make([][]byte, 0, numPubKeys) + for i := 0; i < numPubKeys; i++ { + pubKey, err := vm.dstack.PopByteArray() + if err != nil { + return err + } + pubKeys = append(pubKeys, pubKey) + } + + numSigs, err := vm.dstack.PopInt() + if err != nil { + return err + } + numSignatures := int(numSigs.Int32()) + if numSignatures < 0 { + str := fmt.Sprintf("number of signatures %d is negative", + numSignatures) + return scriptError(ErrInvalidSignatureCount, str) + + } + if numSignatures > numPubKeys { + str := fmt.Sprintf("more signatures than pubkeys: %d > %d", + numSignatures, numPubKeys) + return scriptError(ErrInvalidSignatureCount, str) + } + + signatures := make([]*parsedSigInfoECDSA, 0, numSignatures) + for i := 0; i < numSignatures; i++ { + signature, err := vm.dstack.PopByteArray() + if err != nil { + return err + } + sigInfo := &parsedSigInfoECDSA{signature: signature} + signatures = append(signatures, sigInfo) + } + + success := true + numPubKeys++ + pubKeyIdx := -1 + signatureIdx := 0 + + for numSignatures > 0 { + // When there are more signatures than public keys remaining, + // there is no way to succeed since too many signatures are + // invalid, so exit early. + pubKeyIdx++ + numPubKeys-- + if numSignatures > numPubKeys { + success = false + break + } + + sigInfo := signatures[signatureIdx] + pubKey := pubKeys[pubKeyIdx] + + // The order of the signature and public key evaluation is + // important here since it can be distinguished by an + // OP_CHECKMULTISIG NOT when the strict encoding flag is set. + + rawSig := sigInfo.signature + if len(rawSig) == 0 { + // Skip to the next pubkey if signature is empty. + continue + } + + // Split the signature into hash type and signature components. + hashType := consensushashing.SigHashType(rawSig[len(rawSig)-1]) + signature := rawSig[:len(rawSig)-1] + + // Only parse and check the signature encoding once. + var parsedSig *secp256k1.ECDSASignature + if !sigInfo.parsed { + if !hashType.IsStandardSigHashType() { + return scriptError(ErrInvalidSigHashType, fmt.Sprintf("invalid hash type 0x%x", hashType)) + } + if err := vm.checkSignatureLengthECDSA(signature); err != nil { + return err + } + + // Parse the signature. + parsedSig, err = secp256k1.DeserializeECDSASignatureFromSlice(signature) + sigInfo.parsed = true + if err != nil { + continue + } + + sigInfo.parsedSignature = parsedSig + } else { + // Skip to the next pubkey if the signature is invalid. + if sigInfo.parsedSignature == nil { + continue + } + + // Use the already parsed signature. + parsedSig = sigInfo.parsedSignature + } + + if err := vm.checkPubKeyEncodingECDSA(pubKey); err != nil { + return err + } + + // Parse the pubkey. + parsedPubKey, err := secp256k1.DeserializeECDSAPubKey(pubKey) + if err != nil { + continue + } + + // Generate the signature hash based on the signature hash type. + sigHash, err := consensushashing.CalculateSignatureHashECDSA(&vm.tx, vm.txIdx, hashType, vm.sigHashReusedValues) + if err != nil { + return err + } + + secpHash := secp256k1.Hash(*sigHash.ByteArray()) + var valid bool + if vm.sigCacheECDSA != nil { + valid = vm.sigCacheECDSA.Exists(secpHash, parsedSig, parsedPubKey) + if !valid && parsedPubKey.ECDSAVerify(&secpHash, parsedSig) { + vm.sigCacheECDSA.Add(secpHash, parsedSig, parsedPubKey) + valid = true + } + } else { + valid = parsedPubKey.ECDSAVerify(&secpHash, parsedSig) + } + + if valid { + // PubKey verified, move on to the next signature. + signatureIdx++ + numSignatures-- + } + } + + if !success { + 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 +} + // opcodeCheckMultiSigVerify is a combination of opcodeCheckMultiSig and // opcodeVerify. The opcodeCheckMultiSig is invoked followed by opcodeVerify. // See the documentation for each of those opcodes for more details. diff --git a/domain/consensus/utils/txscript/opcode_test.go b/domain/consensus/utils/txscript/opcode_test.go index 582bb3fdd..ef164d7b7 100644 --- a/domain/consensus/utils/txscript/opcode_test.go +++ b/domain/consensus/utils/txscript/opcode_test.go @@ -71,7 +71,7 @@ func TestOpcodeDisasm(t *testing.T) { 0x9f: "OP_LESSTHAN", 0xa0: "OP_GREATERTHAN", 0xa1: "OP_LESSTHANOREQUAL", 0xa2: "OP_GREATERTHANOREQUAL", 0xa3: "OP_MIN", 0xa4: "OP_MAX", 0xa5: "OP_WITHIN", - 0xa8: "OP_SHA256", + 0xa8: "OP_SHA256", 0xa9: "OP_CHECKMULTISIGECDSA", 0xaa: "OP_BLAKE2B", 0xab: "OP_CHECKSIGECDSA", 0xac: "OP_CHECKSIG", 0xad: "OP_CHECKSIGVERIFY", 0xae: "OP_CHECKMULTISIG", 0xaf: "OP_CHECKMULTISIGVERIFY", @@ -187,7 +187,7 @@ func TestOpcodeDisasm(t *testing.T) { func isOpUnknown(opcodeVal int) bool { return opcodeVal >= 0xba && opcodeVal <= 0xf9 || opcodeVal == 0xfc || - opcodeVal == 0xa6 || opcodeVal == 0xa7 || opcodeVal == 0xa9 + opcodeVal == 0xa6 || opcodeVal == 0xa7 } func isNumberedNop(opcodeVal int) bool {