|
18 | 18 | zerosection = make([]byte, 64) |
19 | 19 | ) |
20 | 20 |
|
21 | | -// Hasher is a reusable hasher for fixed maximum size chunks representing a BMT |
22 | | -// It reuses a pool of trees for amortised memory allocation and resource control, |
23 | | -// and supports order-agnostic concurrent segment writes and section (double segment) writes |
24 | | -// as well as sequential read and write. |
25 | | -// |
26 | | -// The same hasher instance must not be called concurrently on more than one chunk. |
27 | | -// |
28 | | -// The same hasher instance is synchronously reusable. |
29 | | -// |
30 | | -// Sum gives back the tree to the pool and guaranteed to leave |
31 | | -// the tree and itself in a state reusable for hashing a new chunk. |
32 | | -type Hasher struct { |
33 | | - *Conf // configuration |
34 | | - bmt *tree // prebuilt BMT resource for flowcontrol and proofs |
35 | | - size int // bytes written to Hasher since last Reset() |
36 | | - pos int // index of rightmost currently open segment |
37 | | - result chan []byte // result channel |
38 | | - errc chan error // error channel |
39 | | - span []byte // The span of the data subsumed under the chunk |
40 | | -} |
41 | | - |
42 | | -// NewHasher gives back an instance of a Hasher struct |
43 | | -func NewHasher(hasherFact func() hash.Hash) *Hasher { |
44 | | - conf := NewConf(hasherFact, swarm.BmtBranches, 32) |
45 | | - |
46 | | - return &Hasher{ |
47 | | - Conf: conf, |
48 | | - result: make(chan []byte), |
49 | | - errc: make(chan error, 1), |
50 | | - span: make([]byte, SpanSize), |
51 | | - bmt: newTree(conf.maxSize, conf.depth, conf.hasher), |
52 | | - } |
53 | | -} |
54 | | - |
55 | 21 | // Capacity returns the maximum amount of bytes that will be processed by this hasher implementation. |
56 | 22 | // since BMT assumes a balanced binary tree, capacity it is always a power of 2 |
57 | 23 | func (h *Hasher) Capacity() int { |
@@ -91,191 +57,12 @@ func (h *Hasher) BlockSize() int { |
91 | 57 | return 2 * h.segmentSize |
92 | 58 | } |
93 | 59 |
|
94 | | -// Hash returns the BMT root hash of the buffer and an error |
95 | | -// using Hash presupposes sequential synchronous writes (io.Writer interface). |
96 | | -func (h *Hasher) Hash(b []byte) ([]byte, error) { |
97 | | - if h.size == 0 { |
98 | | - return doHash(h.hasher(), h.span, h.zerohashes[h.depth]) |
99 | | - } |
100 | | - copy(h.bmt.buffer[h.size:], zerosection) |
101 | | - // write the last section with final flag set to true |
102 | | - go h.processSection(h.pos, true) |
103 | | - select { |
104 | | - case result := <-h.result: |
105 | | - return doHash(h.hasher(), h.span, result) |
106 | | - case err := <-h.errc: |
107 | | - return nil, err |
108 | | - } |
109 | | -} |
110 | | - |
111 | 60 | // Sum returns the BMT root hash of the buffer, unsafe version of Hash |
112 | 61 | func (h *Hasher) Sum(b []byte) []byte { |
113 | 62 | s, _ := h.Hash(b) |
114 | 63 | return s |
115 | 64 | } |
116 | 65 |
|
117 | | -// Write calls sequentially add to the buffer to be hashed, |
118 | | -// with every full segment calls processSection in a go routine. |
119 | | -func (h *Hasher) Write(b []byte) (int, error) { |
120 | | - l := len(b) |
121 | | - maxVal := h.maxSize - h.size |
122 | | - if l > maxVal { |
123 | | - l = maxVal |
124 | | - } |
125 | | - copy(h.bmt.buffer[h.size:], b) |
126 | | - secsize := 2 * h.segmentSize |
127 | | - from := h.size / secsize |
128 | | - h.size += l |
129 | | - to := h.size / secsize |
130 | | - if l == maxVal { |
131 | | - to-- |
132 | | - } |
133 | | - h.pos = to |
134 | | - for i := from; i < to; i++ { |
135 | | - go h.processSection(i, false) |
136 | | - } |
137 | | - return l, nil |
138 | | -} |
139 | | - |
140 | | -// Reset prepares the Hasher for reuse |
141 | | -func (h *Hasher) Reset() { |
142 | | - h.pos = 0 |
143 | | - h.size = 0 |
144 | | - copy(h.span, zerospan) |
145 | | -} |
146 | | - |
147 | | -// processSection writes the hash of i-th section into level 1 node of the BMT tree. |
148 | | -func (h *Hasher) processSection(i int, final bool) { |
149 | | - secsize := 2 * h.segmentSize |
150 | | - offset := i * secsize |
151 | | - level := 1 |
152 | | - // select the leaf node for the section |
153 | | - n := h.bmt.leaves[i] |
154 | | - isLeft := n.isLeft |
155 | | - hasher := n.hasher |
156 | | - n = n.parent |
157 | | - // hash the section |
158 | | - section, err := doHash(hasher, h.bmt.buffer[offset:offset+secsize]) |
159 | | - if err != nil { |
160 | | - select { |
161 | | - case h.errc <- err: |
162 | | - default: |
163 | | - } |
164 | | - return |
165 | | - } |
166 | | - // write hash into parent node |
167 | | - if final { |
168 | | - // for the last segment use writeFinalNode |
169 | | - h.writeFinalNode(level, n, isLeft, section) |
170 | | - } else { |
171 | | - h.writeNode(n, isLeft, section) |
172 | | - } |
173 | | -} |
174 | | - |
175 | | -// writeNode pushes the data to the node. |
176 | | -// if it is the first of 2 sisters written, the routine terminates. |
177 | | -// if it is the second, it calculates the hash and writes it |
178 | | -// to the parent node recursively. |
179 | | -// since hashing the parent is synchronous the same hasher can be used. |
180 | | -func (h *Hasher) writeNode(n *node, isLeft bool, s []byte) { |
181 | | - var err error |
182 | | - for { |
183 | | - // at the root of the bmt just write the result to the result channel |
184 | | - if n == nil { |
185 | | - h.result <- s |
186 | | - return |
187 | | - } |
188 | | - // otherwise assign child hash to left or right segment |
189 | | - if isLeft { |
190 | | - n.left = s |
191 | | - } else { |
192 | | - n.right = s |
193 | | - } |
194 | | - // the child-thread first arriving will terminate |
195 | | - if n.toggle() { |
196 | | - return |
197 | | - } |
198 | | - // the thread coming second now can be sure both left and right children are written |
199 | | - // so it calculates the hash of left|right and pushes it to the parent |
200 | | - s, err = doHash(n.hasher, n.left, n.right) |
201 | | - if err != nil { |
202 | | - select { |
203 | | - case h.errc <- err: |
204 | | - default: |
205 | | - } |
206 | | - return |
207 | | - } |
208 | | - isLeft = n.isLeft |
209 | | - n = n.parent |
210 | | - } |
211 | | -} |
212 | | - |
213 | | -// writeFinalNode is following the path starting from the final datasegment to the |
214 | | -// BMT root via parents. |
215 | | -// For unbalanced trees it fills in the missing right sister nodes using |
216 | | -// the pool's lookup table for BMT subtree root hashes for all-zero sections. |
217 | | -// Otherwise behaves like `writeNode`. |
218 | | -func (h *Hasher) writeFinalNode(level int, n *node, isLeft bool, s []byte) { |
219 | | - var err error |
220 | | - for { |
221 | | - // at the root of the bmt just write the result to the result channel |
222 | | - if n == nil { |
223 | | - if s != nil { |
224 | | - h.result <- s |
225 | | - } |
226 | | - return |
227 | | - } |
228 | | - var noHash bool |
229 | | - if isLeft { |
230 | | - // coming from left sister branch |
231 | | - // when the final section's path is going via left child node |
232 | | - // we include an all-zero subtree hash for the right level and toggle the node. |
233 | | - n.right = h.zerohashes[level] |
234 | | - if s != nil { |
235 | | - n.left = s |
236 | | - // if a left final node carries a hash, it must be the first (and only thread) |
237 | | - // so the toggle is already in passive state no need no call |
238 | | - // yet thread needs to carry on pushing hash to parent |
239 | | - noHash = false |
240 | | - } else { |
241 | | - // if again first thread then propagate nil and calculate no hash |
242 | | - noHash = n.toggle() |
243 | | - } |
244 | | - } else { |
245 | | - // right sister branch |
246 | | - if s != nil { |
247 | | - // if hash was pushed from right child node, write right segment change state |
248 | | - n.right = s |
249 | | - // if toggle is true, we arrived first so no hashing just push nil to parent |
250 | | - noHash = n.toggle() |
251 | | - } else { |
252 | | - // if s is nil, then thread arrived first at previous node and here there will be two, |
253 | | - // so no need to do anything and keep s = nil for parent |
254 | | - noHash = true |
255 | | - } |
256 | | - } |
257 | | - // the child-thread first arriving will just continue resetting s to nil |
258 | | - // the second thread now can be sure both left and right children are written |
259 | | - // it calculates the hash of left|right and pushes it to the parent |
260 | | - if noHash { |
261 | | - s = nil |
262 | | - } else { |
263 | | - s, err = doHash(n.hasher, n.left, n.right) |
264 | | - if err != nil { |
265 | | - select { |
266 | | - case h.errc <- err: |
267 | | - default: |
268 | | - } |
269 | | - return |
270 | | - } |
271 | | - } |
272 | | - // iterate to parent |
273 | | - isLeft = n.isLeft |
274 | | - n = n.parent |
275 | | - level++ |
276 | | - } |
277 | | -} |
278 | | - |
279 | 66 | // calculates the Keccak256 SHA3 hash of the data |
280 | 67 | func sha3hash(data ...[]byte) ([]byte, error) { |
281 | 68 | return doHash(swarm.NewHasher(), data...) |
|
0 commit comments