7b53a0c239
Signed-off-by: Dmitrii Stepanov <dima-stepan@yandex.ru>
260 lines
7.7 KiB
Go
260 lines
7.7 KiB
Go
package neotest
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"testing"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
|
"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/io"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"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/opcode"
|
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// Signer is a generic interface which can be either a simple- or multi-signature signer.
|
|
type Signer interface {
|
|
// Script returns a signer verification script.
|
|
Script() []byte
|
|
// ScriptHash returns a signer script hash.
|
|
ScriptHash() util.Uint160
|
|
// SignHashable returns an invocation script for signing an item.
|
|
SignHashable(uint32, hash.Hashable) []byte
|
|
// SignTx signs a transaction.
|
|
SignTx(netmode.Magic, *transaction.Transaction) error
|
|
}
|
|
|
|
// SingleSigner is a generic interface for a simple one-signature signer.
|
|
type SingleSigner interface {
|
|
Signer
|
|
// Account returns the underlying account which can be used to
|
|
// get a public key and/or sign arbitrary things.
|
|
Account() *wallet.Account
|
|
}
|
|
|
|
// MultiSigner is an interface for multisignature signing account.
|
|
type MultiSigner interface {
|
|
Signer
|
|
// Single returns a simple-signature signer for the n-th account in a list.
|
|
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
|
|
|
|
// multiSigner represents a single multi-signature signer consisting of the provided accounts.
|
|
type multiSigner struct {
|
|
accounts []*wallet.Account
|
|
m int
|
|
}
|
|
|
|
// NewSingleSigner creates a [SingleSigner] from the provided account. It has
|
|
// just one key, see [NewMultiSigner] for multisignature accounts.
|
|
func NewSingleSigner(acc *wallet.Account) SingleSigner {
|
|
if !vm.IsSignatureContract(acc.Contract.Script) {
|
|
panic("account must have simple-signature verification script")
|
|
}
|
|
return (*signer)(acc)
|
|
}
|
|
|
|
// Script implements Signer interface.
|
|
func (s *signer) Script() []byte {
|
|
return (*wallet.Account)(s).Contract.Script
|
|
}
|
|
|
|
// ScriptHash implements Signer interface.
|
|
func (s *signer) ScriptHash() util.Uint160 {
|
|
return (*wallet.Account)(s).Contract.ScriptHash()
|
|
}
|
|
|
|
// SignHashable implements Signer interface.
|
|
func (s *signer) SignHashable(magic uint32, item hash.Hashable) []byte {
|
|
return append([]byte{byte(opcode.PUSHDATA1), keys.SignatureLen},
|
|
(*wallet.Account)(s).SignHashable(netmode.Magic(magic), item)...)
|
|
}
|
|
|
|
// SignTx implements Signer interface.
|
|
func (s *signer) SignTx(magic netmode.Magic, tx *transaction.Transaction) error {
|
|
return (*wallet.Account)(s).SignTx(magic, tx)
|
|
}
|
|
|
|
// Account implements SingleSigner interface.
|
|
func (s *signer) Account() *wallet.Account {
|
|
return (*wallet.Account)(s)
|
|
}
|
|
|
|
// NewMultiSigner returns a multi-signature signer for the provided account.
|
|
// It must contain at least as many accounts as needed to sign the script.
|
|
func NewMultiSigner(accs ...*wallet.Account) MultiSigner {
|
|
if len(accs) == 0 {
|
|
panic("empty account list")
|
|
}
|
|
script := accs[0].Contract.Script
|
|
m, _, ok := vm.ParseMultiSigContract(script)
|
|
if !ok {
|
|
panic("all accounts must have multi-signature verification script")
|
|
}
|
|
if len(accs) < m {
|
|
panic(fmt.Sprintf("verification script requires %d signatures, "+
|
|
"but only %d accounts were provided", m, len(accs)))
|
|
}
|
|
sort.Slice(accs, func(i, j int) bool {
|
|
p1 := accs[i].PublicKey()
|
|
p2 := accs[j].PublicKey()
|
|
return p1.Cmp(p2) == -1
|
|
})
|
|
for _, acc := range accs {
|
|
if !bytes.Equal(script, acc.Contract.Script) {
|
|
panic("all accounts must have equal verification script")
|
|
}
|
|
}
|
|
|
|
return multiSigner{accounts: accs, m: m}
|
|
}
|
|
|
|
// ScriptHash implements Signer interface.
|
|
func (m multiSigner) ScriptHash() util.Uint160 {
|
|
return m.accounts[0].Contract.ScriptHash()
|
|
}
|
|
|
|
// Script implements Signer interface.
|
|
func (m multiSigner) Script() []byte {
|
|
return m.accounts[0].Contract.Script
|
|
}
|
|
|
|
// SignHashable implements Signer interface.
|
|
func (m multiSigner) SignHashable(magic uint32, item hash.Hashable) []byte {
|
|
var script []byte
|
|
for i := 0; i < m.m; i++ {
|
|
sign := m.accounts[i].SignHashable(netmode.Magic(magic), item)
|
|
script = append(script, byte(opcode.PUSHDATA1), keys.SignatureLen)
|
|
script = append(script, sign...)
|
|
}
|
|
return script
|
|
}
|
|
|
|
// SignTx implements Signer interface.
|
|
func (m multiSigner) SignTx(magic netmode.Magic, tx *transaction.Transaction) error {
|
|
invoc := m.SignHashable(uint32(magic), tx)
|
|
verif := m.Script()
|
|
for i := range tx.Scripts {
|
|
if bytes.Equal(tx.Scripts[i].VerificationScript, verif) {
|
|
tx.Scripts[i].InvocationScript = invoc
|
|
return nil
|
|
}
|
|
}
|
|
tx.Scripts = append(tx.Scripts, transaction.Witness{
|
|
InvocationScript: invoc,
|
|
VerificationScript: verif,
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// Single implements MultiSigner interface.
|
|
func (m multiSigner) Single(n int) SingleSigner {
|
|
if len(m.accounts) <= n {
|
|
panic("invalid index")
|
|
}
|
|
return NewSingleSigner(wallet.NewAccountFromPrivateKey(m.accounts[n].PrivateKey()))
|
|
}
|
|
|
|
func checkMultiSigner(t testing.TB, s Signer) {
|
|
ms, ok := s.(multiSigner)
|
|
require.True(t, ok, "expected to be a multi-signer")
|
|
|
|
accs := ms.accounts
|
|
require.True(t, len(accs) > 0, "empty multi-signer")
|
|
|
|
m := len(accs[0].Contract.Parameters)
|
|
require.True(t, m <= len(accs), "honest not count is too big for a multi-signer")
|
|
|
|
h := accs[0].Contract.ScriptHash()
|
|
for i := 1; i < len(accs); i++ {
|
|
require.Equal(t, m, len(accs[i].Contract.Parameters), "inconsistent multi-signer accounts")
|
|
require.Equal(t, h, accs[i].Contract.ScriptHash(), "inconsistent multi-signer accounts")
|
|
}
|
|
}
|
|
|
|
type contractSigner struct {
|
|
params func(tx *transaction.Transaction) []any
|
|
scriptHash util.Uint160
|
|
}
|
|
|
|
// 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 {
|
|
return &contractSigner{
|
|
scriptHash: h,
|
|
params: getInvParams,
|
|
}
|
|
}
|
|
|
|
// 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{}
|
|
}
|
|
|
|
// ScriptHash implements ContractSigner.
|
|
func (s *contractSigner) ScriptHash() util.Uint160 {
|
|
return s.scriptHash
|
|
}
|
|
|
|
// SignHashable implements ContractSigner.
|
|
func (s *contractSigner) SignHashable(uint32, hash.Hashable) []byte {
|
|
panic("not supported")
|
|
}
|
|
|
|
// 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
|
|
}
|