-
Notifications
You must be signed in to change notification settings - Fork 79
/
signer.go
180 lines (156 loc) · 5.4 KB
/
signer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
package neotest
import (
"bytes"
"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/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"
"github.com/stretchr/testify/require"
)
// Signer is a generic interface which can be either a simple- or multi-signature signer.
type Signer interface {
// ScriptHash returns a signer script hash.
Script() []byte
// Script returns a signer verification script.
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
}
// 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 returns a 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) 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), 64},
(*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), 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
}
// 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")
}
}