txscript: add verification of the post-segwit pub key type constraint
This commit adds verification of the post-segwit standardness requirement that all pubkeys involved in checks operations MUST be serialized as compressed public keys. A new ScriptFlag has been added to guard this behavior when executing scripts.
This commit is contained in:
parent
9367aedfd7
commit
0a7bbda6dd
2 changed files with 39 additions and 18 deletions
|
@ -88,6 +88,11 @@ const (
|
||||||
// ScriptVerifyMinimalIf makes a script with an OP_IF/OP_NOTIF whose
|
// ScriptVerifyMinimalIf makes a script with an OP_IF/OP_NOTIF whose
|
||||||
// operand is anything other than empty vector or [0x01] non-standard.
|
// operand is anything other than empty vector or [0x01] non-standard.
|
||||||
ScriptVerifyMinimalIf
|
ScriptVerifyMinimalIf
|
||||||
|
|
||||||
|
// ScriptVerifyWitnessPubKeyType makes a script within a check-sig
|
||||||
|
// operation whose public key isn't serialized in a compressed format
|
||||||
|
// non-standard.
|
||||||
|
ScriptVerifyWitnessPubKeyType
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -127,7 +132,6 @@ type Engine struct {
|
||||||
hashCache *TxSigHashes
|
hashCache *TxSigHashes
|
||||||
bip16 bool // treat execution as pay-to-script-hash
|
bip16 bool // treat execution as pay-to-script-hash
|
||||||
savedFirstStack [][]byte // stack from first script for bip16 scripts
|
savedFirstStack [][]byte // stack from first script for bip16 scripts
|
||||||
witness bool // treat execution as a witness program
|
|
||||||
witnessVersion int
|
witnessVersion int
|
||||||
witnessProgram []byte
|
witnessProgram []byte
|
||||||
inputAmount int64
|
inputAmount int64
|
||||||
|
@ -237,10 +241,17 @@ func (vm *Engine) curPC() (script int, off int, err error) {
|
||||||
return vm.scriptIdx, vm.scriptOff, nil
|
return vm.scriptIdx, vm.scriptOff, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isWitnessVersionActive returns true if a witness program was extracted
|
||||||
|
// during the initialization of the Engine, and the program's version matches
|
||||||
|
// the specified version.
|
||||||
|
func (vm *Engine) isWitnessVersionActive(version uint) bool {
|
||||||
|
return vm.witnessProgram != nil && uint(vm.witnessVersion) == version
|
||||||
|
}
|
||||||
|
|
||||||
// verifyWitnessProgram validates the stored witness program using the passed
|
// verifyWitnessProgram validates the stored witness program using the passed
|
||||||
// witness as input.
|
// witness as input.
|
||||||
func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
||||||
if vm.witnessVersion == 0 {
|
if vm.isWitnessVersionActive(0) {
|
||||||
switch len(vm.witnessProgram) {
|
switch len(vm.witnessProgram) {
|
||||||
case payToWitnessPubKeyHashDataSize: // P2WKH
|
case payToWitnessPubKeyHashDataSize: // P2WKH
|
||||||
// The witness stack should consist of exactly two
|
// The witness stack should consist of exactly two
|
||||||
|
@ -326,10 +337,10 @@ func (vm *Engine) verifyWitnessProgram(witness [][]byte) error {
|
||||||
// aren't discouraging future unknown witness based soft-forks,
|
// aren't discouraging future unknown witness based soft-forks,
|
||||||
// then we de-activate the segwit behavior within the VM for
|
// then we de-activate the segwit behavior within the VM for
|
||||||
// the remainder of execution.
|
// the remainder of execution.
|
||||||
vm.witness = false
|
vm.witnessProgram = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if vm.witness {
|
if vm.isWitnessVersionActive(0) {
|
||||||
// All elements within the witness stack must not be greater
|
// All elements within the witness stack must not be greater
|
||||||
// than the maximum bytes which are allowed to be pushed onto
|
// than the maximum bytes which are allowed to be pushed onto
|
||||||
// the stack.
|
// the stack.
|
||||||
|
@ -384,11 +395,12 @@ func (vm *Engine) CheckErrorCondition(finalScript bool) error {
|
||||||
"error check when script unfinished")
|
"error check when script unfinished")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're in witness execution mode, and this was the final script,
|
// If we're in version zero witness execution mode, and this was the
|
||||||
// then the stack MUST be clean in order to maintain compatibility with
|
// final script, then the stack MUST be clean in order to maintain
|
||||||
// BIP16.
|
// compatibility with BIP16.
|
||||||
if finalScript && vm.witness && vm.dstack.Depth() != 1 {
|
if finalScript && vm.isWitnessVersionActive(0) && vm.dstack.Depth() != 1 {
|
||||||
return ErrStackCleanStack
|
return scriptError(ErrEvalFalse, "witness program must "+
|
||||||
|
"have clean stack")
|
||||||
}
|
}
|
||||||
|
|
||||||
if finalScript && vm.hasFlag(ScriptVerifyCleanStack) &&
|
if finalScript && vm.hasFlag(ScriptVerifyCleanStack) &&
|
||||||
|
@ -488,8 +500,9 @@ func (vm *Engine) Step() (done bool, err error) {
|
||||||
// Set stack to be the stack from first script minus the
|
// Set stack to be the stack from first script minus the
|
||||||
// script itself
|
// script itself
|
||||||
vm.SetStack(vm.savedFirstStack[:len(vm.savedFirstStack)-1])
|
vm.SetStack(vm.savedFirstStack[:len(vm.savedFirstStack)-1])
|
||||||
} else if (vm.scriptIdx == 1 && vm.witness) ||
|
} else if (vm.scriptIdx == 1 && vm.witnessProgram != nil) ||
|
||||||
(vm.scriptIdx == 2 && vm.witness && vm.bip16) { // Nested P2SH.
|
(vm.scriptIdx == 2 && vm.witnessProgram != nil && vm.bip16) { // Nested P2SH.
|
||||||
|
|
||||||
vm.scriptIdx++
|
vm.scriptIdx++
|
||||||
|
|
||||||
witness := vm.tx.TxIn[vm.txIdx].Witness
|
witness := vm.tx.TxIn[vm.txIdx].Witness
|
||||||
|
@ -569,6 +582,13 @@ func (vm *Engine) checkHashTypeEncoding(hashType SigHashType) error {
|
||||||
// checkPubKeyEncoding returns whether or not the passed public key adheres to
|
// checkPubKeyEncoding returns whether or not the passed public key adheres to
|
||||||
// the strict encoding requirements if enabled.
|
// the strict encoding requirements if enabled.
|
||||||
func (vm *Engine) checkPubKeyEncoding(pubKey []byte) error {
|
func (vm *Engine) checkPubKeyEncoding(pubKey []byte) error {
|
||||||
|
if vm.hasFlag(ScriptVerifyWitnessPubKeyType) &&
|
||||||
|
vm.isWitnessVersionActive(0) && !btcec.IsCompressedPubKey(pubKey) {
|
||||||
|
|
||||||
|
str := "only uncompressed keys are accepted post-segwit"
|
||||||
|
return scriptError(ErrWitnessPubKeyType, str)
|
||||||
|
}
|
||||||
|
|
||||||
if !vm.hasFlag(ScriptVerifyStrictEncoding) {
|
if !vm.hasFlag(ScriptVerifyStrictEncoding) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -581,6 +601,7 @@ func (vm *Engine) checkPubKeyEncoding(pubKey []byte) error {
|
||||||
// Uncompressed
|
// Uncompressed
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return scriptError(ErrPubKeyType, "unsupported public key type")
|
return scriptError(ErrPubKeyType, "unsupported public key type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -916,7 +937,7 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags
|
||||||
// pkScript or as a datapush within the sigScript, then
|
// pkScript or as a datapush within the sigScript, then
|
||||||
// there MUST NOT be any witness data associated with
|
// there MUST NOT be any witness data associated with
|
||||||
// the input being validated.
|
// the input being validated.
|
||||||
if !vm.witness && len(tx.TxIn[txIdx].Witness) != 0 {
|
if vm.witnessProgram == nil && len(tx.TxIn[txIdx].Witness) != 0 {
|
||||||
errStr := "non-witness inputs cannot have a witness"
|
errStr := "non-witness inputs cannot have a witness"
|
||||||
return nil, scriptError(ErrWitnessUnexpected, errStr)
|
return nil, scriptError(ErrWitnessUnexpected, errStr)
|
||||||
}
|
}
|
||||||
|
|
|
@ -928,7 +928,7 @@ func popIfBool(vm *Engine) (bool, error) {
|
||||||
// When not in witness execution mode, not executing a v0 witness
|
// When not in witness execution mode, not executing a v0 witness
|
||||||
// program, or the minimal if flag isn't set pop the top stack item as
|
// program, or the minimal if flag isn't set pop the top stack item as
|
||||||
// a normal bool.
|
// a normal bool.
|
||||||
if !vm.witness || !vm.hasFlag(ScriptVerifyMinimalIf) {
|
if !vm.isWitnessVersionActive(0) || !vm.hasFlag(ScriptVerifyMinimalIf) {
|
||||||
return vm.dstack.PopBool()
|
return vm.dstack.PopBool()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2090,7 +2090,7 @@ func opcodeCheckSig(op *parsedOpcode, vm *Engine) error {
|
||||||
|
|
||||||
// Generate the signature hash based on the signature hash type.
|
// Generate the signature hash based on the signature hash type.
|
||||||
var hash []byte
|
var hash []byte
|
||||||
if vm.witness {
|
if vm.isWitnessVersionActive(0) {
|
||||||
var sigHashes *TxSigHashes
|
var sigHashes *TxSigHashes
|
||||||
if vm.hashCache != nil {
|
if vm.hashCache != nil {
|
||||||
sigHashes = vm.hashCache
|
sigHashes = vm.hashCache
|
||||||
|
@ -2275,9 +2275,9 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
||||||
// Get script starting from the most recent OP_CODESEPARATOR.
|
// Get script starting from the most recent OP_CODESEPARATOR.
|
||||||
script := vm.subScript()
|
script := vm.subScript()
|
||||||
|
|
||||||
// Remove the signature in pre-segwit scripts since there is no way for
|
// Remove the signature in pre version 0 segwit scripts since there is
|
||||||
// a signature to sign itself.
|
// no way for a signature to sign itself.
|
||||||
if !vm.witness {
|
if !vm.isWitnessVersionActive(0) {
|
||||||
for _, sigInfo := range signatures {
|
for _, sigInfo := range signatures {
|
||||||
script = removeOpcodeByData(script, sigInfo.signature)
|
script = removeOpcodeByData(script, sigInfo.signature)
|
||||||
}
|
}
|
||||||
|
@ -2363,7 +2363,7 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error {
|
||||||
|
|
||||||
// Generate the signature hash based on the signature hash type.
|
// Generate the signature hash based on the signature hash type.
|
||||||
var hash []byte
|
var hash []byte
|
||||||
if vm.witness {
|
if vm.isWitnessVersionActive(0) {
|
||||||
var sigHashes *TxSigHashes
|
var sigHashes *TxSigHashes
|
||||||
if vm.hashCache != nil {
|
if vm.hashCache != nil {
|
||||||
sigHashes = vm.hashCache
|
sigHashes = vm.hashCache
|
||||||
|
|
Loading…
Reference in a new issue