-
Notifications
You must be signed in to change notification settings - Fork 7
/
registry.go
151 lines (131 loc) · 4.21 KB
/
registry.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
package rhp
import (
"bytes"
"errors"
"fmt"
"go.sia.tech/core/types"
)
const (
// EntryTypeArbitrary is a registry value where all data is arbitrary.
EntryTypeArbitrary = iota + 1
// EntryTypePubKey is a registry value where the first 20 bytes of data
// corresponds to the hash of a host's public key.
EntryTypePubKey
)
const (
// MaxValueDataSize is the maximum size of a Value's Data
// field.
MaxValueDataSize = 113
)
// A RegistryKey uniquely identifies a value in the host's registry.
type RegistryKey struct {
PublicKey types.PublicKey
Tweak types.Hash256
}
// A RegistryValue is a value associated with a key and a tweak in a host's
// registry.
type RegistryValue struct {
Data []byte
Revision uint64
Type uint8
Signature types.Signature
}
// A RegistryEntry contains the data stored by a host for each registry value.
type RegistryEntry struct {
RegistryKey
RegistryValue
}
// Hash returns the hash of the key.
func (rk *RegistryKey) Hash() types.Hash256 {
h := types.NewHasher()
rk.PublicKey.UnlockKey().EncodeTo(h.E)
rk.Tweak.EncodeTo(h.E)
return h.Sum()
}
// Hash returns the hash used for signing the entry.
func (re *RegistryEntry) Hash() types.Hash256 {
h := types.NewHasher()
re.Tweak.EncodeTo(h.E)
h.E.WriteBytes(re.Data)
h.E.WriteUint64(re.Revision)
if re.Type == EntryTypePubKey {
h.E.WriteUint8(re.Type)
}
return h.Sum()
}
// Work returns the work of an entry.
func (re *RegistryEntry) Work() types.Hash256 {
data := re.Data
if re.Type == EntryTypePubKey {
data = re.Data[20:]
}
h := types.NewHasher()
re.Tweak.EncodeTo(h.E)
h.E.WriteBytes(data)
h.E.WriteUint64(re.Revision)
return h.Sum()
}
// CompareRegistryWork compares the work of two registry entries.
func CompareRegistryWork(r1, r2 RegistryEntry) int {
r1w, r2w := r1.Work(), r2.Work()
return bytes.Compare(r1w[:], r2w[:])
}
// RegistryHostID returns the ID hash of the host for primary registry entries.
func RegistryHostID(pk types.PublicKey) types.Hash256 {
h := types.NewHasher()
pk.UnlockKey().EncodeTo(h.E)
return h.Sum()
}
// ValidateRegistryEntry validates the fields of a registry entry.
func ValidateRegistryEntry(re RegistryEntry) (err error) {
switch re.Type {
case EntryTypeArbitrary:
// no extra validation required
case EntryTypePubKey:
// pub key entries have the first 20 bytes of the host's pub key hash
// prefixed to the data.
if len(re.Data) < 20 {
return errors.New("expected host public key hash")
}
default:
return fmt.Errorf("invalid registry value type: %d", re.Type)
}
if !re.PublicKey.VerifyHash(re.Hash(), re.Signature) {
return errors.New("invalid signature")
} else if len(re.Data) > MaxValueDataSize {
return fmt.Errorf("data size exceeds maximum: %d > %d", len(re.Data), MaxValueDataSize)
}
return nil
}
// ValidateRegistryUpdate validates a registry update against the current entry.
// An updated registry entry must have a greater revision number, more work, or
// be replacing a non-primary registry entry.
func ValidateRegistryUpdate(old, update RegistryEntry, hostID types.Hash256) error {
// if the new revision is greater than the current revision, the update is
// valid.
if update.Revision > old.Revision {
return nil
} else if update.Revision < old.Revision {
return errors.New("update revision must be greater than current revision")
}
// if the revision number is the same, but the work is greater, the update
// is valid.
if w := CompareRegistryWork(update, old); w > 0 {
return nil
} else if w < 0 {
return errors.New("update must have greater work or greater revision number than current entry")
}
// if the update entry is an arbitrary value entry, the update is invalid.
if update.Type == EntryTypeArbitrary {
return errors.New("update must be a primary entry or have a greater revision number")
}
// if the updated entry is not a primary entry, it is invalid.
if !bytes.Equal(update.Data[:20], hostID[:20]) {
return errors.New("update must be a primary entry or have a greater revision number")
}
// if the update and current entry are both primary, the update is invalid
if old.Type == EntryTypePubKey && bytes.Equal(old.Data[:20], hostID[:20]) {
return errors.New("update revision must be greater than current revision")
}
return nil
}