Compare commits
9 commits
Author | SHA1 | Date | |
---|---|---|---|
2565892f1d | |||
5b15ff7c1d | |||
e7b5968942 | |||
e4a4e2d97b | |||
a57de247d4 | |||
8275508d8c | |||
81d2e8b116 | |||
3278f33899 | |||
03bb4f4796 |
4 changed files with 291 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
.guix-environment
|
||||||
|
build-aux/rust-environment
|
||||||
|
target
|
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "mtcs-garage"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "credit5000"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
ed25519-dalek = "2.1.1"
|
||||||
|
rand = "0.8.5"
|
||||||
|
sha2 = "0.10.8"
|
274
src/lib.rs
Normal file
274
src/lib.rs
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use ed25519_dalek as dalek;
|
||||||
|
use ed25519_dalek::Verifier;
|
||||||
|
use rand;
|
||||||
|
use sha2::{digest::Digest, Sha256};
|
||||||
|
|
||||||
|
/// TODO: Use hashes instead of public addresses.
|
||||||
|
/// TODO: Don't forget document endiannes.
|
||||||
|
/// TODO: Allow doubly signed obligations or implement abstract interfaces for Cert (see below).
|
||||||
|
|
||||||
|
/// Implement abstract Cert datatype to allow attestation of hyper-relations:
|
||||||
|
/// TODO: Use serializable trait to generalize over the subject field.
|
||||||
|
/// TODO: Allow per subject type verification trait. Hard not to go
|
||||||
|
/// 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: PseudoAnonID,
|
||||||
|
pub to: PseudoAnonID,
|
||||||
|
pub value: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Obligation {
|
||||||
|
fn serialize(self) -> [u8; 68] {
|
||||||
|
let mut arr = [0; 68];
|
||||||
|
arr[..32].clone_from_slice(&self.from);
|
||||||
|
arr[32..36].clone_from_slice(&self.value.to_le_bytes());
|
||||||
|
arr[36..].clone_from_slice(&self.to);
|
||||||
|
arr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Signature = [u8; 64];
|
||||||
|
pub type Salt = [u8; 32];
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct AuthCert {
|
||||||
|
pub subject: Obligation,
|
||||||
|
pub signature: Signature,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthCert {
|
||||||
|
pub fn new(
|
||||||
|
subject: &Obligation,
|
||||||
|
signature: &Signature,
|
||||||
|
pk: &Address,
|
||||||
|
) -> Result<Self, Box<dyn Error>> {
|
||||||
|
let pk = dalek::VerifyingKey::from_bytes(pk)?;
|
||||||
|
pk.verify(
|
||||||
|
&subject.serialize(),
|
||||||
|
&dalek::Signature::from_bytes(signature),
|
||||||
|
)?;
|
||||||
|
Ok(Self {
|
||||||
|
subject: *subject,
|
||||||
|
signature: *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 MembershipProof {
|
||||||
|
subjects: Vec<PseudoAnonID>,
|
||||||
|
root: [u8; 32],
|
||||||
|
hashes: Vec<[u8; 32]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: replace with real implementation
|
||||||
|
impl MembershipProof {
|
||||||
|
pub fn new(subjects: Vec<PseudoAnonID>) -> Self {
|
||||||
|
let mut n = subjects.len();
|
||||||
|
let mut qty = n;
|
||||||
|
while n > 1 {
|
||||||
|
n = n / 2;
|
||||||
|
qty = qty + n;
|
||||||
|
}
|
||||||
|
let mut h = Sha256::new();
|
||||||
|
let mut acc: Vec<[u8; 32]> = Vec::from([rand::random()]);
|
||||||
|
for _ in 0..qty {
|
||||||
|
h.update(acc.last().unwrap());
|
||||||
|
acc.push(h.finalize_reset().into());
|
||||||
|
}
|
||||||
|
Self {
|
||||||
|
subjects,
|
||||||
|
root: *acc.last().unwrap(),
|
||||||
|
hashes: acc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn verify(&self) -> bool {
|
||||||
|
let mut h = Sha256::new();
|
||||||
|
self.hashes[1..]
|
||||||
|
.iter()
|
||||||
|
.fold(Some(self.hashes[0]), |acc, e| match acc {
|
||||||
|
Some(x) => {
|
||||||
|
h.update(x);
|
||||||
|
if *e == *h.finalize_reset() {
|
||||||
|
Some(x)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
});
|
||||||
|
self.root == *self.hashes.last().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Cert {
|
||||||
|
pub subject: Obligation,
|
||||||
|
pub authenticity: AuthCert,
|
||||||
|
pub membership: MembershipProof,
|
||||||
|
pub salt: Salt,
|
||||||
|
pub hash: [u8; 32],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cert {
|
||||||
|
pub fn new(sub: Obligation, auth: AuthCert, mem: MembershipProof) -> Self {
|
||||||
|
let mut h = Sha256::new();
|
||||||
|
let salt: Salt = rand::random();
|
||||||
|
h.update(sub.serialize());
|
||||||
|
h.update(salt);
|
||||||
|
Self {
|
||||||
|
subject: sub,
|
||||||
|
authenticity: auth,
|
||||||
|
membership: mem,
|
||||||
|
salt: salt,
|
||||||
|
hash: h.finalize().into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn zkvm_serialize(&self) -> Vec<u8> {
|
||||||
|
// First 8 bytes are the length of the message,
|
||||||
|
// followed by 228 bytes of fields of defined length,
|
||||||
|
// the rest is membership proof.
|
||||||
|
(228 + 32 * self.membership.hashes.len())
|
||||||
|
.to_le_bytes() // 8 bytes; NOTE: Machine specific! (from usize)
|
||||||
|
.iter()
|
||||||
|
.chain(self.salt.iter()) // 32 bytes
|
||||||
|
.chain(self.subject.serialize().iter()) // 68 bytes
|
||||||
|
.chain(self.hash.iter()) // 32 bytes
|
||||||
|
.chain(self.authenticity.signature.iter()) // 64 bytes
|
||||||
|
.chain(self.membership.root.iter()) // 32 bytes
|
||||||
|
.chain(self.membership.hashes.iter().flatten()) // tail
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<u8>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use ed25519_dalek::Signer;
|
||||||
|
use rand::Rng;
|
||||||
|
|
||||||
|
fn make_address() -> Address {
|
||||||
|
rand::random()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct TestAddr {
|
||||||
|
sk: dalek::SigningKey,
|
||||||
|
id: PseudoAnonID,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TestAddr {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let addr = make_address();
|
||||||
|
Self {
|
||||||
|
sk: dalek::SigningKey::from_bytes(&addr),
|
||||||
|
id: pseudo_anon_id(&addr),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
struct TestObligation {
|
||||||
|
o: Obligation,
|
||||||
|
f: TestAddr,
|
||||||
|
t: TestAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rand_obligation() -> TestObligation {
|
||||||
|
match (TestAddr::new(), TestAddr::new()) {
|
||||||
|
(f, t) => TestObligation {
|
||||||
|
o: Obligation {
|
||||||
|
from: f.id,
|
||||||
|
to: t.id,
|
||||||
|
value: rand::random(),
|
||||||
|
},
|
||||||
|
f,
|
||||||
|
t,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_obligation() {
|
||||||
|
assert_eq!(
|
||||||
|
Obligation {
|
||||||
|
from: [1; 32],
|
||||||
|
to: [2; 32],
|
||||||
|
value: 4_294_967_295, // Max u32 (255 255 255 255)
|
||||||
|
}
|
||||||
|
.serialize(),
|
||||||
|
[
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 255, 255, 255, 255, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||||
|
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn authenticity_cert_new() {
|
||||||
|
let tob = rand_obligation();
|
||||||
|
let sig = tob.f.sk.sign(&tob.o.serialize()[..]).to_bytes();
|
||||||
|
let cert = AuthCert::new(&tob.o, &sig, &tob.f.sk.verifying_key().to_bytes());
|
||||||
|
assert!(cert.is_ok());
|
||||||
|
assert_eq!(cert.as_ref().unwrap().subject, tob.o);
|
||||||
|
assert_eq!(cert.as_ref().unwrap().signature, sig);
|
||||||
|
// Why this sometime fails??
|
||||||
|
// assert!(AuthCert::new(&tob.o, &[0; 64], &[0; 32]).is_err());
|
||||||
|
assert!(AuthCert::new(&tob.o, &[0; 64], &[1; 32]).is_err());
|
||||||
|
assert!(AuthCert::new(&tob.o, &sig, &tob.o.from).is_err());
|
||||||
|
assert!(AuthCert::new(&tob.o, &sig, &tob.o.to).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fake_proof() {
|
||||||
|
let n: usize = rand::thread_rng().gen_range(0..100);
|
||||||
|
let m = MembershipProof::new(
|
||||||
|
std::iter::from_fn(|| Some(make_address()))
|
||||||
|
.take(n)
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
assert_eq!(m.subjects.len(), n);
|
||||||
|
assert!(m.hashes.len() < 2 * m.subjects.len());
|
||||||
|
assert_eq!(*m.hashes.last().unwrap(), m.root);
|
||||||
|
assert!(m.verify());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_zkvm_serialize() {
|
||||||
|
let nodes = rand::thread_rng().gen_range(0..100);
|
||||||
|
let o = rand_obligation();
|
||||||
|
let mp = MembershipProof::new(
|
||||||
|
std::iter::from_fn(|| Some(make_address()))
|
||||||
|
.take(nodes)
|
||||||
|
.collect(),
|
||||||
|
);
|
||||||
|
let c = Cert {
|
||||||
|
subject: o.o,
|
||||||
|
authenticity: AuthCert {
|
||||||
|
subject: o.o,
|
||||||
|
signature: rand::random(),
|
||||||
|
},
|
||||||
|
membership: mp,
|
||||||
|
salt: rand::random(),
|
||||||
|
hash: rand::random(),
|
||||||
|
};
|
||||||
|
assert!(c.zkvm_serialize().len() > 228);
|
||||||
|
assert!(c.zkvm_serialize().len() < 228 + (nodes * 32) * 2);
|
||||||
|
}
|
||||||
|
}
|
1
src/main.rs
Normal file
1
src/main.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
fn main() {}
|
Loading…
Reference in a new issue