gnarl_hash_test.go raw
1 package crypto
2
3 import (
4 "bytes"
5 "testing"
6 )
7
8 func TestGnarlHashDeterministic(t *testing.T) {
9 msg := []byte("determinism test message")
10 h1 := GHash(msg)
11 h2 := GHash(msg)
12 if h1 != h2 {
13 t.Fatalf("GHash not deterministic: %x != %x", h1, h2)
14 }
15
16 m1 := GMid(msg)
17 m2 := GMid(msg)
18 if m1 != m2 {
19 t.Fatalf("GMid not deterministic")
20 }
21
22 s1 := GShard(msg)
23 s2 := GShard(msg)
24 if s1 != s2 {
25 t.Fatalf("GShard not deterministic")
26 }
27 }
28
29 func TestGnarlHashDistinct(t *testing.T) {
30 h1 := GHash([]byte("message one"))
31 h2 := GHash([]byte("message two"))
32 if h1 == h2 {
33 t.Fatalf("GHash collision on distinct messages")
34 }
35
36 m1 := GMid([]byte("message one"))
37 m2 := GMid([]byte("message two"))
38 if m1 == m2 {
39 t.Fatalf("GMid collision on distinct messages")
40 }
41 }
42
43 func TestGnarlHashEmpty(t *testing.T) {
44 h := GHash([]byte{})
45 if h.IsZero() {
46 t.Fatalf("GHash of empty input is zero")
47 }
48 }
49
50 func TestGnarlHashLong(t *testing.T) {
51 // Message longer than one block (41 bytes).
52 msg := make([]byte, 500)
53 for i := range msg {
54 msg[i] = byte(i % 256)
55 }
56 h1 := GHash(msg)
57 if h1.IsZero() {
58 t.Fatalf("GHash of 500-byte message is zero")
59 }
60
61 // Flip one bit: hash should change.
62 msg[250] ^= 0x01
63 h2 := GHash(msg)
64 if h1 == h2 {
65 t.Fatalf("GHash did not change after bit flip")
66 }
67 }
68
69 func TestGnarlHashMultiBlock(t *testing.T) {
70 // Messages that span exactly 1, 2, and 3 blocks.
71 for _, size := range []int{30, 60, 120} {
72 msg := make([]byte, size)
73 for i := range msg {
74 msg[i] = byte(i*7 + 13)
75 }
76 h := GHash(msg)
77 if h.IsZero() {
78 t.Fatalf("GHash of %d-byte message is zero", size)
79 }
80 }
81 }
82
83 func TestGnarlPackUnpack243(t *testing.T) {
84 // Pack and unpack should be inverse operations.
85 var coeffs [GnarlN]uint16
86 for i := range GnarlN {
87 coeffs[i] = uint16((i*37 + 13) % GnarlP)
88 }
89
90 packed := packCoeffs243(coeffs)
91 unpacked := unpackCoeffs243(packed)
92
93 for i := range GnarlN {
94 if unpacked[i] != coeffs[i] {
95 t.Fatalf("pack/unpack243 mismatch at %d: got %d, want %d",
96 i, unpacked[i], coeffs[i])
97 }
98 }
99 }
100
101 func TestGnarlPackUnpack243MaxValue(t *testing.T) {
102 // All coefficients at max value (270).
103 var coeffs [GnarlN]uint16
104 for i := range GnarlN {
105 coeffs[i] = 270
106 }
107
108 packed := packCoeffs243(coeffs)
109 unpacked := unpackCoeffs243(packed)
110
111 for i := range GnarlN {
112 if unpacked[i] != 270 {
113 t.Fatalf("max value pack/unpack243 mismatch at %d: got %d, want 270",
114 i, unpacked[i])
115 }
116 }
117 }
118
119 func TestGnarlMidFromHash(t *testing.T) {
120 msg := []byte("tier extraction test")
121 h := GHash(msg)
122 m := GMid(msg)
123
124 // Mid extracted from GnarlHash should match GMid computed directly.
125 mFromH := h.Mid()
126 if m != mFromH {
127 t.Fatalf("GMid from GHash differs from direct GMid")
128 }
129 }
130
131 func TestGnarlShardFromHash(t *testing.T) {
132 msg := []byte("shard extraction test")
133 h := GHash(msg)
134 s := GShard(msg)
135
136 sFromH := h.Shard()
137 if s != sFromH {
138 t.Fatalf("GShard from GHash differs from direct GShard")
139 }
140 }
141
142 func TestGnarlShardValues(t *testing.T) {
143 // Each trit in the shard should be 0, 1, or 2.
144 msg := []byte("trit value test")
145 s := GShard(msg)
146
147 bitPos := 0
148 for i := range GnarlN {
149 var v byte
150 for b := range 2 {
151 byteIdx := bitPos / 8
152 bitIdx := uint(bitPos % 8)
153 if s[byteIdx]&(1<<bitIdx) != 0 {
154 v |= 1 << uint(b)
155 }
156 bitPos++
157 }
158 if v > 2 {
159 t.Fatalf("trit %d has value %d (should be 0, 1, or 2)", i, v)
160 }
161 }
162 }
163
164 func TestGnarlHashSum(t *testing.T) {
165 h1 := GHash([]byte("alpha"))
166 h2 := GHash([]byte("beta"))
167 sum := h1.Sum(h2)
168 if sum.IsZero() {
169 t.Fatalf("sum of two hashes is zero")
170 }
171 // Sum should be commutative.
172 sum2 := h2.Sum(h1)
173 if sum != sum2 {
174 t.Fatalf("GnarlHash.Sum is not commutative")
175 }
176 }
177
178 func TestGnarlHashSizes(t *testing.T) {
179 if GnarlHashBytes != 31 {
180 t.Fatalf("GnarlHashBytes = %d, want 31", GnarlHashBytes)
181 }
182 if GnarlMidBytes != 27 {
183 t.Fatalf("GnarlMidBytes = %d, want 27", GnarlMidBytes)
184 }
185 if GnarlShardBytes != 7 {
186 t.Fatalf("GnarlShardBytes = %d, want 7", GnarlShardBytes)
187 }
188 }
189
190 func TestGnarlHashDispersion(t *testing.T) {
191 // Hash many messages and check that all bytes of the GnarlMid are exercised.
192 var orBits [GnarlMidBytes]byte
193 for i := range 100 {
194 msg := []byte{byte(i), byte(i >> 8), byte(i >> 16)}
195 m := GMid(msg)
196 for j := range GnarlMidBytes {
197 orBits[j] |= m[j]
198 }
199 }
200 for i, b := range orBits {
201 if b == 0 {
202 t.Fatalf("byte %d of GnarlMid never set across 100 hashes", i)
203 }
204 }
205 }
206
207 func TestGnarlLengthExtension(t *testing.T) {
208 // Different-length messages with same prefix should hash differently.
209 short := []byte("short")
210 long := append([]byte("short"), bytes.Repeat([]byte{0}, 50)...)
211
212 h1 := GHash(short)
213 h2 := GHash(long)
214 if h1 == h2 {
215 t.Fatalf("length extension: same hash for different-length messages")
216 }
217 }
218