lib: Add membership and all-in-one cert, replace addresses with ids
This commit is contained in:
parent
81d2e8b116
commit
8275508d8c
2 changed files with 182 additions and 34 deletions
|
@ -10,3 +10,8 @@ path = "src/main.rs"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ed25519-dalek = "2.1.1"
|
ed25519-dalek = "2.1.1"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
rs_merkle = "1.4.2"
|
||||||
|
sha2 = "0.10.8"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
once_cell = "1.19.0"
|
||||||
|
|
211
src/lib.rs
211
src/lib.rs
|
@ -1,10 +1,11 @@
|
||||||
use dalek::Verifier;
|
use std::collections::HashMap;
|
||||||
use ed25519_dalek as dalek;
|
|
||||||
use rand::prelude as rand;
|
|
||||||
|
|
||||||
pub type Address = [u8; 32];
|
use ed25519_dalek as dalek;
|
||||||
pub type Signature = [u8; 64];
|
use ed25519_dalek::Verifier;
|
||||||
pub type Salt = [u8; 32];
|
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: Use hashes instead of public addresses.
|
||||||
/// TODO: Don't forget document endiannes.
|
/// 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
|
/// full crypto-conditions but better not to complicate things too
|
||||||
/// much.
|
/// much.
|
||||||
|
|
||||||
|
pub type Address = [u8; 32];
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
pub struct Obligation {
|
pub struct Obligation {
|
||||||
pub from: Address,
|
pub from: PseudoAnonID,
|
||||||
pub to: Address,
|
pub to: PseudoAnonID,
|
||||||
pub value: u32,
|
pub value: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,14 +32,16 @@ impl Obligation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type Signature = [u8; 64];
|
||||||
|
pub type Salt = [u8; 32];
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct Cert {
|
pub struct AuthCert {
|
||||||
pub subject: Obligation,
|
pub subject: Obligation,
|
||||||
pub signature: Signature,
|
pub signature: Signature,
|
||||||
pub salt: Salt,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cert {
|
impl AuthCert {
|
||||||
/// TODO: add optional salt argument
|
/// TODO: add optional salt argument
|
||||||
pub fn new(sub: Obligation, sig: Signature) -> Result<Self, &'static str> {
|
pub fn new(sub: Obligation, sig: Signature) -> Result<Self, &'static str> {
|
||||||
match dalek::VerifyingKey::from_bytes(&sub.from)
|
match dalek::VerifyingKey::from_bytes(&sub.from)
|
||||||
|
@ -46,17 +51,138 @@ impl Cert {
|
||||||
Ok(_) => Ok(Self {
|
Ok(_) => Ok(Self {
|
||||||
subject: sub,
|
subject: sub,
|
||||||
signature: sig,
|
signature: sig,
|
||||||
salt: rand::random(),
|
|
||||||
}),
|
}),
|
||||||
Err(_) => Err("Invalid signature"),
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::{Cert, Obligation};
|
use super::*;
|
||||||
use ed25519_dalek::{Signer, SigningKey};
|
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]
|
#[test]
|
||||||
fn serialize_obligation() {
|
fn serialize_obligation() {
|
||||||
|
@ -76,27 +202,44 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cert_new() {
|
fn authenticity_cert_new() {
|
||||||
let sk_gen = || {
|
let sig = OFIX.f.sign(&OFIX.o.serialize()[..]).to_bytes();
|
||||||
let skobj = SigningKey::from_bytes(&rand::random());
|
let cert = AuthCert::new(OFIX.o, sig);
|
||||||
(
|
assert!(cert.is_ok());
|
||||||
skobj.clone(),
|
assert_eq!(cert.unwrap().subject, OFIX.o);
|
||||||
skobj.to_bytes(),
|
assert_eq!(cert.unwrap().signature, sig);
|
||||||
skobj.verifying_key().to_bytes(),
|
assert!(AuthCert::new(OFIX.o, [0; 64]).is_err());
|
||||||
)
|
}
|
||||||
};
|
|
||||||
let (f, _, fpk) = sk_gen();
|
#[test]
|
||||||
let (_, _, tpk) = sk_gen();
|
fn membership_cert_new() {
|
||||||
let sub = Obligation {
|
fn rand_member() -> PseudoAnonID {
|
||||||
from: fpk,
|
assert_ne!(ADDRESSES.len(), 0);
|
||||||
to: tpk,
|
*MEMBERS
|
||||||
|
.indicies
|
||||||
|
.keys()
|
||||||
|
.choose(&mut rand::thread_rng())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
let o = Obligation {
|
||||||
|
from: rand_member(),
|
||||||
|
to: rand_member(),
|
||||||
value: 1000,
|
value: 1000,
|
||||||
};
|
};
|
||||||
let sig = f.sign(&sub.serialize()[..]).to_bytes();
|
let _c = Cert::new(
|
||||||
let cert = Cert::new(sub, sig);
|
o,
|
||||||
assert!(cert.is_ok());
|
AuthCert::new(
|
||||||
assert_eq!(cert.unwrap().subject, sub);
|
o,
|
||||||
assert_eq!(cert.unwrap().signature, sig);
|
ADDRESSES
|
||||||
assert!(Cert::new(sub, [0; 64]).is_err());
|
.get(&o.from)
|
||||||
|
.unwrap()
|
||||||
|
.sk
|
||||||
|
.sign(&o.serialize())
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
.expect("Auth failed"),
|
||||||
|
MEMBERS.proof(&[o.from, o.to]),
|
||||||
|
);
|
||||||
|
assert!(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue