66
77use curve25519_dalek:: scalar:: Scalar ;
88
9- // XXX This uses experiment fork of tiny_keccak with half-duplex
10- // support that we require in this implementation.
11- // Review this after this PR is merged or updated:
12- // https://github.com/debris/tiny-keccak/pull/24
139use tiny_keccak:: Keccak ;
1410
1511use byteorder:: { ByteOrder , LittleEndian } ;
@@ -31,12 +27,13 @@ use byteorder::{ByteOrder, LittleEndian};
3127/// ensure that their challenge values are bound to the *entire* proof
3228/// transcript, not just the sub-protocol.
3329///
34- /// Internally, the `ProofTranscript` is supposed to use Keccak to
35- /// absorb incoming messages and to squeeze challenges. The
36- /// construction currently used is ad-hoc, has no security analysis,
37- /// and is **only suitable for testing**.
30+ /// ## Warning
3831///
39- /// # Example
32+ /// Internally, the `ProofTranscript` uses ad-hoc duplex construction
33+ /// using Keccak that absorbs incoming messages and squeezes challenges.
34+ /// There is no security analysis yet, so it is **only suitable for testing**.
35+ ///
36+ /// ## Example
4037///
4138/// ```
4239/// # extern crate curve25519_dalek;
@@ -63,41 +60,49 @@ use byteorder::{ByteOrder, LittleEndian};
6360#[ derive( Clone ) ]
6461pub struct ProofTranscript {
6562 hash : Keccak ,
63+ rate : usize ,
64+ write_offset : usize , // index within a block where the message must be absorbed
6665}
6766
6867impl ProofTranscript {
68+ // Implementation notes
69+ //
70+ // The implementation has 3 layers:
71+ // 1. commit/challenge - take input/output buffers <64K, responsible for disambiguation (length prefixing)
72+ // 2. write/read - take arbitrary buffers, responsible for splitting data over Keccak-f invocations and padding
73+ // 3. absorb/squeeze - actual sponge operations, outer layers ensure that absorb/squeeze do not perform unnecessary permutation
74+ //
75+
6976 /// Begin a new, empty proof transcript, using the given `label`
7077 /// for domain separation.
7178 pub fn new ( label : & [ u8 ] ) -> Self {
7279 let mut ro = ProofTranscript {
80+ // NOTE: if you change the security parameter, also change the rate below
7381 hash : Keccak :: new_shake128 ( ) ,
82+ rate : 1600 /8 - ( 2 * 128 /8 ) , // 168 bytes
83+ write_offset : 0 ,
7484 } ;
85+ // We will bump the version prefix each time we
86+ // make a breaking change in order to disambiguate
87+ // from the previous versions of this implementation.
88+ ro. commit ( b"ProofTranscript v2" ) ;
7589 ro. commit ( label) ;
76- // makes sure the label is disambiguated from the rest of the messages.
77- ro. pad ( ) ;
7890 ro
7991 }
8092
81- /// Commit a `message ` to the proof transcript.
93+ /// Commit a `input ` to the proof transcript.
8294 ///
8395 /// # Note
8496 ///
85- /// Each message must be shorter than 64Kb (65536 bytes).
86- pub fn commit ( & mut self , message : & [ u8 ] ) {
87- let len = message. len ( ) ;
97+ /// Each input must be ≤ than the number of bytes
98+ /// returned by `max_commit_size()`.
99+ pub fn commit ( & mut self , input : & [ u8 ] ) {
100+ let len = input. len ( ) ;
88101 if len > ( u16:: max_value ( ) as usize ) {
89- panic ! ( "Committed message must be less than 64Kb!" ) ;
102+ panic ! ( "Committed input must be less than 64Kb!" ) ;
90103 }
91-
92- let mut len_prefix = [ 0u8 ; 2 ] ;
93- LittleEndian :: write_u16 ( & mut len_prefix, len as u16 ) ;
94-
95- // XXX we rely on tiny_keccak experimental support for half-duplex mode and
96- // correct switching from absorbing to squeezing and back.
97- // Review this after this PR is merged or updated:
98- // https://github.com/debris/tiny-keccak/pull/24
99- self . hash . absorb ( & len_prefix) ;
100- self . hash . absorb ( message) ;
104+ self . write_u16 ( len as u16 ) ;
105+ self . write ( input) ;
101106 }
102107
103108 /// Commit a `u64` to the proof transcript.
@@ -112,30 +117,79 @@ impl ProofTranscript {
112117 }
113118
114119 /// Extracts an arbitrary-sized challenge byte slice.
115- pub fn challenge_bytes ( & mut self , mut output : & mut [ u8 ] ) {
116- // XXX we rely on tiny_keccak experimental support for half-duplex mode and
117- // correct switching from absorbing to squeezing and back.
118- // Review this after this PR is merged or updated:
119- // https://github.com/debris/tiny-keccak/pull/24
120- self . hash . squeeze ( & mut output) ;
120+ ///
121+ /// Note: each call performs at least one Keccak permutation,
122+ /// so if you need to read multiple logical challenges at once,
123+ /// you should read a bigger slice in one call for minimal overhead.
124+ pub fn challenge_bytes ( & mut self , output : & mut [ u8 ] ) {
125+ let len = output. len ( ) ;
126+ if output. len ( ) > ( u16:: max_value ( ) as usize ) {
127+ panic ! ( "Challenge output must be less than 64Kb!" ) ;
128+ }
129+ // Note: when reading, length prefix N is followed by keccak padding 10*1
130+ // as if empty message was written; when writing, length prefix N is followed
131+ // by N bytes followed by keccak padding 10*1.
132+ // This creates ambiguity only for case N=0 (empty write or empty read),
133+ // which is safe as no information is actually transmitted in either direction.
134+ self . write_u16 ( len as u16 ) ;
135+ self . read ( output) ;
121136 }
122137
123138 /// Extracts a challenge scalar.
124139 ///
125140 /// This is a convenience method that extracts 64 bytes and
126141 /// reduces modulo the group order.
142+ ///
143+ /// Note: each call performs at least one Keccak permutation,
144+ /// so if you need to read multiple challenge scalars,
145+ /// for the minimal overhead you should read `n*64` bytes
146+ /// using the `challenge_bytes` method and reduce each
147+ /// 64-byte window into a scalar yourself.
127148 pub fn challenge_scalar ( & mut self ) -> Scalar {
128149 let mut buf = [ 0u8 ; 64 ] ;
129150 self . challenge_bytes ( & mut buf) ;
130151 Scalar :: from_bytes_mod_order_wide ( & buf)
131152 }
132153
133- /// Pad separates the prior operations by padding
134- /// the rest of the block with zeroes and applying a permutation.
135- /// Each incoming message is length-prefixed anyway, but padding
136- /// enables pre-computing and re-using the oracle state.
137- fn pad ( & mut self ) {
154+ /// Internal API: writes 2-byte length prefix.
155+ fn write_u16 ( & mut self , integer : u16 ) {
156+ let mut intbuf = [ 0u8 ; 2 ] ;
157+ LittleEndian :: write_u16 ( & mut intbuf, integer) ;
158+ self . write ( & intbuf) ;
159+ }
160+
161+ /// Internal API: writes arbitrary byte slice
162+ /// splitting it over multiple duplex calls if needed.
163+ fn write ( & mut self , mut input : & [ u8 ] ) {
164+ // `write` can be called multiple times.
165+ // If we overflow the available room (`rate-1` at most)
166+ // we absorb what we can, add Keccak padding, permute and continue.
167+ let mut room = self . rate - 1 - self . write_offset ; // 1 byte is reserved for keccak padding 10*1.
168+ while input. len ( ) > room {
169+ self . hash . absorb ( & input[ ..room] ) ;
170+ self . hash . pad ( ) ;
171+ self . hash . fill_block ( ) ;
172+ self . write_offset = 0 ;
173+ room = self . rate - 1 ;
174+ input = & input[ room..] ;
175+ }
176+ self . hash . absorb ( input) ;
177+ self . write_offset += input. len ( ) ; // could end up == (rate-1)
178+ }
179+
180+ /// Internal API: reads arbitrary byte slice
181+ /// splitting it over multiple duplex calls if needed.
182+ fn read ( & mut self , output : & mut [ u8 ] ) {
183+ // Note 1: `read` is called only once after `write`, so we do
184+ // not need to support multiple reads from some offset.
185+ // We only need to complete the pending duplex call by padding and permuting.
186+ // Note 2: Since we hash in the total output buffer length,
187+ // we can use default squeeze behaviour w/o simulating blank inputs:
188+ // the resulting byte-stream will be disambiguated by that length prefix and keccak padding.
189+ self . hash . pad ( ) ;
138190 self . hash . fill_block ( ) ;
191+ self . write_offset = 0 ;
192+ self . hash . squeeze ( output) ;
139193 }
140194}
141195
@@ -149,61 +203,56 @@ mod tests {
149203 {
150204 let mut ro = ProofTranscript :: new ( b"TestProtocol" ) ;
151205 ro. commit ( b"test" ) ;
152- {
153- let mut ch = [ 0u8 ; 32 ] ;
154- ro. challenge_bytes ( & mut ch) ;
155- assert_eq ! (
156- hex:: encode( ch) ,
157- "9ba30a0e71e8632b55fbae92495440b6afb5d2646ba6b1bb419933d97e06b810"
158- ) ;
159- ro. challenge_bytes ( & mut ch) ;
160- assert_eq ! (
161- hex:: encode( ch) ,
162- "add523844517c2320fc23ca72423b0ee072c6d076b05a6a7b6f46d8d2e322f94"
163- ) ;
164- ro. challenge_bytes ( & mut ch) ;
165- assert_eq ! (
166- hex:: encode( ch) ,
167- "ac279a11cac0b1271d210592c552d719d82d67c82d7f86772ed7bc6618b0927c"
168- ) ;
169- }
170-
171- let mut ro = ProofTranscript :: new ( b"TestProtocol" ) ;
172- ro. commit ( b"test" ) ;
173- {
174- let mut ch = [ 0u8 ; 16 ] ;
175- ro. challenge_bytes ( & mut ch) ;
176- assert_eq ! ( hex:: encode( ch) , "9ba30a0e71e8632b55fbae92495440b6" ) ;
177- ro. challenge_bytes ( & mut ch) ;
178- assert_eq ! ( hex:: encode( ch) , "afb5d2646ba6b1bb419933d97e06b810" ) ;
179- ro. challenge_bytes ( & mut ch) ;
180- assert_eq ! ( hex:: encode( ch) , "add523844517c2320fc23ca72423b0ee" ) ;
181- }
206+ let mut ch = [ 0u8 ; 32 ] ;
207+ ro. challenge_bytes ( & mut ch) ;
208+ assert_eq ! (
209+ hex:: encode( ch) ,
210+ "dec44a90f423c15874f7c0afaf62cc6cc0987bf428202cb3508fc7d7c9b5b30a"
211+ ) ;
212+ ro. challenge_bytes ( & mut ch) ;
213+ assert_eq ! (
214+ hex:: encode( ch) ,
215+ "f83256ef4964d71ec6f2dd2f79db70820c781bd8c3d1fceec7cbfa4965d4e530"
216+ ) ;
217+ ro. challenge_bytes ( & mut ch) ;
218+ assert_eq ! (
219+ hex:: encode( ch) ,
220+ "962f9ef161604c5dcbe3387773b293a0e27a6e6ee14ec5d9f6c78a45c36fc0e1"
221+ ) ;
222+ }
182223
224+ {
183225 let mut ro = ProofTranscript :: new ( b"TestProtocol" ) ;
184226 ro. commit ( b"test" ) ;
185- {
186- let mut ch = [ 0u8 ; 16 ] ;
187- ro. challenge_bytes ( & mut ch) ;
188- assert_eq ! ( hex:: encode( ch) , "9ba30a0e71e8632b55fbae92495440b6" ) ;
189- ro. commit ( b"extra commitment" ) ;
190- ro. challenge_bytes ( & mut ch) ;
191- assert_eq ! ( hex:: encode( ch) , "11536e09cedbb6b302d8c7cd96471be5" ) ;
192- ro. challenge_bytes ( & mut ch) ;
193- assert_eq ! ( hex:: encode( ch) , "058c383da5f2e193a381aaa420b505b2" ) ;
194- }
227+ let mut ch = [ 0u8 ; 32 ] ;
228+ ro. challenge_bytes ( & mut ch) ;
229+ assert_eq ! (
230+ hex:: encode( ch) ,
231+ "dec44a90f423c15874f7c0afaf62cc6cc0987bf428202cb3508fc7d7c9b5b30a"
232+ ) ;
233+ ro. commit ( b"extra commitment" ) ;
234+ ro. challenge_bytes ( & mut ch) ;
235+ assert_eq ! (
236+ hex:: encode( ch) ,
237+ "edf99afca6c21e4240f33826d60cb1b7c5d59d3dd363d2928bab7b8f94d24eaa"
238+ ) ;
239+ ro. challenge_bytes ( & mut ch) ;
240+ assert_eq ! (
241+ hex:: encode( ch) ,
242+ "a42eabb9d1c9c73dc8c33c0933cee8d5fabd48fcab686d9fcb8f1680841e4369"
243+ ) ;
195244 }
196245 }
197246
198247 #[ test]
199- fn messages_are_disambiguated_by_length_prefix ( ) {
248+ fn inputs_are_disambiguated_by_length_prefix ( ) {
200249 {
201250 let mut ro = ProofTranscript :: new ( b"TestProtocol" ) ;
202251 ro. commit ( b"msg1msg2" ) ;
203252 {
204253 let mut ch = [ 0u8 ; 8 ] ;
205254 ro. challenge_bytes ( & mut ch) ;
206- assert_eq ! ( hex:: encode( ch) , "1ad843ea2bf7f8b6 " ) ;
255+ assert_eq ! ( hex:: encode( ch) , "3a941266af4275d5 " ) ;
207256 }
208257 }
209258 {
@@ -213,7 +262,7 @@ mod tests {
213262 {
214263 let mut ch = [ 0u8 ; 8 ] ;
215264 ro. challenge_bytes ( & mut ch) ;
216- assert_eq ! ( hex:: encode( ch) , "79abbe29d8c33bb0 " ) ;
265+ assert_eq ! ( hex:: encode( ch) , "644d94299bcc5590 " ) ;
217266 }
218267 }
219268 {
@@ -223,7 +272,7 @@ mod tests {
223272 {
224273 let mut ch = [ 0u8 ; 8 ] ;
225274 ro. challenge_bytes ( & mut ch) ;
226- assert_eq ! ( hex:: encode( ch) , "f88d0f790cde50d5 " ) ;
275+ assert_eq ! ( hex:: encode( ch) , "14f18d260e679f9a " ) ;
227276 }
228277 }
229278 {
@@ -234,8 +283,35 @@ mod tests {
234283 {
235284 let mut ch = [ 0u8 ; 8 ] ;
236285 ro. challenge_bytes ( & mut ch) ;
237- assert_eq ! ( hex:: encode( ch) , "90ca22b443fb78a1 " ) ;
286+ assert_eq ! ( hex:: encode( ch) , "09dccc9d7dfa6f37 " ) ;
238287 }
239288 }
240289 }
290+
291+
292+ #[ test]
293+ fn outputs_are_disambiguated_by_length_prefix ( ) {
294+ let mut ro = ProofTranscript :: new ( b"TestProtocol" ) ;
295+ {
296+ let mut ch = [ 0u8 ; 16 ] ;
297+ ro. challenge_bytes ( & mut ch) ;
298+ assert_eq ! ( hex:: encode( ch) , "60890c8d774932db1aba587941cbffca" ) ;
299+ ro. challenge_bytes ( & mut ch) ;
300+ assert_eq ! ( hex:: encode( ch) , "bb9308c7d34769ae2a3c040394efb2ab" ) ;
301+ }
302+
303+ let mut ro = ProofTranscript :: new ( b"TestProtocol" ) ;
304+ {
305+ let mut ch = [ 0u8 ; 8 ] ;
306+ ro. challenge_bytes ( & mut ch) ;
307+ assert_eq ! ( hex:: encode( ch) , "cc76fac64922bc58" ) ;
308+ ro. challenge_bytes ( & mut ch) ;
309+ assert_eq ! ( hex:: encode( ch) , "d259804aae5c3246" ) ;
310+ ro. challenge_bytes ( & mut ch) ;
311+ assert_eq ! ( hex:: encode( ch) , "6d3a732156286895" ) ;
312+ ro. challenge_bytes ( & mut ch) ;
313+ assert_eq ! ( hex:: encode( ch) , "2165dcd38764b5ae" ) ;
314+ }
315+ }
316+
241317}
0 commit comments