1use iota_sdk_types::{CheckpointContents, Transaction as TransactionData, TransactionDigest};
15use iota_types::committee::Committee;
16use iota_types::digests::ChainIdentifier;
17use iota_types::effects::{TransactionEffects, TransactionEffectsAPI, TransactionEffectsExt, TransactionEvents};
18use iota_types::event::EventID;
19use iota_types::messages_checkpoint::CertifiedCheckpointSummary;
20use iota_types::object::Object;
21use iota_types::transaction::Transaction;
22use serde::{Deserialize, Serialize};
23
24use crate::BoxError;
25
26#[derive(Debug, thiserror::Error)]
28#[non_exhaustive]
29#[error("failed to serialize or deserialize Proof of Inclusion proof")]
30pub struct SerializationError {
31 #[source]
33 pub kind: SerializationErrorKind,
34}
35
36#[derive(Debug, thiserror::Error)]
38#[non_exhaustive]
39pub enum SerializationErrorKind {
40 #[error("json serialization or deserialization failed")]
42 Json {
43 #[source]
45 source: serde_json::Error,
46 },
47}
48
49#[derive(Debug, thiserror::Error)]
51#[non_exhaustive]
52#[error("failed to verify Proof of Inclusion proof")]
53pub struct VerifyError {
54 #[source]
56 pub kind: VerifyErrorKind,
57}
58
59#[derive(Debug, thiserror::Error)]
61#[non_exhaustive]
62pub enum VerifyErrorKind {
63 #[error("checkpoint summary verification failed")]
65 CheckpointSummary {
66 #[source]
68 source: BoxError,
69 },
70 #[error("proof does not contain a target")]
72 MissingTarget,
73 #[error("transaction target does not match the packaged transaction")]
75 TransactionTargetMismatch,
76 #[error("transaction digest does not match the execution digest")]
78 TransactionDigestMismatch,
79 #[error("transaction digest not found in the checkpoint contents")]
81 TransactionNotInCheckpoint,
82 #[error("transaction signatures do not match the checkpoint contents")]
84 TransactionSignaturesMismatch,
85 #[error("events digest does not match the execution digest")]
87 EventsDigestMismatch,
88 #[error("event targets require transaction event data")]
90 MissingEvents,
91 #[error("event target does not belong to the transaction")]
93 EventTransactionMismatch,
94 #[error("event sequence number {sequence} is out of bounds")]
96 EventSequenceOutOfBounds {
97 sequence: u64,
99 },
100 #[error("object target was not found in the transaction effects")]
102 ObjectNotFound,
103}
104
105#[derive(Default, Debug, Serialize, Deserialize, Clone)]
109pub struct ProofTargets {
110 pub transaction: Option<TransactionDigest>,
112
113 pub objects: Vec<Object>,
115
116 pub events: Vec<EventID>,
118}
119
120impl ProofTargets {
121 pub fn new() -> Self {
123 Self::default()
124 }
125
126 pub fn set_transaction(mut self, transaction: TransactionDigest) -> Self {
128 self.transaction = Some(transaction);
129 self
130 }
131
132 pub fn add_object(mut self, object: Object) -> Self {
134 self.objects.push(object);
135 self
136 }
137
138 pub fn add_event(mut self, event_id: EventID) -> Self {
140 self.events.push(event_id);
141 self
142 }
143
144 pub fn is_empty(&self) -> bool {
146 self.transaction.is_none() && self.objects.is_empty() && self.events.is_empty()
147 }
148}
149
150#[derive(Clone, Debug, Serialize, Deserialize)]
155pub struct TransactionProof {
156 pub transaction: Transaction,
158 pub effects: TransactionEffects,
160 pub events: Option<TransactionEvents>,
162}
163
164impl TransactionProof {
165 pub fn new(
167 transaction: Transaction,
168 effects: TransactionEffects,
169 events: impl Into<Option<TransactionEvents>>,
170 ) -> Self {
171 Self {
172 transaction,
173 effects,
174 events: events.into(),
175 }
176 }
177}
178
179#[derive(Debug)]
186#[must_use = "read authenticated targets from the returned VerifiedProof"]
187pub struct VerifiedProof<'proof> {
188 checkpoint_summary: &'proof CertifiedCheckpointSummary,
189 transaction: &'proof TransactionData,
190 transaction_digest: TransactionDigest,
191 targets: &'proof ProofTargets,
192 objects: &'proof [Object],
193 effects: &'proof TransactionEffects,
194 events: Option<&'proof TransactionEvents>,
195}
196
197impl<'proof> VerifiedProof<'proof> {
198 pub fn checkpoint_epoch(&self) -> u64 {
200 self.checkpoint_summary.epoch()
201 }
202
203 pub fn checkpoint_sequence_number(&self) -> u64 {
205 self.checkpoint_summary.sequence_number
206 }
207
208 pub fn checkpoint_timestamp_ms(&self) -> u64 {
210 self.checkpoint_summary.timestamp_ms
211 }
212
213 pub const fn transaction(&self) -> &'proof TransactionData {
218 self.transaction
219 }
220
221 pub const fn transaction_digest(&self) -> TransactionDigest {
223 self.transaction_digest
224 }
225
226 pub const fn transaction_target(&self) -> Option<&'proof TransactionDigest> {
228 self.targets.transaction.as_ref()
229 }
230
231 pub const fn targets(&self) -> &'proof ProofTargets {
233 self.targets
234 }
235
236 pub const fn objects(&self) -> &'proof [Object] {
238 self.objects
239 }
240
241 pub const fn effects(&self) -> &'proof TransactionEffects {
243 self.effects
244 }
245
246 pub const fn events(&self) -> Option<&'proof TransactionEvents> {
251 self.events
252 }
253}
254
255#[derive(Clone, Debug, Serialize, Deserialize)]
267#[non_exhaustive]
268pub enum Proof {
269 ProofV1(ProofV1),
271}
272
273#[derive(Clone, Debug, Serialize, Deserialize)]
281pub struct ProofV1 {
282 pub chain: ChainIdentifier,
284 pub targets: ProofTargets,
286 pub checkpoint_summary: CertifiedCheckpointSummary,
288 pub checkpoint_contents: CheckpointContents,
290 pub transaction_proof: TransactionProof,
292}
293
294impl ProofV1 {
295 const VERSION: u16 = 1;
296
297 pub fn new(
299 chain: ChainIdentifier,
300 targets: ProofTargets,
301 checkpoint_summary: CertifiedCheckpointSummary,
302 checkpoint_contents: CheckpointContents,
303 transaction_proof: TransactionProof,
304 ) -> Self {
305 Self {
306 chain,
307 targets,
308 checkpoint_summary,
309 checkpoint_contents,
310 transaction_proof,
311 }
312 }
313}
314
315impl From<ProofV1> for Proof {
316 fn from(proof: ProofV1) -> Self {
317 Self::ProofV1(proof)
318 }
319}
320
321impl Proof {
322 pub const fn version(&self) -> u16 {
324 match self {
325 Self::ProofV1(_) => ProofV1::VERSION,
326 }
327 }
328
329 pub const fn chain(&self) -> &ChainIdentifier {
333 match self {
334 Self::ProofV1(proof) => &proof.chain,
335 }
336 }
337
338 pub const fn targets(&self) -> &ProofTargets {
343 match self {
344 Self::ProofV1(proof) => &proof.targets,
345 }
346 }
347
348 pub const fn checkpoint_summary(&self) -> &CertifiedCheckpointSummary {
350 match self {
351 Self::ProofV1(proof) => &proof.checkpoint_summary,
352 }
353 }
354
355 pub const fn checkpoint_contents(&self) -> &CheckpointContents {
357 match self {
358 Self::ProofV1(proof) => &proof.checkpoint_contents,
359 }
360 }
361
362 pub const fn transaction_proof(&self) -> &TransactionProof {
364 match self {
365 Self::ProofV1(proof) => &proof.transaction_proof,
366 }
367 }
368
369 pub fn to_json_vec(&self) -> Result<Vec<u8>, SerializationError> {
375 serde_json::to_vec(self).map_err(|source| SerializationError {
376 kind: SerializationErrorKind::Json { source },
377 })
378 }
379
380 pub fn from_json_slice(bytes: &[u8]) -> Result<Self, SerializationError> {
387 serde_json::from_slice(bytes).map_err(|source| SerializationError {
388 kind: SerializationErrorKind::Json { source },
389 })
390 }
391}
392
393#[derive(Clone, Copy, Debug)]
401pub struct ProofVerifier<'committee> {
402 committee: &'committee Committee,
403}
404
405impl<'committee> ProofVerifier<'committee> {
406 pub const fn new(committee: &'committee Committee) -> Self {
408 Self { committee }
409 }
410
411 pub const fn committee(&self) -> &'committee Committee {
413 self.committee
414 }
415
416 pub fn verify<'proof>(&self, proof: &'proof Proof) -> Result<VerifiedProof<'proof>, VerifyError> {
435 match proof {
436 Proof::ProofV1(proof) => self.verify_v1(proof),
437 }
438 }
439
440 fn verify_v1<'proof>(&self, proof: &'proof ProofV1) -> Result<VerifiedProof<'proof>, VerifyError> {
442 if proof.targets.is_empty() {
443 return Err(VerifyError {
444 kind: VerifyErrorKind::MissingTarget,
445 });
446 }
447
448 let summary = &proof.checkpoint_summary;
449 let contents = Some(&proof.checkpoint_contents);
450
451 summary
452 .verify_with_contents(self.committee, contents)
453 .map_err(|source| VerifyError {
454 kind: VerifyErrorKind::CheckpointSummary {
455 source: Box::new(source),
456 },
457 })?;
458
459 self.verify_transaction_proof(&proof.checkpoint_contents, &proof.transaction_proof)?;
460 self.verify_targets(&proof.targets, &proof.transaction_proof)?;
461
462 Ok(VerifiedProof {
463 checkpoint_summary: summary,
464 transaction: proof.transaction_proof.transaction.data().transaction(),
465 transaction_digest: *proof.transaction_proof.transaction.digest(),
466 targets: &proof.targets,
467 objects: &proof.targets.objects,
468 effects: &proof.transaction_proof.effects,
469 events: proof.transaction_proof.events.as_ref(),
470 })
471 }
472
473 fn verify_transaction_proof(
475 &self,
476 checkpoint_contents: &CheckpointContents,
477 transaction_proof: &TransactionProof,
478 ) -> Result<(), VerifyError> {
479 let execution_digests = transaction_proof.effects.execution_digests();
480
481 if transaction_proof.transaction.digest() != &execution_digests.transaction {
482 return Err(VerifyError {
483 kind: VerifyErrorKind::TransactionDigestMismatch,
484 });
485 }
486
487 let checkpoint_transaction = checkpoint_contents
488 .transactions()
489 .iter()
490 .find(|transaction| {
491 transaction.transaction == execution_digests.transaction
492 && transaction.effects == execution_digests.effects
493 })
494 .ok_or(VerifyError {
495 kind: VerifyErrorKind::TransactionNotInCheckpoint,
496 })?;
497
498 if checkpoint_transaction.signatures.as_slice() != transaction_proof.transaction.data().signatures() {
499 return Err(VerifyError {
500 kind: VerifyErrorKind::TransactionSignaturesMismatch,
501 });
502 }
503
504 let events_digest = transaction_proof.events.as_ref().map(TransactionEvents::digest);
505 if transaction_proof.effects.events_digest() != events_digest.as_ref() {
506 return Err(VerifyError {
507 kind: VerifyErrorKind::EventsDigestMismatch,
508 });
509 }
510
511 Ok(())
512 }
513
514 fn verify_targets(&self, targets: &ProofTargets, transaction_proof: &TransactionProof) -> Result<(), VerifyError> {
516 let transaction_digest = transaction_proof.effects.execution_digests().transaction;
517
518 if targets.transaction.is_some_and(|target| target != transaction_digest) {
519 return Err(VerifyError {
520 kind: VerifyErrorKind::TransactionTargetMismatch,
521 });
522 }
523
524 self.verify_event_targets(targets, transaction_proof)?;
525 self.verify_object_targets(targets, transaction_proof)?;
526
527 Ok(())
528 }
529
530 fn verify_event_targets(
532 &self,
533 targets: &ProofTargets,
534 transaction_proof: &TransactionProof,
535 ) -> Result<(), VerifyError> {
536 if targets.events.is_empty() {
537 return Ok(());
538 }
539
540 let Some(events) = &transaction_proof.events else {
541 return Err(VerifyError {
542 kind: VerifyErrorKind::MissingEvents,
543 });
544 };
545
546 let execution_digests = transaction_proof.effects.execution_digests();
547 for event_id in &targets.events {
548 if event_id.tx_digest != execution_digests.transaction {
549 return Err(VerifyError {
550 kind: VerifyErrorKind::EventTransactionMismatch,
551 });
552 }
553
554 let event_exists = usize::try_from(event_id.event_seq)
555 .ok()
556 .is_some_and(|index| events.get(index).is_some());
557 if !event_exists {
558 return Err(VerifyError {
559 kind: VerifyErrorKind::EventSequenceOutOfBounds {
560 sequence: event_id.event_seq,
561 },
562 });
563 }
564 }
565
566 Ok(())
567 }
568
569 fn verify_object_targets(
571 &self,
572 targets: &ProofTargets,
573 transaction_proof: &TransactionProof,
574 ) -> Result<(), VerifyError> {
575 if targets.objects.is_empty() {
576 return Ok(());
577 }
578
579 let changed_objects = transaction_proof.effects.all_changed_objects();
580 for object in &targets.objects {
581 let object_ref = object.as_inner().object_ref();
582 changed_objects
583 .iter()
584 .find(|changed_object_ref| changed_object_ref.0 == object_ref)
585 .ok_or(VerifyError {
586 kind: VerifyErrorKind::ObjectNotFound,
587 })?;
588 }
589
590 Ok(())
591 }
592}