1use crate::foreign_dependencies::thread;
5use crate::heddle::api::{common as host, v1alpha2 as api};
6use crate::hybrid_codec::{Reject, canonical, field, hash, key_id, record, signing_digest, width};
7use crate::import_authority as import;
8use prost::Message;
9
10pub const GENESIS_DOMAIN: &str = "heddle-native-genesis-authority-v1";
11pub const SIGNED_GENESIS_DOMAIN: &str = "heddle-signed-native-genesis-authority-v1";
12record!(api::NativeGenesisAuthorityV1, format_version:u, identity:m, owner_kind:e,
13 genesis_digest:b, original_signatures_digest:b, creator_public_key:b,
14 creator_authority_envelope_digest:b, owner_chain_digest:b, publisher_key_id:b);
15record!(api::SignedNativeGenesisAuthorityV1, body:m, creator_signature:m);
16record!(api::NativeGenesisWitnessV1, format_version:u, kind:e, binding:m,
17 original_genesis:m, creator_authority_envelope:b, boundary_acceptance:o);
18
19pub fn signed_genesis_digest(v: &api::SignedNativeGenesisAuthorityV1) -> Result<Vec<u8>, Reject> {
20 signing_digest(SIGNED_GENESIS_DOMAIN, v)
21}
22#[derive(serde::Deserialize)]
23#[serde(rename_all = "snake_case")]
24enum NativeOwner {
25 Account(Vec<u8>),
26 LocalKey([u8; 32]),
27}
28#[derive(serde::Deserialize)]
29struct GenesisSelectors {
30 version: u16,
31 spool: String,
32 creator: [u8; 32],
33 owner: NativeOwner,
34}
35fn spool_uuid(text: &str) -> Result<Vec<u8>, Reject> {
36 if text.len() != 36
37 || [8, 13, 18, 23].iter().any(|&i| text.as_bytes()[i] != b'-')
38 || text.bytes().enumerate().any(|(i, b)| {
39 ![8, 13, 18, 23].contains(&i) && !b.is_ascii_digit() && !(b'a'..=b'f').contains(&b)
40 })
41 {
42 return Err(Reject::Canonical);
43 }
44 hex::decode(text.replace('-', "")).map_err(|_| Reject::Canonical)
45}
46pub fn verify_genesis_authority(
50 signed: &api::SignedNativeGenesisAuthorityV1,
51 original: &api::SignedRecord,
52 envelope: &[u8],
53) -> Result<(), Reject> {
54 let b = signed.body.as_ref().ok_or(Reject::GenesisBinding)?;
55 if b.format_version != 1 {
56 return Err(Reject::Version);
57 }
58 let id = b.identity.as_ref().ok_or(Reject::Canonical)?;
59 for v in [&id.spool_uuid, &id.owner_account_uuid] {
60 width(v, 16)?;
61 if v.iter().all(|v| *v == 0) {
62 return Err(Reject::Canonical);
63 }
64 }
65 for v in [
66 &id.spool_genesis_digest,
67 &id.owner_id,
68 &id.owner_state_hash,
69 &b.genesis_digest,
70 &b.original_signatures_digest,
71 &b.creator_public_key,
72 &b.creator_authority_envelope_digest,
73 &b.owner_chain_digest,
74 &b.publisher_key_id,
75 ] {
76 width(v, 32)?;
77 }
78 if envelope.len() > import::MAX_RECORD_BYTES {
79 return Err(Reject::Bounds);
80 }
81 import::verify_native(original, "heddle-thread-genesis-v1")?;
82 let g: GenesisSelectors =
83 rmp_serde::from_slice(&original.canonical_record).map_err(|_| Reject::Canonical)?;
84 if g.version != 1
85 || spool_uuid(&g.spool)? != id.spool_uuid
86 || g.creator.as_slice() != b.creator_public_key
87 || original.signatures.len() != 1
88 || original.signatures[0].public_key != b.creator_public_key
89 || import::native_id(original) != b.genesis_digest
90 || import::original_signatures(&[original], &[])? != b.original_signatures_digest
91 || hash(&[envelope]) != b.creator_authority_envelope_digest
92 || key_id(&b.creator_public_key) != b.publisher_key_id
93 {
94 return Err(Reject::GenesisBinding);
95 }
96 match (&g.owner, b.owner_kind) {
97 (NativeOwner::Account(account), 1) if !envelope.is_empty() => {
98 let authority = crate::writer_authority::decode_authority(envelope)?;
99 crate::writer_authority::verify_account_binding(
100 &authority,
101 account,
102 &id.owner_account_uuid,
103 &id.owner_id,
104 )?;
105 }
106 (NativeOwner::LocalKey(key), 2) if key == &g.creator && envelope.is_empty() => (),
107 _ => return Err(Reject::GenesisBinding),
108 }
109 import::verify_authorization_signature(
110 &b.creator_public_key,
111 GENESIS_DOMAIN,
112 b,
113 signed.creator_signature.as_ref().ok_or(Reject::Signature)?,
114 )
115}
116pub fn verify_genesis_payload(
117 s: &host::HostedWitnessStatementV1,
118 p: &api::NativeGenesisWitnessV1,
119) -> Result<(), Reject> {
120 if p.format_version != 1 || p.kind != 2 {
121 return Err(Reject::Version);
122 }
123 let binding = p.binding.as_ref().ok_or(Reject::GenesisBinding)?;
124 let original = p.original_genesis.as_ref().ok_or(Reject::Canonical)?;
125 verify_genesis_authority(binding, original, &p.creator_authority_envelope)?;
126 let b = binding.body.as_ref().ok_or(Reject::GenesisBinding)?;
127 let id = b.identity.as_ref().ok_or(Reject::Canonical)?;
128 import::match_boundary(
129 s,
130 &p.boundary_acceptance.iter().cloned().collect::<Vec<_>>(),
131 )?;
132 if (s.basis == 2) != p.boundary_acceptance.is_some() {
133 return Err(Reject::BoundaryAcceptance);
134 }
135 if let Some(e) = &p.boundary_acceptance {
136 import::boundary_original(e, original)?;
137 }
138 if s.purpose != 1
139 || s.spool_uuid != id.spool_uuid
140 || s.spool_genesis_digest != id.spool_genesis_digest
141 || (s.basis == 1
142 && (s.owner_id != id.owner_id
143 || s.owner_state_hash != id.owner_state_hash
144 || s.ownership_transfer_sequence != id.ownership_transfer_sequence))
145 || s.canonical_payload != canonical(p)?
146 || s.authority_digest != signed_genesis_digest(binding)?
147 || s.original_signatures_digest != b.original_signatures_digest
148 || s.publisher_key_id != b.publisher_key_id
149 {
150 return Err(Reject::Scope);
151 }
152 if s.canonical_payload.len() > import::MAX_RECORD_BYTES {
153 return Err(Reject::Bounds);
154 }
155 Ok(())
156}
157fn sorted<T>(values: &[T], digest: impl Fn(&T) -> Result<Vec<u8>, Reject>) -> Result<(), Reject> {
158 let mut previous = None;
159 for v in values {
160 let h = digest(v)?;
161 if previous.as_ref().is_some_and(|p| p >= &h) {
162 return Err(Reject::Canonical);
163 }
164 previous = Some(h);
165 }
166 Ok(())
167}
168fn authority_payload_digest(p: &api::ImportAuthorityWitnessV1) -> Result<Vec<u8>, Reject> {
169 signing_digest("heddle-import-authority-witness-payload-v1", p)
170}
171pub fn validate_public_bundle(b: &api::NativePublicProofBundleV1) -> Result<(), Reject> {
176 crate::writer_authority::validate_owner_histories(&b.owner_histories)?;
177 if b.format_version != 1 {
178 return Err(Reject::Version);
179 }
180 if b.encoded_len() > import::MAX_BUNDLE_BYTES
181 || b.owner_histories.len() > 64
182 || b.ownership_transfers.len() > 64
183 || b.owner_chains.is_empty()
184 || b.owner_chains.len() > 64
185 || b.policies.len() > 256
186 || b.genesis_witnesses.is_empty()
187 || b.genesis_witnesses.len() > 256
188 || b.authority_witnesses.len() > 256
189 || b.landing_witnesses.len() > 256
190 || b.statements.len() > 1024
191 || b.history_proofs.len() > 1024
192 {
193 return Err(Reject::Bounds);
194 }
195 let mut foreign = crate::foreign_dependencies::References::new(
196 &b.foreign_dependencies,
197 api::ForeignDependencyOrigin::Native,
198 )?;
199 let owner = b
200 .owner_genesis
201 .as_ref()
202 .and_then(|o| o.genesis.as_ref())
203 .ok_or(Reject::Canonical)?;
204 sorted(&b.owner_chains, import::owner_chain_digest)?;
205 let chain = b.owner_chains.first().ok_or(Reject::Canonical)?;
206 b.witness_set.as_ref().ok_or(Reject::Canonical)?;
207 for retained in &b.owner_chains {
208 if retained.spool_genesis_digest != chain.spool_genesis_digest
209 || retained
210 .owner_state_hashes
211 .iter()
212 .any(|h| !b.owner_histories.iter().any(|o| o.state_hash == *h))
213 || retained.transfer_audit_hashes.iter().any(|h| {
214 !b.ownership_transfers
215 .iter()
216 .any(|t| t.audit_record_hash == *h)
217 })
218 {
219 return Err(Reject::Scope);
220 }
221 }
222 sorted(&b.genesis_witnesses, |p| {
223 signed_genesis_digest(p.binding.as_ref().ok_or(Reject::GenesisBinding)?)
224 })?;
225 sorted(&b.authority_witnesses, authority_payload_digest)?;
226 sorted(&b.landing_witnesses, |p| {
227 signing_digest("heddle-hosted-landing-witness-payload-v1", p)
228 })?;
229 sorted(&b.statements, |s| {
230 crate::witness_trust::statement_signing_digest(s.body.as_ref().ok_or(Reject::Canonical)?)
231 })?;
232 for p in &b.genesis_witnesses {
233 let binding = p.binding.as_ref().ok_or(Reject::GenesisBinding)?;
234 let g = binding.body.as_ref().ok_or(Reject::GenesisBinding)?;
235 let id = g.identity.as_ref().ok_or(Reject::Canonical)?;
236 verify_genesis_authority(
237 binding,
238 p.original_genesis.as_ref().ok_or(Reject::Canonical)?,
239 &p.creator_authority_envelope,
240 )?;
241 if id.spool_uuid != owner.spool_uuid
242 || id.spool_genesis_digest != chain.spool_genesis_digest
243 || !b
244 .owner_chains
245 .iter()
246 .any(|c| import::owner_chain_digest(c).is_ok_and(|h| h == g.owner_chain_digest))
247 || !b.owner_histories.iter().any(|h| {
248 h.state_hash == id.owner_state_hash
249 && h.root
250 .as_ref()
251 .and_then(|r| r.root.as_ref())
252 .is_some_and(|r| {
253 r.owner_id == id.owner_id && r.account_uuid == id.owner_account_uuid
254 })
255 })
256 {
257 return Err(Reject::Scope);
258 }
259 if g.owner_kind == 2
260 && !b.authority_witnesses.iter().any(|a| {
261 a.kind == 2
262 && a.original
263 .as_ref()
264 .is_some_and(|o| thread(o).is_ok_and(|t| t == g.genesis_digest))
265 })
266 {
267 return Err(Reject::Scope);
268 }
269 require_statement(b, 1, &canonical(p)?)?;
270 }
271 for p in &b.authority_witnesses {
272 require_statement(b, 2, &canonical(p)?)?;
273 if !requires_authority(p.original.as_ref().ok_or(Reject::Canonical)?)? {
274 return Err(Reject::Scope);
275 }
276 let subject_thread = thread(p.original.as_ref().ok_or(Reject::Canonical)?)?;
277 if !b.genesis_witnesses.iter().any(|g| {
278 g.original_genesis
279 .as_ref()
280 .is_some_and(|g| import::native_id(g) == subject_thread)
281 }) {
282 return Err(Reject::Scope);
283 }
284 for original in p.original.iter().chain(p.dependencies.iter()) {
285 if [
286 "heddle-thread-genesis-v1",
287 "heddle-thread-operation-v1",
288 "heddle-thread-ownership-claim-v1",
289 "heddle-thread-ownership-resolution-v1",
290 ]
291 .contains(&original.format.as_str())
292 {
293 let t = thread(original)?;
294 if !b.genesis_witnesses.iter().any(|g| {
295 g.original_genesis
296 .as_ref()
297 .is_some_and(|o| import::native_id(o) == t)
298 }) {
299 foreign.require(original)?;
300 continue;
301 }
302 if original.format == "heddle-thread-genesis-v1"
303 && !b
304 .genesis_witnesses
305 .iter()
306 .any(|g| g.original_genesis.as_ref() == Some(original))
307 {
308 return Err(Reject::Scope);
309 }
310 if original.format != "heddle-thread-genesis-v1"
311 && p.original.as_ref() != Some(original)
312 {
313 require_native_dependency(b, original, &mut foreign)?;
314 }
315 } else if ![
316 "heddle-original-boundary-acceptance-v1",
317 "heddle-thread-genesis-admission-v2",
318 "heddle-thread-authority-admission-v3",
319 ]
320 .contains(&original.format.as_str())
321 {
322 return Err(Reject::Version);
323 }
324 }
325 }
326 for p in &b.landing_witnesses {
327 require_statement(b, 4, &canonical(p)?)?;
328 let execution = p.execution.as_ref().ok_or(Reject::Canonical)?;
329 let op: OperationSelectors =
330 rmp_serde::from_slice(&execution.canonical_record).map_err(|_| Reject::Canonical)?;
331 if execution.format != "heddle-thread-operation-v1" || op.body.kind != "integration" {
332 return Err(Reject::Scope);
333 }
334 if !b.genesis_witnesses.iter().any(|g| {
335 g.original_genesis
336 .as_ref()
337 .is_some_and(|o| thread(execution).is_ok_and(|t| import::native_id(o) == t))
338 }) {
339 return Err(Reject::Scope);
340 }
341 for original in p.source_operation.iter().chain(p.review_evidence.iter()) {
342 require_native_dependency(b, original, &mut foreign)?;
343 }
344 }
345 for signed in &b.statements {
346 let s = signed.body.as_ref().ok_or(Reject::Canonical)?;
347 if s.spool_uuid != owner.spool_uuid
348 || s.spool_genesis_digest != chain.spool_genesis_digest
349 || !b.owner_histories.iter().any(|h| {
350 h.state_hash == s.owner_state_hash
351 && h.root
352 .as_ref()
353 .and_then(|r| r.root.as_ref())
354 .is_some_and(|r| r.owner_id == s.owner_id)
355 })
356 {
357 return Err(Reject::Scope);
358 }
359 import::require_policy_history(
361 &b.policies,
362 &s.spool_uuid,
363 s.policy_sequence,
364 &s.policy_state_hash,
365 )?;
366 match s.purpose {
367 1 => {
368 let p = b
369 .genesis_witnesses
370 .iter()
371 .find(|p| canonical(*p).is_ok_and(|v| v == s.canonical_payload))
372 .ok_or(Reject::Scope)?;
373 verify_genesis_payload(s, p)?;
374 crate::writer_authority::check_witness_writer(
375 s,
376 &p.creator_authority_envelope,
377 &b.owner_histories,
378 &b.policies,
379 crate::writer_authority::spool_account_for_statement(
380 s,
381 b.genesis_witnesses
382 .iter()
383 .filter_map(|p| p.binding.as_ref()?.body.as_ref()?.identity.as_ref()),
384 &b.ownership_transfers,
385 )?,
386 &[],
387 p.boundary_acceptance.as_ref(),
388 )?;
389 }
390 2 => {
391 let p = b
392 .authority_witnesses
393 .iter()
394 .find(|p| canonical(*p).is_ok_and(|v| v == s.canonical_payload))
395 .ok_or(Reject::Scope)?;
396 import::verify_witness_payload(s, import::WitnessPayload::Authority(p))?;
397 crate::writer_authority::check_witness_writer(
398 s,
399 &p.authority_envelope,
400 &b.owner_histories,
401 &b.policies,
402 crate::writer_authority::spool_account_for_statement(
403 s,
404 b.genesis_witnesses
405 .iter()
406 .filter_map(|p| p.binding.as_ref()?.body.as_ref()?.identity.as_ref()),
407 &b.ownership_transfers,
408 )?,
409 p.original
410 .as_ref()
411 .filter(|_| p.kind == 2 || p.kind == 3)
412 .map(|r| r.signatures.as_slice())
413 .unwrap_or(&[]),
414 p.boundary_acceptances
415 .iter()
416 .find(|e| e.binding == s.boundary_acceptance),
417 )?;
418 }
419 4 => {
420 let p = b
421 .landing_witnesses
422 .iter()
423 .find(|p| canonical(*p).is_ok_and(|v| v == s.canonical_payload))
424 .ok_or(Reject::Scope)?;
425 import::verify_witness_payload(s, import::WitnessPayload::Landing(p))?;
426 crate::writer_authority::check_witness_writer(
427 s,
428 &p.authority_envelope,
429 &b.owner_histories,
430 &b.policies,
431 crate::writer_authority::spool_account_for_statement(
432 s,
433 b.genesis_witnesses
434 .iter()
435 .filter_map(|p| p.binding.as_ref()?.body.as_ref()?.identity.as_ref()),
436 &b.ownership_transfers,
437 )?,
438 &[],
439 None,
440 )?;
441 }
442 _ => return Err(Reject::Version),
443 }
444 }
445 foreign.finish()
446}
447#[derive(serde::Deserialize)]
448struct OperationSelectors {
449 body: OperationBodySelectors,
450}
451#[derive(serde::Deserialize)]
452struct OperationBodySelectors {
453 kind: String,
454}
455#[derive(serde::Deserialize)]
456struct CaptureOperationSelectors {
457 body: CaptureBodySelectors,
458}
459#[derive(serde::Deserialize)]
460struct CaptureBodySelectors {
461 canonical: CaptureSelectors,
462}
463#[derive(serde::Deserialize)]
464struct CaptureSelectors {
465 author: OperationBodySelectors,
466}
467#[derive(serde::Deserialize)]
468struct LocalIntegrationOperationSelectors {
469 body: LocalIntegrationBodySelectors,
470}
471#[derive(serde::Deserialize)]
472struct LocalIntegrationBodySelectors {
473 canonical: Vec<u8>,
474}
475fn requires_authority(record: &api::SignedRecord) -> Result<bool, Reject> {
476 if record.format != "heddle-thread-operation-v1" {
477 return Ok(true);
478 }
479 let op: OperationSelectors =
480 rmp_serde::from_slice(&record.canonical_record).map_err(|_| Reject::Canonical)?;
481 if op.body.kind == "integration" {
482 return Ok(false);
483 }
484 match op.body.kind.as_str() {
485 "capture" => {
486 let op: CaptureOperationSelectors =
487 rmp_serde::from_slice(&record.canonical_record).map_err(|_| Reject::Canonical)?;
488 Ok(op.body.canonical.author.kind != "local_key")
489 }
490 "local_integration" => {
491 let op: LocalIntegrationOperationSelectors =
492 rmp_serde::from_slice(&record.canonical_record).map_err(|_| Reject::Canonical)?;
493 let integration: CaptureSelectors =
494 rmp_serde::from_slice(&op.body.canonical).map_err(|_| Reject::Canonical)?;
495 Ok(integration.author.kind != "local_key")
496 }
497 _ => Ok(true),
498 }
499}
500fn require_native_dependency(
501 b: &api::NativePublicProofBundleV1,
502 original: &api::SignedRecord,
503 foreign: &mut crate::foreign_dependencies::References<'_>,
504) -> Result<(), Reject> {
505 let t = thread(original)?;
506 if !b.genesis_witnesses.iter().any(|g| {
507 g.original_genesis
508 .as_ref()
509 .is_some_and(|o| import::native_id(o) == t)
510 }) {
511 return foreign.require(original);
512 }
513 if requires_authority(original)? {
514 let p = b
515 .authority_witnesses
516 .iter()
517 .find(|p| p.original.as_ref() == Some(original))
518 .ok_or(Reject::Scope)?;
519 return require_statement(b, 2, &canonical(p)?);
520 }
521 let op: OperationSelectors =
522 rmp_serde::from_slice(&original.canonical_record).map_err(|_| Reject::Canonical)?;
523 if op.body.kind == "integration" {
524 let p = b
525 .landing_witnesses
526 .iter()
527 .find(|p| p.execution.as_ref() == Some(original))
528 .ok_or(Reject::Scope)?;
529 return require_statement(b, 4, &canonical(p)?);
530 }
531 import::verify_native(original, "heddle-thread-operation-v1")?;
536 let t = thread(original)?;
537 let claim = b
538 .authority_witnesses
539 .iter()
540 .find(|p| {
541 p.kind == 2
542 && p.original
543 .as_ref()
544 .is_some_and(|o| thread(o).is_ok_and(|id| id == t))
545 })
546 .ok_or(Reject::Scope)?;
547 require_statement(b, 2, &canonical(claim)?)
548}
549fn require_statement(
550 b: &api::NativePublicProofBundleV1,
551 purpose: i32,
552 payload: &[u8],
553) -> Result<(), Reject> {
554 if b.statements
555 .iter()
556 .filter(|s| {
557 s.body
558 .as_ref()
559 .is_some_and(|s| s.purpose == purpose && s.canonical_payload == payload)
560 })
561 .count()
562 != 1
563 {
564 return Err(Reject::Scope);
565 }
566 Ok(())
567}
568pub fn verify_bundle_witnesses(
571 b: &api::NativePublicProofBundleV1,
572 set: &crate::witness_trust::VerifiedWitnessSet,
573 now_ms: i64,
574 forbidden_landing_keys: &[Vec<u8>],
575) -> Result<(), Reject> {
576 validate_public_bundle(b)?;
577 for p in &b.landing_witnesses {
578 import::verify_landing_key_roles(p, set.known_job_keys(), forbidden_landing_keys)?;
579 }
580 let carried = b.witness_set.as_ref().ok_or(Reject::Canonical)?;
581 if carried.body.as_ref() != Some(set.body()) || carried.body_digest != set.digest() {
582 return Err(Reject::StaleContext);
583 }
584 for signed in &b.statements {
585 match crate::witness_trust::resolve_statement(set, signed, None, false, now_ms) {
586 Ok(_) => continue,
587 Err(Reject::Proof) => (),
588 Err(e) => return Err(e),
589 }
590 let statement = signed.body.as_ref().ok_or(Reject::Canonical)?;
591 let entry = set
592 .body()
593 .entries
594 .iter()
595 .find(|e| e.executor_id == statement.executor_id)
596 .ok_or(Reject::Root)?;
597 let leaf = crate::witness_trust::leaf_digest(
598 statement.purpose,
599 &canonical(statement)?,
600 &signed.signature,
601 )?;
602 let proof = b
605 .history_proofs
606 .iter()
607 .find(|p| {
608 p.purpose == statement.purpose
609 && crate::witness_trust::verify_inclusion(&leaf, p, entry).is_ok()
610 })
611 .ok_or(Reject::Proof)?;
612 crate::witness_trust::resolve_statement(set, signed, Some(proof), false, now_ms)?;
613 }
614 Ok(())
615}
616pub fn local_work_cutoff(
621 b: &api::NativePublicProofBundleV1,
622 original: &api::SignedRecord,
623 dependent_admission_order: u64,
624) -> Result<u64, Reject> {
625 let subject = thread(original)?;
626 let mut genesis_count = 0;
627 let mut orders = Vec::new();
628 let mut claims = Vec::new();
629 let mut resolutions = Vec::new();
630 for signed in &b.statements {
631 let s = signed.body.as_ref().ok_or(Reject::Canonical)?;
632 if s.admission_order > dependent_admission_order {
633 continue;
634 }
635 if s.purpose == 1 {
636 for p in &b.genesis_witnesses {
637 let binding = p
638 .binding
639 .as_ref()
640 .and_then(|v| v.body.as_ref())
641 .ok_or(Reject::Canonical)?;
642 if binding.genesis_digest == subject && s.canonical_payload == canonical(p)? {
643 if binding.owner_kind != 2 {
644 return Err(Reject::Scope);
645 }
646 genesis_count += 1;
647 orders.push(s.admission_order);
648 }
649 }
650 } else if s.purpose == 2 {
651 for p in &b.authority_witnesses {
652 if ![2, 3].contains(&p.kind) || s.canonical_payload != canonical(p)? {
653 continue;
654 }
655 let r = p.original.as_ref().ok_or(Reject::Canonical)?;
656 if thread(r)? != subject {
657 continue;
658 }
659 orders.push(s.admission_order);
660 if p.kind == 2 {
661 claims.push(import::native_id(r));
662 } else {
663 resolutions.push(r);
664 }
665 }
666 }
667 }
668 if genesis_count != 1 {
669 return Err(Reject::Scope);
670 }
671 match resolutions.as_slice() {
672 [] if claims.len() == 1 => (),
673 [r] => {
674 #[derive(serde::Deserialize)]
675 struct Resolution {
676 winning_claim: Vec<u8>,
677 conflicting_claims: Vec<Vec<u8>>,
678 }
679 let r: Resolution =
680 rmp_serde::from_slice(&r.canonical_record).map_err(|_| Reject::Canonical)?;
681 claims.sort();
682 if claims != r.conflicting_claims || !claims.contains(&r.winning_claim) {
683 return Err(Reject::Scope);
684 }
685 }
686 _ => return Err(Reject::Scope),
687 }
688 orders
689 .into_iter()
690 .max()
691 .filter(|v| *v > 0)
692 .ok_or(Reject::Scope)
693}
694pub fn validate_carriers(
697 imported: Option<&api::ImportPublicProofBundleV1>,
698 native: Option<&api::NativePublicProofBundleV1>,
699) -> Result<(), Reject> {
700 match (imported, native) {
701 (Some(_), Some(_)) => Err(Reject::Protocol),
702 (Some(b), None) => import::validate_public_bundle(b),
703 (None, Some(b)) => validate_public_bundle(b),
704 (None, None) => Ok(()), }
706}