Skip to main content

heddle_thread_api/
thread_ownership.rs

1//! Exact ownership proof framing. Verification never enrolls its carried keys.
2use crypto::{
3    thread_ownership_claim::{SignedOwnershipAcceptance, SignedOwnershipClaim},
4    thread_ownership_resolution::SignedOwnershipResolution,
5};
6use heddle_object_model::object::thread_replication::{
7    ownership_claim::{FORMAT, ThreadOwnershipClaim},
8    ownership_resolution::{FORMAT as RESOLUTION_FORMAT, ThreadOwnershipResolution},
9};
10
11use crate::{
12    contract::{RecordSignature, SignedRecord},
13    transport::Error,
14};
15
16pub enum ClaimProof {
17    Acceptance(SignedOwnershipAcceptance),
18    Complete(SignedOwnershipClaim),
19}
20pub fn decode(record: &SignedRecord) -> Result<ClaimProof, Error> {
21    if record.format != FORMAT {
22        return Err(Error::Protocol("ownership claim format required"));
23    }
24    let value = ThreadOwnershipClaim::decode(&record.canonical_record)
25        .map_err(|_| Error::Protocol("invalid canonical ownership claim"))?;
26    match record.signatures.as_slice() {
27        [acceptor] if acceptor.public_key == value.accepting_publisher => {
28            let proof = SignedOwnershipAcceptance {
29                canonical: record.canonical_record.clone(),
30                signature: acceptor.signature.clone(),
31            };
32            proof
33                .verify()
34                .map_err(|_| Error::Protocol("invalid account acceptance signature"))?;
35            Ok(ClaimProof::Acceptance(proof))
36        }
37        [local, acceptor]
38            if local.public_key == value.prior_local_key
39                && acceptor.public_key == value.accepting_publisher =>
40        {
41            let proof = SignedOwnershipClaim {
42                canonical: record.canonical_record.clone(),
43                local_signature: local.signature.clone(),
44                acceptance_signature: acceptor.signature.clone(),
45            };
46            proof
47                .verify()
48                .map_err(|_| Error::Protocol("invalid dual ownership signatures"))?;
49            Ok(ClaimProof::Complete(proof))
50        }
51        _ => Err(Error::Protocol(
52            "ownership requires acceptor or ordered owner and acceptor signatures",
53        )),
54    }
55}
56pub fn encode(proof: &SignedOwnershipClaim) -> Result<SignedRecord, Error> {
57    let value = proof
58        .verify()
59        .map_err(|_| Error::Protocol("invalid dual ownership signatures"))?;
60    Ok(SignedRecord {
61        format: FORMAT.into(),
62        canonical_record: proof.canonical.clone(),
63        signatures: vec![
64            RecordSignature {
65                public_key: value.prior_local_key.to_vec(),
66                signature: proof.local_signature.clone(),
67            },
68            RecordSignature {
69                public_key: value.accepting_publisher.to_vec(),
70                signature: proof.acceptance_signature.clone(),
71            },
72        ],
73    })
74}
75pub fn encode_acceptance(proof: &SignedOwnershipAcceptance) -> Result<SignedRecord, Error> {
76    let value = proof
77        .verify()
78        .map_err(|_| Error::Protocol("invalid account acceptance signature"))?;
79    Ok(SignedRecord {
80        format: FORMAT.into(),
81        canonical_record: proof.canonical.clone(),
82        signatures: vec![RecordSignature {
83            public_key: value.accepting_publisher.to_vec(),
84            signature: proof.signature.clone(),
85        }],
86    })
87}
88pub fn decode_resolution(record: &SignedRecord) -> Result<SignedOwnershipResolution, Error> {
89    if record.format != RESOLUTION_FORMAT {
90        return Err(Error::Protocol("ownership resolution format required"));
91    }
92    let value = ThreadOwnershipResolution::decode(&record.canonical_record)
93        .map_err(|_| Error::Protocol("invalid canonical ownership resolution"))?;
94    let [local, acceptor] = record.signatures.as_slice() else {
95        return Err(Error::Protocol(
96            "resolution needs ordered owner and recipient signatures",
97        ));
98    };
99    if local.public_key != value.local_owner || acceptor.public_key != value.accepting_publisher {
100        return Err(Error::Protocol(
101            "resolution signature roles differ from canonical record",
102        ));
103    }
104    Ok(SignedOwnershipResolution {
105        canonical: record.canonical_record.clone(),
106        local_signature: local.signature.clone(),
107        acceptance_signature: acceptor.signature.clone(),
108    })
109}
110pub fn encode_resolution(proof: &SignedOwnershipResolution) -> Result<SignedRecord, Error> {
111    let value = ThreadOwnershipResolution::decode(&proof.canonical)
112        .map_err(|_| Error::Protocol("invalid ownership resolution"))?;
113    Ok(SignedRecord {
114        format: RESOLUTION_FORMAT.into(),
115        canonical_record: proof.canonical.clone(),
116        signatures: vec![
117            RecordSignature {
118                public_key: value.local_owner.to_vec(),
119                signature: proof.local_signature.clone(),
120            },
121            RecordSignature {
122                public_key: value.accepting_publisher.to_vec(),
123                signature: proof.acceptance_signature.clone(),
124            },
125        ],
126    })
127}