Skip to main content

guardian_shared/
auth_request_payload.rs

1use miden_protocol::crypto::hash::rpo::Rpo256;
2use miden_protocol::{Felt, Word};
3use serde::Serialize;
4use serde_json::Value;
5
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct AuthRequestPayload {
8    digest: Word,
9}
10
11impl AuthRequestPayload {
12    pub fn empty() -> Self {
13        Self {
14            digest: Word::from([Felt::ZERO; 4]),
15        }
16    }
17
18    pub fn from_bytes(bytes: &[u8]) -> Self {
19        if bytes.is_empty() {
20            return Self::empty();
21        }
22
23        let mut payload_elements = Vec::with_capacity(bytes.len().div_ceil(8));
24        for chunk in bytes.chunks(8) {
25            let mut chunk_bytes = [0u8; 8];
26            chunk_bytes[..chunk.len()].copy_from_slice(chunk);
27            payload_elements.push(crate::felt::felt_from_u64_reduced(u64::from_le_bytes(
28                chunk_bytes,
29            )));
30        }
31
32        Self {
33            digest: Rpo256::hash_elements(&payload_elements),
34        }
35    }
36
37    pub fn from_json_bytes(bytes: &[u8]) -> Result<Self, String> {
38        let value: Value =
39            serde_json::from_slice(bytes).map_err(|e| format!("Invalid JSON payload: {e}"))?;
40        Self::from_json_value(&value)
41    }
42
43    pub fn from_json_value(value: &Value) -> Result<Self, String> {
44        let canonical = canonicalize_json(value);
45        let bytes =
46            serde_json::to_vec(&canonical).map_err(|e| format!("Failed to serialize JSON: {e}"))?;
47        Ok(Self::from_bytes(&bytes))
48    }
49
50    pub fn from_json_serializable<T: Serialize>(value: &T) -> Result<Self, String> {
51        let json = serde_json::to_value(value)
52            .map_err(|e| format!("Failed to convert payload to JSON value: {e}"))?;
53        Self::from_json_value(&json)
54    }
55
56    pub fn from_protobuf_message<T: prost::Message>(value: &T) -> Self {
57        Self::from_bytes(&value.encode_to_vec())
58    }
59
60    pub fn as_elements(&self) -> [Felt; 4] {
61        let elements = self.digest.as_elements();
62        [elements[0], elements[1], elements[2], elements[3]]
63    }
64
65    pub fn to_word(&self) -> Word {
66        self.digest
67    }
68}
69
70fn canonicalize_json(value: &Value) -> Value {
71    match value {
72        Value::Object(map) => {
73            let mut keys = map.keys().cloned().collect::<Vec<_>>();
74            keys.sort();
75
76            let mut sorted = serde_json::Map::with_capacity(map.len());
77            for key in keys {
78                let item = map
79                    .get(&key)
80                    .expect("key collected from map must always exist");
81                sorted.insert(key, canonicalize_json(item));
82            }
83            Value::Object(sorted)
84        }
85        Value::Array(items) => Value::Array(items.iter().map(canonicalize_json).collect()),
86        _ => value.clone(),
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::AuthRequestPayload;
93
94    #[test]
95    fn json_payload_hash_is_order_insensitive() {
96        let left = br#"{"b":2,"a":1}"#;
97        let right = br#"{"a":1,"b":2}"#;
98
99        let left_payload = AuthRequestPayload::from_json_bytes(left).expect("left json");
100        let right_payload = AuthRequestPayload::from_json_bytes(right).expect("right json");
101
102        assert_eq!(left_payload, right_payload);
103    }
104
105    #[test]
106    fn empty_payload_hash_is_zero_word() {
107        let payload = AuthRequestPayload::from_bytes(b"");
108        assert_eq!(payload, AuthRequestPayload::empty());
109    }
110}