Skip to main content

nwep/
checkpoint.rs

1use crate::anchor::AnchorSet;
2use crate::bls::{BlsKeypair, BlsPubkey, BlsSig};
3use crate::error::{Error, check, check_ssize};
4use crate::ffi;
5use crate::types::{MerkleHash, Tstamp};
6
7/// `CHECKPOINT_MESSAGE_SIZE` is the byte length of the canonical message that anchors sign.
8///
9/// The message is `epoch (8) + timestamp (8) + merkle_root (32) + log_size (8) = 56` bytes.
10pub const CHECKPOINT_MESSAGE_SIZE: usize = 56;
11
12/// `Checkpoint` is a BLS-signed snapshot of the Merkle log at a given epoch.
13///
14/// `Checkpoint` records the Merkle root, log size, and the aggregate BLS signature
15/// from anchor nodes that attested to this state. Use [`Checkpoint::verify`] to
16/// confirm that a quorum of trusted anchors signed the checkpoint before trusting
17/// any identity proofs that reference it.
18#[derive(Clone, Debug)]
19pub struct Checkpoint {
20    /// Monotonically increasing checkpoint sequence number.
21    pub epoch: u64,
22    /// Unix timestamp (nanoseconds) when the checkpoint was created.
23    pub timestamp: Tstamp,
24    /// Merkle root hash of the log at this checkpoint.
25    pub merkle_root: MerkleHash,
26    /// Number of entries in the log at this checkpoint.
27    pub log_size: u64,
28    /// Aggregated BLS signature from all signing anchor nodes.
29    pub signature: BlsSig,
30    /// BLS public keys of the anchors that contributed to `signature`.
31    pub signers: Vec<BlsPubkey>,
32}
33
34impl Checkpoint {
35    /// `new` creates an unsigned checkpoint for the given log state.
36    ///
37    /// After creation, each anchor in the quorum must call [`sign`](Checkpoint::sign)
38    /// to add its BLS signature before the checkpoint can be verified.
39    ///
40    /// # Errors
41    ///
42    /// Returns `Err` if the C library call fails (e.g. invalid parameters).
43    pub fn new(
44        epoch: u64,
45        timestamp: Tstamp,
46        merkle_root: MerkleHash,
47        log_size: u64,
48    ) -> Result<Self, Error> {
49        let mut cp = unsafe { std::mem::zeroed::<ffi::nwep_checkpoint>() };
50        let ffi_root = ffi::nwep_merkle_hash {
51            data: merkle_root.0,
52        };
53        check(unsafe { ffi::nwep_checkpoint_new(&mut cp, epoch, timestamp, &ffi_root, log_size) })?;
54        Ok(Checkpoint::from_ffi(&cp))
55    }
56
57    /// `sign` adds this anchor's BLS signature to the checkpoint.
58    ///
59    /// Multiple anchors may call `sign` independently; the resulting signatures are
60    /// aggregated by the C library. Once enough anchors have signed, [`verify`](Checkpoint::verify)
61    /// will succeed against the corresponding [`AnchorSet`].
62    ///
63    /// # Errors
64    ///
65    /// Returns `Err` if signing fails (e.g. invalid keypair or message).
66    pub fn sign(&mut self, anchor_kp: &BlsKeypair) -> Result<(), Error> {
67        let mut ffi_cp = self.to_ffi();
68        check(unsafe { ffi::nwep_checkpoint_sign(&mut ffi_cp, anchor_kp.as_ffi()) })?;
69        *self = Checkpoint::from_ffi(&ffi_cp);
70        Ok(())
71    }
72
73    /// `verify` checks that a quorum of anchors from `anchor_set` have signed this checkpoint.
74    ///
75    /// Confirms that the aggregate BLS signature in `self.signature` is a valid signature
76    /// by at least `anchor_set.threshold()` of the registered anchor keys over the canonical
77    /// checkpoint message (epoch + timestamp + merkle_root + log_size).
78    ///
79    /// # Errors
80    ///
81    /// Returns `Err` if the signature is invalid, insufficient signers, or a C error occurs.
82    pub fn verify(&self, anchor_set: &AnchorSet) -> Result<(), Error> {
83        let ffi_cp = self.to_ffi();
84        check(unsafe { ffi::nwep_checkpoint_verify(&ffi_cp, anchor_set.as_ptr()) })
85    }
86
87    /// `encode` serializes the checkpoint to a byte vector for storage or transmission.
88    ///
89    /// # Errors
90    ///
91    /// Returns `Err` if serialization fails.
92    pub fn encode(&self) -> Result<Vec<u8>, Error> {
93        let ffi_cp = self.to_ffi();
94        let mut buf = vec![0u8; 4096];
95        let n = check_ssize(unsafe {
96            ffi::nwep_checkpoint_encode(buf.as_mut_ptr(), buf.len(), &ffi_cp)
97        })?;
98        buf.truncate(n);
99        Ok(buf)
100    }
101
102    /// `decode` deserializes a checkpoint from bytes produced by [`encode`](Checkpoint::encode).
103    ///
104    /// # Errors
105    ///
106    /// Returns `Err` if `data` is truncated or malformed.
107    pub fn decode(data: &[u8]) -> Result<Self, Error> {
108        let mut cp = unsafe { std::mem::zeroed::<ffi::nwep_checkpoint>() };
109        check(unsafe { ffi::nwep_checkpoint_decode(&mut cp, data.as_ptr(), data.len()) })?;
110        Ok(Checkpoint::from_ffi(&cp))
111    }
112
113    /// `message` returns the canonical [`CHECKPOINT_MESSAGE_SIZE`]-byte byte string that anchors sign.
114    ///
115    /// The message is the concatenation of epoch, timestamp, merkle_root, and log_size,
116    /// all in big-endian byte order. This is the exact byte string passed to BLS signing.
117    ///
118    /// # Errors
119    ///
120    /// Returns `Err` if the C library call fails.
121    pub fn message(&self) -> Result<Vec<u8>, Error> {
122        let ffi_cp = self.to_ffi();
123        let mut buf = vec![0u8; 64];
124        let n = check_ssize(unsafe {
125            ffi::nwep_checkpoint_message(buf.as_mut_ptr(), buf.len(), &ffi_cp)
126        })?;
127        buf.truncate(n);
128        Ok(buf)
129    }
130
131    pub(crate) fn to_ffi(&self) -> ffi::nwep_checkpoint {
132        let mut cp = unsafe { std::mem::zeroed::<ffi::nwep_checkpoint>() };
133        cp.epoch = self.epoch;
134        cp.timestamp = self.timestamp;
135        cp.merkle_root = ffi::nwep_merkle_hash {
136            data: self.merkle_root.0,
137        };
138        cp.log_size = self.log_size;
139        cp.signature = ffi::nwep_bls_sig {
140            data: self.signature.0,
141        };
142        cp.num_signers = self.signers.len().min(crate::types::MAX_ANCHORS);
143        for (i, s) in self
144            .signers
145            .iter()
146            .take(crate::types::MAX_ANCHORS)
147            .enumerate()
148        {
149            cp.signers[i] = ffi::nwep_bls_pubkey { data: s.0 };
150        }
151        cp
152    }
153
154    pub(crate) fn from_ffi(cp: &ffi::nwep_checkpoint) -> Self {
155        let signers = (0..cp.num_signers)
156            .map(|i| BlsPubkey(cp.signers[i].data))
157            .collect();
158        Checkpoint {
159            epoch: cp.epoch,
160            timestamp: cp.timestamp,
161            merkle_root: MerkleHash(cp.merkle_root.data),
162            log_size: cp.log_size,
163            signature: BlsSig(cp.signature.data),
164            signers,
165        }
166    }
167}