1use crate::{
7 alloc::{Allocator, Global, vec::Vec},
8 codec::{Decode, DecodeIn, Decoder, Encode, Encoder, v1::MayHaveInput},
9 error::{DecodeError, EncodeError},
10 utils::Hexed,
11};
12use alloy_chains::Chain;
13use alloy_primitives::{B256, FixedBytes, fixed_bytes as tag};
14use core::fmt;
15use std::{borrow::Cow, sync::OnceLock};
16
17const TAG_SIZE: usize = 8;
19
20pub type AttestationTag = FixedBytes<TAG_SIZE>;
22
23static BITCOIN_TAG: AttestationTag = tag!("0x0588960d73d71901");
25
26static PENDING_TAG: AttestationTag = tag!("0x83dfe30d2ef90c8e");
28
29static EAS_ATTEST_TAG: AttestationTag = tag!("0x8bf46bf4cfd674fa");
36
37static EAS_TIMESTAMP_TAG: AttestationTag = tag!("0x5aafceeb1c7ad58e");
44
45#[derive(Clone)]
47pub struct RawAttestation<A: Allocator = Global> {
48 pub tag: AttestationTag,
49 pub data: Vec<u8, A>,
50 pub(crate) value: OnceLock<Vec<u8, A>>,
52}
53
54impl<A: Allocator> fmt::Debug for RawAttestation<A> {
55 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56 f.debug_struct("RawAttestation")
57 .field("tag", &Hexed(&self.tag))
58 .field("data", &Hexed(&self.data))
59 .finish()
60 }
61}
62
63impl<A: Allocator> DecodeIn<A> for RawAttestation<A> {
64 fn decode_in(decoder: &mut impl Decoder, alloc: A) -> Result<Self, DecodeError> {
65 let tag = decoder.decode()?;
66
67 let len = decoder.decode()?;
68 let mut data = Vec::with_capacity_in(len, alloc);
69 data.resize(len, 0);
70 decoder.read_exact(&mut data)?;
71
72 Ok(RawAttestation {
73 tag,
74 data,
75 value: OnceLock::new(),
76 })
77 }
78}
79
80impl<A: Allocator> Encode for RawAttestation<A> {
81 #[inline]
82 fn encode(&self, encoder: &mut impl Encoder) -> Result<(), EncodeError> {
83 encoder.write_all(self.tag)?;
84 encoder.encode_bytes(&self.data)
85 }
86}
87
88impl<A: Allocator> PartialEq for RawAttestation<A> {
89 fn eq(&self, other: &Self) -> bool {
90 self.tag == other.tag && self.data.as_slice() == other.data.as_slice()
91 }
92}
93
94impl<A: Allocator> Eq for RawAttestation<A> {}
95
96impl<A: Allocator> RawAttestation<A> {
97 #[inline]
99 pub fn allocator(&self) -> &A {
100 self.data.allocator()
101 }
102
103 #[inline]
105 pub fn value(&self) -> Option<&[u8]> {
106 self.value.get().map(|v| v.as_slice())
107 }
108}
109
110pub trait Attestation<'a>: Sized {
111 const TAG: AttestationTag;
112
113 fn from_raw<A: Allocator>(raw: &'a RawAttestation<A>) -> Result<Self, DecodeError> {
114 if raw.tag != Self::TAG {
115 return Err(DecodeError::BadAttestationTag);
116 }
117
118 Self::from_raw_data(&raw.data)
119 }
120
121 fn to_raw(&self) -> Result<RawAttestation, EncodeError> {
122 self.to_raw_in(Global)
123 }
124
125 fn to_raw_in<A: Allocator>(&self, alloc: A) -> Result<RawAttestation<A>, EncodeError> {
126 Ok(RawAttestation {
127 tag: Self::TAG,
128 data: self.to_raw_data_in(alloc)?,
129 value: OnceLock::new(),
130 })
131 }
132
133 fn from_raw_data(data: &'a [u8]) -> Result<Self, DecodeError>;
134 fn to_raw_data_in<A: Allocator>(&self, alloc: A) -> Result<Vec<u8, A>, EncodeError>;
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct BitcoinAttestation {
139 pub height: u32,
140}
141
142impl fmt::Display for BitcoinAttestation {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 write!(f, "Bitcoin at height {}", self.height)
145 }
146}
147
148impl Attestation<'_> for BitcoinAttestation {
149 const TAG: AttestationTag = BITCOIN_TAG;
150
151 fn from_raw_data(data: &[u8]) -> Result<Self, DecodeError> {
152 let height = u32::decode(&mut &*data)?;
153 Ok(BitcoinAttestation { height })
154 }
155
156 fn to_raw_data_in<A: Allocator>(&self, alloc: A) -> Result<Vec<u8, A>, EncodeError> {
157 let mut buffer = Vec::with_capacity_in(u32::BITS.div_ceil(7) as usize, alloc);
158 buffer.encode(self.height)?;
159 Ok(buffer)
160 }
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
165pub struct EASAttestation {
166 pub chain: Chain,
167 pub uid: B256,
168}
169
170impl fmt::Display for EASAttestation {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 write!(f, "EAS attestation {} on {}", self.uid, self.chain)
173 }
174}
175
176impl<'a> Attestation<'a> for EASAttestation {
177 const TAG: AttestationTag = EAS_ATTEST_TAG;
178
179 fn from_raw_data(data: &'a [u8]) -> Result<Self, DecodeError> {
180 let data = &mut &data[..];
181 let chain_id = u64::decode(data)?;
182 let uid = B256::decode(data)?;
183 Ok(EASAttestation {
184 chain: Chain::from_id(chain_id),
185 uid,
186 })
187 }
188
189 fn to_raw_data_in<A: Allocator>(&self, alloc: A) -> Result<Vec<u8, A>, EncodeError> {
190 let mut buffer =
191 Vec::with_capacity_in(u64::BITS.div_ceil(7) as usize + size_of::<B256>(), alloc);
192 buffer.encode(self.chain.id())?;
193 buffer.encode(self.uid)?;
194 Ok(buffer)
195 }
196}
197
198#[derive(Debug, Clone, PartialEq, Eq)]
200pub struct EASTimestamped {
201 pub chain: Chain,
202}
203
204impl fmt::Display for EASTimestamped {
205 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206 write!(f, "EAS timestamped on {}", self.chain)
207 }
208}
209
210impl<'a> Attestation<'a> for EASTimestamped {
211 const TAG: AttestationTag = EAS_TIMESTAMP_TAG;
212
213 fn from_raw_data(data: &'a [u8]) -> Result<Self, DecodeError> {
214 let chain_id = u64::decode(&mut &data[..])?;
215 Ok(EASTimestamped {
216 chain: Chain::from_id(chain_id),
217 })
218 }
219
220 fn to_raw_data_in<A: Allocator>(&self, alloc: A) -> Result<Vec<u8, A>, EncodeError> {
221 let mut buffer = Vec::with_capacity_in(u64::BITS.div_ceil(7) as usize, alloc);
222 buffer.encode(self.chain.id())?;
223 Ok(buffer)
224 }
225}
226
227#[derive(Debug, Clone, PartialEq, Eq)]
228pub struct PendingAttestation<'a> {
229 pub uri: Cow<'a, str>,
230}
231
232impl PendingAttestation<'_> {
233 pub const MAX_URI_LEN: usize = 1000;
235
236 #[inline]
237 pub fn validate_uri(uri: &str) -> bool {
238 uri.chars()
239 .all(|ch| matches!(ch, 'a'..='z' | 'A'..='Z' | '0'..='9' | '.' | '-' | '_' | '/' | ':'))
240 }
241}
242
243impl fmt::Display for PendingAttestation<'_> {
244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245 write!(f, "Pending at {}", self.uri)
246 }
247}
248
249impl<'a> Attestation<'a> for PendingAttestation<'a> {
250 const TAG: AttestationTag = PENDING_TAG;
251
252 fn from_raw_data(data: &'a [u8]) -> Result<Self, DecodeError> {
253 let data = &mut &data[..];
254 let length = u32::decode(data)? as usize; if length > Self::MAX_URI_LEN {
256 return Err(DecodeError::UriTooLong);
257 }
258 if data.len() < length {
259 return Err(DecodeError::UnexpectedEof);
260 }
261 let uri = core::str::from_utf8(&data[..length]).map_err(|_| DecodeError::InvalidUriChar)?;
262 if !Self::validate_uri(uri) {
263 return Err(DecodeError::InvalidUriChar);
264 }
265 Ok(PendingAttestation {
266 uri: Cow::Borrowed(uri),
267 })
268 }
269
270 fn to_raw_data_in<A: Allocator>(&self, alloc: A) -> Result<Vec<u8, A>, EncodeError> {
271 if self.uri.len() > Self::MAX_URI_LEN {
272 return Err(EncodeError::UriTooLong);
273 }
274 if !Self::validate_uri(&self.uri) {
275 return Err(EncodeError::InvalidUriChar);
276 }
277 let mut buffer =
278 Vec::with_capacity_in(self.uri.len() + u32::BITS.div_ceil(7) as usize, alloc);
279 buffer.encode_bytes(self.uri.as_bytes())?;
280 Ok(buffer)
281 }
282}
283
284impl<A: Allocator> fmt::Display for RawAttestation<A> {
285 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286 match &self.tag {
287 tag if *tag == *PENDING_TAG => {
288 let att = PendingAttestation::from_raw(self).expect("Valid Pending attestation");
289 write!(f, "{}", att)
290 }
291 tag if *tag == *BITCOIN_TAG => {
292 let att = BitcoinAttestation::from_raw(self).expect("Valid Bitcoin attestation");
293 write!(f, "{}", att)
294 }
295 tag if *tag == *EAS_ATTEST_TAG => {
296 let att = EASAttestation::from_raw(self).expect("Valid EAS attestation");
297 write!(f, "{}", att)
298 }
299 tag if *tag == *EAS_TIMESTAMP_TAG => {
300 let att = EASTimestamped::from_raw(self).expect("Valid EAS Timestamp attestation");
301 write!(f, "{}", att)
302 }
303 _ => write!(f, "Unknown Attestation with tag {}", Hexed(&self.tag)),
304 }
305 }
306}
307
308impl<A: Allocator> MayHaveInput for RawAttestation<A> {
309 #[inline]
310 fn input(&self) -> Option<&[u8]> {
311 self.value.get().map(|v| v.as_slice())
312 }
313}