//! The ownership chain: signature-bound transitions (treatise §8.4, §10.1). //! //! Each transition moves the byte from its current owner to a new one at a //! lockstep frame. The owner signs (byte identity, previous history hash, //! from, to, frame), which scopes the signature to exactly one transition of //! exactly one byte at one point in its history. The history hash starts as //! the byte's identity and advances one step per transition: //! `next = BLAKE3(previous ‖ payload)`, the payload being the transition's //! from, to, frame and signature. //! //! A transition that moves the byte to another cluster (treatise §11.3.3) //! also names its `destination`, and the signature and the history hash //! cover it. A transition within a cluster has no `destination` member. use serde::{Deserialize, Serialize}; use crate::Blake3Hash; use crate::keys::{ControllerSignature, Signer}; use crate::said::{Aid, Said}; /// One link in a byte's ownership chain. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] pub struct Transition { /// The byte's history hash before this transition. #[serde(with = "crate::wire::fixed_bytes")] pub previous_history_hash: Blake3Hash, /// The owner giving the byte up. pub from: Aid, /// The owner receiving it. pub to: Aid, /// The lockstep frame the transition is applied in. pub frame: u64, /// The cluster the byte moves to, for a transition that leaves the /// cluster it is applied in (treatise §11.3.3). Absent otherwise. #[serde(default, skip_serializing_if = "Option::is_none")] pub destination: Option, /// `from`'s signature over [`Transition::signed_bytes`]. pub signature: ControllerSignature, } #[derive(Serialize)] struct Signed<'a> { byte: &'a Said, #[serde(with = "serde_bytes")] previous_history_hash: &'a [u8], from: &'a Aid, to: &'a Aid, frame: u64, #[serde(skip_serializing_if = "Option::is_none")] destination: Option<&'a Said>, } #[derive(Serialize)] struct Payload<'a> { from: &'a Aid, to: &'a Aid, frame: u64, #[serde(skip_serializing_if = "Option::is_none")] destination: Option<&'a Said>, signature: &'a ControllerSignature, } impl Transition { /// A transition of byte `byte` from `owner` to `to` at `frame`, from the /// history point `previous_history_hash`, signed by `owner`. pub fn sign( owner: &Signer, byte: &Said, previous_history_hash: Blake3Hash, to: Aid, frame: u64, ) -> Self { Self::signed(owner, byte, previous_history_hash, to, frame, None) } /// A transition that moves the byte to `destination`, another cluster, /// where `to` becomes its owner (treatise §11.3.3). pub fn sign_export( owner: &Signer, byte: &Said, previous_history_hash: Blake3Hash, to: Aid, frame: u64, destination: Said, ) -> Self { Self::signed( owner, byte, previous_history_hash, to, frame, Some(destination), ) } fn signed( owner: &Signer, byte: &Said, previous_history_hash: Blake3Hash, to: Aid, frame: u64, destination: Option, ) -> Self { let mut t = Transition { previous_history_hash, from: owner.aid, to, frame, destination, signature: ControllerSignature { establishment: 0, signatures: vec![], }, }; t.signature = owner.sign(&t.signed_bytes(byte)); t } /// The message `from` signs: the canonical encoding of (byte identity, /// previous history hash, from, to, frame) — and the destination, for a /// transition that leaves the cluster (treatise §8.4, §11.3.3). pub fn signed_bytes(&self, byte: &Said) -> Vec { crate::wire::to_vec(&Signed { byte, previous_history_hash: &self.previous_history_hash, from: &self.from, to: &self.to, frame: self.frame, destination: self.destination.as_ref(), }) .expect("a transition's signed fields always encode") } /// The history hash after this transition (treatise §10.1). pub fn next_history_hash(&self) -> Blake3Hash { let payload = crate::wire::to_vec(&Payload { from: &self.from, to: &self.to, frame: self.frame, destination: self.destination.as_ref(), signature: &self.signature, }) .expect("a transition's payload always encodes"); let mut h = blake3::Hasher::new(); h.update(&self.previous_history_hash); h.update(&payload); *h.finalize().as_bytes() } }