wire.go raw
1 package gnarlring
2
3 // CommitmentFrame is the wire format for a single child commitment (63 bytes).
4 //
5 // Offset Size Field
6 // 0 1 Index (uint8, 0..26)
7 // 1 31 PubKey (Poly27, 9-bit unsigned)
8 // 32 31 W (commitment, Poly27, 9-bit unsigned)
9 const CommitmentFrameSize = 1 + PolyBytes + PolyBytes // 63
10
11 func MarshalCommitmentFrame(cc *ChildCommitment) []byte {
12 buf := make([]byte, CommitmentFrameSize)
13 buf[0] = cc.Index
14 copy(buf[1:1+PolyBytes], cc.PubKey.MarshalBinary())
15 copy(buf[1+PolyBytes:], cc.W.MarshalBinary())
16 return buf
17 }
18
19 func UnmarshalCommitmentFrame(data []byte) *ChildCommitment {
20 if len(data) < CommitmentFrameSize {
21 return nil
22 }
23 cc := &ChildCommitment{Index: data[0]}
24 var err error
25 cc.PubKey, err = UnmarshalBinary(data[1 : 1+PolyBytes])
26 if err != nil {
27 return nil
28 }
29 cc.W, err = UnmarshalBinary(data[1+PolyBytes : CommitmentFrameSize])
30 if err != nil {
31 return nil
32 }
33 return cc
34 }
35
36 // EpochFrame is the full epoch broadcast (≈1783 bytes).
37 //
38 // Offset Size Field
39 // 0 8 Counter (uint64 LE)
40 // 8 31 RootPK (Poly27, 9-bit unsigned)
41 // 39 63×27 Frames [N]CommitmentFrame
42 // 1740 47 RootSig (16B salt + 31B s2 at 9-bit signed)
43 const EpochFrameHeaderSize = 8 + PolyBytes // 39
44 const EpochFrameSize = EpochFrameHeaderSize + N*CommitmentFrameSize + sigBytes // 39 + 1701 + 47 = 1787
45
46 func MarshalEpochFrame(es *EpochState) []byte {
47 buf := make([]byte, EpochFrameSize)
48 putUint64LE(buf[0:8], es.Counter)
49 copy(buf[8:8+PolyBytes], es.Coordinator.MarshalBinary())
50
51 off := EpochFrameHeaderSize
52 for i := 0; i < N; i++ {
53 if es.Agg.Children[i] != nil {
54 copy(buf[off:off+CommitmentFrameSize], MarshalCommitmentFrame(es.Agg.Children[i]))
55 }
56 off += CommitmentFrameSize
57 }
58
59 if es.RootSig != nil {
60 copy(buf[off:], es.RootSig.MarshalBinary())
61 }
62 return buf
63 }
64
65 func UnmarshalEpochFrame(data []byte) *EpochState {
66 if len(data) < EpochFrameSize {
67 return nil
68 }
69 c := getUint64LE(data[0:8])
70 pk, err := UnmarshalBinary(data[8 : 8+PolyBytes])
71 if err != nil {
72 return nil
73 }
74
75 es := StartEpoch(c, &NTRUPublicKey{H: pk})
76 off := EpochFrameHeaderSize
77 for i := 0; i < N; i++ {
78 cc := UnmarshalCommitmentFrame(data[off : off+CommitmentFrameSize])
79 if cc != nil {
80 es.Agg.Add(cc)
81 }
82 off += CommitmentFrameSize
83 }
84
85 sig, err := UnmarshalNTRUSig(data[off : off+sigBytes])
86 if err == nil {
87 es.RootSig = sig
88 es.finalized = true
89 }
90 return es
91 }
92
93 // EpochCheckFrame is the compact relay-path proof (113 bytes).
94 //
95 // Offset Size Field
96 // 0 8 Counter (uint64 LE)
97 // 8 31 RootPK (Poly27, 9-bit unsigned)
98 // 39 27 WCompressed (hash of all w_i + epoch, 27 bytes)
99 // 66 47 RootSig (16B salt + 31B s2)
100 //
101 // Does NOT carry the full w_i list. The verifier must have cached the
102 // epoch frame or trust the WCompressed binding.
103 const EpochCheckFrameSize = 8 + PolyBytes + 27 + sigBytes // 113
104
105 func MarshalEpochCheckFrame(es *EpochState) []byte {
106 buf := make([]byte, EpochCheckFrameSize)
107 putUint64LE(buf[0:8], es.Counter)
108 copy(buf[8:8+PolyBytes], es.Coordinator.MarshalBinary())
109 copy(buf[8+PolyBytes:8+PolyBytes+27], es.Agg.WCompressed(es.Counter))
110 if es.RootSig != nil {
111 copy(buf[8+PolyBytes+27:], es.RootSig.MarshalBinary())
112 }
113 return buf
114 }
115
116 func UnmarshalEpochCheckFrame(data []byte) (*EpochCheckFrame, error) {
117 if len(data) < EpochCheckFrameSize {
118 return nil, errShortData
119 }
120 cf := &EpochCheckFrame{Counter: getUint64LE(data[0:8])}
121 var err error
122 cf.PK, err = UnmarshalBinary(data[8 : 8+PolyBytes])
123 if err != nil {
124 return nil, err
125 }
126 copy(cf.WCompressed[:], data[8+PolyBytes:8+PolyBytes+27])
127 cf.Sig, err = UnmarshalNTRUSig(data[8+PolyBytes+27 : EpochCheckFrameSize])
128 if err != nil {
129 return nil, err
130 }
131 return cf, nil
132 }
133
134 // EpochCheckFrame is a deserialized compact frame.
135 type EpochCheckFrame struct {
136 Counter uint64
137 PK *Poly27
138 WCompressed [27]byte
139 Sig *NTRUSignature
140 }
141
142 func putUint64LE(buf []byte, v uint64) {
143 buf[0] = byte(v)
144 buf[1] = byte(v >> 8)
145 buf[2] = byte(v >> 16)
146 buf[3] = byte(v >> 24)
147 buf[4] = byte(v >> 32)
148 buf[5] = byte(v >> 40)
149 buf[6] = byte(v >> 48)
150 buf[7] = byte(v >> 56)
151 }
152
153 func getUint64LE(buf []byte) uint64 {
154 return uint64(buf[0]) | uint64(buf[1])<<8 | uint64(buf[2])<<16 |
155 uint64(buf[3])<<24 | uint64(buf[4])<<32 | uint64(buf[5])<<40 |
156 uint64(buf[6])<<48 | uint64(buf[7])<<56
157 }
158