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        [_, _] if record.signatures[0].public_key < record.signatures[1].public_key => {
38            let local = record
39                .signatures
40                .iter()
41                .find(|s| s.public_key == value.prior_local_key)
42                .ok_or(Error::Protocol("local ownership signature missing"))?;
43            let acceptor = record
44                .signatures
45                .iter()
46                .find(|s| s.public_key == value.accepting_publisher)
47                .ok_or(Error::Protocol("accepting ownership signature missing"))?;
48            let proof = SignedOwnershipClaim {
49                canonical: record.canonical_record.clone(),
50                local_signature: local.signature.clone(),
51                acceptance_signature: acceptor.signature.clone(),
52            };
53            proof
54                .verify()
55                .map_err(|_| Error::Protocol("invalid dual ownership signatures"))?;
56            Ok(ClaimProof::Complete(proof))
57        }
58        _ => Err(Error::Protocol(
59            "ownership requires acceptor or ordered owner and acceptor signatures",
60        )),
61    }
62}
63pub fn encode(proof: &SignedOwnershipClaim) -> Result<SignedRecord, Error> {
64    let value = proof
65        .verify()
66        .map_err(|_| Error::Protocol("invalid dual ownership signatures"))?;
67    let mut signatures = vec![
68        RecordSignature {
69            public_key: value.prior_local_key.to_vec(),
70            signature: proof.local_signature.clone(),
71        },
72        RecordSignature {
73            public_key: value.accepting_publisher.to_vec(),
74            signature: proof.acceptance_signature.clone(),
75        },
76    ];
77    signatures.sort_by(|a, b| a.public_key.cmp(&b.public_key));
78    Ok(SignedRecord {
79        format: FORMAT.into(),
80        canonical_record: proof.canonical.clone(),
81        signatures,
82    })
83}
84pub fn encode_acceptance(proof: &SignedOwnershipAcceptance) -> Result<SignedRecord, Error> {
85    let value = proof
86        .verify()
87        .map_err(|_| Error::Protocol("invalid account acceptance signature"))?;
88    Ok(SignedRecord {
89        format: FORMAT.into(),
90        canonical_record: proof.canonical.clone(),
91        signatures: vec![RecordSignature {
92            public_key: value.accepting_publisher.to_vec(),
93            signature: proof.signature.clone(),
94        }],
95    })
96}
97pub fn decode_resolution(record: &SignedRecord) -> Result<SignedOwnershipResolution, Error> {
98    if record.format != RESOLUTION_FORMAT {
99        return Err(Error::Protocol("ownership resolution format required"));
100    }
101    let value = ThreadOwnershipResolution::decode(&record.canonical_record)
102        .map_err(|_| Error::Protocol("invalid canonical ownership resolution"))?;
103    if record.signatures.len() != 2
104        || record.signatures[0].public_key >= record.signatures[1].public_key
105    {
106        return Err(Error::Protocol(
107            "resolution requires two canonical ordered signatures",
108        ));
109    }
110    let local = record
111        .signatures
112        .iter()
113        .find(|s| s.public_key == value.local_owner)
114        .ok_or(Error::Protocol("local resolution signature missing"))?;
115    let acceptor = record
116        .signatures
117        .iter()
118        .find(|s| s.public_key == value.accepting_publisher)
119        .ok_or(Error::Protocol("accepting resolution signature missing"))?;
120    Ok(SignedOwnershipResolution {
121        canonical: record.canonical_record.clone(),
122        local_signature: local.signature.clone(),
123        acceptance_signature: acceptor.signature.clone(),
124    })
125}
126pub fn encode_resolution(proof: &SignedOwnershipResolution) -> Result<SignedRecord, Error> {
127    let value = ThreadOwnershipResolution::decode(&proof.canonical)
128        .map_err(|_| Error::Protocol("invalid ownership resolution"))?;
129    let mut record = SignedRecord {
130        format: RESOLUTION_FORMAT.into(),
131        canonical_record: proof.canonical.clone(),
132        signatures: vec![
133            RecordSignature {
134                public_key: value.local_owner.to_vec(),
135                signature: proof.local_signature.clone(),
136            },
137            RecordSignature {
138                public_key: value.accepting_publisher.to_vec(),
139                signature: proof.acceptance_signature.clone(),
140            },
141        ],
142    };
143    record
144        .signatures
145        .sort_by(|a, b| a.public_key.cmp(&b.public_key));
146    Ok(record)
147}