Skip to main content

hypercall_blocks/
authority.rs

1use serde::{Deserialize, Serialize};
2
3/// Cold-key delegation authorizing a hot key to sign blocks for a time window.
4///
5/// The delegator is the long-lived authority key. The delegate is the hot key
6/// used by runtime code to sign individual block headers. The signature is
7/// stored next to the delegation fields but is excluded from `delegation_hash()`.
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9pub struct SigningAuthority {
10    pub delegator: [u8; 20],
11    pub delegate: [u8; 20],
12    pub valid_from: u64,
13    pub valid_until: u64,
14    pub signature: Vec<u8>,
15}
16
17impl SigningAuthority {
18    /// Compute the hash of the delegation fields. The signature is excluded.
19    pub fn delegation_hash(&self) -> [u8; 32] {
20        use sha3::{Digest, Keccak256};
21
22        let mut h = Keccak256::new();
23        h.update(self.delegator);
24        h.update(self.delegate);
25        h.update(self.valid_from.to_be_bytes());
26        h.update(self.valid_until.to_be_bytes());
27        h.finalize().into()
28    }
29
30    /// Check if the delegation is valid at the given timestamp in ms.
31    pub fn is_valid_at(&self, timestamp_ms: u64) -> bool {
32        timestamp_ms >= self.valid_from && timestamp_ms < self.valid_until
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use super::*;
39
40    fn authority() -> SigningAuthority {
41        SigningAuthority {
42            delegator: [1; 20],
43            delegate: [2; 20],
44            valid_from: 1000,
45            valid_until: 2000,
46            signature: vec![3; 65],
47        }
48    }
49
50    #[test]
51    fn validity_window_is_inclusive_start_exclusive_end() {
52        let auth = authority();
53
54        assert!(!auth.is_valid_at(999));
55        assert!(auth.is_valid_at(1000));
56        assert!(auth.is_valid_at(1500));
57        assert!(!auth.is_valid_at(2000));
58    }
59
60    #[test]
61    fn delegation_hash_is_deterministic() {
62        let auth = authority();
63
64        assert_eq!(auth.delegation_hash(), auth.delegation_hash());
65    }
66
67    #[test]
68    fn delegation_hash_excludes_signature() {
69        let mut auth = authority();
70        let hash = auth.delegation_hash();
71
72        auth.signature = vec![0xff; 65];
73
74        assert_eq!(hash, auth.delegation_hash());
75    }
76
77    #[test]
78    fn delegation_hash_changes_when_signed_field_changes() {
79        let baseline = authority();
80        let baseline_hash = baseline.delegation_hash();
81
82        let mut changed = baseline.clone();
83        changed.delegator = [0x10; 20];
84        assert_ne!(baseline_hash, changed.delegation_hash());
85
86        let mut changed = baseline.clone();
87        changed.delegate = [0x20; 20];
88        assert_ne!(baseline_hash, changed.delegation_hash());
89
90        let mut changed = baseline.clone();
91        changed.valid_from += 1;
92        assert_ne!(baseline_hash, changed.delegation_hash());
93
94        let mut changed = baseline;
95        changed.valid_until += 1;
96        assert_ne!(baseline_hash, changed.delegation_hash());
97    }
98
99    #[test]
100    fn authority_roundtrips_through_msgpack() {
101        let auth = authority();
102
103        assert_eq!(msgpack_roundtrip(&auth), auth);
104    }
105
106    fn msgpack_roundtrip<T>(value: &T) -> T
107    where
108        T: Clone + PartialEq + std::fmt::Debug + Serialize + for<'de> Deserialize<'de>,
109    {
110        let bytes = rmp_serde::to_vec_named(value).expect("serialize signing authority");
111        rmp_serde::from_slice(&bytes).expect("deserialize signing authority")
112    }
113}