aven-protocol 0.1.0

Storage-independent encrypted sync protocol for Aven
Documentation
use anyhow::{Result, ensure};
use ed25519_dalek::{Signature, VerifyingKey};

pub use crate::codec::{bytes, cce, hash};

use super::{CLAIM_BYTES, GENESIS_BYTES, Genesis, LocalSharedStatePackageContext};

pub const MEMBERSHIP_SIGN: &str = "aven-e2ee/v1/membership/sign";

pub fn state_hash(state: &[u8]) -> [u8; 32] {
    hash(&cce("aven-e2ee/v1/membership/state", &[state]))
}

/// Signed membership core shared by genesis, publication and later actions.
pub fn membership_core(
    vault: &[u8],
    sequence: u64,
    head: &[u8],
    signer: &[u8],
    action: u8,
    body: &[u8],
    state: &[u8],
) -> Vec<u8> {
    let mut out = vec![1];
    bytes(&mut out, vault);
    out.extend(sequence.to_be_bytes());
    bytes(&mut out, head);
    out.push(1);
    bytes(&mut out, signer);
    out.push(action);
    bytes(&mut out, body);
    bytes(&mut out, &state_hash(state));
    out
}

pub fn valid(condition: bool) -> Result<()> {
    ensure!(condition, "error seed-claim-invalid");
    Ok(())
}

pub struct Reader<'a>(pub &'a [u8]);

impl<'a> Reader<'a> {
    pub fn take(&mut self, n: usize) -> Result<&'a [u8]> {
        valid(n <= self.0.len())?;
        let (value, rest) = self.0.split_at(n);
        self.0 = rest;
        Ok(value)
    }

    pub fn array<const N: usize>(&mut self) -> Result<[u8; N]> {
        Ok(self.take(N)?.try_into()?)
    }

    pub fn blob(&mut self, n: usize) -> Result<&'a [u8]> {
        valid(u32::from_be_bytes(self.array()?) as u64 == n as u64)?;
        self.take(n)
    }

    pub fn end(self) -> Result<()> {
        valid(self.0.is_empty())
    }
}

pub fn claim_record(request: &[u8]) -> Result<&[u8]> {
    valid(request.len() == CLAIM_BYTES)?;
    let mut reader = Reader(request);
    valid(reader.take(6)? == b"AVCL\0\x01")?;
    let record = reader.blob(GENESIS_BYTES)?;
    reader.end()?;
    Ok(record)
}

type Components<'a> = (&'a [u8], &'a [u8], &'a [u8], &'a [u8]);

pub fn components(record: &[u8]) -> Result<Components<'_>> {
    valid(record.len() == GENESIS_BYTES)?;
    let mut reader = Reader(record);
    valid(reader.take(1)? == [1])?;
    let parts = (
        reader.blob(229)?,
        reader.blob(280)?,
        reader.blob(312)?,
        reader.blob(64)?,
    );
    reader.end()?;
    Ok(parts)
}

pub(super) fn parse(record: &[u8]) -> Result<Genesis> {
    let (core, state, attachments, signature) = components(record)?;
    let mut r = Reader(state);
    valid(r.take(7)? == b"AVGS\0\x01\x01")?;
    let vault_id = r.array()?;
    valid(r.take(4)? == [0, 0, 0, 1])?;
    let device = r.array()?;
    let signing_public = r.array()?;
    let hpke_public = r.array()?;
    let verifier = r.array()?;
    valid(r.take(4)? == 1_u32.to_be_bytes())?;
    let claim = r.array()?;
    valid(r.take(1)? == [1])?;
    let generation_id = r.array()?;
    let generation_commitment = r.array()?;
    valid(r.take(8)? == [0; 8])?;
    r.end()?;

    let mut r = Reader(core);
    valid(r.take(1)? == [1])?;
    valid(r.blob(32)? == vault_id)?;
    valid(r.take(8)? == [0; 8])?;
    valid(r.blob(32)? == [0; 32])?;
    valid(r.take(1)? == [1])?;
    valid(r.blob(32)? == device)?;
    valid(r.take(1)? == [1])?;
    let mut body = Reader(r.blob(70)?);
    valid(body.take(6)? == b"AVGC\0\x01")?;
    let setup = body.array()?;
    valid(body.take(32)? == claim)?;
    body.end()?;
    valid(r.blob(32)? == state_hash(state))?;
    r.end()?;

    let mut r = Reader(attachments);
    valid(r.take(9)? == b"AVGA\0\x01\x01\x01\x01")?;
    valid(r.blob(32)? == device)?;
    valid(r.blob(32)? == hpke_public)?;
    r.blob(32)?;
    r.blob(191)?;
    r.end()?;

    let public = VerifyingKey::from_bytes(&signing_public)
        .map_err(|_| anyhow::anyhow!("error seed-claim-signature"))?;
    let signature = Signature::from_slice(signature)
        .map_err(|_| anyhow::anyhow!("error seed-claim-signature"))?;
    public
        .verify_strict(&cce(MEMBERSHIP_SIGN, &[core, attachments]), &signature)
        .map_err(|_| anyhow::anyhow!("error seed-claim-signature"))?;
    Ok(Genesis {
        record: record.try_into()?,
        context: LocalSharedStatePackageContext {
            vault_id,
            generation_id,
        },
        setup,
        claim,
        device,
        signing_public,
        hpke_public,
        verifier,
        generation_commitment,
    })
}