commitment.go raw
1 package gnarlring
2
3 import (
4 "crypto/rand"
5 "io"
6 "math"
7 )
8
9 // ChildCommitment holds one child's per-epoch short-vector commitment.
10 // The child produces w = H_i * z (z freshly sampled Gaussian), and the
11 // coordinator collects all 27 w_i to hash into the epoch target.
12 type ChildCommitment struct {
13 Index uint8 // child position 0..26
14 PubKey *Poly27 // child's NTRU public key H_i (31 bytes serialized)
15 W *Poly27 // commitment w_i = H_i * z_i (31 bytes serialized)
16 }
17
18 // NewChildCommitment creates a commitment: samples fresh z, computes w = pk * z.
19 func NewChildCommitment(index int, pk *NTRUPublicKey, rng io.Reader) *ChildCommitment {
20 if rng == nil {
21 rng = rand.Reader
22 }
23 gs := NewGaussSamplerFrom(DefaultSigma(), rng)
24 z := gs.SamplePoly()
25 w := Mul(pk.H, z)
26 return &ChildCommitment{
27 Index: uint8(index),
28 PubKey: pk.H,
29 W: w,
30 }
31 }
32
33 // AggregatedCommitment collects up to 27 child commitments by index.
34 type AggregatedCommitment struct {
35 Children [N]*ChildCommitment // nil for unused slots
36 Count int
37 }
38
39 // NewAggregatedCommitment returns an empty aggregation.
40 func NewAggregatedCommitment() *AggregatedCommitment {
41 return &AggregatedCommitment{}
42 }
43
44 // Add inserts a child commitment at its index. Returns false on conflict.
45 func (ac *AggregatedCommitment) Add(cc *ChildCommitment) bool {
46 if cc.Index >= N {
47 return false
48 }
49 if ac.Children[cc.Index] != nil {
50 return false
51 }
52 ac.Children[cc.Index] = cc
53 ac.Count++
54 return true
55 }
56
57 // Remove clears a slot.
58 func (ac *AggregatedCommitment) Remove(index uint8) {
59 if index < N && ac.Children[index] != nil {
60 ac.Children[index] = nil
61 ac.Count--
62 }
63 }
64
65 // IsComplete reports whether all N slots are filled.
66 func (ac *AggregatedCommitment) IsComplete() bool {
67 return ac.Count == N
68 }
69
70 // Target produces the sparse challenge polynomial for the coordinator to sign.
71 // Hashes epoch counter + all w_i (zero for empty slots) + message. Returns a
72 // dense Poly27 suitable as ffSampling target.
73 func (ac *AggregatedCommitment) Target(msg []byte, epoch uint64) *Poly27 {
74 input := make([]byte, 0, 16+N*PolyBytes+len(msg))
75 input = append(input, []byte("gnarl-epoch-v1")...)
76 input = appendUint64LE(input, epoch)
77 for i := 0; i < N; i++ {
78 if ac.Children[i] != nil {
79 input = append(input, ac.Children[i].W.MarshalBinary()...)
80 } else {
81 input = append(input, make([]byte, PolyBytes)...)
82 }
83 }
84 input = append(input, msg...)
85
86 return hashBytesToPoly(input)
87 }
88
89 // WCompressed returns the GMid-style hash of all w_i for compact verification.
90 func (ac *AggregatedCommitment) WCompressed(epoch uint64) []byte {
91 input := make([]byte, 0, 16+N*PolyBytes)
92 input = append(input, []byte("gnarl-wcomp-v1")...)
93 input = appendUint64LE(input, epoch)
94 for i := 0; i < N; i++ {
95 if ac.Children[i] != nil {
96 input = append(input, ac.Children[i].W.MarshalBinary()...)
97 } else {
98 input = append(input, make([]byte, PolyBytes)...)
99 }
100 }
101 return hashBytes(input)
102 }
103
104 func appendUint64LE(buf []byte, v uint64) []byte {
105 var tmp [8]byte
106 tmp[0] = byte(v)
107 tmp[1] = byte(v >> 8)
108 tmp[2] = byte(v >> 16)
109 tmp[3] = byte(v >> 24)
110 tmp[4] = byte(v >> 32)
111 tmp[5] = byte(v >> 40)
112 tmp[6] = byte(v >> 48)
113 tmp[7] = byte(v >> 56)
114 return append(buf, tmp[:]...)
115 }
116
117 // DefaultSigma returns the standard Gaussian sigma for the gnarl ring.
118 func DefaultSigma() float64 {
119 return math.Sqrt(float64(N)) * 2.0
120 }
121
122 // hashBytes returns a 27-byte hash of input using FNV-style state chaining.
123 func hashBytes(input []byte) []byte {
124 var state uint64 = 14695981039346656037
125 out := make([]byte, 27)
126 for i := 0; i < 27; i++ {
127 for _, b := range input {
128 state ^= uint64(b)
129 state *= 1099511628211
130 }
131 state ^= uint64(i)
132 state *= 1099511628211
133 out[i] = byte(state >> 32)
134 }
135 return out
136 }
137
138 // hashBytesToPoly returns a dense Poly27 derived from input.
139 func hashBytesToPoly(input []byte) *Poly27 {
140 var state uint64 = 14695981039346656037
141 c := NewPoly27()
142 for i := 0; i < N; i++ {
143 for _, b := range input {
144 state ^= uint64(b)
145 state *= 1099511628211
146 }
147 state ^= uint64(i)
148 state *= 1099511628211
149 c.Coeffs[i] = uint16(state % uint64(Q))
150 }
151 return c
152 }
153
154 // salt generates a random 16-byte salt.
155 func salt16() ([16]byte, error) {
156 var s [16]byte
157 _, err := rand.Read(s[:])
158 return s, err
159 }
160