1use crate::cert::{Certificate, KeyHash, Signature, Witness};
25use borsh::{BorshDeserialize, BorshSerialize};
26use libveritas_zk::guest::CommitmentKind;
27use risc0_zkvm::{Receipt, VerifierContext};
28use serde::{Deserialize, Deserializer, Serialize, Serializer};
29use spacedb::subtree::SubTree;
30use spacedb::{Hash, NodeHasher, Sha256Hasher};
31use spaces_nums::RootAnchor;
32use spaces_nums::constants::COMMITMENT_FINALITY_INTERVAL;
33use spaces_protocol::bitcoin::ScriptBuf;
34use spaces_protocol::bitcoin::hashes::{Hash as HashUtil, HashEngine, sha256};
35use spaces_protocol::bitcoin::secp256k1::{self, XOnlyPublicKey};
36use spaces_protocol::constants::SPACES_SIGNED_MSG_PREFIX;
37use spaces_protocol::slabel::SLabel;
38use spaces_protocol::sname::SName;
39use std::collections::HashSet;
40use std::fmt;
41use std::io::{Read, Write};
42use std::sync::OnceLock;
43
44pub mod builder;
45pub mod cert;
46pub mod constants;
47#[cfg(feature = "inspect")]
48pub mod inspect;
49pub mod msg;
50pub mod names;
51
52pub use sip7;
53use spaces_nums::num_id::NumId;
54pub use spaces_protocol;
55
56pub const VERIFY_DEFAULT: u32 = 0;
58pub const VERIFY_DEV_MODE: u32 = 1 << 0;
59pub const VERIFY_ENABLE_SNARK: u32 = 1 << 1;
60
61pub struct VerifiedMessage {
66 pub zones: Vec<Zone>,
67 pub message: msg::Message,
68}
69
70impl VerifiedMessage {
71 pub fn certificates(&self) -> CertificateIter<'_> {
77 CertificateIter {
78 zones: &self.zones,
79 bundles: self.message.spaces.iter(),
80 current_bundle: None,
82 current_epoch: None,
83 epochs: None,
84 handles: None,
85 }
86 }
87}
88
89pub struct CertificateIter<'a> {
91 zones: &'a [Zone],
92 bundles: std::slice::Iter<'a, msg::Bundle>,
93 current_bundle: Option<&'a msg::Bundle>,
94 current_epoch: Option<&'a msg::Epoch>,
95 epochs: Option<std::slice::Iter<'a, msg::Epoch>>,
96 handles: Option<std::slice::Iter<'a, msg::Handle>>,
97}
98
99impl<'a> Iterator for CertificateIter<'a> {
100 type Item = Certificate;
101
102 fn next(&mut self) -> Option<Self::Item> {
103 loop {
104 if let Some(handles) = &mut self.handles {
106 if let Some(h) = handles.next() {
107 let bundle = self.current_bundle?;
108 let epoch = self.current_epoch?;
109 let subject = SName::join(&h.name, &bundle.subject)
110 .expect("handle names in a verified message are joinable");
111
112 return Some(Certificate::new(
113 subject,
114 Witness::Leaf {
115 genesis_spk: h.genesis_spk.clone(),
116 handles: epoch.tree.clone(),
117 signature: h.signature,
118 },
119 ));
120 }
121 }
122
123 if let Some(epochs) = &mut self.epochs {
125 if let Some(epoch) = epochs.next() {
126 self.current_epoch = Some(epoch);
127 self.handles = Some(epoch.handles.iter());
128 continue;
129 }
130 }
131
132 let bundle = self.bundles.next()?;
134 self.current_bundle = Some(bundle);
135 self.epochs = Some(bundle.epochs.iter());
136 self.current_epoch = None;
137 self.handles = None;
138
139 let root_handle = SName::from_space(&bundle.subject);
141 if self.zones.iter().any(|z| z.canonical == root_handle) {
142 return Some(Certificate::new(
143 root_handle,
144 Witness::Root {
145 receipt: bundle.receipt.clone(),
146 },
147 ));
148 }
149 }
150 }
151}
152
153#[derive(Clone)]
154pub struct Veritas {
155 anchors: Vec<RootAnchor>,
156 oldest_anchor: u32,
157 newest_anchor: u32,
158}
159
160#[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize)]
161#[serde(rename_all = "lowercase")]
162pub enum SovereigntyState {
163 Sovereign,
165
166 Pending,
169
170 Dependent,
172}
173
174impl fmt::Display for SovereigntyState {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 match self {
177 Self::Sovereign => write!(f, "sovereign"),
178 Self::Pending => write!(f, "pending"),
179 Self::Dependent => write!(f, "dependent"),
180 }
181 }
182}
183
184#[derive(Clone, Serialize, Deserialize)]
190pub struct Zone {
191 pub anchor: u32,
193 #[serde(
195 serialize_with = "serialize_hash",
196 deserialize_with = "deserialize_hash"
197 )]
198 pub anchor_hash: Hash,
199 pub sovereignty: SovereigntyState,
201 pub handle: SName,
204 pub canonical: SName,
206 pub alias: Option<SLabel>,
208 pub script_pubkey: ScriptBuf,
210 pub records: sip7::RecordSet,
212 pub fallback_records: sip7::RecordSet,
214 pub delegate: ProvableOption<Delegate>,
216 pub commitment: ProvableOption<CommitmentInfo>,
218 pub num_id: Option<NumId>,
223}
224
225#[derive(Clone, Serialize, Deserialize)]
227pub struct CommitmentInfo {
228 pub onchain: spaces_nums::Commitment,
230 #[serde(
232 serialize_with = "serialize_option_hash",
233 deserialize_with = "deserialize_option_hash"
234 )]
235 pub receipt_hash: Option<Hash>,
236}
237
238impl CommitmentInfo {
239 pub fn empty() -> Self {
240 let empty_root = SubTree::<Sha256Hasher>::empty()
241 .compute_root()
242 .expect("valid");
243 Self {
244 onchain: spaces_nums::Commitment {
245 state_root: empty_root,
246 prev_root: None,
247 rolling_hash: empty_root,
248 block_height: 0,
249 },
250 receipt_hash: None,
251 }
252 }
253}
254
255#[derive(Clone, Serialize, Deserialize)]
256pub struct Delegate {
257 pub script_pubkey: ScriptBuf,
258 pub records: sip7::RecordSet,
260 pub fallback_records: sip7::RecordSet,
261}
262
263#[derive(Clone, Serialize, Deserialize)]
264#[serde(tag = "status", rename_all = "snake_case")]
265pub enum ProvableOption<T> {
266 Exists { value: T },
267 Empty,
268 Unknown,
269}
270
271impl BorshSerialize for SovereigntyState {
272 fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
273 let variant: u8 = match self {
274 Self::Sovereign => 0,
275 Self::Pending => 1,
276 Self::Dependent => 2,
277 };
278 BorshSerialize::serialize(&variant, writer)
279 }
280}
281
282impl BorshDeserialize for SovereigntyState {
283 fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
284 let variant = u8::deserialize_reader(reader)?;
285 match variant {
286 0 => Ok(Self::Sovereign),
287 1 => Ok(Self::Pending),
288 2 => Ok(Self::Dependent),
289 _ => Err(std::io::Error::new(
290 std::io::ErrorKind::InvalidData,
291 format!("invalid SovereigntyState variant: {}", variant),
292 )),
293 }
294 }
295}
296
297impl BorshSerialize for Delegate {
298 fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
299 BorshSerialize::serialize(&self.script_pubkey.as_bytes().to_vec(), writer)?;
300 BorshSerialize::serialize(&self.fallback_records.as_slice().to_vec(), writer)?;
301 BorshSerialize::serialize(&self.records.as_slice().to_vec(), writer)
302 }
303}
304
305impl BorshDeserialize for Delegate {
306 fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
307 let spk_bytes: Vec<u8> = Vec::deserialize_reader(reader)?;
308 let fallback_bytes: Vec<u8> = Vec::deserialize_reader(reader)?;
309 let records_bytes: Vec<u8> = Vec::deserialize_reader(reader)?;
310 Ok(Delegate {
311 script_pubkey: ScriptBuf::from_bytes(spk_bytes),
312 fallback_records: sip7::RecordSet::new(fallback_bytes),
313 records: sip7::RecordSet::new(records_bytes),
314 })
315 }
316}
317
318impl BorshSerialize for CommitmentInfo {
319 fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
320 BorshSerialize::serialize(&self.onchain, writer)?;
321 BorshSerialize::serialize(&self.receipt_hash, writer)
322 }
323}
324
325impl BorshDeserialize for CommitmentInfo {
326 fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
327 let onchain = spaces_nums::Commitment::deserialize_reader(reader)?;
328 let receipt_hash = Option::<Hash>::deserialize_reader(reader)?;
329 Ok(CommitmentInfo {
330 onchain,
331 receipt_hash,
332 })
333 }
334}
335
336impl<T: BorshSerialize> BorshSerialize for ProvableOption<T> {
337 fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
338 match self {
339 Self::Exists { value } => {
340 BorshSerialize::serialize(&0u8, writer)?;
341 BorshSerialize::serialize(value, writer)
342 }
343 Self::Empty => BorshSerialize::serialize(&1u8, writer),
344 Self::Unknown => BorshSerialize::serialize(&2u8, writer),
345 }
346 }
347}
348
349impl<T: BorshDeserialize> BorshDeserialize for ProvableOption<T> {
350 fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
351 let variant = u8::deserialize_reader(reader)?;
352 match variant {
353 0 => {
354 let value = T::deserialize_reader(reader)?;
355 Ok(Self::Exists { value })
356 }
357 1 => Ok(Self::Empty),
358 2 => Ok(Self::Unknown),
359 _ => Err(std::io::Error::new(
360 std::io::ErrorKind::InvalidData,
361 format!("invalid ProvableOption variant: {}", variant),
362 )),
363 }
364 }
365}
366
367impl BorshSerialize for Zone {
368 fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
369 BorshSerialize::serialize(&self.anchor, writer)?;
370 BorshSerialize::serialize(&self.anchor_hash, writer)?;
371 BorshSerialize::serialize(&self.sovereignty, writer)?;
372 BorshSerialize::serialize(&self.canonical, writer)?;
373 BorshSerialize::serialize(&self.handle, writer)?;
374 BorshSerialize::serialize(&self.alias, writer)?;
375 BorshSerialize::serialize(&self.script_pubkey.as_bytes().to_vec(), writer)?;
376 BorshSerialize::serialize(&self.fallback_records.as_slice().to_vec(), writer)?;
377 BorshSerialize::serialize(&self.records.as_slice().to_vec(), writer)?;
378 BorshSerialize::serialize(&self.delegate, writer)?;
379 BorshSerialize::serialize(&self.commitment, writer)?;
380 BorshSerialize::serialize(&self.num_id, writer)
381 }
382}
383
384impl BorshDeserialize for Zone {
385 fn deserialize_reader<R: Read>(reader: &mut R) -> std::io::Result<Self> {
386 let anchor = u32::deserialize_reader(reader)?;
387 let anchor_hash = <[u8; 32]>::deserialize_reader(reader)?;
388 let sovereignty = SovereigntyState::deserialize_reader(reader)?;
389 let canonical = SName::deserialize_reader(reader)?;
390 let handle = SName::deserialize_reader(reader)?;
391 let alias = Option::<SLabel>::deserialize_reader(reader)?;
392 let spk_bytes: Vec<u8> = Vec::deserialize_reader(reader)?;
393 let fallback_bytes: Vec<u8> = Vec::deserialize_reader(reader)?;
394 let records_bytes: Vec<u8> = Vec::deserialize_reader(reader)?;
395 let delegate: ProvableOption<Delegate> = ProvableOption::deserialize_reader(reader)?;
396 let commitment: ProvableOption<CommitmentInfo> =
397 ProvableOption::deserialize_reader(reader)?;
398
399 let script_pubkey = ScriptBuf::from_bytes(spk_bytes);
400 let num_id = Option::<NumId>::deserialize_reader(reader)?;
401 Ok(Zone {
402 anchor,
403 anchor_hash,
404 sovereignty,
405 handle,
406 canonical,
407 alias,
408 script_pubkey,
409 fallback_records: sip7::RecordSet::new(fallback_bytes),
410 records: sip7::RecordSet::new(records_bytes),
411 delegate,
412 commitment,
413 num_id,
414 })
415 }
416}
417
418pub fn compute_root_id(root: &RootAnchor) -> [u8; 32] {
420 let mut engine = sha256::Hash::engine();
421 engine.input(&root.block.hash[..]);
422 engine.input(&root.block.height.to_le_bytes());
423 engine.input(&root.spaces_root);
424 engine.input(&root.nums_root.unwrap_or([0u8; 32]));
425 sha256::Hash::from_engine(engine).to_byte_array()
426}
427
428#[derive(Clone)]
430pub struct TrustSet {
431 pub id: [u8; 32],
432 pub roots: Vec<[u8; 32]>,
433}
434
435pub fn compute_trust_set(anchors: &[RootAnchor]) -> TrustSet {
437 let roots: Vec<[u8; 32]> = anchors.iter().map(compute_root_id).collect();
438 let mut engine = sha256::Hash::engine();
439 for r in &roots {
440 engine.input(r);
441 }
442 TrustSet {
443 id: sha256::Hash::from_engine(engine).to_byte_array(),
444 roots,
445 }
446}
447
448pub(crate) fn secp256k1_verify_ctx() -> &'static secp256k1::Secp256k1<secp256k1::VerifyOnly> {
454 static CTX: OnceLock<secp256k1::Secp256k1<secp256k1::VerifyOnly>> = OnceLock::new();
455 CTX.get_or_init(secp256k1::Secp256k1::verification_only)
456}
457
458pub fn hash_signable_message(msg: &[u8]) -> secp256k1::Message {
459 let mut engine = sha256::Hash::engine();
460 engine.input(SPACES_SIGNED_MSG_PREFIX);
461 engine.input(msg);
462 let digest = sha256::Hash::from_engine(engine);
463 secp256k1::Message::from_digest(digest.to_byte_array())
464}
465
466pub fn verify_spaces_message(
472 msg: &[u8],
473 signature: &[u8; 64],
474 pubkey: &[u8; 32],
475) -> Result<(), SignatureError> {
476 let xonly = XOnlyPublicKey::from_slice(pubkey).map_err(|_| SignatureError::InvalidPublicKey)?;
477 let sig = secp256k1::schnorr::Signature::from_slice(signature)
478 .map_err(|_| SignatureError::InvalidSignature)?;
479 let hashed = hash_signable_message(msg);
480 secp256k1_verify_ctx()
481 .verify_schnorr(&sig, &hashed, &xonly)
482 .map_err(|_| SignatureError::VerificationFailed)
483}
484
485pub fn verify_schnorr(
491 msg_hash: &[u8; 32],
492 signature: &[u8; 64],
493 pubkey: &[u8; 32],
494) -> Result<(), SignatureError> {
495 let xonly = XOnlyPublicKey::from_slice(pubkey).map_err(|_| SignatureError::InvalidPublicKey)?;
496 let sig = secp256k1::schnorr::Signature::from_slice(signature)
497 .map_err(|_| SignatureError::InvalidSignature)?;
498 let msg = secp256k1::Message::from_digest(*msg_hash);
499 secp256k1_verify_ctx()
500 .verify_schnorr(&sig, &msg, &xonly)
501 .map_err(|_| SignatureError::VerificationFailed)
502}
503
504fn records_cmp(a: &sip7::RecordSet, b: &sip7::RecordSet) -> std::cmp::Ordering {
506 let a_seq = a.seq().unwrap_or(0);
507 let b_seq = b.seq().unwrap_or(0);
508 a_seq.cmp(&b_seq).then_with(|| {
509 let hash_a = Sha256Hasher::hash(a.as_slice());
510 let hash_b = Sha256Hasher::hash(b.as_slice());
511 hash_a.cmp(&hash_b)
512 })
513}
514
515impl Zone {
516 pub fn from_slice(bytes: &[u8]) -> Result<Self, std::io::Error> {
517 borsh::from_slice(bytes)
518 }
519
520 pub fn to_bytes(&self) -> Vec<u8> {
521 borsh::to_vec(self).expect("zone serialization should not fail")
522 }
523
524 pub fn signing_bytes(&self) -> Vec<u8> {
530 let mut zone = self.clone();
531 zone.anchor = 0;
532 zone.anchor_hash = [0u8; 32];
533 zone.records = sip7::RecordSet::default();
534 borsh::to_vec(&zone).expect("zone serialization should not fail")
535 }
536
537 pub fn verify_signature(
542 &self,
543 signature: &Signature,
544 signer: &ScriptBuf,
545 ) -> Result<(), SignatureError> {
546 if !signer.is_p2tr() {
547 return Err(SignatureError::InvalidPublicKey);
548 }
549 let pubkey = XOnlyPublicKey::from_slice(&signer.as_bytes()[2..])
550 .map_err(|_| SignatureError::InvalidPublicKey)?;
551
552 let msg = hash_signable_message(&self.signing_bytes());
553 let sig = secp256k1::schnorr::Signature::from_slice(&signature.0)
554 .map_err(|_| SignatureError::InvalidSignature)?;
555
556 secp256k1_verify_ctx()
557 .verify_schnorr(&sig, &msg, &pubkey)
558 .map_err(|_| SignatureError::VerificationFailed)
559 }
560
561 pub fn is_better_than(&self, other: &Self) -> Result<bool, ZoneCompareError> {
575 if self.canonical != other.canonical {
576 return Err(ZoneCompareError::DifferentHandles);
577 }
578
579 match (&self.commitment, &other.commitment) {
582 (ProvableOption::Exists { value: a }, ProvableOption::Exists { value: b })
583 if a.onchain.block_height != b.onchain.block_height =>
584 {
585 return Ok(a.onchain.block_height > b.onchain.block_height);
586 }
587 (ProvableOption::Exists { .. }, ProvableOption::Empty | ProvableOption::Unknown) => {
588 return Ok(true);
589 }
590 (ProvableOption::Empty | ProvableOption::Unknown, ProvableOption::Exists { .. }) => {
591 return Ok(false);
592 }
593 (ProvableOption::Empty, ProvableOption::Unknown) => return Ok(true),
594 (ProvableOption::Unknown, ProvableOption::Empty) => return Ok(false),
595 _ => {}
596 }
597
598 match (&self.delegate, &other.delegate) {
600 (ProvableOption::Exists { value: a }, ProvableOption::Exists { value: b })
601 if (!a.records.is_empty() || !b.records.is_empty()) =>
602 {
603 if a.records.is_empty() {
604 return Ok(false);
605 }
606 if b.records.is_empty() {
607 return Ok(true);
608 }
609 match records_cmp(&a.records, &b.records) {
610 std::cmp::Ordering::Greater => return Ok(true),
611 std::cmp::Ordering::Less => return Ok(false),
612 std::cmp::Ordering::Equal => {}
613 }
614 }
615 (ProvableOption::Exists { .. }, ProvableOption::Empty | ProvableOption::Unknown) => {
616 return Ok(true);
617 }
618 (ProvableOption::Empty | ProvableOption::Unknown, ProvableOption::Exists { .. }) => {
619 return Ok(false);
620 }
621 (ProvableOption::Empty, ProvableOption::Unknown) => return Ok(true),
622 (ProvableOption::Unknown, ProvableOption::Empty) => return Ok(false),
623 _ => {}
624 }
625
626 if !self.records.is_empty() || !other.records.is_empty() {
628 if self.records.is_empty() {
629 return Ok(false);
630 }
631 if other.records.is_empty() {
632 return Ok(true);
633 }
634 match records_cmp(&self.records, &other.records) {
635 std::cmp::Ordering::Greater => return Ok(true),
636 std::cmp::Ordering::Less => return Ok(false),
637 std::cmp::Ordering::Equal => {}
638 }
639 }
640
641 if self.anchor != other.anchor {
643 return Ok(self.anchor > other.anchor);
644 }
645
646 Ok(false) }
648
649 pub fn update_receipt_cache(&mut self, other: &Self) {
652 if let (ProvableOption::Exists { value: mine }, ProvableOption::Exists { value: theirs }) =
653 (&mut self.commitment, &other.commitment)
654 {
655 if mine.onchain.state_root == theirs.onchain.state_root && mine.receipt_hash.is_none() {
656 mine.receipt_hash = theirs.receipt_hash;
657 }
658 }
659 }
660
661 pub fn requires_receipt(&mut self) -> Option<&mut CommitmentInfo> {
669 match &mut self.commitment {
670 ProvableOption::Exists { value } => {
671 if value.receipt_hash.is_some() {
672 return None;
673 }
674 value.onchain.prev_root?;
675 Some(value)
676 }
677 _ => None,
678 }
679 }
680}
681
682fn verify_receipt(
683 ci: &mut CommitmentInfo,
684 space: &SLabel,
685 receipt: &Receipt,
686 options: u32,
687) -> Result<(), MessageError> {
688 let space_str = space.to_string();
689 let zkc = decode_journal(receipt, space)?;
690 verify_zk_journal_matches_onchain(space, &zkc, &ci.onchain)?;
691 let dev_mode = options & VERIFY_DEV_MODE != 0;
692 let mut ctx = VerifierContext::default().with_dev_mode(dev_mode);
693 if options & VERIFY_ENABLE_SNARK == 0 {
694 if matches!(receipt.inner, risc0_zkvm::InnerReceipt::Groth16(_)) {
695 return Err(MessageError::ReceiptInvalid {
696 space: space_str,
697 reason: "SNARK receipts require VERIFY_ENABLE_SNARK".to_string(),
698 });
699 }
700 ctx.groth16_verifier_parameters = None;
701 }
702 let image_id = match zkc.kind {
703 CommitmentKind::Fold => constants::FOLD_ID,
704 CommitmentKind::Step => constants::STEP_ID,
705 };
706 receipt
707 .verify_with_context(&ctx, image_id)
708 .map_err(|e| MessageError::ReceiptInvalid {
709 space: space_str,
710 reason: e.to_string(),
711 })?;
712 let receipt_hash = hash_receipt(receipt);
713 ci.receipt_hash = Some(receipt_hash);
714 Ok(())
715}
716
717#[derive(Debug, Clone)]
718pub enum SignatureError {
719 InvalidPublicKey,
721 InvalidSignature,
723 VerificationFailed,
725 SignerMismatch,
727}
728
729impl fmt::Display for SignatureError {
730 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
731 match self {
732 Self::InvalidPublicKey => write!(f, "invalid schnorr public key"),
733 Self::InvalidSignature => write!(f, "invalid signature format"),
734 Self::VerificationFailed => write!(f, "signature verification failed"),
735 Self::SignerMismatch => write!(f, "sig record canonical name mismatch"),
736 }
737 }
738}
739
740impl std::error::Error for SignatureError {}
741
742#[derive(Debug, Clone)]
743pub enum ZoneCompareError {
744 DifferentHandles,
746}
747
748impl fmt::Display for ZoneCompareError {
749 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
750 match self {
751 Self::DifferentHandles => write!(f, "cannot compare zones for different handles"),
752 }
753 }
754}
755
756impl std::error::Error for ZoneCompareError {}
757
758#[derive(Debug, Clone)]
760pub enum AnchorError {
761 NotSorted,
762}
763
764impl fmt::Display for AnchorError {
765 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
766 match self {
767 Self::NotSorted => write!(f, "anchors must be sorted by height in descending order"),
768 }
769 }
770}
771
772impl std::error::Error for AnchorError {}
773
774impl Default for Veritas {
775 fn default() -> Self {
776 Self::new()
777 }
778}
779
780impl Veritas {
781 pub fn new() -> Self {
782 Veritas {
783 anchors: vec![],
784 oldest_anchor: 0,
785 newest_anchor: 0,
786 }
787 }
788
789 pub fn with_anchors(mut self, anchors: Vec<RootAnchor>) -> Result<Self, AnchorError> {
790 if !anchors.is_empty() {
791 if !anchors.iter().rev().is_sorted_by_key(|a| a.block.height) {
792 return Err(AnchorError::NotSorted);
793 }
794 self.newest_anchor = anchors[0].block.height;
795 self.oldest_anchor = anchors.last().unwrap().block.height;
796 }
797 self.anchors = anchors;
798 Ok(self)
799 }
800
801 pub fn oldest_anchor(&self) -> u32 {
802 self.oldest_anchor
803 }
804
805 pub fn newest_anchor(&self) -> u32 {
806 self.newest_anchor
807 }
808
809 pub fn compute_trust_set(&self) -> TrustSet {
810 compute_trust_set(&self.anchors)
811 }
812
813 pub fn is_finalized(&self, commitment_height: u32) -> bool {
814 let time_passed = self.newest_anchor.saturating_sub(commitment_height);
815 time_passed >= COMMITMENT_FINALITY_INTERVAL
816 }
817
818 pub fn sovereignty_for(&self, commitment_height: u32) -> SovereigntyState {
820 if self.is_finalized(commitment_height) {
821 SovereigntyState::Sovereign
822 } else {
823 SovereigntyState::Pending
824 }
825 }
826
827 pub fn verify(
829 &self,
830 ctx: &msg::QueryContext,
831 msg: crate::msg::Message,
832 ) -> Result<VerifiedMessage, MessageError> {
833 self.verify_with_options(ctx, msg, VERIFY_DEFAULT)
834 }
835
836 pub fn verify_with_options(
843 &self,
844 ctx: &msg::QueryContext,
845 msg: crate::msg::Message,
846 options: u32,
847 ) -> Result<VerifiedMessage, MessageError> {
848 let anchor = self.check_msg_anchor(&msg)?;
849 self.check_msg_chain_proofs(&msg, &anchor)?;
850 self.check_msg_duplicate_spaces(&msg)?;
851
852 let mut zones = Vec::new();
853 let mut verified_bundles = Vec::new();
854 let nums_verified = anchor.nums_root.is_some();
855
856 for bundle in msg.spaces {
857 let (bundle_zones, verified_bundle) =
858 self.verify_bundle(ctx, &msg.chain, options, bundle, nums_verified)?;
859 zones.extend(bundle_zones);
860 if let Some(vb) = verified_bundle {
861 verified_bundles.push(vb);
862 }
863 }
864
865 let resolver = names::NameResolver::from_zones(&zones);
866 resolver.expand_zones(&mut zones);
867
868 let anchor_hash = compute_root_id(&anchor);
869 for zone in &mut zones {
870 zone.anchor_hash = anchor_hash;
871 }
872
873 Ok(VerifiedMessage {
874 zones,
875 message: msg::Message {
876 chain: msg.chain,
877 spaces: verified_bundles,
878 },
879 })
880 }
881
882 fn verify_bundle(
883 &self,
884 ctx: &msg::QueryContext,
885 chain: &msg::ChainProof,
886 options: u32,
887 bundle: msg::Bundle,
888 nums_verified: bool,
889 ) -> Result<(Vec<Zone>, Option<msg::Bundle>), MessageError> {
890 let space = bundle.subject.clone();
891 let cached_parent = ctx.get_parent_zone(&space);
892 let mut extracted = self.extract_parent_zone(chain, &bundle, nums_verified)?;
893
894 let root_handle = SName::from_space(&space);
895
896 let mut zones: Vec<Zone> = Vec::new();
897 let mut receipt_verified = false;
898
899 let target_zone: &Zone = match &cached_parent {
901 Some(cached) => {
902 extracted.update_receipt_cache(cached);
903 if extracted.is_better_than(cached).unwrap_or(false) {
904 receipt_verified = maybe_verify_receipt(
905 &mut extracted,
906 bundle.receipt.as_ref(),
907 &space,
908 options,
909 )?;
910 &extracted
911 } else {
912 cached
913 }
914 }
915 None => {
916 receipt_verified =
917 maybe_verify_receipt(&mut extracted, bundle.receipt.as_ref(), &space, options)?;
918 &extracted
919 }
920 };
921
922 let wants_root = ctx.wants(&root_handle);
923 if wants_root {
924 zones.push(target_zone.clone());
925 }
926
927 let verified_tip = match target_zone.commitment.clone() {
928 ProvableOption::Exists { value } => value,
929 ProvableOption::Empty => CommitmentInfo::empty(),
930 ProvableOption::Unknown => {
931 let verified_bundle = if wants_root {
933 Some(msg::Bundle {
934 subject: space,
935 receipt: if receipt_verified {
936 bundle.receipt
937 } else {
938 None
939 },
940 epochs: vec![],
941 records: bundle.records,
942 delegate_records: bundle.delegate_records,
943 })
944 } else {
945 None
946 };
947 return Ok((zones, verified_bundle));
948 }
949 };
950
951 let mut checked: HashSet<Hash> = HashSet::with_capacity(bundle.epochs.len());
952 let mut verified_epochs: Vec<msg::Epoch> = Vec::new();
953
954 for epoch in bundle.epochs {
955 let root =
956 epoch
957 .tree
958 .compute_root()
959 .map_err(|e| MessageError::HandleProofMalformed {
960 handle: format!("*@{}", space),
961 reason: e.to_string(),
962 })?;
963
964 if checked.contains(&root) {
965 return Err(MessageError::DuplicateEpoch {
966 space: space.to_string(),
967 root,
968 });
969 }
970 checked.insert(root);
971
972 let sovereignty = if epoch.tree.0.is_empty() {
974 SovereigntyState::Dependent
975 } else {
976 let onchain = chain
977 .nums
978 .find_commitment(&space, root)
979 .map_err(|e| MessageError::NumsProofMalformed {
980 reason: e.to_string(),
981 })?
982 .ok_or_else(|| MessageError::CommitmentNotFound {
983 space: space.to_string(),
984 root,
985 })?;
986
987 if onchain.block_height > verified_tip.onchain.block_height {
988 return Err(MessageError::EpochExceedsTip {
989 space: space.to_string(),
990 });
991 }
992
993 self.sovereignty_for(onchain.block_height)
994 };
995
996 let mut verified_handles: Vec<msg::Handle> = Vec::new();
997
998 for handle in epoch.handles {
999 let subject = SName::join(&handle.name, &space).map_err(|_| {
1000 MessageError::InvalidSubject {
1001 subject: format!("{}@{}", handle.name, space),
1002 }
1003 })?;
1004
1005 if !ctx.wants(&subject) {
1006 continue;
1007 }
1008
1009 let zone = if handle.signature.is_some() {
1010 if root != verified_tip.onchain.state_root {
1011 return Err(MessageError::TemporaryRequiresTip {
1012 handle: subject.to_string(),
1013 tip: verified_tip.onchain.state_root,
1014 got: root,
1015 });
1016 }
1017 verify_temporary_handle(
1018 chain.anchor.height,
1019 &handle,
1020 &subject,
1021 &epoch.tree,
1022 target_zone,
1023 )?
1024 } else {
1025 verify_final_handle(
1026 chain.anchor.height,
1027 &handle,
1028 &subject,
1029 &epoch.tree,
1030 &chain.nums,
1031 sovereignty,
1032 )?
1033 };
1034
1035 push_best_zone(ctx, &mut zones, zone);
1036 verified_handles.push(handle);
1037 }
1038
1039 if !verified_handles.is_empty() {
1040 verified_epochs.push(msg::Epoch {
1041 tree: epoch.tree,
1042 handles: verified_handles,
1043 });
1044 }
1045 }
1046
1047 if !wants_root && !zones.is_empty() {
1051 zones.insert(0, target_zone.clone());
1052 }
1053
1054 let verified_bundle = if wants_root || !verified_epochs.is_empty() {
1056 Some(msg::Bundle {
1057 subject: space,
1058 receipt: if receipt_verified {
1059 bundle.receipt
1060 } else {
1061 None
1062 },
1063 epochs: verified_epochs,
1064 records: bundle.records,
1065 delegate_records: bundle.delegate_records,
1066 })
1067 } else {
1068 None
1069 };
1070
1071 Ok((zones, verified_bundle))
1072 }
1073
1074 fn check_msg_anchor(&self, msg: &crate::msg::Message) -> Result<RootAnchor, MessageError> {
1075 let height = msg.chain.anchor.height;
1076
1077 if height < self.oldest_anchor {
1078 return Err(MessageError::AnchorStale {
1079 anchor: height,
1080 oldest: self.oldest_anchor,
1081 });
1082 }
1083 if height > self.newest_anchor {
1084 return Err(MessageError::AnchorAhead {
1085 anchor: height,
1086 tip: self.newest_anchor,
1087 });
1088 }
1089
1090 let anchor = self
1091 .find_by_anchor(height)
1092 .ok_or(MessageError::NoAnchorAtHeight { anchor: height })?
1093 .clone();
1094
1095 if msg.chain.anchor.hash != anchor.block.hash {
1096 return Err(MessageError::AnchorHashMismatch {
1097 height,
1098 expected: anchor.block.hash.to_byte_array(),
1099 got: msg.chain.anchor.hash.to_byte_array(),
1100 });
1101 }
1102
1103 Ok(anchor)
1104 }
1105
1106 fn check_msg_chain_proofs(
1107 &self,
1108 msg: &crate::msg::Message,
1109 anchor: &RootAnchor,
1110 ) -> Result<(), MessageError> {
1111 let spaces_root =
1112 msg.chain
1113 .spaces
1114 .compute_root()
1115 .map_err(|_| MessageError::SpacesRootMismatch {
1116 expected: anchor.spaces_root,
1117 got: [0u8; 32],
1118 })?;
1119
1120 if spaces_root != anchor.spaces_root {
1121 return Err(MessageError::SpacesRootMismatch {
1122 expected: anchor.spaces_root,
1123 got: spaces_root,
1124 });
1125 }
1126
1127 match anchor.nums_root {
1128 Some(expected) => {
1129 let nums_root =
1130 msg.chain
1131 .nums
1132 .compute_root()
1133 .map_err(|_| MessageError::NumsRootMismatch {
1134 expected: Some(expected),
1135 got: [0u8; 32],
1136 })?;
1137
1138 if nums_root != expected {
1139 return Err(MessageError::NumsRootMismatch {
1140 expected: Some(expected),
1141 got: nums_root,
1142 });
1143 }
1144 }
1145 None => {
1146 if !msg.chain.nums.0.is_empty() {
1149 return Err(MessageError::NumsRootMismatch {
1150 expected: None,
1151 got: msg.chain.nums.compute_root().unwrap_or([0u8; 32]),
1152 });
1153 }
1154 }
1155 }
1156
1157 Ok(())
1158 }
1159
1160 fn check_msg_duplicate_spaces(&self, msg: &crate::msg::Message) -> Result<(), MessageError> {
1161 use std::collections::HashSet;
1162 let mut seen: HashSet<&[u8]> = HashSet::new();
1163 for bundle in &msg.spaces {
1164 if !seen.insert(bundle.subject.as_ref()) {
1165 return Err(MessageError::DuplicateSpace {
1166 space: bundle.subject.to_string(),
1167 });
1168 }
1169 }
1170 Ok(())
1171 }
1172
1173 fn find_by_anchor(&self, anchor: u32) -> Option<&RootAnchor> {
1174 self.anchors.iter().find(|a| a.block.height == anchor)
1175 }
1176
1177 pub fn find_anchor(&self, height: u32) -> Option<&RootAnchor> {
1179 self.find_by_anchor(height)
1180 }
1181
1182 fn extract_parent_zone(
1187 &self,
1188 chain: &msg::ChainProof,
1189 bundle: &msg::Bundle,
1190 nums_verified: bool,
1191 ) -> Result<Zone, MessageError> {
1192 let mut num_id = None;
1193 let (spk, records) = if !bundle.subject.is_numeric() {
1194 let Some(spaceout) = chain.spaces.find_space(&bundle.subject) else {
1195 return Err(MessageError::SpaceNotFound {
1196 space: bundle.subject.to_string(),
1197 });
1198 };
1199 let Some(space) = spaceout.space else {
1200 return Err(MessageError::SpaceNotFound {
1201 space: bundle.subject.to_string(),
1202 });
1203 };
1204 let data = space
1205 .data()
1206 .filter(|d| !d.is_empty())
1207 .map(|d| sip7::RecordSet::new(d.to_vec()))
1208 .unwrap_or_default();
1209 (spaceout.script_pubkey, data)
1210 } else {
1211 if !nums_verified {
1212 return Err(MessageError::NumsRootMissing {
1213 space: bundle.subject.to_string(),
1214 });
1215 }
1216 let Some(numout) = chain
1217 .nums
1218 .find_numeric(&bundle.subject.clone().try_into().expect("numeric"))
1219 .ok()
1220 .flatten()
1221 else {
1222 return Err(MessageError::NumericNotFound {
1223 numeric: bundle.subject.to_string(),
1224 });
1225 };
1226 num_id = Some(numout.num.id);
1227 let data = numout
1228 .num
1229 .data
1230 .filter(|d| !d.is_empty())
1231 .map(|d| sip7::RecordSet::new(d.to_vec()))
1232 .unwrap_or_default();
1233 (numout.script_pubkey, data)
1234 };
1235
1236 let handle = SName::from_space(&bundle.subject);
1237
1238 let mut z = Zone {
1239 anchor: chain.anchor.height,
1240 sovereignty: SovereigntyState::Sovereign,
1241 canonical: handle.clone(),
1242 handle,
1243 alias: None,
1244 script_pubkey: spk,
1245 fallback_records: records,
1246 records: sip7::RecordSet::default(),
1247 delegate: ProvableOption::Unknown,
1248 commitment: ProvableOption::Unknown,
1249 num_id,
1250 anchor_hash: [0u8; 32],
1251 };
1252
1253 if let Some(records) = &bundle.records {
1255 msg::verify_records(records, &z.script_pubkey, &z.canonical).map_err(|e| {
1256 MessageError::RecordsInvalid {
1257 handle: z.handle.to_string(),
1258 reason: e.to_string(),
1259 }
1260 })?;
1261 z.records = records.clone();
1262 }
1263
1264 if !nums_verified {
1267 return Ok(z);
1268 }
1269
1270 if let Ok(delegate) = chain.nums.find_num(&z.script_pubkey) {
1272 match delegate {
1273 None => z.delegate = ProvableOption::Empty,
1274 Some(delegate) => {
1275 let mut delegate_records = sip7::RecordSet::default();
1276 if let Some(records) = &bundle.delegate_records {
1277 msg::verify_records(records, &delegate.script_pubkey, &z.canonical)
1278 .map_err(|e| MessageError::RecordsInvalid {
1279 handle: z.handle.to_string(),
1280 reason: e.to_string(),
1281 })?;
1282 delegate_records = records.clone();
1283 }
1284 z.delegate = ProvableOption::Exists {
1285 value: Delegate {
1286 script_pubkey: delegate.script_pubkey,
1287 fallback_records: delegate
1288 .num
1289 .data
1290 .filter(|d| !d.is_empty())
1291 .map(|d| sip7::RecordSet::new(d.to_vec()))
1292 .unwrap_or_default(),
1293 records: delegate_records,
1294 },
1295 }
1296 }
1297 }
1298 }
1299
1300 if let Ok(root) = chain.nums.get_latest_commitment_root(&bundle.subject) {
1302 match root {
1303 None => z.commitment = ProvableOption::Empty,
1304 Some(root) => {
1305 let commitment = chain.nums.find_commitment(&bundle.subject, root);
1306 if let Ok(Some(commitment)) = commitment {
1307 z.commitment = ProvableOption::Exists {
1308 value: CommitmentInfo {
1309 onchain: commitment,
1310 receipt_hash: None,
1311 },
1312 };
1313 }
1314 }
1315 }
1316 }
1317
1318 Ok(z)
1319 }
1320}
1321
1322fn verify_temporary_handle(
1324 anchor_height: u32,
1325 handle: &msg::Handle,
1326 subject: &SName,
1327 epoch_tree: &cert::HandleSubtree,
1328 parent_zone: &Zone,
1329) -> Result<Zone, MessageError> {
1330 let exists =
1333 epoch_tree
1334 .contains_name(&handle.name)
1335 .map_err(|e| MessageError::HandleProofMalformed {
1336 handle: subject.to_string(),
1337 reason: e.to_string(),
1338 })?;
1339
1340 if exists {
1341 return Err(MessageError::HandleAlreadyExists {
1342 handle: subject.to_string(),
1343 });
1344 }
1345
1346 let signer = match &parent_zone.delegate {
1347 ProvableOption::Exists { value: delegate } => &delegate.script_pubkey,
1348 ProvableOption::Empty => &parent_zone.script_pubkey,
1349 ProvableOption::Unknown => {
1350 return Err(MessageError::ParentDelegateUnknown {
1351 handle: subject.to_string(),
1352 });
1353 }
1354 };
1355
1356 let mut verified_records = sip7::RecordSet::default();
1357 if let Some(records) = &handle.records {
1358 msg::verify_records(records, &handle.genesis_spk, subject).map_err(|e| {
1359 MessageError::RecordsInvalid {
1360 handle: subject.to_string(),
1361 reason: e.to_string(),
1362 }
1363 })?;
1364 verified_records = records.clone();
1365 }
1366
1367 let num_id = Some(NumId::from_spk::<KeyHash>(handle.genesis_spk.clone()));
1368 let zone = Zone {
1369 anchor: anchor_height,
1370 sovereignty: SovereigntyState::Dependent,
1371 canonical: subject.clone(),
1372 handle: subject.clone(),
1373 alias: None,
1374 script_pubkey: handle.genesis_spk.clone(),
1375 fallback_records: sip7::RecordSet::default(),
1376 records: verified_records,
1377 delegate: ProvableOption::Unknown,
1378 commitment: ProvableOption::Unknown,
1379 num_id,
1380 anchor_hash: [0u8; 32],
1381 };
1382
1383 zone.verify_signature(handle.signature.as_ref().unwrap(), signer)
1384 .map_err(|e| MessageError::SignatureInvalid {
1385 handle: zone.handle.to_string(),
1386 reason: e.to_string(),
1387 })?;
1388
1389 Ok(zone)
1390}
1391
1392fn verify_final_handle(
1394 anchor_height: u32,
1395 handle: &msg::Handle,
1396 subject: &SName,
1397 epoch_tree: &cert::HandleSubtree,
1398 nums: &cert::NumsSubtree,
1399 sovereignty: SovereigntyState,
1400) -> Result<Zone, MessageError> {
1401 if epoch_tree.0.is_empty() {
1402 return Err(MessageError::FinalCertRequiresTree {
1403 handle: subject.to_string(),
1404 });
1405 }
1406
1407 let included = epoch_tree
1408 .contains_subspace(&handle.name, &handle.genesis_spk)
1409 .map_err(|e| MessageError::HandleProofMalformed {
1410 handle: subject.to_string(),
1411 reason: e.to_string(),
1412 })?;
1413
1414 if !included {
1415 return Err(MessageError::HandleNotFound {
1416 handle: subject.to_string(),
1417 });
1418 }
1419
1420 let numout =
1422 nums.find_num(&handle.genesis_spk)
1423 .map_err(|e| MessageError::NumsProofMalformed {
1424 reason: e.to_string(),
1425 })?;
1426
1427 let (num_id, spk, onchain_data, alias) = match numout {
1428 Some(numout) => (
1429 numout.num.id,
1430 numout.script_pubkey,
1431 numout
1432 .num
1433 .data
1434 .filter(|d| !d.is_empty())
1435 .map(|d| sip7::RecordSet::new(d.to_vec()))
1436 .unwrap_or_default(),
1437 Some(numout.num.name.to_slabel()),
1438 ),
1439 None => (
1440 NumId::from_spk::<KeyHash>(handle.genesis_spk.clone()),
1441 handle.genesis_spk.clone(),
1442 sip7::RecordSet::default(),
1443 None,
1444 ),
1445 };
1446
1447 let mut verified_records = sip7::RecordSet::default();
1448 if let Some(records) = &handle.records {
1449 msg::verify_records(records, &spk, subject).map_err(|e| MessageError::RecordsInvalid {
1450 handle: subject.to_string(),
1451 reason: e.to_string(),
1452 })?;
1453 verified_records = records.clone();
1454 }
1455
1456 let zone = Zone {
1457 anchor: anchor_height,
1458 sovereignty,
1459 canonical: subject.clone(),
1460 handle: subject.clone(),
1461 alias,
1462 script_pubkey: spk,
1463 fallback_records: onchain_data,
1464 records: verified_records,
1465 delegate: ProvableOption::Unknown,
1466 commitment: ProvableOption::Unknown,
1467 num_id: Some(num_id),
1468 anchor_hash: [0u8; 32],
1469 };
1470
1471 Ok(zone)
1472}
1473
1474#[derive(Debug, Clone)]
1476pub enum MessageError {
1477 AnchorStale { anchor: u32, oldest: u32 },
1479 AnchorAhead { anchor: u32, tip: u32 },
1481 NoAnchorAtHeight { anchor: u32 },
1483 AnchorHashMismatch {
1485 height: u32,
1486 expected: Hash,
1487 got: Hash,
1488 },
1489 DuplicateSpace { space: String },
1491 MalformedReceipt { space: String, reason: String },
1493 ReceiptPolicyMismatch { space: String },
1495 SpacesRootMismatch { expected: Hash, got: Hash },
1497 NumsRootMismatch { expected: Option<Hash>, got: Hash },
1499 NumsRootMissing { space: String },
1501 SpaceNotFound { space: String },
1503 NumericNotFound { numeric: String },
1505 CommitmentNotFound { space: String, root: Hash },
1507 ReceiptRequired { space: String },
1509 HandleProofMalformed { handle: String, reason: String },
1511 DuplicateEpoch { space: String, root: Hash },
1513 EpochExceedsTip { space: String },
1515 InvalidSubject { subject: String },
1517 TemporaryRequiresTip {
1519 handle: String,
1520 tip: Hash,
1521 got: Hash,
1522 },
1523 HandleAlreadyExists { handle: String },
1525 ParentDelegateUnknown { handle: String },
1527 SignatureInvalid { handle: String, reason: String },
1529 RecordsInvalid { handle: String, reason: String },
1531 FinalCertRequiresTree { handle: String },
1533 HandleNotFound { handle: String },
1535 NumsProofMalformed { reason: String },
1537 ReceiptInvalid { space: String, reason: String },
1539 CommitmentReceiptMismatch { space: String, field: &'static str },
1541}
1542
1543impl fmt::Display for MessageError {
1544 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1545 match self {
1546 Self::AnchorStale { anchor, oldest } => {
1547 write!(f, "anchor {} is stale, oldest is {}", anchor, oldest)
1548 }
1549 Self::AnchorAhead { anchor, tip } => {
1550 write!(f, "anchor {} is ahead of tip {}", anchor, tip)
1551 }
1552 Self::NoAnchorAtHeight { anchor } => {
1553 write!(f, "no anchor at height {}", anchor)
1554 }
1555 Self::AnchorHashMismatch {
1556 height,
1557 expected,
1558 got,
1559 } => {
1560 write!(
1561 f,
1562 "anchor hash mismatch at {}: expected {}, got {}",
1563 height,
1564 hex::encode(expected),
1565 hex::encode(got)
1566 )
1567 }
1568 Self::DuplicateSpace { space } => {
1569 write!(f, "duplicate space in message: {}", space)
1570 }
1571 Self::MalformedReceipt { space, reason } => {
1572 write!(f, "malformed receipt for {}: {}", space, reason)
1573 }
1574 Self::ReceiptPolicyMismatch { space } => {
1575 write!(f, "receipt policy mismatch for {}", space)
1576 }
1577 Self::SpacesRootMismatch { expected, got } => {
1578 write!(
1579 f,
1580 "spaces root mismatch: expected {}, got {}",
1581 hex::encode(expected),
1582 hex::encode(got)
1583 )
1584 }
1585 Self::NumsRootMismatch { expected, got } => {
1586 write!(
1587 f,
1588 "nums root mismatch: expected {}, got {}",
1589 expected.map(hex::encode).unwrap_or_else(|| "none".into()),
1590 hex::encode(got)
1591 )
1592 }
1593 Self::NumsRootMissing { space } => {
1594 write!(
1595 f,
1596 "anchor has no nums root, cannot verify nums data for {}",
1597 space
1598 )
1599 }
1600 Self::SpaceNotFound { space } => {
1601 write!(f, "space {} not found in proof", space)
1602 }
1603 Self::NumericNotFound { numeric } => {
1604 write!(f, "numeric space {} not found in proof", numeric)
1605 }
1606 Self::CommitmentNotFound { space, root } => {
1607 write!(
1608 f,
1609 "commitment {} not found for {}",
1610 hex::encode(root),
1611 space
1612 )
1613 }
1614 Self::ReceiptRequired { space } => {
1615 write!(f, "receipt required for {}", space)
1616 }
1617 Self::HandleProofMalformed { handle, reason } => {
1618 write!(f, "handle proof malformed for {}: {}", handle, reason)
1619 }
1620 Self::DuplicateEpoch { space, root } => {
1621 write!(f, "duplicate epoch {} for {}", hex::encode(root), space)
1622 }
1623 Self::EpochExceedsTip { space } => {
1624 write!(f, "epoch commitment exceeds tip for {}", space)
1625 }
1626 Self::InvalidSubject { subject } => {
1627 write!(f, "invalid subject: {}", subject)
1628 }
1629 Self::TemporaryRequiresTip { handle, tip, got } => {
1630 write!(
1631 f,
1632 "Temporary handle {} verifies against {} but requires tip {}",
1633 handle,
1634 hex::encode(got),
1635 hex::encode(tip)
1636 )
1637 }
1638 Self::HandleAlreadyExists { handle } => {
1639 write!(f, "handle {} already exists", handle)
1640 }
1641 Self::ParentDelegateUnknown { handle } => {
1642 write!(f, "parent delegate unknown for {}", handle)
1643 }
1644 Self::SignatureInvalid { handle, reason } => {
1645 write!(f, "signature invalid for {}: {}", handle, reason)
1646 }
1647 Self::RecordsInvalid { handle, reason } => {
1648 write!(f, "records invalid for {}: {}", handle, reason)
1649 }
1650 Self::FinalCertRequiresTree { handle } => {
1651 write!(
1652 f,
1653 "final certificate requires non-empty tree for {}",
1654 handle
1655 )
1656 }
1657 Self::HandleNotFound { handle } => {
1658 write!(f, "handle {} not found", handle)
1659 }
1660 Self::NumsProofMalformed { reason } => {
1661 write!(f, "nums proof malformed: {}", reason)
1662 }
1663 Self::ReceiptInvalid { space, reason } => {
1664 write!(f, "receipt invalid for {}: {}", space, reason)
1665 }
1666 Self::CommitmentReceiptMismatch { space, field } => {
1667 write!(f, "commitment {} mismatch for {}", field, space)
1668 }
1669 }
1670 }
1671}
1672
1673impl std::error::Error for MessageError {}
1674
1675fn push_best_zone(ctx: &msg::QueryContext, zones: &mut Vec<Zone>, zone: Zone) {
1677 let Some(cached) = ctx.get_zone(&zone.canonical) else {
1678 zones.push(zone);
1679 return;
1680 };
1681 if !zone.is_better_than(cached).unwrap_or(false) {
1682 zones.push(cached.clone());
1683 return;
1684 }
1685 zones.push(zone);
1686}
1687
1688fn maybe_verify_receipt(
1691 zone: &mut Zone,
1692 receipt: Option<&risc0_zkvm::Receipt>,
1693 space: &SLabel,
1694 options: u32,
1695) -> Result<bool, MessageError> {
1696 let Some(ci) = zone.requires_receipt() else {
1697 return Ok(false);
1698 };
1699 let receipt = receipt.ok_or_else(|| MessageError::ReceiptRequired {
1700 space: space.to_string(),
1701 })?;
1702 verify_receipt(ci, space, receipt, options)?;
1703 Ok(true)
1704}
1705
1706fn decode_journal(
1708 receipt: &risc0_zkvm::Receipt,
1709 space: &SLabel,
1710) -> Result<libveritas_zk::guest::Commitment, MessageError> {
1711 receipt
1712 .journal
1713 .decode()
1714 .map_err(|e| MessageError::MalformedReceipt {
1715 space: space.to_string(),
1716 reason: e.to_string(),
1717 })
1718}
1719
1720pub(crate) fn serialize_hash<S>(hash: &Hash, serializer: S) -> Result<S::Ok, S::Error>
1721where
1722 S: Serializer,
1723{
1724 if serializer.is_human_readable() {
1725 serializer.serialize_str(&hex::encode(hash))
1726 } else {
1727 serializer.serialize_bytes(hash)
1728 }
1729}
1730
1731pub(crate) fn deserialize_hash<'de, D>(deserializer: D) -> Result<Hash, D::Error>
1732where
1733 D: Deserializer<'de>,
1734{
1735 if deserializer.is_human_readable() {
1736 let s: String = <String as Deserialize>::deserialize(deserializer)?;
1737 let mut bytes = [0u8; 32];
1738 hex::decode_to_slice(&s, &mut bytes).map_err(serde::de::Error::custom)?;
1739 Ok(bytes)
1740 } else {
1741 <[u8; 32] as Deserialize>::deserialize(deserializer)
1742 }
1743}
1744
1745pub(crate) fn serialize_option_hash<S>(
1746 hash: &Option<Hash>,
1747 serializer: S,
1748) -> Result<S::Ok, S::Error>
1749where
1750 S: Serializer,
1751{
1752 match hash {
1753 Some(bytes) => {
1754 if serializer.is_human_readable() {
1755 serializer.serialize_some(&hex::encode(bytes))
1756 } else {
1757 serializer.serialize_some(bytes)
1758 }
1759 }
1760 None => serializer.serialize_none(),
1761 }
1762}
1763
1764pub(crate) fn deserialize_option_hash<'de, D>(deserializer: D) -> Result<Option<Hash>, D::Error>
1765where
1766 D: Deserializer<'de>,
1767{
1768 if deserializer.is_human_readable() {
1769 let opt: Option<String> = <Option<String> as Deserialize>::deserialize(deserializer)?;
1770 match opt {
1771 None => Ok(None),
1772 Some(s) => {
1773 let mut bytes = [0u8; 32];
1774 hex::decode_to_slice(&s, &mut bytes).map_err(serde::de::Error::custom)?;
1775 Ok(Some(bytes))
1776 }
1777 }
1778 } else {
1779 let opt: Option<[u8; 32]> = <Option<[u8; 32]> as Deserialize>::deserialize(deserializer)?;
1780 Ok(opt)
1781 }
1782}
1783
1784fn verify_zk_journal_matches_onchain(
1785 space: &SLabel,
1786 zk: &libveritas_zk::guest::Commitment,
1787 onchain: &spaces_nums::Commitment,
1788) -> Result<(), MessageError> {
1789 let space_str = space.to_string();
1790 if zk.policy_fold != constants::FOLD_ID || zk.policy_step != constants::STEP_ID {
1791 return Err(MessageError::ReceiptPolicyMismatch { space: space_str });
1792 }
1793 if zk.final_root != onchain.state_root {
1794 return Err(MessageError::CommitmentReceiptMismatch {
1795 space: space_str.clone(),
1796 field: "state_root",
1797 });
1798 }
1799 if zk.rolling_hash != onchain.rolling_hash {
1800 return Err(MessageError::CommitmentReceiptMismatch {
1801 space: space_str,
1802 field: "rolling_hash",
1803 });
1804 }
1805 Ok(())
1806}
1807
1808fn hash_receipt(receipt: &Receipt) -> Hash {
1809 Sha256Hasher::hash(&borsh::to_vec(receipt).expect("receipt serialization should not fail"))
1810}