Skip to content

Commit 897bfb5

Browse files
committed
Add a streaming API
Not for blind signatures yet because I'm lazy.
1 parent 0f249f3 commit 897bfb5

File tree

3 files changed

+206
-28
lines changed

3 files changed

+206
-28
lines changed

README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* Lightweight
1010
* Zero dependencies if randomness is provided by the application
1111
* Only one portable dependency (`getrandom`) if not
12+
* Supports incremental signatures (streaming API)
1213
* Safe and simple Rust interface
1314

1415
## [API documentation](https://docs.rs/ed25519-compact)
@@ -52,6 +53,31 @@ let signature_as_bytes: &[u8] = signature.as_ref();
5253
println!("Signature as bytes: {:?}", signature_as_bytes);
5354
```
5455

56+
## Incremental API example usage
57+
58+
Messages can also be supplied as multiple parts (streaming API):
59+
60+
```rust
61+
/// Creates a new key pair.
62+
let kp = KeyPair::generate();
63+
64+
/// Create a state for an incremental signer.
65+
let mut st = kp.sk.sign_incremental(Noise::default());
66+
67+
/// Feed the message as any number of chunks, and sign the concatenation.
68+
st.absorb("mes");
69+
st.absorb("sage");
70+
let signature = st.sign();
71+
72+
/// Create a state for an incremental verifier.
73+
let mut st = kp.pk.verify_incremental(&signature).unwrap();
74+
75+
/// Feed the message as any number of chunks, and verify the concatenation.
76+
st.absorb("mess");
77+
st.absorb("age");
78+
assert!(st.verify().is_ok());
79+
```
80+
5581
## Cargo features
5682

5783
* `self-verify`: after having computed a new signature, verify that is it valid. This is slower, but improves resilience against fault attacks. It is enabled by default on WebAssembly targets.

src/ed25519.rs

Lines changed: 152 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -156,35 +156,60 @@ impl Deref for Signature {
156156
}
157157
}
158158

159-
impl PublicKey {
160-
/// Verifies that the signature `signature` is valid for the message
161-
/// `message`.
162-
pub fn verify(&self, message: impl AsRef<[u8]>, signature: &Signature) -> Result<(), Error> {
159+
/// The state of a streaming verification operation.
160+
pub struct VerifyingState {
161+
hasher: sha512::Hash,
162+
signature: Signature,
163+
a: GeP3,
164+
}
165+
166+
impl Drop for VerifyingState {
167+
fn drop(&mut self) {
168+
Mem::wipe(self.signature.0);
169+
}
170+
}
171+
172+
impl VerifyingState {
173+
fn new(pk: &PublicKey, signature: &Signature) -> Result<Self, Error> {
163174
let r = &signature[0..32];
164175
let s = &signature[32..64];
165176
sc_reject_noncanonical(s)?;
166-
if is_identity(self) || self.iter().fold(0, |acc, x| acc | x) == 0 {
177+
if is_identity(pk) || pk.iter().fold(0, |acc, x| acc | x) == 0 {
167178
return Err(Error::WeakPublicKey);
168179
}
169-
let a = match GeP3::from_bytes_negate_vartime(self) {
180+
let a = match GeP3::from_bytes_negate_vartime(pk) {
170181
Some(g) => g,
171182
None => {
172183
return Err(Error::InvalidPublicKey);
173184
}
174185
};
175-
176186
let mut hasher = sha512::Hash::new();
177187
hasher.update(r);
178-
hasher.update(&self[..]);
179-
hasher.update(message);
180-
let mut hash = hasher.finalize();
188+
hasher.update(&pk[..]);
189+
Ok(VerifyingState {
190+
hasher,
191+
signature: *signature,
192+
a,
193+
})
194+
}
195+
196+
/// Appends data to the message being verified.
197+
pub fn absorb(&mut self, chunk: impl AsRef<[u8]>) {
198+
self.hasher.update(chunk)
199+
}
200+
201+
/// Verifies the signature and return it.
202+
pub fn verify(&self) -> Result<(), Error> {
203+
let s = &self.signature[32..64];
204+
205+
let mut hash = self.hasher.finalize();
181206
sc_reduce(&mut hash);
182207

183-
let r = GeP2::double_scalarmult_vartime(hash.as_ref(), a, s);
208+
let r = GeP2::double_scalarmult_vartime(hash.as_ref(), self.a, s);
184209
if r.to_bytes()
185210
.as_ref()
186211
.iter()
187-
.zip(signature.iter())
212+
.zip(self.signature.iter())
188213
.fold(0, |acc, (x, y)| acc | (x ^ y))
189214
!= 0
190215
{
@@ -195,7 +220,99 @@ impl PublicKey {
195220
}
196221
}
197222

223+
impl PublicKey {
224+
/// Verify the signature of a multi-part message (streaming).
225+
pub fn verify_incremental(&self, signature: &Signature) -> Result<VerifyingState, Error> {
226+
VerifyingState::new(self, signature)
227+
}
228+
229+
/// Verifies that the signature `signature` is valid for the message
230+
/// `message`.
231+
pub fn verify(&self, message: impl AsRef<[u8]>, signature: &Signature) -> Result<(), Error> {
232+
let mut st = VerifyingState::new(self, signature)?;
233+
st.absorb(message);
234+
st.verify()
235+
}
236+
}
237+
238+
/// The state of a streaming signature operation.
239+
pub struct SigningState {
240+
hasher: sha512::Hash,
241+
az: [u8; 64],
242+
nonce: [u8; 64],
243+
}
244+
245+
impl Drop for SigningState {
246+
fn drop(&mut self) {
247+
Mem::wipe(self.az);
248+
Mem::wipe(self.nonce);
249+
}
250+
}
251+
252+
impl SigningState {
253+
fn new(nonce: [u8; 64], az: [u8; 64], pk_: &[u8]) -> Self {
254+
let mut prefix: [u8; 64] = [0; 64];
255+
let r = ge_scalarmult_base(&nonce[0..32]);
256+
prefix[0..32].copy_from_slice(&r.to_bytes()[..]);
257+
prefix[32..64].copy_from_slice(pk_);
258+
259+
let mut st = sha512::Hash::new();
260+
st.update(prefix);
261+
262+
SigningState {
263+
hasher: st,
264+
nonce,
265+
az,
266+
}
267+
}
268+
269+
/// Appends data to the message being signed.
270+
pub fn absorb(&mut self, chunk: impl AsRef<[u8]>) {
271+
self.hasher.update(chunk)
272+
}
273+
274+
/// Computes the signature and return it.
275+
pub fn sign(self) -> Signature {
276+
let mut signature: [u8; 64] = [0; 64];
277+
let r = ge_scalarmult_base(&self.nonce[0..32]);
278+
signature[0..32].copy_from_slice(&r.to_bytes()[..]);
279+
let mut hram = self.hasher.finalize();
280+
sc_reduce(&mut hram);
281+
sc_muladd(
282+
&mut signature[32..64],
283+
&hram[0..32],
284+
&self.az[0..32],
285+
&self.nonce[0..32],
286+
);
287+
Signature(signature)
288+
}
289+
}
290+
198291
impl SecretKey {
292+
/// Sign a multi-part message (streaming API).
293+
/// It is critical for `noise` to never repeat.
294+
pub fn sign_incremental(&self, noise: Noise) -> SigningState {
295+
let seed = &self[0..32];
296+
let pk = &self[32..64];
297+
let az: [u8; 64] = {
298+
let mut hash_output = sha512::Hash::hash(seed);
299+
hash_output[0] &= 248;
300+
hash_output[31] &= 63;
301+
hash_output[31] |= 64;
302+
hash_output
303+
};
304+
let mut st = sha512::Hash::new();
305+
#[cfg(feature = "random")]
306+
{
307+
let additional_noise = Noise::generate();
308+
st.update(additional_noise.as_ref());
309+
}
310+
st.update(noise.as_ref());
311+
st.update(seed);
312+
let nonce = st.finalize();
313+
SigningState::new(nonce, az, pk)
314+
}
315+
199316
/// Computes a signature for the message `message` using the secret key.
200317
/// The noise parameter is optional, but recommended in order to mitigate
201318
/// fault attacks.
@@ -222,22 +339,9 @@ impl SecretKey {
222339
sc_reduce(&mut hash_output[0..64]);
223340
hash_output
224341
};
225-
let mut signature: [u8; 64] = [0; 64];
226-
let r = ge_scalarmult_base(&nonce[0..32]);
227-
signature[0..32].copy_from_slice(&r.to_bytes()[..]);
228-
signature[32..64].copy_from_slice(pk);
229-
let mut hasher = sha512::Hash::new();
230-
hasher.update(signature.as_ref());
231-
hasher.update(&message);
232-
let mut hram = hasher.finalize();
233-
sc_reduce(&mut hram);
234-
sc_muladd(
235-
&mut signature[32..64],
236-
&hram[0..32],
237-
&az[0..32],
238-
&nonce[0..32],
239-
);
240-
let signature = Signature(signature);
342+
let mut st = SigningState::new(nonce, az, pk);
343+
st.absorb(&message);
344+
let signature = st.sign();
241345

242346
#[cfg(feature = "self-verify")]
243347
{
@@ -246,6 +350,7 @@ impl SecretKey {
246350
.verify(message, &signature)
247351
.expect("Newly created signature cannot be verified");
248352
}
353+
249354
signature
250355
}
251356
}
@@ -751,3 +856,22 @@ fn test_blind_ed25519() {
751856
assert_eq!(Hex::decode_to_vec("947bacfabc63448f8955dc20630e069e58f37b72bb433ae17f2fa904ea860b44deb761705a3cc2168a6673ee0b41ff7765c7a4896941eec6833c1689315acb0b",
752857
None).unwrap(), signature.as_ref());
753858
}
859+
860+
#[test]
861+
fn test_streaming() {
862+
let kp = KeyPair::generate();
863+
864+
let msg1 = "mes";
865+
let msg2 = "sage";
866+
let mut st = kp.sk.sign_incremental(Noise::default());
867+
st.absorb(msg1);
868+
st.absorb(msg2);
869+
let signature = st.sign();
870+
871+
let msg1 = "mess";
872+
let msg2 = "age";
873+
let mut st = kp.pk.verify_incremental(&signature).unwrap();
874+
st.absorb(msg1);
875+
st.absorb(msg2);
876+
assert!(st.verify().is_ok());
877+
}

src/lib.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//! * Lightweight
88
//! * Zero dependencies if randomness is provided by the application
99
//! * Only one portable dependency (`getrandom`) if not
10+
//! * Supports incremental signatures (streaming API)
1011
//! * Safe and simple Rust interface.
1112
//!
1213
//! Example usage:
@@ -41,6 +42,33 @@
4142
//! println!("Signature as bytes: {:?}", signature_as_bytes);
4243
//! ```
4344
//!
45+
//! ## Incremental API example usage
46+
//!
47+
//! Messages can also be supplied as multiple parts (streaming API):
48+
//!
49+
//! ```rust
50+
//! use ed25519_compact::*;
51+
//!
52+
//! /// Creates a new key pair.
53+
//! let kp = KeyPair::generate();
54+
//!
55+
//! /// Create a state for an incremental signer.
56+
//! let mut st = kp.sk.sign_incremental(Noise::default());
57+
//!
58+
//! /// Feed the message as any number of chunks, and sign the concatenation.
59+
//! st.absorb("mes");
60+
//! st.absorb("sage");
61+
//! let signature = st.sign();
62+
//!
63+
//! /// Create a state for an incremental verifier.
64+
//! let mut st = kp.pk.verify_incremental(&signature).unwrap();
65+
//!
66+
//! /// Feed the message as any number of chunks, and verify the concatenation.
67+
//! st.absorb("mess");
68+
//! st.absorb("age");
69+
//! assert!(st.verify().is_ok());
70+
//! ```
71+
//!
4472
//! Cargo features:
4573
//!
4674
//! * `self-verify`: after having computed a new signature, verify that is it

0 commit comments

Comments
 (0)