guardian_shared/
lookup_auth_message.rs1use miden_protocol::crypto::hash::rpo::Rpo256;
2use miden_protocol::{Felt, Word};
3use std::sync::OnceLock;
4
5const DOMAIN_TAG_BYTES: &[u8] = b"guardian.lookup.v1";
14
15fn domain_tag() -> Word {
17 static TAG: OnceLock<Word> = OnceLock::new();
18 *TAG.get_or_init(|| {
19 let mut elements = Vec::with_capacity(DOMAIN_TAG_BYTES.len().div_ceil(8));
20 for chunk in DOMAIN_TAG_BYTES.chunks(8) {
21 let mut chunk_bytes = [0u8; 8];
22 chunk_bytes[..chunk.len()].copy_from_slice(chunk);
23 elements.push(crate::felt::felt_from_u64_reduced(u64::from_le_bytes(
24 chunk_bytes,
25 )));
26 }
27 Rpo256::hash_elements(&elements)
28 })
29}
30
31#[derive(Clone, Debug, PartialEq, Eq)]
40pub struct LookupAuthMessage {
41 timestamp_ms: i64,
42 key_commitment: Word,
43}
44
45impl LookupAuthMessage {
46 pub fn new(timestamp_ms: i64, key_commitment: Word) -> Self {
47 Self {
48 timestamp_ms,
49 key_commitment,
50 }
51 }
52
53 pub fn timestamp_ms(&self) -> i64 {
54 self.timestamp_ms
55 }
56
57 pub fn key_commitment(&self) -> Word {
58 self.key_commitment
59 }
60
61 pub fn to_word(&self) -> Word {
73 let tag = domain_tag();
74 let tag_elements = tag.as_elements();
75 let kc_elements = self.key_commitment.as_elements();
76 let timestamp_felt = crate::felt::felt_from_u64_reduced(self.timestamp_ms as u64);
77 let message_elements: [Felt; 9] = [
78 tag_elements[0],
79 tag_elements[1],
80 tag_elements[2],
81 tag_elements[3],
82 timestamp_felt,
83 kc_elements[0],
84 kc_elements[1],
85 kc_elements[2],
86 kc_elements[3],
87 ];
88 Rpo256::hash_elements(&message_elements)
89 }
90}
91
92pub fn lookup_domain_tag() -> Word {
95 domain_tag()
96}
97
98#[cfg(test)]
99mod tests {
100 use super::*;
101 use crate::auth_request_message::AuthRequestMessage;
102 use crate::auth_request_payload::AuthRequestPayload;
103 use miden_protocol::account::{AccountId, AccountIdVersion, AccountType};
104
105 fn sample_commitment(seed: u32) -> Word {
106 Word::from([
107 seed,
108 seed.wrapping_add(1),
109 seed.wrapping_add(2),
110 seed.wrapping_add(3),
111 ])
112 }
113
114 #[test]
115 fn domain_tag_is_stable_across_calls() {
116 let a = lookup_domain_tag();
117 let b = lookup_domain_tag();
118 assert_eq!(a, b);
119 }
120
121 #[test]
122 fn domain_tag_is_not_zero_word() {
123 let tag = lookup_domain_tag();
124 assert_ne!(tag, Word::from([Felt::ZERO; 4]));
125 }
126
127 #[test]
128 fn digest_changes_with_commitment() {
129 let timestamp = 1_700_000_000_000i64;
130 let left = LookupAuthMessage::new(timestamp, sample_commitment(1)).to_word();
131 let right = LookupAuthMessage::new(timestamp, sample_commitment(2)).to_word();
132 assert_ne!(left, right);
133 }
134
135 #[test]
136 fn digest_changes_with_timestamp() {
137 let commitment = sample_commitment(7);
138 let left = LookupAuthMessage::new(1_700_000_000_000, commitment).to_word();
139 let right = LookupAuthMessage::new(1_700_000_000_001, commitment).to_word();
140 assert_ne!(left, right);
141 }
142
143 #[test]
144 fn digest_is_deterministic() {
145 let msg = LookupAuthMessage::new(1_700_000_000_000, sample_commitment(42));
146 assert_eq!(msg.to_word(), msg.to_word());
147 }
148
149 #[test]
150 fn digest_handles_extreme_timestamps() {
151 let commitment = sample_commitment(99);
152 let zero = LookupAuthMessage::new(0, commitment).to_word();
153 let large = LookupAuthMessage::new(i64::MAX, commitment).to_word();
154 assert_ne!(zero, large);
155 let negative = LookupAuthMessage::new(-1, commitment).to_word();
158 assert_ne!(negative, zero);
159 }
160
161 #[test]
162 fn lookup_digest_is_distinct_from_auth_request_digest() {
163 let timestamp = 1_700_000_000_000i64;
167 let commitment = sample_commitment(123);
168
169 let lookup_digest = LookupAuthMessage::new(timestamp, commitment).to_word();
170
171 let account_id =
172 AccountId::dummy([0x8a; 15], AccountIdVersion::Version1, AccountType::Private);
173 let payload = AuthRequestPayload::from_bytes(&commitment.as_bytes());
174 let request_digest = AuthRequestMessage::new(account_id, timestamp, payload).to_word();
175
176 assert_ne!(lookup_digest, request_digest);
177 }
178
179 #[test]
180 fn domain_tag_is_known_constant() {
181 let tag = lookup_domain_tag();
185 let mut elements: Vec<Felt> = Vec::new();
189 for chunk in DOMAIN_TAG_BYTES.chunks(8) {
190 let mut bytes = [0u8; 8];
191 bytes[..chunk.len()].copy_from_slice(chunk);
192 elements.push(crate::felt::felt_from_u64_reduced(u64::from_le_bytes(
193 bytes,
194 )));
195 }
196 let expected = Rpo256::hash_elements(&elements);
197 assert_eq!(tag, expected);
198 }
199}