lib: Add membership and all-in-one cert, replace addresses with ids

This commit is contained in:
Davie Li 2024-04-23 16:16:57 +00:00
parent 81d2e8b116
commit 8275508d8c
2 changed files with 182 additions and 34 deletions

View file

@ -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"

View file

@ -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<Self, &'static str> {
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<Sha256Algorithm>,
indicies: HashMap<PseudoAnonID, usize>,
}
pub type MembershipProof = MerkleProof<Sha256Algorithm>;
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::<Vec<_>>(),
)
}
}
pub struct Cert {
pub subject: Obligation,
pub authenticity: AuthCert,
pub membership: MembershipProof,
pub salt: Salt,
pub hash: Vec<u8>,
}
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<ObligationFixture> = 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<HashMap<Address, AddrMeta>> = Lazy::new(|| {
let mut v: HashMap<Address, AddrMeta> = 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<Members> = Lazy::new(|| Members {
data: MerkleTree::<Sha256Algorithm>::from_leaves(
&ADDRESSES.keys().map(|x| x.clone()).collect::<Vec<_>>(),
),
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)
}
}