neotest: support painless multi-signing
Implementing a separate `Signer` interface is beneficial in multiple ways: 1. Support both single and multiple transaction witnesses. 2. It should be easy to add contract signer this way. Tests should use accounts created with `NewAccount` so hiding all details doesn't seem to be an issue. Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
This commit is contained in:
parent
1f9fd4a472
commit
950adb7b89
5 changed files with 197 additions and 87 deletions
|
@ -11,7 +11,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -37,7 +36,7 @@ func TestNameService_Price(t *testing.T) {
|
|||
|
||||
t.Run("set, not signed by committee", func(t *testing.T) {
|
||||
acc := c.NewAccount(t)
|
||||
cAcc := c.WithSigner(acc)
|
||||
cAcc := c.WithSigners(acc)
|
||||
cAcc.InvokeFail(t, "not witnessed by committee", "setPrice", minPrice+1)
|
||||
})
|
||||
|
||||
|
@ -68,7 +67,7 @@ func TestNameService_Price(t *testing.T) {
|
|||
func TestNonfungible(t *testing.T) {
|
||||
c := newNSClient(t)
|
||||
|
||||
c.Signer = c.NewAccount(t)
|
||||
c.Signers = []neotest.Signer{c.NewAccount(t)}
|
||||
c.Invoke(t, "NNS", "symbol")
|
||||
c.Invoke(t, 0, "decimals")
|
||||
c.Invoke(t, 0, "totalSupply")
|
||||
|
@ -82,7 +81,7 @@ func TestAddRoot(t *testing.T) {
|
|||
})
|
||||
t.Run("not signed by committee", func(t *testing.T) {
|
||||
acc := c.NewAccount(t)
|
||||
c := c.WithSigner(acc)
|
||||
c := c.WithSigners(acc)
|
||||
c.InvokeFail(t, "not witnessed by committee", "addRoot", "some")
|
||||
})
|
||||
|
||||
|
@ -98,14 +97,14 @@ func TestExpiration(t *testing.T) {
|
|||
bc := e.Chain
|
||||
|
||||
acc := e.NewAccount(t)
|
||||
cAcc := c.WithSigner(acc)
|
||||
cAcc := c.WithSigners(acc)
|
||||
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||
cAcc.Invoke(t, true, "register", "first.com", acc.Contract.ScriptHash())
|
||||
cAcc.Invoke(t, true, "register", "first.com", acc.ScriptHash())
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "first.com", int64(nns.TXT), "sometext")
|
||||
b1 := e.TopBlock(t)
|
||||
|
||||
tx := cAcc.PrepareInvoke(t, "register", "second.com", acc.Contract.ScriptHash())
|
||||
tx := cAcc.PrepareInvoke(t, "register", "second.com", acc.ScriptHash())
|
||||
b2 := e.NewUnsignedBlock(t, tx)
|
||||
b2.Index = b1.Index + 1
|
||||
b2.PrevHash = b1.Hash()
|
||||
|
@ -191,7 +190,7 @@ func TestSetGetRecord(t *testing.T) {
|
|||
e := c.Executor
|
||||
|
||||
acc := e.NewAccount(t)
|
||||
cAcc := c.WithSigner(acc)
|
||||
cAcc := c.WithSigners(acc)
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||
|
||||
t.Run("set before register", func(t *testing.T) {
|
||||
|
@ -295,22 +294,22 @@ func TestSetAdmin(t *testing.T) {
|
|||
e := c.Executor
|
||||
|
||||
owner := e.NewAccount(t)
|
||||
cOwner := c.WithSigner(owner)
|
||||
cOwner := c.WithSigners(owner)
|
||||
admin := e.NewAccount(t)
|
||||
cAdmin := c.WithSigner(admin)
|
||||
cAdmin := c.WithSigners(admin)
|
||||
guest := e.NewAccount(t)
|
||||
cGuest := c.WithSigner(guest)
|
||||
cGuest := c.WithSigners(guest)
|
||||
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||
|
||||
cOwner.Invoke(t, true, "register", "neo.com", owner.PrivateKey().GetScriptHash())
|
||||
cGuest.InvokeFail(t, "not witnessed", "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash())
|
||||
cOwner.Invoke(t, true, "register", "neo.com", owner.ScriptHash())
|
||||
cGuest.InvokeFail(t, "not witnessed", "setAdmin", "neo.com", admin.ScriptHash())
|
||||
|
||||
// Must be witnessed by both owner and admin.
|
||||
cOwner.InvokeFail(t, "not witnessed by admin", "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash())
|
||||
cAdmin.InvokeFail(t, "not witnessed by owner", "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash())
|
||||
cc := c.WithSigner([]*wallet.Account{owner, admin})
|
||||
cc.Invoke(t, stackitem.Null{}, "setAdmin", "neo.com", admin.PrivateKey().GetScriptHash())
|
||||
cOwner.InvokeFail(t, "not witnessed by admin", "setAdmin", "neo.com", admin.ScriptHash())
|
||||
cAdmin.InvokeFail(t, "not witnessed by owner", "setAdmin", "neo.com", admin.ScriptHash())
|
||||
cc := c.WithSigners(owner, admin)
|
||||
cc.Invoke(t, stackitem.Null{}, "setAdmin", "neo.com", admin.ScriptHash())
|
||||
|
||||
t.Run("set and delete by admin", func(t *testing.T) {
|
||||
cAdmin.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.TXT), "sometext")
|
||||
|
@ -330,18 +329,18 @@ func TestTransfer(t *testing.T) {
|
|||
e := c.Executor
|
||||
|
||||
from := e.NewAccount(t)
|
||||
cFrom := c.WithSigner(from)
|
||||
cFrom := c.WithSigners(from)
|
||||
to := e.NewAccount(t)
|
||||
cTo := c.WithSigner(to)
|
||||
cTo := c.WithSigners(to)
|
||||
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||
cFrom.Invoke(t, true, "register", "neo.com", from.PrivateKey().GetScriptHash())
|
||||
cFrom.Invoke(t, true, "register", "neo.com", from.ScriptHash())
|
||||
cFrom.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
cFrom.InvokeFail(t, "token not found", "transfer", to.Contract.ScriptHash(), "not.exists", nil)
|
||||
c.Invoke(t, false, "transfer", to.Contract.ScriptHash(), "neo.com", nil)
|
||||
cFrom.Invoke(t, true, "transfer", to.Contract.ScriptHash(), "neo.com", nil)
|
||||
cFrom.InvokeFail(t, "token not found", "transfer", to.ScriptHash(), "not.exists", nil)
|
||||
c.Invoke(t, false, "transfer", to.ScriptHash(), "neo.com", nil)
|
||||
cFrom.Invoke(t, true, "transfer", to.ScriptHash(), "neo.com", nil)
|
||||
cFrom.Invoke(t, 1, "totalSupply")
|
||||
cFrom.Invoke(t, to.Contract.ScriptHash().BytesBE(), "ownerOf", "neo.com")
|
||||
cFrom.Invoke(t, to.ScriptHash().BytesBE(), "ownerOf", "neo.com")
|
||||
|
||||
// without onNEP11Transfer
|
||||
ctr := neotest.CompileSource(t, e.CommitteeHash,
|
||||
|
@ -368,16 +367,16 @@ func TestTokensOf(t *testing.T) {
|
|||
e := c.Executor
|
||||
|
||||
acc1 := e.NewAccount(t)
|
||||
cAcc1 := c.WithSigner(acc1)
|
||||
cAcc1 := c.WithSigners(acc1)
|
||||
acc2 := e.NewAccount(t)
|
||||
cAcc2 := c.WithSigner(acc2)
|
||||
cAcc2 := c.WithSigners(acc2)
|
||||
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||
cAcc1.Invoke(t, true, "register", "neo.com", acc1.PrivateKey().GetScriptHash())
|
||||
cAcc2.Invoke(t, true, "register", "nspcc.com", acc2.PrivateKey().GetScriptHash())
|
||||
cAcc1.Invoke(t, true, "register", "neo.com", acc1.ScriptHash())
|
||||
cAcc2.Invoke(t, true, "register", "nspcc.com", acc2.ScriptHash())
|
||||
|
||||
testTokensOf(t, c, [][]byte{[]byte("neo.com")}, acc1.Contract.ScriptHash().BytesBE())
|
||||
testTokensOf(t, c, [][]byte{[]byte("nspcc.com")}, acc2.Contract.ScriptHash().BytesBE())
|
||||
testTokensOf(t, c, [][]byte{[]byte("neo.com")}, acc1.ScriptHash().BytesBE())
|
||||
testTokensOf(t, c, [][]byte{[]byte("nspcc.com")}, acc2.ScriptHash().BytesBE())
|
||||
testTokensOf(t, c, [][]byte{[]byte("neo.com"), []byte("nspcc.com")})
|
||||
testTokensOf(t, c, [][]byte{}, util.Uint160{}.BytesBE()) // empty hash is a valid hash still
|
||||
}
|
||||
|
@ -408,14 +407,14 @@ func TestResolve(t *testing.T) {
|
|||
e := c.Executor
|
||||
|
||||
acc := e.NewAccount(t)
|
||||
cAcc := c.WithSigner(acc)
|
||||
cAcc := c.WithSigners(acc)
|
||||
|
||||
c.Invoke(t, stackitem.Null{}, "addRoot", "com")
|
||||
cAcc.Invoke(t, true, "register", "neo.com", acc.PrivateKey().GetScriptHash())
|
||||
cAcc.Invoke(t, true, "register", "neo.com", acc.ScriptHash())
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.A), "1.2.3.4")
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "neo.com", int64(nns.CNAME), "alias.com")
|
||||
|
||||
cAcc.Invoke(t, true, "register", "alias.com", acc.PrivateKey().GetScriptHash())
|
||||
cAcc.Invoke(t, true, "register", "alias.com", acc.ScriptHash())
|
||||
cAcc.Invoke(t, stackitem.Null{}, "setRecord", "alias.com", int64(nns.TXT), "sometxt")
|
||||
|
||||
c.Invoke(t, "1.2.3.4", "resolve", "neo.com", int64(nns.A))
|
||||
|
|
|
@ -19,7 +19,6 @@ import (
|
|||
"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/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -28,19 +27,20 @@ import (
|
|||
// Executor is a wrapper over chain state.
|
||||
type Executor struct {
|
||||
Chain blockchainer.Blockchainer
|
||||
Committee *wallet.Account
|
||||
Committee Signer
|
||||
CommitteeHash util.Uint160
|
||||
Contracts map[string]*Contract
|
||||
}
|
||||
|
||||
// NewExecutor creates new executor instance from provided blockchain and committee.
|
||||
func NewExecutor(t *testing.T, bc blockchainer.Blockchainer, committee *wallet.Account) *Executor {
|
||||
func NewExecutor(t *testing.T, bc blockchainer.Blockchainer, committee Signer) *Executor {
|
||||
require.Equal(t, 1, len(bc.GetConfig().StandbyCommittee))
|
||||
require.IsType(t, multiSigner{}, committee, "committee must be a multi-signer")
|
||||
|
||||
return &Executor{
|
||||
Chain: bc,
|
||||
Committee: committee,
|
||||
CommitteeHash: committee.Contract.ScriptHash(),
|
||||
CommitteeHash: committee.ScriptHash(),
|
||||
Contracts: make(map[string]*Contract),
|
||||
}
|
||||
}
|
||||
|
@ -74,57 +74,41 @@ func (e *Executor) NewUnsignedTx(t *testing.T, hash util.Uint160, method string,
|
|||
|
||||
// NewTx creates new transaction which invokes contract method.
|
||||
// Transaction is signed with signer.
|
||||
func (e *Executor) NewTx(t *testing.T, signer interface{},
|
||||
func (e *Executor) NewTx(t *testing.T, signers []Signer,
|
||||
hash util.Uint160, method string, args ...interface{}) *transaction.Transaction {
|
||||
tx := e.NewUnsignedTx(t, hash, method, args...)
|
||||
return e.SignTx(t, tx, -1, signer)
|
||||
return e.SignTx(t, tx, -1, signers...)
|
||||
}
|
||||
|
||||
// SignTx signs a transaction using provided signers.
|
||||
// signers can be either *wallet.Account or []*wallet.Account.
|
||||
func (e *Executor) SignTx(t *testing.T, tx *transaction.Transaction, sysFee int64, signers interface{}) *transaction.Transaction {
|
||||
switch s := signers.(type) {
|
||||
case *wallet.Account:
|
||||
func (e *Executor) SignTx(t *testing.T, tx *transaction.Transaction, sysFee int64, signers ...Signer) *transaction.Transaction {
|
||||
for _, acc := range signers {
|
||||
tx.Signers = append(tx.Signers, transaction.Signer{
|
||||
Account: s.Contract.ScriptHash(),
|
||||
Account: acc.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
})
|
||||
addNetworkFee(e.Chain, tx, s)
|
||||
addSystemFee(e.Chain, tx, sysFee)
|
||||
require.NoError(t, s.SignTx(e.Chain.GetConfig().Magic, tx))
|
||||
case []*wallet.Account:
|
||||
for _, acc := range s {
|
||||
tx.Signers = append(tx.Signers, transaction.Signer{
|
||||
Account: acc.Contract.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
})
|
||||
}
|
||||
for _, acc := range s {
|
||||
addNetworkFee(e.Chain, tx, acc)
|
||||
}
|
||||
addSystemFee(e.Chain, tx, sysFee)
|
||||
for _, acc := range s {
|
||||
require.NoError(t, acc.SignTx(e.Chain.GetConfig().Magic, tx))
|
||||
}
|
||||
default:
|
||||
panic("invalid signer")
|
||||
}
|
||||
addNetworkFee(e.Chain, tx, signers...)
|
||||
addSystemFee(e.Chain, tx, sysFee)
|
||||
|
||||
for _, acc := range signers {
|
||||
require.NoError(t, acc.SignTx(e.Chain.GetConfig().Magic, tx))
|
||||
}
|
||||
return tx
|
||||
}
|
||||
|
||||
// NewAccount returns new account holding 100.0 GAS. This method advances the chain
|
||||
// NewAccount returns new signer holding 100.0 GAS. This method advances the chain
|
||||
// by one block with a transfer transaction.
|
||||
func (e *Executor) NewAccount(t *testing.T) *wallet.Account {
|
||||
func (e *Executor) NewAccount(t *testing.T) Signer {
|
||||
acc, err := wallet.NewAccount()
|
||||
require.NoError(t, err)
|
||||
|
||||
tx := e.NewTx(t, e.Committee,
|
||||
tx := e.NewTx(t, []Signer{e.Committee},
|
||||
e.NativeHash(t, nativenames.Gas), "transfer",
|
||||
e.Committee.Contract.ScriptHash(), acc.Contract.ScriptHash(), int64(100_0000_0000), nil)
|
||||
e.Committee.ScriptHash(), acc.Contract.ScriptHash(), int64(100_0000_0000), nil)
|
||||
e.AddNewBlock(t, tx)
|
||||
e.CheckHalt(t, tx.Hash())
|
||||
return acc
|
||||
return NewSingleSigner(acc)
|
||||
}
|
||||
|
||||
// DeployContract compiles and deploys contract to bc.
|
||||
|
@ -174,7 +158,7 @@ func (e *Executor) NewDeployTx(t *testing.T, bc blockchainer.Blockchainer, c *Co
|
|||
tx.Nonce = nonce()
|
||||
tx.ValidUntilBlock = bc.BlockHeight() + 1
|
||||
tx.Signers = []transaction.Signer{{
|
||||
Account: e.Committee.Contract.ScriptHash(),
|
||||
Account: e.Committee.ScriptHash(),
|
||||
Scopes: transaction.Global,
|
||||
}}
|
||||
addNetworkFee(bc, tx, e.Committee)
|
||||
|
@ -191,12 +175,14 @@ func addSystemFee(bc blockchainer.Blockchainer, tx *transaction.Transaction, sys
|
|||
tx.SystemFee = v.GasConsumed()
|
||||
}
|
||||
|
||||
func addNetworkFee(bc blockchainer.Blockchainer, tx *transaction.Transaction, sender *wallet.Account) {
|
||||
func addNetworkFee(bc blockchainer.Blockchainer, tx *transaction.Transaction, signers ...Signer) {
|
||||
baseFee := bc.GetPolicer().GetBaseExecFee()
|
||||
size := io.GetVarSize(tx)
|
||||
netFee, sizeDelta := fee.Calculate(baseFee, sender.Contract.Script)
|
||||
tx.NetworkFee += netFee
|
||||
size += sizeDelta
|
||||
for _, sgr := range signers {
|
||||
netFee, sizeDelta := fee.Calculate(baseFee, sgr.Script())
|
||||
tx.NetworkFee += netFee
|
||||
size += sizeDelta
|
||||
}
|
||||
tx.NetworkFee += int64(size) * bc.FeePerByte()
|
||||
}
|
||||
|
||||
|
@ -205,9 +191,9 @@ func (e *Executor) NewUnsignedBlock(t *testing.T, txs ...*transaction.Transactio
|
|||
lastBlock := e.TopBlock(t)
|
||||
b := &block.Block{
|
||||
Header: block.Header{
|
||||
NextConsensus: e.Committee.Contract.ScriptHash(),
|
||||
NextConsensus: e.Committee.ScriptHash(),
|
||||
Script: transaction.Witness{
|
||||
VerificationScript: e.Committee.Contract.Script,
|
||||
VerificationScript: e.Committee.Script(),
|
||||
},
|
||||
Timestamp: lastBlock.Timestamp + 1,
|
||||
},
|
||||
|
@ -233,8 +219,8 @@ func (e *Executor) AddNewBlock(t *testing.T, txs ...*transaction.Transaction) *b
|
|||
|
||||
// SignBlock add validators signature to b.
|
||||
func (e *Executor) SignBlock(b *block.Block) *block.Block {
|
||||
sign := e.Committee.PrivateKey().SignHashable(uint32(e.Chain.GetConfig().Magic), b)
|
||||
b.Script.InvocationScript = append([]byte{byte(opcode.PUSHDATA1), 64}, sign...)
|
||||
invoc := e.Committee.SignHashable(uint32(e.Chain.GetConfig().Magic), b)
|
||||
b.Script.InvocationScript = invoc
|
||||
return b
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
@ -30,7 +31,7 @@ func init() {
|
|||
|
||||
// NewSingle creates new blockchain instance with a single validator and
|
||||
// setups cleanup functions.
|
||||
func NewSingle(t *testing.T) (*core.Blockchain, *wallet.Account) {
|
||||
func NewSingle(t *testing.T) (*core.Blockchain, neotest.Signer) {
|
||||
protoCfg := config.ProtocolConfiguration{
|
||||
Magic: netmode.UnitTestNet,
|
||||
SecondsPerBlock: 1,
|
||||
|
@ -46,5 +47,5 @@ func NewSingle(t *testing.T) (*core.Blockchain, *wallet.Account) {
|
|||
require.NoError(t, err)
|
||||
go bc.Run()
|
||||
t.Cleanup(bc.Close)
|
||||
return bc, committeeAcc
|
||||
return bc, neotest.NewMultiSigner(committeeAcc)
|
||||
}
|
||||
|
|
|
@ -14,8 +14,8 @@ import (
|
|||
// ContractInvoker is a client for specific contract.
|
||||
type ContractInvoker struct {
|
||||
*Executor
|
||||
Hash util.Uint160
|
||||
Signer interface{}
|
||||
Hash util.Uint160
|
||||
Signers []Signer
|
||||
}
|
||||
|
||||
// CommitteeInvoker creates new ContractInvoker for contract with hash h.
|
||||
|
@ -23,7 +23,7 @@ func (e *Executor) CommitteeInvoker(h util.Uint160) *ContractInvoker {
|
|||
return &ContractInvoker{
|
||||
Executor: e,
|
||||
Hash: h,
|
||||
Signer: e.Committee,
|
||||
Signers: []Signer{e.Committee},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,16 +39,16 @@ func (c *ContractInvoker) TestInvoke(t *testing.T, method string, args ...interf
|
|||
return v.Estack(), err
|
||||
}
|
||||
|
||||
// WithSigner creates new client with the provided signer.
|
||||
func (c *ContractInvoker) WithSigner(signer interface{}) *ContractInvoker {
|
||||
// WithSigners creates new client with the provided signer.
|
||||
func (c *ContractInvoker) WithSigners(signers ...Signer) *ContractInvoker {
|
||||
newC := *c
|
||||
newC.Signer = signer
|
||||
newC.Signers = signers
|
||||
return &newC
|
||||
}
|
||||
|
||||
// PrepareInvoke creates new invocation transaction.
|
||||
func (c *ContractInvoker) PrepareInvoke(t *testing.T, method string, args ...interface{}) *transaction.Transaction {
|
||||
return c.Executor.NewTx(t, c.Signer, c.Hash, method, args...)
|
||||
return c.Executor.NewTx(t, c.Signers, c.Hash, method, args...)
|
||||
}
|
||||
|
||||
// PrepareInvokeNoSign creates new unsigned invocation transaction.
|
||||
|
@ -68,7 +68,7 @@ func (c *ContractInvoker) Invoke(t *testing.T, result interface{}, method string
|
|||
// InvokeWithFeeFail is like InvokeFail but sets custom system fee for the transaction.
|
||||
func (c *ContractInvoker) InvokeWithFeeFail(t *testing.T, message string, sysFee int64, method string, args ...interface{}) util.Uint256 {
|
||||
tx := c.PrepareInvokeNoSign(t, method, args...)
|
||||
c.Executor.SignTx(t, tx, sysFee, c.Signer)
|
||||
c.Executor.SignTx(t, tx, sysFee, c.Signers...)
|
||||
c.AddNewBlock(t, tx)
|
||||
c.CheckFault(t, tx.Hash(), message)
|
||||
return tx.Hash()
|
||||
|
|
124
pkg/neotest/signer.go
Normal file
124
pkg/neotest/signer.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
package neotest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"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/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
||||
// Signer is a generic interface which can be either simple- or multi-signature signer.
|
||||
type Signer interface {
|
||||
// ScriptHash returns signer script hash.
|
||||
Script() []byte
|
||||
// Script returns signer verification script.
|
||||
ScriptHash() util.Uint160
|
||||
// SignHashable returns invocation script for signing an item.
|
||||
SignHashable(uint32, hash.Hashable) []byte
|
||||
// SignTx signs a transaction.
|
||||
SignTx(netmode.Magic, *transaction.Transaction) error
|
||||
}
|
||||
|
||||
// signer represents simple-signature signer.
|
||||
type signer wallet.Account
|
||||
|
||||
// multiSigner represents single multi-signature signer consisting of provided accounts.
|
||||
type multiSigner []*wallet.Account
|
||||
|
||||
// NewSingleSigner returns multi-signature signer for the provided account.
|
||||
// It must contain exactly as many accounts as needed to sign the script.
|
||||
func NewSingleSigner(acc *wallet.Account) Signer {
|
||||
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), 64},
|
||||
(*wallet.Account)(s).PrivateKey().SignHashable(magic, item)...)
|
||||
}
|
||||
|
||||
// SignTx implements Signer interface.
|
||||
func (s *signer) SignTx(magic netmode.Magic, tx *transaction.Transaction) error {
|
||||
return (*wallet.Account)(s).SignTx(magic, tx)
|
||||
}
|
||||
|
||||
// NewMultiSigner returns 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) Signer {
|
||||
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)))
|
||||
}
|
||||
for _, acc := range accs {
|
||||
if !bytes.Equal(script, acc.Contract.Script) {
|
||||
panic("all accounts must have equal verification script")
|
||||
}
|
||||
}
|
||||
|
||||
return multiSigner(accs[:m])
|
||||
}
|
||||
|
||||
// ScriptHash implements Signer interface.
|
||||
func (m multiSigner) ScriptHash() util.Uint160 {
|
||||
return m[0].Contract.ScriptHash()
|
||||
}
|
||||
|
||||
// Script implements Signer interface.
|
||||
func (m multiSigner) Script() []byte {
|
||||
return m[0].Contract.Script
|
||||
}
|
||||
|
||||
// SignHashable implements Signer interface.
|
||||
func (m multiSigner) SignHashable(magic uint32, item hash.Hashable) []byte {
|
||||
var script []byte
|
||||
for _, acc := range m {
|
||||
sign := acc.PrivateKey().SignHashable(magic, item)
|
||||
script = append(script, byte(opcode.PUSHDATA1), 64)
|
||||
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
|
||||
}
|
Loading…
Reference in a new issue