/
commit.go
176 lines (147 loc) · 5.73 KB
/
commit.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
package types
import (
"bytes"
"encoding/binary"
"fmt"
"io"
"unicode/utf8"
"github.com/ava-labs/avalanchego/utils/cb58"
"github.com/ethereum/go-ethereum/crypto"
)
type Commitment [32]byte
func CommitmentFromUint256(n *U256) (Commitment, error) {
var bytes [32]byte
bigEndian := n.Bytes()
if len(bigEndian) > 32 {
return Commitment{}, fmt.Errorf("integer out of range for U256 (%d)", n)
}
// `n` might have fewer than 32 bytes, if the commitment starts with one or more zeros. Pad out
// to 32 bytes exactly, adding zeros at the beginning to be consistent with big-endian byte
// order.
if len(bigEndian) < 32 {
zeros := make([]byte, 32-len(bigEndian))
bigEndian = append(zeros, bigEndian...)
}
for i, b := range bigEndian {
// Bytes() returns the bytes in big endian order and we need a 32 byte array so we populate it this way
bytes[i] = b
}
return bytes, nil
}
func (c Commitment) Uint256() *U256 {
return NewU256().SetBytes(c)
}
func (c Commitment) Equals(other Commitment) bool {
return bytes.Equal(c[:], other[:])
}
func (c Commitment) String() string {
// We assume that the maximum size of a byte slice that
// can be stringified is at least the length of an ID
s, _ := cb58.Encode(c[:])
return s
}
type RawCommitmentBuilder struct {
hasher crypto.KeccakState
}
func NewRawCommitmentBuilder(name string) *RawCommitmentBuilder {
b := new(RawCommitmentBuilder)
b.hasher = crypto.NewKeccakState()
return b.ConstantString(name)
}
// Append a constant string to the running hash.
//
// WARNING: The string `s` must be a constant. This function does not encode the length of `s` in
// the hash, which can lead to domain collisions when different strings with different lengths are
// used depending on the input object.
func (b *RawCommitmentBuilder) ConstantString(s string) *RawCommitmentBuilder {
// The commitment scheme is only designed to work with UTF-8 strings.
if !utf8.Valid([]byte(s)) {
panic(fmt.Sprintf("ConstantString must only be called with valid UTF-8 strings: %v", s))
}
if _, err := io.WriteString(b.hasher, s); err != nil {
panic(fmt.Sprintf("KeccakState Writer is not supposed to fail, but it did: %v", err))
}
// To denote the end of the string and act as a domain separator, include a byte sequence which
// can never appear in a valid UTF-8 string.
invalidUtf8 := []byte{0xC0, 0x7F}
return b.FixedSizeBytes(invalidUtf8)
}
// Include a named field of another committable type.
func (b *RawCommitmentBuilder) Field(f string, c Commitment) *RawCommitmentBuilder {
return b.ConstantString(f).FixedSizeBytes(c[:])
}
func (b *RawCommitmentBuilder) OptionalField(f string, c *Commitment) *RawCommitmentBuilder {
b.ConstantString(f)
// Encode a 0 or 1 to separate the nil domain from the non-nil domain.
if c == nil {
b.Uint64(0)
} else {
b.Uint64(1)
b.FixedSizeBytes((*c)[:])
}
return b
}
// Include a named field of type `uint256` in the hash.
func (b *RawCommitmentBuilder) Uint256Field(f string, n *U256) *RawCommitmentBuilder {
return b.ConstantString(f).Uint256(n)
}
// Include a value of type `uint256` in the hash.
func (b *RawCommitmentBuilder) Uint256(n *U256) *RawCommitmentBuilder {
bytes := make([]byte, 32)
n.FillBytes(bytes)
return b.FixedSizeBytes(bytes)
}
// Include a named field of type `uint64` in the hash.
func (b *RawCommitmentBuilder) Uint64Field(f string, n uint64) *RawCommitmentBuilder {
return b.ConstantString(f).Uint64(n)
}
// Include a value of type `uint64` in the hash.
func (b *RawCommitmentBuilder) Uint64(n uint64) *RawCommitmentBuilder {
bytes := make([]byte, 8)
binary.BigEndian.PutUint64(bytes, n)
return b.FixedSizeBytes(bytes)
}
// Include a named field of fixed length in the hash.
//
// WARNING: Go's type system cannot express the requirement that `bytes` is a fixed size array of
// any size. The best we can do is take a dynamically sized slice. However, this function uses a
// fixed-size encoding; namely, it does not encode the length of `bytes` in the hash, which can lead
// to domain collisions when this function is called with a slice which can have different lengths
// depending on the input object.
//
// The caller must ensure that this function is only used with slices whose length is statically
// determined by the type being committed to.
func (b *RawCommitmentBuilder) FixedSizeField(f string, bytes Bytes) *RawCommitmentBuilder {
return b.ConstantString(f).FixedSizeBytes(bytes)
}
// Append a fixed size byte array to the running hash.
//
// WARNING: Go's type system cannot express the requirement that `bytes` is a fixed size array of
// any size. The best we can do is take a dynamically sized slice. However, this function uses a
// fixed-size encoding; namely, it does not encode the length of `bytes` in the hash, which can lead
// to domain collisions when this function is called with a slice which can have different lengths
// depending on the input object.
//
// The caller must ensure that this function is only used with slices whose length is statically
// determined by the type being committed to.
func (b *RawCommitmentBuilder) FixedSizeBytes(bytes Bytes) *RawCommitmentBuilder {
b.hasher.Write(bytes)
return b
}
// Include a named field of dynamic length in the hash.
func (b *RawCommitmentBuilder) VarSizeField(f string, bytes Bytes) *RawCommitmentBuilder {
return b.ConstantString(f).VarSizeBytes(bytes)
}
// Include a byte array whose length can be dynamic to the running hash.
func (b *RawCommitmentBuilder) VarSizeBytes(bytes Bytes) *RawCommitmentBuilder {
// First commit to the length, to prevent length extension and domain collision attacks.
b.Uint64(uint64(len(bytes)))
b.hasher.Write(bytes)
return b
}
func (b *RawCommitmentBuilder) Finalize() Commitment {
var comm Commitment
bytes := b.hasher.Sum(nil)
copy(comm[:], bytes)
return comm
}