Skip to main content

crypto/
thread_operation.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Portable Thread operation signatures, independent of storage and transport.
3//! Verification proves the publisher and exact bytes. The receiving host must
4//! still authorize the Thread/facet and validate accepted causal parents.
5
6use heddle_object_model::{
7    error::HeddleError,
8    object::thread_replication::{
9        GENESIS_FORMAT, OPERATION_FORMAT, ThreadGenesis, ThreadOperation,
10    },
11};
12use serde::{Deserialize, Serialize};
13
14use crate::{Ed25519Signer, Signer, SignerError};
15
16#[derive(Debug, thiserror::Error)]
17pub enum Error {
18    #[error("Thread operation: {0}")]
19    Operation(#[from] HeddleError),
20    #[error("Thread signature: {0}")]
21    Signature(#[from] SignerError),
22    #[error("publisher differs from signing key")]
23    Publisher,
24}
25
26#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
27pub struct SignedOperation {
28    pub canonical: Vec<u8>,
29    pub signature: Vec<u8>,
30}
31
32impl SignedOperation {
33    pub fn sign(operation: &ThreadOperation, signer: &impl Signer) -> Result<Self, Error> {
34        if signer.public_key() != operation.publisher {
35            return Err(Error::Publisher);
36        }
37        let canonical = operation.encode()?;
38        let signature = signer.sign(&signing_bytes(OPERATION_FORMAT, &canonical))?;
39        Ok(Self {
40            canonical,
41            signature,
42        })
43    }
44
45    pub fn verify(&self) -> Result<ThreadOperation, Error> {
46        let operation = ThreadOperation::decode(&self.canonical)?;
47        Ed25519Signer::verify_with_public_key(
48            &signing_bytes(OPERATION_FORMAT, &self.canonical),
49            &operation.publisher,
50            &self.signature,
51        )?;
52        Ok(operation)
53    }
54}
55
56fn signing_bytes(format: &str, canonical: &[u8]) -> Vec<u8> {
57    let mut bytes = format.as_bytes().to_vec();
58    bytes.push(0);
59    bytes.extend_from_slice(canonical);
60    bytes
61}
62
63/// Original creation record, signed once by its creator. A later publisher can
64/// relay this record without changing Thread identity or claiming authorship.
65#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
66pub struct SignedGenesis {
67    pub canonical: Vec<u8>,
68    pub signature: Vec<u8>,
69}
70impl SignedGenesis {
71    pub fn sign(genesis: &ThreadGenesis, signer: &impl Signer) -> Result<Self, Error> {
72        if signer.public_key() != genesis.creator {
73            return Err(Error::Publisher);
74        }
75        let canonical = genesis.encode()?;
76        let signature = signer.sign(&signing_bytes(GENESIS_FORMAT, &canonical))?;
77        Ok(Self {
78            canonical,
79            signature,
80        })
81    }
82    pub fn verify(&self) -> Result<ThreadGenesis, Error> {
83        let genesis = ThreadGenesis::decode(&self.canonical)?;
84        Ed25519Signer::verify_with_public_key(
85            &signing_bytes(GENESIS_FORMAT, &self.canonical),
86            &genesis.creator,
87            &self.signature,
88        )?;
89        Ok(genesis)
90    }
91}