heddle_thread_api/replication/
ownership.rs1use std::collections::BTreeMap;
4
5use crypto::{
6 thread_authority_admission::SignedAuthorityAdmission,
7 thread_ownership_claim::SignedOwnershipClaim,
8 thread_ownership_resolution::SignedOwnershipResolution,
9};
10use heddle_object_model::object::{
11 ContentHash,
12 thread_replication::{ThreadGenesis, integration::TrustedHostedExecutor},
13};
14
15use crate::{contract::ThreadGenesisRecord, transport::Error};
16
17pub struct OriginalClaim {
18 pub original: SignedOwnershipClaim,
19 pub authority_admission: Option<SignedAuthorityAdmission>,
20}
21pub struct OriginalResolution {
22 pub original: SignedOwnershipResolution,
23 pub authority_admission: Option<SignedAuthorityAdmission>,
24}
25pub fn verify_resolutions(
26 record: &ThreadGenesisRecord,
27 genesis: &ThreadGenesis,
28) -> Result<Vec<OriginalResolution>, Error> {
29 if record.ownership_resolutions.len() > 1 || record.ownership_resolution_admissions.len() > 1 {
30 return Err(Error::Protocol(
31 "ownership resolution witness exceeds bound",
32 ));
33 }
34 let claims = verify_claims(record, genesis)?;
35 let mut evidence = crate::boundary_acceptance::wrapper_evidence(record)?;
36 let admission = record
37 .ownership_resolution_admissions
38 .first()
39 .map(|wire| {
40 let mut receipt = crate::authority_admission::decode(wire)?;
41 let statement = receipt
42 .verify_signature()
43 .map_err(|_| Error::Protocol("invalid resolution admission signature"))?;
44 receipt.boundary_acceptance = evidence.matched(&statement.basis)?;
45 Ok::<_, Error>((receipt, statement))
46 })
47 .transpose()?;
48 let Some(wire) = record.ownership_resolutions.first() else {
49 if admission.is_some() {
50 return Err(Error::Protocol("unmatched resolution admission"));
51 }
52 return Ok(Vec::new());
53 };
54 let original = crate::thread_ownership::decode_resolution(wire)?;
55 let value = heddle_object_model::object::thread_replication::ownership_resolution::ThreadOwnershipResolution::decode(&original.canonical)
56 .map_err(|_| Error::Protocol("invalid ownership resolution"))?;
57 value
58 .validate_genesis(genesis)
59 .map_err(|_| Error::Protocol("resolution differs from immutable genesis"))?;
60 let claim_ids = claims
61 .iter()
62 .map(|claim| {
63 claim
64 .original
65 .verify()
66 .and_then(|value| value.id().map_err(Into::into))
67 })
68 .collect::<Result<std::collections::BTreeSet<_>, _>>()
69 .map_err(|_| Error::Protocol("invalid ownership claims"))?;
70 if claim_ids != value.conflicting_claims {
71 return Err(Error::Protocol(
72 "resolution differs from exact bundled claim set",
73 ));
74 }
75 let winner = claims
76 .iter()
77 .find(|claim| {
78 claim
79 .original
80 .verify()
81 .and_then(|value| value.id().map_err(Into::into))
82 .ok()
83 == Some(value.winning_claim)
84 })
85 .ok_or(Error::Protocol("winning claim absent"))?;
86 original
87 .verify(
88 &winner
89 .original
90 .verify()
91 .map_err(|_| Error::Protocol("invalid winning claim"))?,
92 )
93 .map_err(|_| Error::Protocol("invalid dual resolution signatures"))?;
94 let authority_admission = admission
95 .map(|(receipt, statement)| {
96 let trust = TrustedHostedExecutor {
97 spool: statement.spool,
98 spool_genesis: statement.spool_genesis,
99 executor: statement.executor,
100 };
101 receipt
102 .verify_resolution(
103 &original,
104 &winner
105 .original
106 .verify()
107 .map_err(|_| Error::Protocol("invalid winning claim"))?,
108 genesis,
109 &trust,
110 )
111 .map_err(|_| Error::Protocol("resolution admission differs from original"))?;
112 Ok::<_, Error>(receipt)
113 })
114 .transpose()?;
115 evidence.finish()?;
116 Ok(vec![OriginalResolution {
117 original,
118 authority_admission,
119 }])
120}
121pub fn verify_claims(
124 record: &ThreadGenesisRecord,
125 genesis: &ThreadGenesis,
126) -> Result<Vec<OriginalClaim>, Error> {
127 if record.ownership_claims.len() > 2 || record.ownership_claim_admissions.len() > 2 {
128 return Err(Error::Protocol("ownership claim witness exceeds bound"));
129 }
130 let mut evidence = crate::boundary_acceptance::wrapper_evidence(record)?;
131 let mut receipts = BTreeMap::new();
132 for wire in &record.ownership_claim_admissions {
133 let mut receipt = crate::authority_admission::decode(wire)?;
134 let statement = receipt
135 .verify_signature()
136 .map_err(|_| Error::Protocol("invalid claim admission signature"))?;
137 receipt.boundary_acceptance = evidence.matched(&statement.basis)?;
138 let id = statement.subject.claim_id().ok_or(Error::Protocol(
139 "claim witness cannot carry operation admission",
140 ))?;
141 if receipts.insert(id, (receipt, statement)).is_some() {
142 return Err(Error::Protocol("duplicate claim admission"));
143 }
144 }
145 let mut ids = std::collections::BTreeSet::<ContentHash>::new();
146 let mut output = Vec::new();
147 for wire in &record.ownership_claims {
148 let crate::thread_ownership::ClaimProof::Complete(original) =
149 crate::thread_ownership::decode(wire)?
150 else {
151 return Err(Error::Protocol(
152 "transferred claim requires both original signatures",
153 ));
154 };
155 let claim = original
156 .verify()
157 .map_err(|_| Error::Protocol("invalid original claim"))?;
158 claim
159 .validate_genesis(genesis)
160 .map_err(|_| Error::Protocol("claim differs from immutable genesis"))?;
161 let id = claim
162 .id()
163 .map_err(|_| Error::Protocol("invalid claim identity"))?;
164 if !ids.insert(id) {
165 return Err(Error::Protocol("duplicate original ownership claim"));
166 }
167 let admission = receipts
168 .remove(&id)
169 .map(
170 |(receipt, statement)| -> Result<SignedAuthorityAdmission, Error> {
171 let trust = TrustedHostedExecutor {
172 spool: statement.spool,
173 spool_genesis: statement.spool_genesis,
174 executor: statement.executor,
175 };
176 receipt
179 .verify_claim(&original, genesis, &trust)
180 .map_err(|_| Error::Protocol("claim admission differs from original"))?;
181 Ok(receipt)
182 },
183 )
184 .transpose()?;
185 output.push(OriginalClaim {
186 original,
187 authority_admission: admission,
188 });
189 }
190 if !receipts.is_empty() {
191 return Err(Error::Protocol("unmatched ownership claim admission"));
192 }
193 evidence.finish()?;
194 Ok(output)
195}