provenance-mark 0.23.1

A cryptographically-secured system for establishing and verifying the authenticity of works
Documentation
use std::fmt::Formatter;

#[cfg(feature = "envelope")]
use bc_envelope::prelude::*;
use bc_rand::RandomNumberGenerator;
use dcbor::Date;
#[cfg(not(feature = "envelope"))]
use dcbor::prelude::*;
use serde::{Deserialize, Serialize};

use crate::{
    Error, ProvenanceMark, ProvenanceMarkResolution, ProvenanceSeed, Result,
    RngState,
    crypto_utils::sha256,
    util::{deserialize_base64, serialize_base64},
    xoshiro256starstar::Xoshiro256StarStar,
};

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct ProvenanceMarkGenerator {
    res: ProvenanceMarkResolution,
    seed: ProvenanceSeed,
    #[serde(rename = "chainID")]
    #[serde(
        serialize_with = "serialize_base64",
        deserialize_with = "deserialize_base64"
    )]
    chain_id: Vec<u8>,
    #[serde(rename = "nextSeq")]
    next_seq: u32,
    #[serde(rename = "rngState")]
    rng_state: RngState,
}

impl ProvenanceMarkGenerator {
    pub fn res(&self) -> &ProvenanceMarkResolution { &self.res }

    pub fn seed(&self) -> &ProvenanceSeed { &self.seed }

    pub fn chain_id(&self) -> &[u8] { &self.chain_id }

    pub fn next_seq(&self) -> u32 { self.next_seq }

    pub fn rng_state(&self) -> &RngState { &self.rng_state }
}

impl ProvenanceMarkGenerator {
    pub fn new_with_seed(
        res: ProvenanceMarkResolution,
        seed: ProvenanceSeed,
    ) -> Self {
        // Definitely don't use the bare seed as the chain ID!
        let digest1 = sha256(seed.to_bytes().as_ref());
        let chain_id = digest1[..res.link_length()].to_vec();
        let digest2 = sha256(digest1);
        Self::new(res, seed.clone(), chain_id, 0, digest2.into()).unwrap()
    }

    pub fn new_with_passphrase(
        res: ProvenanceMarkResolution,
        passphrase: &str,
    ) -> Self {
        let seed = ProvenanceSeed::new_with_passphrase(passphrase);
        Self::new_with_seed(res, seed)
    }

    pub fn new_using(
        res: ProvenanceMarkResolution,
        rng: &mut impl RandomNumberGenerator,
    ) -> Self {
        // Randomness for a new seed can come from any secure random number
        // generator.
        let seed = ProvenanceSeed::new_using(rng);
        Self::new_with_seed(res, seed)
    }

    pub fn new_random(res: ProvenanceMarkResolution) -> Self {
        let seed = ProvenanceSeed::new();
        Self::new_with_seed(res, seed)
    }

    pub fn new(
        res: ProvenanceMarkResolution,
        seed: ProvenanceSeed,
        chain_id: Vec<u8>,
        next_seq: u32,
        rng_state: RngState,
    ) -> Result<Self> {
        if chain_id.len() != res.link_length() {
            return Err(Error::InvalidChainIdLength {
                expected: res.link_length(),
                actual: chain_id.len(),
            });
        }
        Ok(Self { res, seed, chain_id, next_seq, rng_state })
    }

    pub fn next(
        &mut self,
        date: Date,
        info: Option<impl CBOREncodable>,
    ) -> ProvenanceMark {
        let data: [u8; 32] = self.rng_state.clone().into();
        let mut rng = Xoshiro256StarStar::from_data(&data);

        let seq = self.next_seq;
        self.next_seq += 1;

        let key;
        if seq == 0 {
            key = self.chain_id.clone();
        } else {
            // The randomness generated by the PRNG should be portable across
            // implementations.
            key = rng.next_bytes(self.res.link_length());
            self.rng_state = rng.to_data().into();
        }

        let mut next_rng = rng.clone();
        let next_key = next_rng.next_bytes(self.res.link_length());

        ProvenanceMark::new(
            self.res,
            key,
            next_key,
            self.chain_id.clone(),
            seq,
            date,
            info,
        )
        .unwrap()
    }
}

impl std::fmt::Display for ProvenanceMarkGenerator {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "ProvenanceMarkGenerator(chainID: {}, res: {}, seed: {}, nextSeq: {}, rngState: {:?})",
            hex::encode(&self.chain_id),
            self.res,
            self.seed.hex(),
            self.next_seq,
            self.rng_state
        )
    }
}

#[cfg(feature = "envelope")]
impl From<ProvenanceMarkGenerator> for Envelope {
    fn from(generator: ProvenanceMarkGenerator) -> Self {
        Envelope::new(CBOR::to_byte_string(generator.chain_id()))
            .add_type("provenance-generator")
            .add_assertion("res", generator.res().to_cbor())
            .add_assertion("seed", generator.seed().to_cbor())
            .add_assertion("next-seq", generator.next_seq())
            .add_assertion("rng-state", generator.rng_state().to_cbor())
    }
}

#[cfg(feature = "envelope")]
impl TryFrom<Envelope> for ProvenanceMarkGenerator {
    type Error = Error;

    fn try_from(envelope: Envelope) -> Result<Self> {
        envelope.check_type("provenance-generator")?;
        let chain_id: Vec<u8> = envelope.subject().try_byte_string()?;
        const EXPECTED_KEY_COUNT: usize = 5;
        let assertion_count = envelope.assertions().len();
        if assertion_count != EXPECTED_KEY_COUNT {
            return Err(Error::ExtraKeys(EXPECTED_KEY_COUNT, assertion_count));
        }
        let res: ProvenanceMarkResolution = envelope
            .object_for_predicate("res")?
            .try_leaf()?
            .try_into()?;
        let seed: ProvenanceSeed = envelope
            .object_for_predicate("seed")?
            .try_leaf()?
            .try_into()?;
        let next_seq: u32 = envelope
            .object_for_predicate("next-seq")?
            .try_leaf()?
            .try_into()?;
        let rng_state: RngState = envelope
            .object_for_predicate("rng-state")?
            .try_leaf()?
            .try_into()?;

        ProvenanceMarkGenerator::new(res, seed, chain_id, next_seq, rng_state)
    }
}