Merge pull request #3256 from fyfyrchik/fix-signer
Allow complex contract signature schemes in `wallet.Account`
This commit is contained in:
commit
56d32a010e
4 changed files with 74 additions and 54 deletions
|
@ -301,8 +301,9 @@ func AddNetworkFee(t testing.TB, bc *core.Blockchain, tx *transaction.Transactio
|
|||
baseFee := bc.GetBaseExecFee()
|
||||
size := io.GetVarSize(tx)
|
||||
for _, sgr := range signers {
|
||||
if csgr, ok := sgr.(ContractSigner); ok {
|
||||
sc, err := csgr.InvocationScript(tx)
|
||||
csgr, ok := sgr.(SingleSigner)
|
||||
if ok && csgr.Account().Contract.InvocationBuilder != nil {
|
||||
sc, err := csgr.Account().Contract.InvocationBuilder(tx)
|
||||
require.NoError(t, err)
|
||||
|
||||
txCopy := *tx
|
||||
|
|
|
@ -2,7 +2,6 @@ package neotest
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
|
@ -11,6 +10,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
|
@ -47,13 +47,6 @@ type MultiSigner interface {
|
|||
Single(n int) SingleSigner
|
||||
}
|
||||
|
||||
// ContractSigner is an interface for contract signer.
|
||||
type ContractSigner interface {
|
||||
Signer
|
||||
// InvocationScript returns an invocation script to be used as invocation script for contract-based witness.
|
||||
InvocationScript(tx *transaction.Transaction) ([]byte, error)
|
||||
}
|
||||
|
||||
// signer represents a simple-signature signer.
|
||||
type signer wallet.Account
|
||||
|
||||
|
@ -190,33 +183,30 @@ func checkMultiSigner(t testing.TB, s Signer) {
|
|||
}
|
||||
}
|
||||
|
||||
type contractSigner struct {
|
||||
params func(tx *transaction.Transaction) []any
|
||||
scriptHash util.Uint160
|
||||
}
|
||||
type contractSigner wallet.Account
|
||||
|
||||
// NewContractSigner returns a contract signer for the provided contract hash.
|
||||
// getInvParams must return params to be used as invocation script for contract-based witness.
|
||||
func NewContractSigner(h util.Uint160, getInvParams func(tx *transaction.Transaction) []any) ContractSigner {
|
||||
func NewContractSigner(h util.Uint160, getInvParams func(tx *transaction.Transaction) []any) SingleSigner {
|
||||
return &contractSigner{
|
||||
scriptHash: h,
|
||||
params: getInvParams,
|
||||
Address: address.Uint160ToString(h),
|
||||
Contract: &wallet.Contract{
|
||||
Deployed: true,
|
||||
InvocationBuilder: func(tx *transaction.Transaction) ([]byte, error) {
|
||||
params := getInvParams(tx)
|
||||
script := io.NewBufBinWriter()
|
||||
for i := range params {
|
||||
emit.Any(script.BinWriter, params[i])
|
||||
}
|
||||
if script.Err != nil {
|
||||
return nil, script.Err
|
||||
}
|
||||
return script.Bytes(), nil
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// InvocationScript implements ContractSigner.
|
||||
func (s *contractSigner) InvocationScript(tx *transaction.Transaction) ([]byte, error) {
|
||||
params := s.params(tx)
|
||||
script := io.NewBufBinWriter()
|
||||
for i := range params {
|
||||
emit.Any(script.BinWriter, params[i])
|
||||
}
|
||||
if script.Err != nil {
|
||||
return nil, script.Err
|
||||
}
|
||||
return script.Bytes(), nil
|
||||
}
|
||||
|
||||
// Script implements ContractSigner.
|
||||
func (s *contractSigner) Script() []byte {
|
||||
return []byte{}
|
||||
|
@ -224,7 +214,12 @@ func (s *contractSigner) Script() []byte {
|
|||
|
||||
// ScriptHash implements ContractSigner.
|
||||
func (s *contractSigner) ScriptHash() util.Uint160 {
|
||||
return s.scriptHash
|
||||
return s.Account().ScriptHash()
|
||||
}
|
||||
|
||||
// ScriptHash implements ContractSigner.
|
||||
func (s *contractSigner) Account() *wallet.Account {
|
||||
return (*wallet.Account)(s)
|
||||
}
|
||||
|
||||
// SignHashable implements ContractSigner.
|
||||
|
@ -234,27 +229,7 @@ func (s *contractSigner) SignHashable(uint32, hash.Hashable) []byte {
|
|||
|
||||
// SignTx implements ContractSigner.
|
||||
func (s *contractSigner) SignTx(magic netmode.Magic, tx *transaction.Transaction) error {
|
||||
pos := -1
|
||||
for idx := range tx.Signers {
|
||||
if tx.Signers[idx].Account.Equals(s.ScriptHash()) {
|
||||
pos = idx
|
||||
break
|
||||
}
|
||||
}
|
||||
if pos < 0 {
|
||||
return fmt.Errorf("signer %s not found", s.ScriptHash().String())
|
||||
}
|
||||
if len(tx.Scripts) < pos {
|
||||
return errors.New("transaction is not yet signed by the previous signer")
|
||||
}
|
||||
invoc, err := s.InvocationScript(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(tx.Scripts) == pos {
|
||||
tx.Scripts = append(tx.Scripts, transaction.Witness{})
|
||||
}
|
||||
tx.Scripts[pos].InvocationScript = invoc
|
||||
tx.Scripts[pos].VerificationScript = s.Script()
|
||||
return nil
|
||||
// Here we rely on `len(s.Contract.Parameters) == 0` being after the `s.Contract.InvocationBuilder != nil` check,
|
||||
// because we cannot determine the list of parameters unless we already have tx.
|
||||
return s.Account().SignTx(magic, tx)
|
||||
}
|
||||
|
|
|
@ -191,6 +191,14 @@ func (a *Actor) MakeUnsignedUncheckedRun(script []byte, sysFee int64, attrs []tr
|
|||
for i := range a.signers {
|
||||
if !a.signers[i].Account.Contract.Deployed {
|
||||
tx.Scripts[i].VerificationScript = a.signers[i].Account.Contract.Script
|
||||
continue
|
||||
}
|
||||
if build := a.signers[i].Account.Contract.InvocationBuilder; build != nil {
|
||||
invoc, err := build(tx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("building witness for contract signer: %w", err)
|
||||
}
|
||||
tx.Scripts[i].InvocationScript = invoc
|
||||
}
|
||||
}
|
||||
// CalculateNetworkFee doesn't call Hash or Size, only serializes the
|
||||
|
|
|
@ -9,8 +9,10 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
)
|
||||
|
||||
|
@ -55,6 +57,12 @@ type Contract struct {
|
|||
|
||||
// Indicates whether the contract has been deployed to the blockchain.
|
||||
Deployed bool `json:"deployed"`
|
||||
|
||||
// InvocationBuilder returns invocation script for deployed contracts.
|
||||
// In case contract is not deployed or has 0 arguments, this field is ignored.
|
||||
// It might be executed on a partially formed tx, and is primarily needed to properly
|
||||
// calculate network fee for complex contract signers.
|
||||
InvocationBuilder func(tx *transaction.Transaction) ([]byte, error) `json:"-"`
|
||||
}
|
||||
|
||||
// ContractParam is a descriptor of a contract parameter
|
||||
|
@ -78,6 +86,29 @@ func NewAccount() (*Account, error) {
|
|||
return NewAccountFromPrivateKey(priv), nil
|
||||
}
|
||||
|
||||
// NewContractAccount creates a contract account belonging to some deployed contract.
|
||||
// SignTx can be called on this account with no error and will create invocation script,
|
||||
// which puts provided arguments on stack for use in `verify`.
|
||||
func NewContractAccount(hash util.Uint160, args ...any) *Account {
|
||||
return &Account{
|
||||
Address: address.Uint160ToString(hash),
|
||||
Contract: &Contract{
|
||||
Parameters: make([]ContractParam, len(args)),
|
||||
Deployed: true,
|
||||
InvocationBuilder: func(tx *transaction.Transaction) ([]byte, error) {
|
||||
w := io.NewBufBinWriter()
|
||||
for i := range args {
|
||||
emit.Any(w.BinWriter, args[i])
|
||||
}
|
||||
if w.Err != nil {
|
||||
return nil, w.Err
|
||||
}
|
||||
return w.Bytes(), nil
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// SignTx signs transaction t and updates it's Witnesses.
|
||||
func (a *Account) SignTx(net netmode.Magic, t *transaction.Transaction) error {
|
||||
var (
|
||||
|
@ -108,6 +139,11 @@ func (a *Account) SignTx(net netmode.Magic, t *transaction.Transaction) error {
|
|||
VerificationScript: a.Contract.Script, // Can be nil for deployed contract.
|
||||
})
|
||||
}
|
||||
if a.Contract.Deployed && a.Contract.InvocationBuilder != nil {
|
||||
invoc, err := a.Contract.InvocationBuilder(t)
|
||||
t.Scripts[pos].InvocationScript = invoc
|
||||
return err
|
||||
}
|
||||
if len(a.Contract.Parameters) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue