Skip to main content

confium_log_server/
witness.rs

1//! Witness gossip protocol.
2//!
3//! A **witness** is an independent third party that countersigns
4//! tree heads published by the log. Monitors verify that every
5//! witness sees the same tree head for the same tree size — if
6//! the log presents different heads to different witnesses, the
7//! monitor detects the split.
8//!
9//! ## Wire format
10//!
11//! A witness signature is over:
12//!
13//! ```text
14//! "ConfiumWitness/v1" || tree_size_be(8 bytes) || root_hash(32 bytes)
15//! ```
16//!
17//! Witness IDs are arbitrary strings. A typical witness uses its
18//! domain name (`witness.example.com`) so monitors can fetch the
19//! witness's published policy separately.
20
21use sha2::{Digest, Sha256};
22
23/// Build the canonical signing message for a `(tree_size, root_hash)`
24/// pair. Witnesses sign this; monitors verify it.
25pub fn witness_signing_message(tree_size: u64, root_hash: &[u8; 32]) -> Vec<u8> {
26    let mut msg = Vec::with_capacity(b"ConfiumWitness/v1".len() + 8 + 32);
27    msg.extend_from_slice(b"ConfiumWitness/v1");
28    msg.extend_from_slice(&tree_size.to_be_bytes());
29    msg.extend_from_slice(root_hash);
30    msg
31}
32
33/// Convenience: SHA-256 of the signing message. Some witnesses sign
34/// the digest; some sign the raw message. The monitor must know
35/// which the witness uses (per the witness's published policy).
36pub fn witness_signing_digest(tree_size: u64, root_hash: &[u8; 32]) -> [u8; 32] {
37    let mut h = Sha256::new();
38    h.update(witness_signing_message(tree_size, root_hash));
39    let digest: [u8; 32] = h.finalize().into();
40    digest
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn signing_message_has_fixed_shape() {
49        let root = [0xaa; 32];
50        let prefix = b"ConfiumWitness/v1";
51        let msg = witness_signing_message(42, &root);
52        assert_eq!(msg.len(), prefix.len() + 8 + 32);
53        assert_eq!(&msg[..prefix.len()], prefix);
54        assert_eq!(&msg[prefix.len()..prefix.len() + 8], &42u64.to_be_bytes());
55        assert_eq!(&msg[prefix.len() + 8..], &root);
56    }
57
58    #[test]
59    fn digest_is_deterministic() {
60        let root = [0x42; 32];
61        assert_eq!(
62            witness_signing_digest(1, &root),
63            witness_signing_digest(1, &root)
64        );
65    }
66
67    #[test]
68    fn different_tree_sizes_produce_different_messages() {
69        let root = [0x00; 32];
70        assert_ne!(
71            witness_signing_message(1, &root),
72            witness_signing_message(2, &root)
73        );
74    }
75}