/
tx.go
461 lines (359 loc) · 9.87 KB
/
tx.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
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
package ngtypes
import (
"bytes"
"encoding/hex"
"math/big"
"github.com/c0mm4nd/rlp"
"github.com/cbergoon/merkletree"
"github.com/mr-tron/base58"
"github.com/ngchain/go-schnorr"
"github.com/ngchain/secp256k1"
"github.com/pkg/errors"
"golang.org/x/crypto/sha3"
"github.com/ngchain/ngbiz/utils"
)
type TxType uint8
const (
InvalidTx TxType = iota
GenerateTx
RegisterTx
DestroyTx // renamed from logout
TransactTx
AppendTx // add content to the tail of contract
DeleteTx
LockTx // TODO: cannot assign nor append, but can run vm
UnlockTx // TODO: disable vm, but enable assign and append
)
// Tx is the basic transaction, or operation, in ngchain network
type Tx struct {
Network Network
Type TxType
Height uint64 // lock the tx on the specific height, rather than the hash, to make the tx can act on forking
Convener AccountNum
Participants []Address
Fee *big.Int
Values []*big.Int // each value is a free-length slice
Extra []byte
Sign []byte `rlp:"optional"`
}
// NewTx is the default constructor for ngtypes.Tx
func NewTx(network Network, txType TxType, height uint64, convener AccountNum, participants []Address, values []*big.Int, fee *big.Int,
extraData, sign []byte) *Tx {
if participants == nil {
participants = []Address{}
}
if values == nil {
values = []*big.Int{}
}
if fee == nil {
fee = big.NewInt(0)
}
if extraData == nil {
extraData = []byte{}
}
if sign == nil {
sign = []byte{}
}
tx := &Tx{
Network: network,
Type: txType,
Height: height,
Convener: convener,
Participants: participants,
Fee: fee,
Values: values,
Extra: extraData,
Sign: sign,
}
return tx
}
// NewUnsignedTx will return an unsigned tx, must using Signature().
func NewUnsignedTx(network Network, txType TxType, height uint64, convener AccountNum, participants []Address, values []*big.Int, fee *big.Int,
extraData []byte) *Tx {
return NewTx(network, txType, height, convener, participants, values, fee, extraData, nil)
}
// IsSigned will return whether the op has been signed.
func (x *Tx) IsSigned() bool {
return x.Sign != nil
}
// Verify helps verify the transaction whether signed by the public key owner.
func (x *Tx) Verify(publicKey secp256k1.PublicKey) error {
if x.Sign == nil {
return ErrTxUnsigned
}
if publicKey.X == nil || publicKey.Y == nil {
return ErrInvalidPublicKey
}
hash := [32]byte{}
copy(hash[:], x.GetUnsignedHash())
var signature [64]byte
copy(signature[:], x.Sign)
if len(x.Extra) > TxMaxExtraSize {
return ErrTxExtraExcess
}
var key [33]byte
copy(key[:], publicKey.SerializeCompressed())
if ok, err := schnorr.Verify(key, hash, signature); !ok {
if err != nil {
return err
}
return ErrTxSignInvalid
}
return nil
}
// BS58 is a tx's Readable Raw in string.
func (x *Tx) BS58() string {
b, err := rlp.EncodeToBytes(x)
if err != nil {
log.Error(err)
}
return base58.FastBase58Encoding(b)
}
// ID is a tx's Readable ID(hash) in string.
func (x *Tx) ID() string {
return hex.EncodeToString(x.GetHash())
}
// GetHash mainly for calculating the tire root of txs and sign tx.
// The returned hash is sha3_256(tx_with_sign)
func (x *Tx) GetHash() []byte {
hash, err := x.CalculateHash()
if err != nil {
panic(err)
}
return hash
}
// GetUnsignedHash mainly for signing and verifying.
// The returned hash is sha3_256(tx_without_sign)
func (x *Tx) GetUnsignedHash() []byte {
sign := x.Sign
x.Sign = nil
raw, err := rlp.EncodeToBytes(x)
if err != nil {
panic(err)
}
x.Sign = sign
hash := sha3.Sum256(raw)
return hash[:]
}
// CalculateHash mainly for calculating the tire root of txs and sign tx.
func (x *Tx) CalculateHash() ([]byte, error) {
raw, err := rlp.EncodeToBytes(x)
if err != nil {
return nil, err
}
hash := sha3.Sum256(raw)
return hash[:], nil
}
// Equals mainly for calculating the tire root of txs.
func (x *Tx) Equals(other merkletree.Content) (bool, error) {
tx, ok := other.(*Tx)
if !ok {
panic("comparing with non-tx struct")
}
if x.Network != tx.Network {
return false, nil
}
if x.Convener != tx.Convener {
return false, nil
}
if x.Height != tx.Height {
return false, nil
}
if len(x.Participants) != len(tx.Participants) {
return false, nil
}
for i := range x.Participants {
if !bytes.Equal(x.Participants[i], tx.Participants[i]) {
return false, nil
}
}
if len(x.Values) != len(tx.Values) {
return false, nil
}
for i := range x.Values {
if x.Values[i].Cmp(tx.Values[i]) != 0 {
return false, nil
}
}
if x.Fee.Cmp(tx.Fee) != 0 {
return false, nil
}
if !bytes.Equal(x.Sign, tx.Sign) {
return false, nil
}
if !bytes.Equal(x.Extra, tx.Extra) {
return false, nil
}
return true, nil
}
// CheckGenerate does a self check for generate tx
func (x *Tx) CheckGenerate(blockHeight uint64) error {
if x == nil {
return ErrBlockNoHeader
}
if x.Convener != 0 {
return errors.Wrap(ErrTxConvenerInvalid, "generate's convener should be 0")
}
if len(x.Values) != len(x.Participants) {
return errors.Wrap(ErrTxParticipantsInvalid, "generate should have same len with participants")
}
if !(x.TotalExpenditure().Cmp(GetBlockReward(blockHeight)) == 0) {
return errors.Wrapf(ErrRewardInvalid, "expect %s but reward is %s", GetBlockReward(blockHeight), x.TotalExpenditure())
}
if x.Fee.Cmp(big.NewInt(0)) != 0 {
return errors.Wrap(ErrTxFeeInvalid, "generate's fee should be ZERO")
}
publicKey := x.Participants[0].PubKey()
err := x.Verify(publicKey)
if err != nil {
return err
}
return nil
}
// CheckRegister does a self check for register tx
func (x *Tx) CheckRegister() error {
if x == nil {
return ErrTxNoHeader
}
if x.Convener != 0o1 {
return errors.Wrap(ErrTxConvenerInvalid, "register's convener should be 1")
}
if len(x.Participants) != 1 {
return errors.Wrap(ErrTxParticipantsInvalid, "register should have only one participant")
}
if len(x.Values) != 1 {
return errors.Wrap(ErrTxValuesInvalid, "register should have only one value")
}
if x.Values[0].Cmp(big.NewInt(0)) != 0 {
return errors.Wrap(ErrTxValuesInvalid, "register should have only one value, the amount of which is 0")
}
if x.Fee.Cmp(RegisterFee) < 0 {
return errors.Wrap(ErrTxFeeInvalid, "register should have at least 10NG(one block reward) fee")
}
if len(x.Extra) != 1<<3 {
return errors.Wrap(ErrTxExtraInvalid, "register should have uint64 little-endian bytes as extra")
}
publicKey := x.Participants[0].PubKey()
err := x.Verify(publicKey)
if err != nil {
return err
}
return nil
}
// CheckDestroy does a self check for destroy tx
func (x *Tx) CheckDestroy(publicKey secp256k1.PublicKey) error {
if x == nil {
return ErrTxNoHeader
}
if len(x.Participants) != 0 {
return errors.Wrap(ErrTxParticipantsInvalid, "destroy should have NO participant")
}
if x.Convener == 0 {
return errors.Wrap(ErrTxConvenerInvalid, "destroy's convener should NOT be 0")
}
if len(x.Participants) != 0 {
return errors.Wrap(ErrTxParticipantsInvalid, "destroy should have no participants")
}
if len(x.Values) != 0 {
return errors.Wrap(ErrTxValuesInvalid, "destroy should have NO value")
}
err := x.Verify(publicKey)
if err != nil {
return err
}
// RULE: destroy should takes owner's pubKey in Extra for verify and recording to make Tx reversible
publicKeyFromExtra := utils.Bytes2PublicKey(x.Extra)
if !publicKey.IsEqual(&publicKeyFromExtra) {
return errors.Wrap(ErrTxExtraInvalid, "invalid raw bytes public key in destroy's Extra field")
}
return nil
}
// CheckTransaction does a self check for normal transaction tx
func (x *Tx) CheckTransaction(publicKey secp256k1.PublicKey) error {
if x == nil {
return ErrTxNoHeader
}
if x.Convener == 0 {
return errors.Wrap(ErrTxConvenerInvalid, "transact's convener should NOT be 0")
}
if len(x.Values) != len(x.Participants) {
return errors.Wrap(ErrTxParticipantsInvalid, "transact should have same len with participants")
}
err := x.Verify(publicKey)
if err != nil {
return err
}
return nil
}
// CheckAppend does a self check for append tx
func (x *Tx) CheckAppend(key secp256k1.PublicKey) error {
if x == nil {
return ErrTxNoHeader
}
if x.Convener == 0 {
return errors.Wrap(ErrTxConvenerInvalid, "append's convener should NOT be 0")
}
if len(x.Participants) != 0 {
return errors.Wrap(ErrTxParticipantsInvalid, "append should have NO participant")
}
if len(x.Values) != 0 {
return errors.Wrap(ErrTxValuesInvalid, "append should have NO value")
}
err := x.Verify(key)
if err != nil {
return err
}
// check this on chain
// var appendExtra AppendExtra
// err = rlp.DecodeBytes(x.Extra, &appendExtra)
// if err != nil {
// return err
// }
return nil
}
// CheckDelete does a self check for delete tx
func (x *Tx) CheckDelete(publicKey secp256k1.PublicKey) error {
if x == nil {
return ErrTxNoHeader
}
if x.Convener == 0 {
return errors.Wrap(ErrTxConvenerInvalid, "deleteTx convener should NOT be 0")
}
if len(x.Participants) != 0 {
return errors.Wrap(ErrTxParticipantsInvalid, "deleteTx should have NO participant")
}
if len(x.Values) != 0 {
return errors.Wrap(ErrTxValuesInvalid, "deleteTx should have NO value")
}
err := x.Verify(publicKey)
if err != nil {
return err
}
return nil
}
// Signature will re-sign the Tx with private key.
func (x *Tx) Signature(privateKeys ...*secp256k1.PrivateKey) (err error) {
ds := make([]*big.Int, len(privateKeys))
for i := range privateKeys {
ds[i] = privateKeys[i].D
}
hash := [32]byte{}
copy(hash[:], x.GetUnsignedHash())
sign, err := schnorr.AggregateSignatures(ds, hash)
if err != nil {
panic(err)
}
x.Sign = sign[:]
return
}
func (x *Tx) ManuallySetSignature(sign []byte) {
x.Sign = sign
}
// TotalExpenditure helps calculate the total expenditure which the tx caller should pay
func (x *Tx) TotalExpenditure() *big.Int {
total := big.NewInt(0)
for i := range x.Values {
total.Add(total, x.Values[i])
}
return new(big.Int).Add(x.Fee, total)
}