diff --git a/Cargo.toml b/Cargo.toml index 6675cc3..59bdbed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ bullet-proof-sizing = [] dev = ["clippy"] [dependencies] +arbitrary = { version = "0.4.7", optional = true, features = ["derive"] } arrayvec = "0.3" clippy = {version = "0.0", optional = true} rand = "0.5" diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index b1c23b6..3730008 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -14,6 +14,7 @@ libfuzzer-sys = "0.3" [dependencies.grin_secp256k1zkp] path = ".." +features = ["arbitrary"] # Prevent this from interfering with workspaces [workspace] diff --git a/fuzz/fuzz_targets/fuzz_aggsig.rs b/fuzz/fuzz_targets/fuzz_aggsig.rs index e5b681e..c440dda 100644 --- a/fuzz/fuzz_targets/fuzz_aggsig.rs +++ b/fuzz/fuzz_targets/fuzz_aggsig.rs @@ -13,54 +13,50 @@ use secp256k1zkp::{ }; use secp256k1zkp::aggsig::AggSigContext; -use secp256k1zkp::rand::{Rng, thread_rng}; -fuzz_target!(|data: &[u8]| { - let numkeys = 3; - if data.len() < (numkeys + 1) * 32 { - return (); - } +fuzz_target!(|keys_msg: (Vec<(SecretKey, PublicKey)>, Message)| { + let (keys, msg) = keys_msg; + let numkeys = keys.len(); + + if numkeys == 0 { return } - let mut rng = thread_rng(); let secp = Secp256k1::with_caps(ContextFlag::Full); + + // public keys for valid verification let mut pks: Vec = Vec::with_capacity(numkeys); - let mut keypairs: Vec<(SecretKey, PublicKey)> = Vec::with_capacity(numkeys); - for i in 0..numkeys { - if let Ok(sk) = SecretKey::from_slice(&secp, &data[i*32..(i+1)*32]) { - let pk = PublicKey::from_secret_key(&secp, &sk).unwrap(); - pks.push(pk.clone()); - keypairs.push((sk, pk)); - } else { - let (sk, pk) = secp.generate_keypair(&mut rng).unwrap(); - pks.push(pk.clone()); - keypairs.push((sk, pk)); - } + for (sk, _) in keys.iter() { + let pk = PublicKey::from_secret_key(&secp, &sk).unwrap(); + pks.push(pk.clone()); } let aggsig = AggSigContext::new(&secp, &pks); + // generate signature nonces for i in 0..numkeys { if aggsig.generate_nonce(i) != true { panic!("failed to generate aggsig nonce: {}", i); } } - - let mut msg_in = [0u8; 32]; - rng.fill(&mut msg_in); - let msg = Message::from_slice(&msg_in).unwrap(); let mut partial_sigs: Vec = vec![]; - for (i, (ss, _)) in keypairs.iter().enumerate() { - match aggsig.partial_sign(msg.clone(), ss.clone(), i) { + // create partial signatures + for (i, (sk, _)) in keys.iter().enumerate() { + match aggsig.partial_sign(msg.clone(), sk.clone(), i) { Ok(res) => partial_sigs.push(res), Err(e) => panic!("error creating partial signature: {:?}", e), } } + // aggregate signatures match aggsig.combine_signatures(&partial_sigs) { - Ok(full_sig) => { let _ = aggsig.verify(full_sig, msg.clone(), &pks); () }, + Ok(full_sig) => { + // verify with valid keys + assert!(aggsig.verify(full_sig, msg.clone(), &pks)); + // verify with random keys, unlikely to ever return true + assert_eq!(aggsig.verify(full_sig, msg.clone(), &keys.iter().map(|(_, pk)| *pk).collect::>()), false); + }, Err(e) => panic!("error combining signatures: {:?}", e), } }); diff --git a/fuzz/fuzz_targets/fuzz_ecdh.rs b/fuzz/fuzz_targets/fuzz_ecdh.rs index 187040a..76db664 100644 --- a/fuzz/fuzz_targets/fuzz_ecdh.rs +++ b/fuzz/fuzz_targets/fuzz_ecdh.rs @@ -6,18 +6,9 @@ extern crate secp256k1zkp; use secp256k1zkp::{Secp256k1, PublicKey, SecretKey}; use secp256k1zkp::ecdh::SharedSecret; -fuzz_target!(|data: &[u8]| { - if data.len() < 32 { - return (); - } - +fuzz_target!(|keys: (SecretKey, PublicKey)| { let s = Secp256k1::new(); + let (sk, pk) = keys; - if let Ok(sk) = SecretKey::from_slice(&s, &data[..32]) { - match PublicKey::from_secret_key(&s, &sk) { - Ok(pk) => { let _ = SharedSecret::new(&s, &pk, &sk); () }, - Err(e) => panic!("cannot create public key from secret: {}", e), - } - } + let _ = SharedSecret::new(&s, &pk, &sk); }); - diff --git a/fuzz/fuzz_targets/fuzz_sign.rs b/fuzz/fuzz_targets/fuzz_sign.rs index 5e86e73..2d1a7fb 100644 --- a/fuzz/fuzz_targets/fuzz_sign.rs +++ b/fuzz/fuzz_targets/fuzz_sign.rs @@ -5,24 +5,17 @@ extern crate secp256k1zkp; use secp256k1zkp::{Message, Secp256k1, PublicKey, SecretKey}; -fuzz_target!(|data: &[u8]| { - if data.len() < 64 { - return (); - } - +fuzz_target!(|sk_msg: (SecretKey, Message)| { + let (sk, msg) = sk_msg; let s = Secp256k1::new(); - let msg = Message::from_slice(&data[..32]).unwrap(); - - if let Ok(sk) = SecretKey::from_slice(&s, &data[32..64]) { - match s.sign(&msg, &sk) { - Ok(sig) => { - match PublicKey::from_secret_key(&s, &sk) { - Ok(pk) => s.verify(&msg, &sig, &pk).unwrap(), - Err(e) => panic!("cannot create public key from secret: {}", e), - } + match s.sign(&msg, &sk) { + Ok(sig) => { + match PublicKey::from_secret_key(&s, &sk) { + Ok(pk) => s.verify(&msg, &sig, &pk).unwrap(), + Err(e) => panic!("cannot create public key from secret: {}", e), } - Err(e) => panic!("error creating signature: {}", e), - } + }, + Err(e) => panic!("error creating signature: {}", e), } }); diff --git a/src/ffi.rs b/src/ffi.rs index 8bab551..f23aea8 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -117,6 +117,23 @@ impl Signature { pub unsafe fn blank() -> Signature { mem::MaybeUninit::uninit().assume_init() } } +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary for Signature +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + let mut sig_bytes = [0 as c_uchar; 64]; + + let iter = u.arbitrary_iter::()?; + + for (i, byte) in iter.enumerate() { + if i == 64 { break; } + sig_bytes[i] = byte?; + } + + Ok(Signature(sig_bytes)) + } +} + impl RecoverableSignature { /// Create a new (zeroed) signature usable for the FFI interface pub fn new() -> RecoverableSignature { RecoverableSignature([0; 65]) } diff --git a/src/key.rs b/src/key.rs index 8d576ec..4438a1b 100644 --- a/src/key.rs +++ b/src/key.rs @@ -150,6 +150,24 @@ impl SecretKey { } } +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary for SecretKey +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + let mut sec_bytes = [0_u8; constants::SECRET_KEY_SIZE]; + + let s = Secp256k1::new(); + let iter = u.arbitrary_iter::()?; + + for (i, byte) in iter.enumerate() { + if i == constants::SECRET_KEY_SIZE { break; } + sec_bytes[i] = byte?; + } + + SecretKey::from_slice(&s, &sec_bytes).map_err(|_| arbitrary::Error::IncorrectFormat) + } +} + impl PublicKey { /// Creates a new zeroed out public key #[inline] @@ -402,6 +420,24 @@ impl Serialize for PublicKey { } } +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary for PublicKey +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + let mut pub_bytes = [0_u8; constants::PUBLIC_KEY_SIZE]; + + let s = Secp256k1::new(); + let iter = u.arbitrary_iter::()?; + + for (i, byte) in iter.enumerate() { + if i == constants::PUBLIC_KEY_SIZE { break; } + pub_bytes[i] = byte?; + } + + PublicKey::from_slice(&s, &pub_bytes).map_err(|_| arbitrary::Error::IncorrectFormat) + } +} + #[cfg(test)] mod test { extern crate rand_core; diff --git a/src/lib.rs b/src/lib.rs index d8486d5..c0d6a72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,6 +71,7 @@ pub struct RecoveryId(i32); /// An ECDSA signature #[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Signature(ffi::Signature); impl std::convert::AsRef<[u8]> for Signature { @@ -423,6 +424,7 @@ impl ops::Index for Signature { } /// A (hashed) message input to an ECDSA signature +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] pub struct Message([u8; constants::MESSAGE_SIZE]); impl Copy for Message {} impl_array_newtype!(Message, u8, constants::MESSAGE_SIZE); diff --git a/src/pedersen.rs b/src/pedersen.rs index aa7adb4..dd6f6ba 100644 --- a/src/pedersen.rs +++ b/src/pedersen.rs @@ -122,6 +122,23 @@ impl Commitment { } +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary for Commitment +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + let mut com_bytes = [0u8; constants::PEDERSEN_COMMITMENT_SIZE]; + + let iter = u.arbitrary_iter::()?; + + for (i, byte) in iter.enumerate() { + if i == constants::PEDERSEN_COMMITMENT_SIZE { break; } + com_bytes[i] = byte?; + } + + Ok(Commitment(com_bytes)) + } +} + /// A range proof. Typically much larger in memory that the above (~5k). #[derive(Copy)] pub struct RangeProof { @@ -249,6 +266,25 @@ impl RangeProof { } } +#[cfg(feature = "arbitrary")] +impl arbitrary::Arbitrary for RangeProof +{ + fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + let mut proof_bytes = [0u8; constants::MAX_PROOF_SIZE]; + let mut plen = 0; + + let iter = u.arbitrary_iter::()?; + + for (i, byte) in iter.enumerate() { + if i == constants::MAX_PROOF_SIZE { plen = i; break; } + proof_bytes[i] = byte?; + plen = i + 1; + } + + Ok(RangeProof{ proof: proof_bytes, plen: plen }) + } +} + /// A message included in a range proof. /// The message is recoverable by rewinding a range proof /// passing in the same nonce that was used to originally create the range proof.