forked from TrueCloudLab/neoneo-go
core: allow to use verification contracts
In NEO3 we can't just appcall hash, as verification script has no access to state. Instead we use `verify` method of an arbitrary contract.
This commit is contained in:
parent
7f2a931fb6
commit
1eb9a4c6c6
4 changed files with 75 additions and 6 deletions
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
@ -1430,15 +1431,33 @@ func ScriptFromWitness(hash util.Uint160, witness *transaction.Witness) ([]byte,
|
||||||
|
|
||||||
// Various witness verification errors.
|
// Various witness verification errors.
|
||||||
var (
|
var (
|
||||||
ErrWitnessHashMismatch = errors.New("witness hash mismatch")
|
ErrWitnessHashMismatch = errors.New("witness hash mismatch")
|
||||||
ErrVerificationFailed = errors.New("signature check failed")
|
ErrVerificationFailed = errors.New("signature check failed")
|
||||||
|
ErrUnknownVerificationContract = errors.New("unknown verification contract")
|
||||||
|
ErrInvalidVerificationContract = errors.New("verification contract is missing `verify` method")
|
||||||
)
|
)
|
||||||
|
|
||||||
// verifyHashAgainstScript verifies given hash against the given witness.
|
// verifyHashAgainstScript verifies given hash against the given witness.
|
||||||
func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transaction.Witness, interopCtx *interop.Context, useKeys bool, gas int64) error {
|
func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transaction.Witness, interopCtx *interop.Context, useKeys bool, gas int64) error {
|
||||||
verification, err := ScriptFromWitness(hash, witness)
|
var offset int
|
||||||
if err != nil {
|
var initMD *manifest.Method
|
||||||
return err
|
verification := witness.VerificationScript
|
||||||
|
if len(verification) != 0 {
|
||||||
|
if witness.ScriptHash() != hash {
|
||||||
|
return ErrWitnessHashMismatch
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cs, err := interopCtx.DAO.GetContractState(hash)
|
||||||
|
if err != nil {
|
||||||
|
return ErrUnknownVerificationContract
|
||||||
|
}
|
||||||
|
md := cs.Manifest.ABI.GetMethod(manifest.MethodVerify)
|
||||||
|
if md == nil {
|
||||||
|
return ErrInvalidVerificationContract
|
||||||
|
}
|
||||||
|
verification = cs.Script
|
||||||
|
offset = md.Offset
|
||||||
|
initMD = cs.Manifest.ABI.GetMethod(manifest.MethodInit)
|
||||||
}
|
}
|
||||||
|
|
||||||
gasPolicy := bc.contracts.Policy.GetMaxVerificationGas(interopCtx.DAO)
|
gasPolicy := bc.contracts.Policy.GetMaxVerificationGas(interopCtx.DAO)
|
||||||
|
@ -1450,6 +1469,10 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
|
||||||
vm.SetPriceGetter(getPrice)
|
vm.SetPriceGetter(getPrice)
|
||||||
vm.GasLimit = gas
|
vm.GasLimit = gas
|
||||||
vm.LoadScriptWithFlags(verification, smartcontract.NoneFlag)
|
vm.LoadScriptWithFlags(verification, smartcontract.NoneFlag)
|
||||||
|
vm.Jump(vm.Context(), offset)
|
||||||
|
if initMD != nil {
|
||||||
|
vm.Call(vm.Context(), initMD.Offset)
|
||||||
|
}
|
||||||
vm.LoadScript(witness.InvocationScript)
|
vm.LoadScript(witness.InvocationScript)
|
||||||
if useKeys {
|
if useKeys {
|
||||||
bc.keyCacheLock.RLock()
|
bc.keyCacheLock.RLock()
|
||||||
|
@ -1458,7 +1481,7 @@ func (bc *Blockchain) verifyHashAgainstScript(hash util.Uint160, witness *transa
|
||||||
}
|
}
|
||||||
bc.keyCacheLock.RUnlock()
|
bc.keyCacheLock.RUnlock()
|
||||||
}
|
}
|
||||||
err = vm.Run()
|
err := vm.Run()
|
||||||
if vm.HasFailed() {
|
if vm.HasFailed() {
|
||||||
return fmt.Errorf("%w: vm execution has failed: %v", ErrVerificationFailed, err)
|
return fmt.Errorf("%w: vm execution has failed: %v", ErrVerificationFailed, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/internal/testchain"
|
"github.com/nspcc-dev/neo-go/pkg/internal/testchain"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||||
|
@ -300,6 +301,42 @@ func TestVerifyTx(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestVerifyHashAgainstScript(t *testing.T) {
|
||||||
|
bc := newTestChain(t)
|
||||||
|
defer bc.Close()
|
||||||
|
|
||||||
|
cs, csInvalid := getTestContractState()
|
||||||
|
ic := bc.newInteropContext(trigger.Verification, bc.dao, nil, nil)
|
||||||
|
require.NoError(t, ic.DAO.PutContractState(cs))
|
||||||
|
require.NoError(t, ic.DAO.PutContractState(csInvalid))
|
||||||
|
|
||||||
|
gas := bc.contracts.Policy.GetMaxVerificationGas(ic.DAO)
|
||||||
|
t.Run("Contract", func(t *testing.T) {
|
||||||
|
t.Run("Missing", func(t *testing.T) {
|
||||||
|
newH := cs.ScriptHash()
|
||||||
|
newH[0] = ^newH[0]
|
||||||
|
w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}}
|
||||||
|
err := bc.verifyHashAgainstScript(newH, w, ic, false, gas)
|
||||||
|
require.True(t, errors.Is(err, ErrUnknownVerificationContract))
|
||||||
|
})
|
||||||
|
t.Run("Invalid", func(t *testing.T) {
|
||||||
|
w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}}
|
||||||
|
err := bc.verifyHashAgainstScript(csInvalid.ScriptHash(), w, ic, false, gas)
|
||||||
|
require.True(t, errors.Is(err, ErrInvalidVerificationContract))
|
||||||
|
})
|
||||||
|
t.Run("ValidSignature", func(t *testing.T) {
|
||||||
|
w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}}
|
||||||
|
err := bc.verifyHashAgainstScript(cs.ScriptHash(), w, ic, false, gas)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
t.Run("InvalidSignature", func(t *testing.T) {
|
||||||
|
w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH3)}}
|
||||||
|
err := bc.verifyHashAgainstScript(cs.ScriptHash(), w, ic, false, gas)
|
||||||
|
require.True(t, errors.Is(err, ErrVerificationFailed))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestHasBlock(t *testing.T) {
|
func TestHasBlock(t *testing.T) {
|
||||||
bc := newTestChain(t)
|
bc := newTestChain(t)
|
||||||
blocks, err := bc.genBlocks(50)
|
blocks, err := bc.genBlocks(50)
|
||||||
|
|
|
@ -337,6 +337,7 @@ func getTestContractState() (*state.Contract, *state.Contract) {
|
||||||
byte(opcode.LDSFLD0), byte(opcode.ADD), byte(opcode.RET),
|
byte(opcode.LDSFLD0), byte(opcode.ADD), byte(opcode.RET),
|
||||||
byte(opcode.PUSH1), byte(opcode.PUSH2), byte(opcode.RET),
|
byte(opcode.PUSH1), byte(opcode.PUSH2), byte(opcode.RET),
|
||||||
byte(opcode.RET),
|
byte(opcode.RET),
|
||||||
|
byte(opcode.LDSFLD0), byte(opcode.SUB), byte(opcode.CONVERT), byte(stackitem.BooleanT), byte(opcode.RET),
|
||||||
}
|
}
|
||||||
h := hash.Hash160(script)
|
h := hash.Hash160(script)
|
||||||
m := manifest.NewManifest(h)
|
m := manifest.NewManifest(h)
|
||||||
|
@ -384,6 +385,11 @@ func getTestContractState() (*state.Contract, *state.Contract) {
|
||||||
Offset: 18,
|
Offset: 18,
|
||||||
ReturnType: smartcontract.IntegerType,
|
ReturnType: smartcontract.IntegerType,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: manifest.MethodVerify,
|
||||||
|
Offset: 19,
|
||||||
|
ReturnType: smartcontract.BoolType,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
cs := &state.Contract{
|
cs := &state.Contract{
|
||||||
Script: script,
|
Script: script,
|
||||||
|
|
|
@ -15,6 +15,9 @@ const (
|
||||||
// MethodInit is a name for default initialization method.
|
// MethodInit is a name for default initialization method.
|
||||||
MethodInit = "_initialize"
|
MethodInit = "_initialize"
|
||||||
|
|
||||||
|
// MethodVerify is a name for default verification method.
|
||||||
|
MethodVerify = "verify"
|
||||||
|
|
||||||
// NEP5StandardName represents the name of NEP5 smartcontract standard.
|
// NEP5StandardName represents the name of NEP5 smartcontract standard.
|
||||||
NEP5StandardName = "NEP-5"
|
NEP5StandardName = "NEP-5"
|
||||||
// NEP10StandardName represents the name of NEP10 smartcontract standard.
|
// NEP10StandardName represents the name of NEP10 smartcontract standard.
|
||||||
|
|
Loading…
Reference in a new issue