Merge pull request #411 from nspcc-dev/verifywitnesses-logic

Fixes #269, #368. Tested by RPC sendrawtransaction test.
This commit is contained in:
Roman Khimov 2019-09-24 14:43:26 +03:00 committed by GitHub
commit dac1f9367c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 580 additions and 161 deletions

View file

@ -1,6 +1,7 @@
package core package core
import ( import (
"bytes"
"context" "context"
"fmt" "fmt"
"math" "math"
@ -12,6 +13,7 @@ import (
"github.com/CityOfZion/neo-go/pkg/core/transaction" "github.com/CityOfZion/neo-go/pkg/core/transaction"
"github.com/CityOfZion/neo-go/pkg/io" "github.com/CityOfZion/neo-go/pkg/io"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
"github.com/CityOfZion/neo-go/pkg/vm"
"github.com/pkg/errors" "github.com/pkg/errors"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
@ -902,28 +904,35 @@ func (bc *Blockchain) VerifyWitnesses(t *transaction.Transaction) error {
verification := witnesses[i].VerificationScript verification := witnesses[i].VerificationScript
if len(verification) == 0 { if len(verification) == 0 {
/*TODO: replicate following C# code: bb := new(bytes.Buffer)
using (ScriptBuilder sb = new ScriptBuilder()) err = vm.EmitAppCall(bb, hashes[i], false)
{ if err != nil {
sb.EmitAppCall(hashes[i].ToArray()); return err
verification = sb.ToArray();
} }
*/ verification = bb.Bytes()
} else { } else {
if h := witnesses[i].ScriptHash(); hashes[i] != h { if h := witnesses[i].ScriptHash(); hashes[i] != h {
return errors.Errorf("hash mismatch for script #%d", i) return errors.Errorf("hash mismatch for script #%d", i)
} }
} }
/*TODO: replicate following C# code: vm := vm.New(vm.ModeMute)
using (ApplicationEngine engine = new ApplicationEngine(TriggerType.Verification, verifiable, snapshot, Fixed8.Zero)) vm.SetCheckedHash(t.VerificationHash().Bytes())
{ vm.LoadScript(verification)
engine.LoadScript(verification); vm.LoadScript(witnesses[i].InvocationScript)
engine.LoadScript(verifiable.Witnesses[i].InvocationScript); vm.Run()
if (!engine.Execute()) return false; if vm.HasFailed() {
if (engine.ResultStack.Count != 1 || !engine.ResultStack.Pop().GetBoolean()) return false; return errors.Errorf("vm failed to execute the script")
}*/ }
res := vm.PopResult()
switch res.(type) {
case bool:
if !(res.(bool)) {
return errors.Errorf("signature check failed")
}
default:
return errors.Errorf("vm returned non-boolean result")
}
} }
return nil return nil

View file

@ -39,9 +39,12 @@ type Transaction struct {
// and invocation script. // and invocation script.
Scripts []*Witness `json:"scripts"` Scripts []*Witness `json:"scripts"`
// hash of the transaction // Hash of the transaction (double SHA256).
hash util.Uint256 hash util.Uint256
// Hash of the transaction used to verify it (single SHA256).
verificationHash util.Uint256
// Trimmed indicates this is a transaction from trimmed // Trimmed indicates this is a transaction from trimmed
// data. // data.
Trimmed bool `json:"-"` Trimmed bool `json:"-"`
@ -59,11 +62,23 @@ func NewTrimmedTX(hash util.Uint256) *Transaction {
// Hash return the hash of the transaction. // Hash return the hash of the transaction.
func (t *Transaction) Hash() util.Uint256 { func (t *Transaction) Hash() util.Uint256 {
if t.hash.Equals(util.Uint256{}) { if t.hash.Equals(util.Uint256{}) {
t.createHash() if t.createHash() != nil {
panic("failed to compute hash!")
}
} }
return t.hash return t.hash
} }
// VerificationHash returns the hash of the transaction used to verify it.
func (t *Transaction) VerificationHash() util.Uint256 {
if t.verificationHash.Equals(util.Uint256{}) {
if t.createHash() != nil {
panic("failed to compute hash!")
}
}
return t.verificationHash
}
// AddOutput adds the given output to the transaction outputs. // AddOutput adds the given output to the transaction outputs.
func (t *Transaction) AddOutput(out *Output) { func (t *Transaction) AddOutput(out *Output) {
t.Outputs = append(t.Outputs, out) t.Outputs = append(t.Outputs, out)
@ -196,7 +211,9 @@ func (t *Transaction) createHash() error {
return buf.Err return buf.Err
} }
t.hash = hash.DoubleSha256(buf.Bytes()) b := buf.Bytes()
t.hash = hash.DoubleSha256(b)
t.verificationHash = hash.Sha256(b)
return nil return nil
} }

View file

@ -1,6 +1,7 @@
package vm package vm
import ( import (
"fmt"
"math/big" "math/big"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
@ -280,3 +281,39 @@ func (s *Stack) Iter(f func(*Element)) {
f(e) f(e)
} }
} }
// popSigElements pops keys or signatures from the stack as needed for
// CHECKMULTISIG.
func (s *Stack) popSigElements() ([][]byte, error) {
var num int
var elems [][]byte
item := s.Pop()
if item == nil {
return nil, fmt.Errorf("nothing on the stack")
}
switch item.value.(type) {
case *ArrayItem:
num = len(item.Array())
if num < 1 {
return nil, fmt.Errorf("less than one element in the array")
}
elems = make([][]byte, num)
for k, v := range item.Array() {
b, ok := v.Value().([]byte)
if !ok {
return nil, fmt.Errorf("bad element %s", v.String())
}
elems[k] = b
}
default:
num = int(item.BigInt().Int64())
if num < 1 || num > s.Len() {
return nil, fmt.Errorf("wrong number of elements: %d", num)
}
elems = make([][]byte, num)
for i := 0; i < num; i++ {
elems[i] = s.Pop().Bytes()
}
}
return elems, nil
}

View file

@ -217,6 +217,44 @@ func TestSwapElemValues(t *testing.T) {
assert.Equal(t, int64(4), s.Pop().BigInt().Int64()) assert.Equal(t, int64(4), s.Pop().BigInt().Int64())
} }
func TestPopSigElements(t *testing.T) {
s := NewStack("test")
_, err := s.popSigElements()
assert.NotNil(t, err)
s.PushVal([]StackItem{})
_, err = s.popSigElements()
assert.NotNil(t, err)
s.PushVal([]StackItem{NewBoolItem(false)})
_, err = s.popSigElements()
assert.NotNil(t, err)
b1 := []byte("smth")
b2 := []byte("strange")
s.PushVal([]StackItem{NewByteArrayItem(b1), NewByteArrayItem(b2)})
z, err := s.popSigElements()
assert.Nil(t, err)
assert.Equal(t, z, [][]byte{b1, b2})
s.PushVal(2)
_, err = s.popSigElements()
assert.NotNil(t, err)
s.PushVal(b1)
s.PushVal(2)
_, err = s.popSigElements()
assert.NotNil(t, err)
s.PushVal(b2)
s.PushVal(b1)
s.PushVal(2)
z, err = s.popSigElements()
assert.Nil(t, err)
assert.Equal(t, z, [][]byte{b1, b2})
}
func makeElements(n int) []*Element { func makeElements(n int) []*Element {
elems := make([]*Element, n) elems := make([]*Element, n)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {

View file

@ -11,6 +11,7 @@ import (
"text/tabwriter" "text/tabwriter"
"github.com/CityOfZion/neo-go/pkg/crypto/hash" "github.com/CityOfZion/neo-go/pkg/crypto/hash"
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
@ -43,6 +44,8 @@ type VM struct {
// Mute all output after execution. // Mute all output after execution.
mute bool mute bool
// Hash to verify in CHECKSIG/CHECKMULTISIG.
checkhash []byte
} }
// New returns a new VM object ready to load .avm bytecode scripts. // New returns a new VM object ready to load .avm bytecode scripts.
@ -233,6 +236,18 @@ func (v *VM) Step() {
} }
} }
// HasFailed returns whether VM is in the failed state now. Usually used to
// check status after Run.
func (v *VM) HasFailed() bool {
return v.state.HasFlag(faultState)
}
// SetCheckedHash sets checked hash for CHECKSIG and CHECKMULTISIG instructions.
func (v *VM) SetCheckedHash(h []byte) {
v.checkhash = make([]byte, len(h))
copy(v.checkhash, h)
}
// execute performs an instruction cycle in the VM. Acting on the instruction (opcode). // execute performs an instruction cycle in the VM. Acting on the instruction (opcode).
func (v *VM) execute(ctx *Context, op Instruction) { func (v *VM) execute(ctx *Context, op Instruction) {
// Instead of polluting the whole VM logic with error handling, we will recover // Instead of polluting the whole VM logic with error handling, we will recover
@ -842,7 +857,62 @@ func (v *VM) execute(ctx *Context, op Instruction) {
v.state = haltState v.state = haltState
} }
case CHECKSIG, VERIFY, CHECKMULTISIG, NEWMAP, HASKEY, KEYS, VALUES: case CHECKSIG, VERIFY:
var hashToCheck []byte
keyb := v.estack.Pop().Bytes()
signature := v.estack.Pop().Bytes()
if op == CHECKSIG {
if v.checkhash == nil {
panic("VM is not set up properly for signature checks")
}
hashToCheck = v.checkhash
} else { // VERIFY
msg := v.estack.Pop().Bytes()
hashToCheck = hash.Sha256(msg).Bytes()
}
pkey := &keys.PublicKey{}
err := pkey.DecodeBytes(keyb)
if err != nil {
panic(err.Error())
}
res := pkey.Verify(signature, hashToCheck)
v.estack.PushVal(res)
case CHECKMULTISIG:
pkeys, err := v.estack.popSigElements()
if err != nil {
panic(fmt.Sprintf("wrong parameters: %s", err.Error()))
}
sigs, err := v.estack.popSigElements()
if err != nil {
panic(fmt.Sprintf("wrong parameters: %s", err.Error()))
}
if len(pkeys) < len(sigs) {
panic("more signatures than there are keys")
}
if v.checkhash == nil {
panic("VM is not set up properly for signature checks")
}
sigok := true
j := 0
for i := 0; sigok && i < len(pkeys) && j < len(sigs); {
pkey := &keys.PublicKey{}
err := pkey.DecodeBytes(pkeys[j])
if err != nil {
panic(err.Error())
}
if pkey.Verify(sigs[i], v.checkhash) {
i++
}
j++
if len(pkeys)-i > len(sigs)-j {
sigok = false
}
}
v.estack.PushVal(sigok)
case NEWMAP, HASKEY, KEYS, VALUES:
panic("unimplemented") panic("unimplemented")
// Cryptographic operations. // Cryptographic operations.

File diff suppressed because it is too large Load diff