diff --git a/Cargo.toml b/Cargo.toml index ed17f89..9f690bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,8 @@ path = "src/main.rs" [dependencies] ed25519-dalek = "2.1.1" rand = "0.8.5" +rs_merkle = "1.4.2" +sha2 = "0.10.8" + +[dev-dependencies] +once_cell = "1.19.0" diff --git a/src/lib.rs b/src/lib.rs index f9f8cef..5721a46 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,11 @@ -use dalek::Verifier; -use ed25519_dalek as dalek; -use rand::prelude as rand; +use std::collections::HashMap; -pub type Address = [u8; 32]; -pub type Signature = [u8; 64]; -pub type Salt = [u8; 32]; +use ed25519_dalek as dalek; +use ed25519_dalek::Verifier; +use rand::prelude::random; +use rs_merkle::algorithms::Sha256 as Sha256Algorithm; // Imports from sha2 crate +use rs_merkle::{MerkleProof, MerkleTree}; +use sha2::{Digest, Sha256}; /// TODO: Use hashes instead of public addresses. /// TODO: Don't forget document endiannes. @@ -16,10 +17,12 @@ pub type Salt = [u8; 32]; /// full crypto-conditions but better not to complicate things too /// much. +pub type Address = [u8; 32]; + #[derive(Copy, Clone, PartialEq, Debug)] pub struct Obligation { - pub from: Address, - pub to: Address, + pub from: PseudoAnonID, + pub to: PseudoAnonID, pub value: u32, } @@ -29,14 +32,16 @@ impl Obligation { } } +pub type Signature = [u8; 64]; +pub type Salt = [u8; 32]; + #[derive(Copy, Clone)] -pub struct Cert { +pub struct AuthCert { pub subject: Obligation, pub signature: Signature, - pub salt: Salt, } -impl Cert { +impl AuthCert { /// TODO: add optional salt argument pub fn new(sub: Obligation, sig: Signature) -> Result { match dalek::VerifyingKey::from_bytes(&sub.from) @@ -46,17 +51,138 @@ impl Cert { Ok(_) => Ok(Self { subject: sub, signature: sig, - salt: rand::random(), }), Err(_) => Err("Invalid signature"), } } } +pub type PseudoAnonID = [u8; 32]; + +fn pseudo_anon_id(a: Address) -> PseudoAnonID { + let mut h = Sha256::new(); + h.update(a); + let res: PseudoAnonID = h.finalize().into(); + res +} + +pub struct Members { + data: MerkleTree, + indicies: HashMap, +} + +pub type MembershipProof = MerkleProof; + +impl Members { + pub fn add(mut self, m: Address) { + self.data.insert(m).commit() + } + pub fn proof(&self, members: &[PseudoAnonID]) -> MembershipProof { + self.data.proof( + &members + .iter() + .map(|x| *self.indicies.get(x).unwrap()) + .collect::>(), + ) + } +} + +pub struct Cert { + pub subject: Obligation, + pub authenticity: AuthCert, + pub membership: MembershipProof, + pub salt: Salt, + pub hash: Vec, +} + +impl Cert { + pub fn new(sub: Obligation, auth: AuthCert, mem: MembershipProof) -> Self { + let mut h = Sha256::new(); + let salt: Salt = random(); + h.update(sub.serialize()); + h.update(salt); + Self { + subject: sub, + authenticity: auth, + membership: mem, + salt: salt, + hash: h.finalize().to_vec(), + } + } +} + #[cfg(test)] mod tests { - use crate::{Cert, Obligation}; - use ed25519_dalek::{Signer, SigningKey}; + use super::*; + use ed25519_dalek::Signer; + use once_cell::sync::Lazy; + use rand::seq::IteratorRandom; + + fn sk_gen() -> (dalek::SigningKey, [u8; dalek::SECRET_KEY_LENGTH], Address) { + let skobj = dalek::SigningKey::from_bytes(&random()); + ( + skobj.clone(), + skobj.to_bytes(), + skobj.verifying_key().to_bytes(), + ) + } + + #[allow(dead_code)] + struct ObligationFixture { + o: Obligation, + f: dalek::SigningKey, + t: dalek::SigningKey, + fpk: [u8; 32], + tpk: [u8; 32], + fsk: [u8; 32], + tsk: [u8; 32], + } + + static OFIX: Lazy = Lazy::new(|| { + let (f, fsk, fpk) = sk_gen(); + let (t, tsk, tpk) = sk_gen(); + ObligationFixture { + o: Obligation { + from: fpk, + to: tpk, + value: 1000, + }, + f: f, + t: t, + fpk: fpk, + tpk: tpk, + fsk: fsk, + tsk: tsk, + } + }); + + #[allow(dead_code)] + struct AddrMeta { + sk: dalek::SigningKey, + id: PseudoAnonID, + } + + static ADDRESSES: Lazy> = Lazy::new(|| { + let mut v: HashMap = HashMap::new(); + for _i in 0..10 { + let (obj, _, pk) = sk_gen(); + v.insert( + pk, + AddrMeta { + sk: obj, + id: pseudo_anon_id(pk), + }, + ); + } + v + }); + + static MEMBERS: Lazy = Lazy::new(|| Members { + data: MerkleTree::::from_leaves( + &ADDRESSES.keys().map(|x| x.clone()).collect::>(), + ), + indicies: HashMap::from_iter(ADDRESSES.keys().enumerate().map(|(i, x)| (x.clone(), i))), + }); #[test] fn serialize_obligation() { @@ -76,27 +202,44 @@ mod tests { } #[test] - fn cert_new() { - let sk_gen = || { - let skobj = SigningKey::from_bytes(&rand::random()); - ( - skobj.clone(), - skobj.to_bytes(), - skobj.verifying_key().to_bytes(), - ) - }; - let (f, _, fpk) = sk_gen(); - let (_, _, tpk) = sk_gen(); - let sub = Obligation { - from: fpk, - to: tpk, + fn authenticity_cert_new() { + let sig = OFIX.f.sign(&OFIX.o.serialize()[..]).to_bytes(); + let cert = AuthCert::new(OFIX.o, sig); + assert!(cert.is_ok()); + assert_eq!(cert.unwrap().subject, OFIX.o); + assert_eq!(cert.unwrap().signature, sig); + assert!(AuthCert::new(OFIX.o, [0; 64]).is_err()); + } + + #[test] + fn membership_cert_new() { + fn rand_member() -> PseudoAnonID { + assert_ne!(ADDRESSES.len(), 0); + *MEMBERS + .indicies + .keys() + .choose(&mut rand::thread_rng()) + .unwrap() + } + let o = Obligation { + from: rand_member(), + to: rand_member(), value: 1000, }; - let sig = f.sign(&sub.serialize()[..]).to_bytes(); - let cert = Cert::new(sub, sig); - assert!(cert.is_ok()); - assert_eq!(cert.unwrap().subject, sub); - assert_eq!(cert.unwrap().signature, sig); - assert!(Cert::new(sub, [0; 64]).is_err()); + let _c = Cert::new( + o, + AuthCert::new( + o, + ADDRESSES + .get(&o.from) + .unwrap() + .sk + .sign(&o.serialize()) + .into(), + ) + .expect("Auth failed"), + MEMBERS.proof(&[o.from, o.to]), + ); + assert!(true) } }