plexi_core/
auditor.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
use std::collections::HashMap;

#[cfg(feature = "auditor")]
use akd::{
    append_only_zks::InsertMode,
    local_auditing::AuditBlobName,
    storage::{memory::AsyncInMemoryDatabase, StorageManager},
    Azks, Digest, SingleAppendOnlyProof, WhatsAppV1Configuration,
};
#[cfg(feature = "auditor")]
use anyhow::anyhow;
use anyhow::Context as _;
#[cfg(feature = "auditor")]
use protobuf::Message as _;
use serde::{Deserialize, Serialize};
#[cfg(feature = "openapi")]
use utoipa::ToSchema;

#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct KeyInfo {
    public_key: String,
    not_before: u64,
}

impl KeyInfo {
    pub fn new(public_key: &str, not_before: u64) -> Self {
        Self {
            public_key: public_key.into(),
            not_before,
        }
    }

    pub fn public_key(&self) -> &String {
        &self.public_key
    }

    pub fn not_before(&self) -> u64 {
        self.not_before
    }

    pub fn key_id(&self) -> u8 {
        *hex::decode(&self.public_key)
            .expect("KeyInfo.public_key is always stored as hex")
            .last()
            .expect("fixed size array has a last element")
    }
}

impl From<KeyInfo> for HashMap<String, String> {
    fn from(val: KeyInfo) -> Self {
        let mut map = HashMap::new();
        // Clone the String for key 'public_key'
        map.insert("public_key".to_string(), val.public_key.clone());
        // Convert u64 to String for key 'not_before'
        map.insert("not_before".to_string(), val.not_before.to_string());
        map
    }
}

impl TryFrom<HashMap<String, String>> for KeyInfo {
    type Error = anyhow::Error;

    fn try_from(value: HashMap<String, String>) -> Result<Self, Self::Error> {
        Ok(Self {
            public_key: value
                .get("public_key")
                .context("getting KeyInfo public key")?
                .clone(),
            not_before: value
                .get("not_before")
                .context("getting KeyInfo not_before")?
                .parse()?,
        })
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(ToSchema))]
pub struct Configuration {
    keys: Vec<KeyInfo>,
    logs: Vec<String>,
}

impl Configuration {
    pub fn new(keys: &[KeyInfo], logs: &[String]) -> Self {
        Self {
            keys: keys.to_vec(),
            logs: logs.to_vec(),
        }
    }

    pub fn keys(&self) -> &Vec<KeyInfo> {
        &self.keys
    }

    pub fn logs(&self) -> &Vec<String> {
        &self.logs
    }
}

#[cfg(feature = "auditor")]
pub async fn compute_start_root_hash(raw_proof: &[u8]) -> anyhow::Result<Digest> {
    let proto = akd::proto::specs::types::SingleAppendOnlyProof::parse_from_bytes(raw_proof)
        .context("unable to parse proof bytes")?;

    let proof = SingleAppendOnlyProof::try_from(&proto)
        .map_err(|e| anyhow::anyhow!(e.to_string()))
        .context("converting parsed protobuf proof to `SingleAppendOnlyProof`")?;

    let db = AsyncInMemoryDatabase::new();
    let manager = StorageManager::new_no_cache(db);

    let mut azks = Azks::new::<WhatsAppV1Configuration, _>(&manager).await?;
    azks.batch_insert_nodes::<WhatsAppV1Configuration, _>(
        &manager,
        proof.unchanged_nodes.clone(),
        InsertMode::Auditor,
    )
    .await?;

    Ok(azks
        .get_root_hash::<WhatsAppV1Configuration, _>(&manager)
        .await?)
}

#[cfg(feature = "auditor")]
pub async fn verify_raw_proof(blob: &AuditBlobName, raw_proof: &[u8]) -> anyhow::Result<()> {
    let proto = akd::proto::specs::types::SingleAppendOnlyProof::parse_from_bytes(raw_proof)
        .context("unable to parse proof bytes")?;

    let proof = SingleAppendOnlyProof::try_from(&proto)
        .map_err(|e| anyhow::anyhow!(e.to_string()))
        .context("converting parsed protobuf proof to `SingleAppendOnlyProof`")?;

    akd::auditor::verify_consecutive_append_only::<WhatsAppV1Configuration>(
        &proof,
        blob.previous_hash,
        blob.current_hash,
        blob.epoch,
    )
    .await
    .with_context(|| format!("verifying raw proof: {blob}", blob = blob.to_string()))
    .map_err(|e| anyhow!(e))
}