-
Notifications
You must be signed in to change notification settings - Fork 151
/
validator.go
247 lines (216 loc) · 8.43 KB
/
validator.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
package migrator
import (
"bytes"
"crypto"
"encoding"
"fmt"
"math"
"github.com/pkg/errors"
"github.com/iotaledger/hornet/pkg/common"
"github.com/iotaledger/hornet/pkg/whiteflag"
"github.com/iotaledger/iota.go/address"
"github.com/iotaledger/iota.go/api"
"github.com/iotaledger/iota.go/bundle"
legacy "github.com/iotaledger/iota.go/consts"
"github.com/iotaledger/iota.go/encoding/b1t6"
"github.com/iotaledger/iota.go/encoding/t5b1"
"github.com/iotaledger/iota.go/merkle"
"github.com/iotaledger/iota.go/transaction"
"github.com/iotaledger/iota.go/trinary"
iotago "github.com/iotaledger/iota.go/v2"
// import implementation
_ "golang.org/x/crypto/blake2b"
)
// ErrEmptyBundle is returned when a bundle contains no transaction.
var ErrEmptyBundle = errors.New("empty bundle")
var hasher = whiteflag.NewHasher(crypto.BLAKE2b_512)
// LegacyAPI defines the calls of the legacy API that are used.
type LegacyAPI interface {
GetNodeInfo() (*api.GetNodeInfoResponse, error)
GetWhiteFlagConfirmation(milestoneIndex uint32) (*api.WhiteFlagConfirmation, error)
}
// Validator takes care of fetching and validating white-flag confirmation data from legacy nodes
// and wrapping them into receipts.
type Validator struct {
api LegacyAPI
coordinatorAddress trinary.Hash
coordinatorMerkleTreeDepth int
}
// NewValidator creates a new Validator.
func NewValidator(api LegacyAPI, coordinatorAddress trinary.Hash, coordinatorMerkleTreeDepth int) *Validator {
return &Validator{
api: api,
coordinatorAddress: coordinatorAddress,
coordinatorMerkleTreeDepth: coordinatorMerkleTreeDepth,
}
}
// QueryMigratedFunds queries the legacy network for the white-flag confirmation data for the given milestone
// index, verifies the signatures of the milestone and included bundles and then compiles a slice of migrated fund entries.
func (m *Validator) QueryMigratedFunds(milestoneIndex uint32) ([]*iotago.MigratedFundsEntry, error) {
confirmation, err := m.api.GetWhiteFlagConfirmation(milestoneIndex)
if err != nil {
return nil, common.SoftError(fmt.Errorf("API call failed: %w", err))
}
included, err := m.validateConfirmation(confirmation, milestoneIndex)
if err != nil {
return nil, common.CriticalError(fmt.Errorf("invalid confirmation data: %w", err))
}
migrated := make([]*iotago.MigratedFundsEntry, 0, len(included))
for i := range included {
output := included[i][0]
// don't need to check the error here because if it wouldn't be a migration address,
// it wouldn't have passed 'validateConfirmation' in the first place
edAddr, _ := address.ParseMigrationAddress(output.Address)
entry := &iotago.MigratedFundsEntry{
Address: (*iotago.Ed25519Address)(&edAddr),
Deposit: uint64(output.Value),
}
copy(entry.TailTransactionHash[:], t5b1.EncodeTrytes(bundle.TailTransactionHash(included[i])))
migrated = append(migrated, entry)
}
return migrated, nil
}
// QueryNextMigratedFunds queries the next existing migrations starting from milestone index startIndex.
// It returns the migrations as well as milestone index that confirmed those migrations.
// If there are currently no more migrations, it returns the latest milestone index that was checked.
func (m *Validator) QueryNextMigratedFunds(startIndex uint32) (uint32, []*iotago.MigratedFundsEntry, error) {
latestIndex, err := m.queryLatestMilestoneIndex()
if err != nil {
return 0, nil, common.SoftError(fmt.Errorf("failed to get node info: %w", err))
}
for index := startIndex; index <= latestIndex; index++ {
migrated, err := m.QueryMigratedFunds(index)
if err != nil {
return 0, nil, fmt.Errorf("failed to query migration funds: %w", err)
}
if len(migrated) > 0 {
return index, migrated, nil
}
}
return latestIndex, nil, nil
}
// queryLatestMilestoneIndex uses getNodeInfo API call to query the index of the latest milestone.
func (m *Validator) queryLatestMilestoneIndex() (uint32, error) {
info, err := m.api.GetNodeInfo()
if err != nil {
return 0, err
}
index := info.LatestSolidSubtangleMilestoneIndex
// do some sanity checks
if index < 0 || index >= math.MaxUint32 {
return 0, fmt.Errorf("invalid milestone index in response: %d", index)
}
return uint32(index), nil
}
// validateMilestoneBundle performs syntactic validation of the milestone and checks whether it has the correct index.
func (m *Validator) validateMilestoneBundle(ms bundle.Bundle, msIndex uint32) error {
// since in a milestone bundle only the (complete) head is signed, there is no need to validate other transactions
head := ms[len(ms)-1]
tag := trinary.IntToTrytes(int64(msIndex), legacy.TagTrinarySize/legacy.TritsPerTryte)
lastIndex := uint64(len(ms) - 1)
// the address must match the configured address
if head.Address != m.coordinatorAddress {
return legacy.ErrInvalidAddress
}
// the milestone must be a 0-value transaction
if head.Value != 0 {
return legacy.ErrInvalidValue
}
// the tag must match the milestone index
if head.ObsoleteTag != tag || head.Tag != tag {
return legacy.ErrInvalidTag
}
// the head transaction must indeed be the last transaction in the bundle
if head.CurrentIndex != lastIndex || head.LastIndex != lastIndex {
return legacy.ErrInvalidIndex
}
return nil
}
// validateMilestoneSignature validates the signature of the given milestone bundle.
func (m *Validator) validateMilestoneSignature(ms bundle.Bundle) error {
head := ms[len(ms)-1]
msData := head.SignatureMessageFragment
msIndex := uint32(trinary.TrytesToInt(head.Tag))
var auditPath []trinary.Trytes
for i := 0; i < m.coordinatorMerkleTreeDepth; i++ {
auditPath = append(auditPath, msData[i*legacy.HashTrytesSize:(i+1)*legacy.HashTrytesSize])
}
var fragments []trinary.Trytes
for i := 0; i < len(ms)-1; i++ {
fragments = append(fragments, ms[i].SignatureMessageFragment)
}
valid, err := merkle.ValidateSignatureFragments(m.coordinatorAddress, msIndex, auditPath, fragments, head.Hash)
if err != nil {
return fmt.Errorf("failed to validate signature: %w", err)
}
if !valid {
return legacy.ErrInvalidSignature
}
return nil
}
// whiteFlagMerkleTreeHash returns the Merkle tree root of the included state-mutating transactions.
func (m *Validator) whiteFlagMerkleTreeHash(ms bundle.Bundle) ([]byte, error) {
head := ms[len(ms)-1]
data := head.SignatureMessageFragment[m.coordinatorMerkleTreeDepth*legacy.HashTrytesSize:]
trytesLen := b1t6.EncodedLen(hasher.Size()) / legacy.TritsPerTryte
hash, err := b1t6.DecodeTrytes(data[:trytesLen])
if err != nil {
return nil, err
}
return hash, nil
}
type trinaryHash trinary.Hash
func (h trinaryHash) MarshalBinary() ([]byte, error) {
return t5b1.EncodeTrytes(trinary.Trytes(h)), nil
}
func asBundle(rawTrytes []trinary.Trytes) (bundle.Bundle, error) {
if len(rawTrytes) == 0 {
return nil, ErrEmptyBundle
}
txs, err := transaction.AsTransactionObjects(rawTrytes, nil)
if err != nil {
return nil, err
}
// validate the bundle, but also accept non-migration milestone bundles
if err := bundle.ValidBundle(txs, false); err != nil {
return nil, err
}
return txs, nil
}
func (m *Validator) validateConfirmation(confirmation *api.WhiteFlagConfirmation, msIndex uint32) ([]bundle.Bundle, error) {
ms, err := asBundle(confirmation.MilestoneBundle)
if err != nil {
return nil, fmt.Errorf("failed to parse milestone bundle: %w", err)
}
if err := m.validateMilestoneBundle(ms, msIndex); err != nil {
return nil, fmt.Errorf("invalid milestone bundle: %w", err)
}
if err := m.validateMilestoneSignature(ms); err != nil {
return nil, fmt.Errorf("invalid milestone signature: %w", err)
}
var includedTails []encoding.BinaryMarshaler
var includedBundles []bundle.Bundle
for i, rawTrytes := range confirmation.IncludedBundles {
bndl, err := asBundle(rawTrytes)
if err != nil {
return nil, fmt.Errorf("failed to parse included bundle %d: %w", i, err)
}
if err := bundle.ValidBundle(bndl, true); err != nil {
return nil, fmt.Errorf("invalid included bundle %d: %w", i, err)
}
includedBundles = append(includedBundles, bndl)
includedTails = append(includedTails, trinaryHash(bundle.TailTransactionHash(bndl)))
}
msMerkleHash, err := m.whiteFlagMerkleTreeHash(ms)
if err != nil {
return nil, fmt.Errorf("failed to parse Merkle tree hash: %w", err)
}
merkleHash, err := hasher.Hash(includedTails)
if err != nil {
return nil, fmt.Errorf("failed to compute Merkle tree hash: %w", err)
}
if !bytes.Equal(msMerkleHash, merkleHash) {
return nil, fmt.Errorf("invalid MerkleTreeHash %s", merkleHash)
}
return includedBundles, nil
}