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}