heddle-thread-api 0.25.4

Native Thread clients and durable peer replication over Iroh
Documentation
//! Exact ownership proof framing. Verification never enrolls its carried keys.
use crypto::{
    thread_ownership_claim::{SignedOwnershipAcceptance, SignedOwnershipClaim},
    thread_ownership_resolution::SignedOwnershipResolution,
};
use heddle_object_model::object::thread_replication::{
    ownership_claim::{FORMAT, ThreadOwnershipClaim},
    ownership_resolution::{FORMAT as RESOLUTION_FORMAT, ThreadOwnershipResolution},
};

use crate::{
    contract::{RecordSignature, SignedRecord},
    transport::Error,
};

pub enum ClaimProof {
    Acceptance(SignedOwnershipAcceptance),
    Complete(SignedOwnershipClaim),
}
pub fn decode(record: &SignedRecord) -> Result<ClaimProof, Error> {
    if record.format != FORMAT {
        return Err(Error::Protocol("ownership claim format required"));
    }
    let value = ThreadOwnershipClaim::decode(&record.canonical_record)
        .map_err(|_| Error::Protocol("invalid canonical ownership claim"))?;
    match record.signatures.as_slice() {
        [acceptor] if acceptor.public_key == value.accepting_publisher => {
            let proof = SignedOwnershipAcceptance {
                canonical: record.canonical_record.clone(),
                signature: acceptor.signature.clone(),
            };
            proof
                .verify()
                .map_err(|_| Error::Protocol("invalid account acceptance signature"))?;
            Ok(ClaimProof::Acceptance(proof))
        }
        [local, acceptor]
            if local.public_key == value.prior_local_key
                && acceptor.public_key == value.accepting_publisher =>
        {
            let proof = SignedOwnershipClaim {
                canonical: record.canonical_record.clone(),
                local_signature: local.signature.clone(),
                acceptance_signature: acceptor.signature.clone(),
            };
            proof
                .verify()
                .map_err(|_| Error::Protocol("invalid dual ownership signatures"))?;
            Ok(ClaimProof::Complete(proof))
        }
        _ => Err(Error::Protocol(
            "ownership requires acceptor or ordered owner and acceptor signatures",
        )),
    }
}
pub fn encode(proof: &SignedOwnershipClaim) -> Result<SignedRecord, Error> {
    let value = proof
        .verify()
        .map_err(|_| Error::Protocol("invalid dual ownership signatures"))?;
    Ok(SignedRecord {
        format: FORMAT.into(),
        canonical_record: proof.canonical.clone(),
        signatures: vec![
            RecordSignature {
                public_key: value.prior_local_key.to_vec(),
                signature: proof.local_signature.clone(),
            },
            RecordSignature {
                public_key: value.accepting_publisher.to_vec(),
                signature: proof.acceptance_signature.clone(),
            },
        ],
    })
}
pub fn encode_acceptance(proof: &SignedOwnershipAcceptance) -> Result<SignedRecord, Error> {
    let value = proof
        .verify()
        .map_err(|_| Error::Protocol("invalid account acceptance signature"))?;
    Ok(SignedRecord {
        format: FORMAT.into(),
        canonical_record: proof.canonical.clone(),
        signatures: vec![RecordSignature {
            public_key: value.accepting_publisher.to_vec(),
            signature: proof.signature.clone(),
        }],
    })
}
pub fn decode_resolution(record: &SignedRecord) -> Result<SignedOwnershipResolution, Error> {
    if record.format != RESOLUTION_FORMAT {
        return Err(Error::Protocol("ownership resolution format required"));
    }
    let value = ThreadOwnershipResolution::decode(&record.canonical_record)
        .map_err(|_| Error::Protocol("invalid canonical ownership resolution"))?;
    let [local, acceptor] = record.signatures.as_slice() else {
        return Err(Error::Protocol(
            "resolution needs ordered owner and recipient signatures",
        ));
    };
    if local.public_key != value.local_owner || acceptor.public_key != value.accepting_publisher {
        return Err(Error::Protocol(
            "resolution signature roles differ from canonical record",
        ));
    }
    Ok(SignedOwnershipResolution {
        canonical: record.canonical_record.clone(),
        local_signature: local.signature.clone(),
        acceptance_signature: acceptor.signature.clone(),
    })
}
pub fn encode_resolution(proof: &SignedOwnershipResolution) -> Result<SignedRecord, Error> {
    let value = ThreadOwnershipResolution::decode(&proof.canonical)
        .map_err(|_| Error::Protocol("invalid ownership resolution"))?;
    Ok(SignedRecord {
        format: RESOLUTION_FORMAT.into(),
        canonical_record: proof.canonical.clone(),
        signatures: vec![
            RecordSignature {
                public_key: value.local_owner.to_vec(),
                signature: proof.local_signature.clone(),
            },
            RecordSignature {
                public_key: value.accepting_publisher.to_vec(),
                signature: proof.acceptance_signature.clone(),
            },
        ],
    })
}