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(Felt::new(u64::from_le_bytes(chunk_bytes)));
28        }
29
30        Self {
31            digest: Rpo256::hash_elements(&payload_elements),
32        }
33    }
34
35    pub fn from_json_bytes(bytes: &[u8]) -> Result<Self, String> {
36        let value: Value =
37            serde_json::from_slice(bytes).map_err(|e| format!("Invalid JSON payload: {e}"))?;
38        Self::from_json_value(&value)
39    }
40
41    pub fn from_json_value(value: &Value) -> Result<Self, String> {
42        let canonical = canonicalize_json(value);
43        let bytes =
44            serde_json::to_vec(&canonical).map_err(|e| format!("Failed to serialize JSON: {e}"))?;
45        Ok(Self::from_bytes(&bytes))
46    }
47
48    pub fn from_json_serializable<T: Serialize>(value: &T) -> Result<Self, String> {
49        let json = serde_json::to_value(value)
50            .map_err(|e| format!("Failed to convert payload to JSON value: {e}"))?;
51        Self::from_json_value(&json)
52    }
53
54    pub fn from_protobuf_message<T: prost::Message>(value: &T) -> Self {
55        Self::from_bytes(&value.encode_to_vec())
56    }
57
58    pub fn as_elements(&self) -> [Felt; 4] {
59        let elements = self.digest.as_elements();
60        [elements[0], elements[1], elements[2], elements[3]]
61    }
62
63    pub fn to_word(&self) -> Word {
64        self.digest
65    }
66}
67
68fn canonicalize_json(value: &Value) -> Value {
69    match value {
70        Value::Object(map) => {
71            let mut keys = map.keys().cloned().collect::<Vec<_>>();
72            keys.sort();
73
74            let mut sorted = serde_json::Map::with_capacity(map.len());
75            for key in keys {
76                let item = map
77                    .get(&key)
78                    .expect("key collected from map must always exist");
79                sorted.insert(key, canonicalize_json(item));
80            }
81            Value::Object(sorted)
82        }
83        Value::Array(items) => Value::Array(items.iter().map(canonicalize_json).collect()),
84        _ => value.clone(),
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::AuthRequestPayload;
91
92    #[test]
93    fn json_payload_hash_is_order_insensitive() {
94        let left = br#"{"b":2,"a":1}"#;
95        let right = br#"{"a":1,"b":2}"#;
96
97        let left_payload = AuthRequestPayload::from_json_bytes(left).expect("left json");
98        let right_payload = AuthRequestPayload::from_json_bytes(right).expect("right json");
99
100        assert_eq!(left_payload, right_payload);
101    }
102
103    #[test]
104    fn empty_payload_hash_is_zero_word() {
105        let payload = AuthRequestPayload::from_bytes(b"");
106        assert_eq!(payload, AuthRequestPayload::empty());
107    }
108}