lib: Membership proofs [failing]
This commit is contained in:
parent
8275508d8c
commit
a57de247d4
1 changed files with 127 additions and 29 deletions
156
src/lib.rs
156
src/lib.rs
|
@ -3,10 +3,11 @@ use std::collections::HashMap;
|
||||||
use ed25519_dalek as dalek;
|
use ed25519_dalek as dalek;
|
||||||
use ed25519_dalek::Verifier;
|
use ed25519_dalek::Verifier;
|
||||||
use rand::prelude::random;
|
use rand::prelude::random;
|
||||||
use rs_merkle::algorithms::Sha256 as Sha256Algorithm; // Imports from sha2 crate
|
|
||||||
use rs_merkle::{MerkleProof, MerkleTree};
|
use rs_merkle::{MerkleProof, MerkleTree};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
||||||
|
pub use rs_merkle::algorithms::Sha256 as DefaultHasher; // Imports from sha2 crate
|
||||||
|
|
||||||
/// TODO: Use hashes instead of public addresses.
|
/// TODO: Use hashes instead of public addresses.
|
||||||
/// TODO: Don't forget document endiannes.
|
/// TODO: Don't forget document endiannes.
|
||||||
/// TODO: Allow doubly signed obligations or implement abstract interfaces for Cert (see below).
|
/// TODO: Allow doubly signed obligations or implement abstract interfaces for Cert (see below).
|
||||||
|
@ -67,23 +68,63 @@ fn pseudo_anon_id(a: Address) -> PseudoAnonID {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Members {
|
pub struct Members {
|
||||||
data: MerkleTree<Sha256Algorithm>,
|
pub data: MerkleTree<DefaultHasher>,
|
||||||
indicies: HashMap<PseudoAnonID, usize>,
|
indicies: HashMap<PseudoAnonID, usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type MembershipProof = MerkleProof<Sha256Algorithm>;
|
pub struct Member {
|
||||||
|
pub id: PseudoAnonID,
|
||||||
|
index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MembershipProof {
|
||||||
|
subjects: Vec<Member>,
|
||||||
|
root: [u8; 32],
|
||||||
|
hashes: MerkleProof<DefaultHasher>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MembershipProof {
|
||||||
|
pub fn verify(&self) -> bool {
|
||||||
|
self.hashes.verify(
|
||||||
|
self.root,
|
||||||
|
&self.subjects.iter().map(|x| x.index).collect::<Vec<_>>(),
|
||||||
|
&self.subjects.iter().map(|x| x.id).collect::<Vec<_>>(),
|
||||||
|
self.subjects.len(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Members {
|
impl Members {
|
||||||
pub fn add(mut self, m: Address) {
|
pub fn add(mut self, m: Address) {
|
||||||
self.data.insert(m).commit()
|
self.data.insert(m).commit()
|
||||||
}
|
}
|
||||||
pub fn proof(&self, members: &[PseudoAnonID]) -> MembershipProof {
|
pub fn proof(&self, subjects: Vec<PseudoAnonID>) -> Result<MembershipProof, &'static str> {
|
||||||
self.data.proof(
|
if (1..subjects.len()).any(|i| subjects[i..].contains(&subjects[i - 1])) {
|
||||||
&members
|
return Err("Duplicate subjects");
|
||||||
|
}
|
||||||
|
let (subjects, indices) = {
|
||||||
|
let mut tosort = subjects
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| *self.indicies.get(x).unwrap())
|
.map(|x| Member {
|
||||||
.collect::<Vec<_>>(),
|
id: *x,
|
||||||
)
|
index: *self.indicies.get(x).unwrap(),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
tosort.sort_by_key(|x| x.index);
|
||||||
|
let indices: Vec<usize> = Vec::from_iter(tosort.iter().map(|x| x.index));
|
||||||
|
(tosort, indices)
|
||||||
|
};
|
||||||
|
Ok(MembershipProof {
|
||||||
|
subjects: subjects,
|
||||||
|
hashes: self.data.proof(&indices),
|
||||||
|
root: self.data.root().unwrap(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn index(&self, member: PseudoAnonID) -> Option<usize> {
|
||||||
|
match self.indicies.get(&member) {
|
||||||
|
Some(i) => Some(*i),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +158,12 @@ mod tests {
|
||||||
use ed25519_dalek::Signer;
|
use ed25519_dalek::Signer;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use rand::seq::IteratorRandom;
|
use rand::seq::IteratorRandom;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn make_address() -> Address {
|
||||||
|
random()
|
||||||
|
}
|
||||||
|
|
||||||
fn sk_gen() -> (dalek::SigningKey, [u8; dalek::SECRET_KEY_LENGTH], Address) {
|
fn sk_gen() -> (dalek::SigningKey, [u8; dalek::SECRET_KEY_LENGTH], Address) {
|
||||||
let skobj = dalek::SigningKey::from_bytes(&random());
|
let skobj = dalek::SigningKey::from_bytes(&random());
|
||||||
|
@ -177,13 +224,70 @@ mod tests {
|
||||||
v
|
v
|
||||||
});
|
});
|
||||||
|
|
||||||
static MEMBERS: Lazy<Members> = Lazy::new(|| Members {
|
static MEMBERS: Lazy<Members> = Lazy::new(|| {
|
||||||
data: MerkleTree::<Sha256Algorithm>::from_leaves(
|
let tree = MerkleTree::<DefaultHasher>::from_leaves(
|
||||||
&ADDRESSES.keys().map(|x| x.clone()).collect::<Vec<_>>(),
|
&ADDRESSES.keys().cloned().collect::<Vec<PseudoAnonID>>(),
|
||||||
),
|
);
|
||||||
indicies: HashMap::from_iter(ADDRESSES.keys().enumerate().map(|(i, x)| (x.clone(), i))),
|
let mut indices: HashMap<PseudoAnonID, usize> = HashMap::with_capacity(tree.leaves_len());
|
||||||
|
tree.leaves()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, x)| {
|
||||||
|
indices.insert(*x, i);
|
||||||
|
});
|
||||||
|
Members {
|
||||||
|
data: tree,
|
||||||
|
indicies: indices,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fn rand_member() -> Option<PseudoAnonID> {
|
||||||
|
MEMBERS
|
||||||
|
.indicies
|
||||||
|
.keys()
|
||||||
|
.choose(&mut rand::thread_rng())
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rand_unique_member_gen() -> Box<impl FnMut() -> PseudoAnonID> {
|
||||||
|
let mut cache: HashSet<PseudoAnonID> = HashSet::new();
|
||||||
|
// note: will go in endless loop if the cache >= set of members
|
||||||
|
Box::new(move || loop {
|
||||||
|
let rmem = rand_member();
|
||||||
|
if cache.get(&rmem.unwrap()).is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cache.insert(rmem.unwrap());
|
||||||
|
break rmem.unwrap();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sort_by_indices(m: Vec<PseudoAnonID>) -> Vec<PseudoAnonID> {
|
||||||
|
let mut res = m.clone();
|
||||||
|
res.sort_by_cached_key(|x| MEMBERS.indicies.get(x));
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rand_members(n: usize) -> Vec<PseudoAnonID> {
|
||||||
|
let l = if n > ADDRESSES.len() {
|
||||||
|
ADDRESSES.len()
|
||||||
|
} else {
|
||||||
|
n
|
||||||
|
};
|
||||||
|
let mut f = rand_unique_member_gen();
|
||||||
|
sort_by_indices(std::iter::from_fn(|| Some(f())).take(l).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rand_obligation() -> Obligation {
|
||||||
|
let mut r = rand_unique_member_gen();
|
||||||
|
Obligation {
|
||||||
|
from: r(),
|
||||||
|
to: r(),
|
||||||
|
value: random(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn serialize_obligation() {
|
fn serialize_obligation() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -213,19 +317,7 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn membership_cert_new() {
|
fn membership_cert_new() {
|
||||||
fn rand_member() -> PseudoAnonID {
|
let o = rand_obligation();
|
||||||
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 _c = Cert::new(
|
let _c = Cert::new(
|
||||||
o,
|
o,
|
||||||
AuthCert::new(
|
AuthCert::new(
|
||||||
|
@ -238,8 +330,14 @@ mod tests {
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
.expect("Auth failed"),
|
.expect("Auth failed"),
|
||||||
MEMBERS.proof(&[o.from, o.to]),
|
MEMBERS.proof([o.from, o.to].to_vec()).unwrap(),
|
||||||
);
|
);
|
||||||
assert!(true)
|
}
|
||||||
|
|
||||||
|
// FIXME: Failing test - some weirdnes in rs_merkle or indexes fiddling.
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn test_merkle() {
|
||||||
|
assert!(MEMBERS.proof(rand_members(2)).unwrap().verify());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue