guardian_shared/
auth_request_payload.rs1use 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}