@@ -14,6 +14,7 @@ use ark_std::{
14
14
UniformRand ,
15
15
borrow:: { Borrow , BorrowMut } ,
16
16
io:: { self , Read , Write } , // Result
17
+ vec:: Vec ,
17
18
} ;
18
19
use ark_serialize:: { CanonicalSerialize } ;
19
20
use ark_ff:: { Field } ;
@@ -96,31 +97,58 @@ impl<'a, const N: usize> IntoTranscript for &'a [u8; N] {
96
97
}
97
98
}
98
99
100
+ /// Inner hasher or accumulator object.
101
+ ///
102
+ /// We make this distinction at runtime instead of at compile-time
103
+ /// for simplicity elsewhere.
104
+ #[ derive( Clone ) ]
105
+ enum Mode {
106
+ /// Actual Shake128 hasher being written to.
107
+ Hash ( Shake128 ) ,
108
+ /// Accumulate bytes instead of hashing them.
109
+ Accumulate ( Vec < u8 > ) ,
110
+ }
111
+
112
+ impl Mode {
113
+ /// Abstracts over the writing modes
114
+ fn raw_write ( & mut self , bytes : & [ u8 ] ) {
115
+ match self {
116
+ Mode :: Hash ( hasher) => hasher. update ( bytes) ,
117
+ Mode :: Accumulate ( acc) => acc. extend_from_slice ( bytes) ,
118
+ }
119
+ }
120
+
121
+ /// Switch from writing to reading
122
+ ///
123
+ /// Panics if called in accumulation mode
124
+ fn raw_reader ( self ) -> Reader {
125
+ #[ cfg( feature = "debug-transcript" ) ]
126
+ println ! ( "Shake128 {}transcript XoF reader" , self . debug_name) ;
127
+ match self {
128
+ Mode :: Hash ( hasher) => Reader ( hasher. clone ( ) . finalize_xof ( ) ) ,
129
+ Mode :: Accumulate ( _) => panic ! ( "Attempt to read from accumulating Transcript" ) ,
130
+ }
131
+ }
132
+ }
133
+
99
134
/// Shake128 transcript style hasher.
100
135
#[ derive( Clone ) ]
101
136
pub struct Transcript {
102
137
/// Length writen between `seperate()` calls. Always less than 2^31.
103
138
/// `None` means `write` was not yet invoked, so seperate() does nothing.
104
139
/// We need this to distinguish zero length write calls.
105
140
length : Option < u32 > ,
141
+ /// Actual Shake128 hasher being written to, or maybe an accumulator
142
+ mode : Mode ,
106
143
/// Is this a witness transcript?
107
144
#[ cfg( feature = "debug-transcript" ) ]
108
145
debug_name : & ' static str ,
109
- /// Actual Shake128 hasher being written to.
110
- h : Shake128 ,
111
146
}
112
147
113
148
impl Default for Transcript {
114
149
/// Create a fresh empty `Transcript`.
115
150
fn default ( ) -> Transcript {
116
- #[ cfg( feature = "debug-transcript" ) ]
117
- println ! ( "Initial Shake128 transcript.." ) ;
118
- Transcript {
119
- length : None ,
120
- #[ cfg( feature = "debug-transcript" ) ]
121
- debug_name : "" ,
122
- h : Shake128 :: default ( ) ,
123
- }
151
+ Transcript :: new_blank ( )
124
152
}
125
153
}
126
154
@@ -145,6 +173,78 @@ impl Write for Transcript {
145
173
146
174
147
175
impl Transcript {
176
+ /// Create a `Transcript` from `Shake128`.
177
+ pub fn from_shake128 ( hasher : Shake128 ) -> Transcript {
178
+ Transcript {
179
+ length : None ,
180
+ mode : Mode :: Hash ( hasher) ,
181
+ #[ cfg( feature = "debug-transcript" ) ]
182
+ debug_name : "" ,
183
+ }
184
+ }
185
+
186
+ /// Create a `Transcript` from previously accumulated bytes.
187
+ ///
188
+ /// We do not domain seperate these initial bytes, but we domain
189
+ /// seperate everything after this, making this safe.
190
+ pub fn from_accumulation ( acc : impl AsRef < [ u8 ] > ) -> Transcript {
191
+ let mut hasher = Shake128 :: default ( ) ;
192
+ hasher. update ( acc. as_ref ( ) ) ;
193
+ Transcript :: from_shake128 ( hasher)
194
+ }
195
+
196
+ /// Create an empty `Transcript`.
197
+ pub fn new_blank ( ) -> Transcript {
198
+ #[ cfg( feature = "debug-transcript" ) ]
199
+ println ! ( "Initial Shake128 transcript.." ) ;
200
+ Transcript :: from_accumulation ( & [ ] )
201
+ }
202
+
203
+ /// Create a fresh `Transcript` with an initial domain label.
204
+ ///
205
+ /// We implicitly have an initial zero length user data write
206
+ /// preceeding this first label.
207
+ pub fn new ( label : impl AsLabel ) -> Transcript {
208
+ let mut t = Transcript :: new_blank ( ) ;
209
+ t. label ( label) ;
210
+ t
211
+ }
212
+
213
+ /// Create an empty `Transcript` in bytes accumulation mode.
214
+ ///
215
+ /// You cannot create `Reader`s in accumulation mode, but
216
+ /// `accumulator_finalize` exports the accumulated `Vec<u8>`.
217
+ /// You could then transport this elsewhere and start a
218
+ /// real hasher using `from_accumulation`.
219
+ pub fn new_blank_accumulator ( ) -> Transcript {
220
+ #[ cfg( feature = "debug-transcript" ) ]
221
+ println ! ( "Initial Shake128 transcript.." ) ;
222
+ Transcript {
223
+ length : None ,
224
+ mode : Mode :: Accumulate ( Vec :: new ( ) ) ,
225
+ #[ cfg( feature = "debug-transcript" ) ]
226
+ debug_name : "" ,
227
+ }
228
+ }
229
+
230
+ /// Avoid repeated allocations by reserving additional space when in accumulation mode.
231
+ pub fn accumulator_reserve ( & mut self , additional : usize ) {
232
+ match & mut self . mode {
233
+ Mode :: Accumulate ( acc) => acc. reserve ( additional) ,
234
+ _ => { } ,
235
+ }
236
+ }
237
+
238
+ /// Invokes `seperate` and exports the accumulated transcript bytes,
239
+ /// which you later pass into `Transcript::from_accumulation`.
240
+ pub fn accumulator_finalize ( mut self ) -> Vec < u8 > {
241
+ self . seperate ( ) ;
242
+ match self . mode {
243
+ Mode :: Hash ( _) => panic ! ( "Attempte to accumulator_finalize a hashing Transcript" ) ,
244
+ Mode :: Accumulate ( acc) => acc,
245
+ }
246
+ }
247
+
148
248
/// Write basic unlabeled domain seperator into the hasher.
149
249
///
150
250
/// Implemented by writing in big endian the number of bytes
@@ -160,7 +260,7 @@ impl Transcript {
160
260
#[ cfg( feature = "debug-transcript" ) ]
161
261
println ! ( "Shake128 {}transcript seperator: {}" , self . debug_name, self . length) ;
162
262
if let Some ( l) = self . length {
163
- self . h . update ( & l. to_be_bytes ( ) ) ;
263
+ self . mode . raw_write ( & l. to_be_bytes ( ) ) ;
164
264
}
165
265
self . length = None ;
166
266
}
@@ -183,7 +283,7 @@ impl Transcript {
183
283
println ! ( "Shake128 {}transcript write of {} bytes out of {}" , self . debug_name, l, bytes. len( ) ) ;
184
284
}
185
285
}
186
- self . h . update ( & bytes[ 0 ..l] ) ;
286
+ self . mode . raw_write ( & bytes[ 0 ..l] ) ;
187
287
bytes = & bytes[ l..] ;
188
288
if bytes. len ( ) == 0 {
189
289
* length += u32:: try_from ( l) . unwrap ( ) ;
@@ -252,23 +352,6 @@ impl Transcript {
252
352
self . seperate ( ) ;
253
353
}
254
354
255
- /// Create a fresh `Transcript` with an initial domain label.
256
- ///
257
- /// We implicitly have an initial zero length user data write
258
- /// preceeding this first label.
259
- pub fn new ( label : impl AsLabel ) -> Transcript {
260
- let mut t = Transcript :: default ( ) ;
261
- t. label ( label) ;
262
- t
263
- }
264
-
265
- /// Switch from writing to reading
266
- fn raw_reader ( self ) -> Reader {
267
- #[ cfg( feature = "debug-transcript" ) ]
268
- println ! ( "Shake128 {}transcript XoF reader" , self . debug_name) ;
269
- Reader ( self . h . clone ( ) . finalize_xof ( ) )
270
- }
271
-
272
355
/// Create a challenge reader.
273
356
///
274
357
/// Invoking `self.label(label)` has the same effect upon `self`,
@@ -278,7 +361,7 @@ impl Transcript {
278
361
println ! ( "Shake128 {}transcript challenge" , self . debug_name) ;
279
362
self . seperate ( ) ;
280
363
self . write_bytes ( label. as_label ( ) ) ;
281
- let reader = self . clone ( ) . raw_reader ( ) ;
364
+ let reader = self . mode . clone ( ) . raw_reader ( ) ;
282
365
self . seperate ( ) ;
283
366
reader
284
367
}
@@ -328,7 +411,7 @@ impl Transcript {
328
411
let mut rand = [ 0u8 ; 32 ] ;
329
412
rng. fill_bytes ( & mut rand) ;
330
413
self . write_bytes ( & rand) ;
331
- self . raw_reader ( )
414
+ self . mode . raw_reader ( )
332
415
}
333
416
}
334
417
0 commit comments