Skip to main content

monetize_estate_side/pump/
key.rs

1//! **This box's own Ed25519 key** — what the ONE monetize knows the box by. Minted once
2//! under the data root, owner-only, and pinned by monetize on the box's first call
3//! inside its pin window (TOFU).
4
5use std::path::Path;
6
7use ed25519_dalek::Signer as _;
8use monetize_embed::fleet::{fleet_call_message, FleetCall};
9
10/// Where the key lives under the data root (32 raw seed bytes, 0600).
11pub const BOX_KEY_REL: &str = "fleet/box.ed25519";
12
13pub struct BoxKey(ed25519_dalek::SigningKey);
14
15impl BoxKey {
16    pub fn from_seed(seed: [u8; 32]) -> BoxKey {
17        BoxKey(ed25519_dalek::SigningKey::from_bytes(&seed))
18    }
19
20    /// `<root>/fleet/box.ed25519`, minted on first use. Minted ONCE whoever asks first: a
21    /// fresh seed is written beside and LINKED into place, which fails when another caller
22    /// got there first — then the one on disk is used.
23    pub fn load_or_mint(root: &Path) -> std::io::Result<BoxKey> {
24        let path = root.join(BOX_KEY_REL);
25        match std::fs::read(&path) {
26            Ok(bytes) => {
27                let seed: [u8; 32] = bytes.as_slice().try_into().map_err(|_| {
28                    std::io::Error::new(std::io::ErrorKind::InvalidData, format!("{} holds {} bytes, not a 32-byte Ed25519 seed", path.display(), bytes.len()))
29                })?;
30                Ok(BoxKey::from_seed(seed))
31            }
32            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
33                let mut seed = [0u8; 32];
34                getrandom::fill(&mut seed).map_err(|e| std::io::Error::other(format!("no randomness for the box key: {e}")))?;
35                let mut tag = [0u8; 8];
36                getrandom::fill(&mut tag).map_err(|e| std::io::Error::other(format!("no randomness: {e}")))?;
37                let fresh = path.with_extension(format!("new-{}", crate::hex(&tag)));
38                {
39                    use std::io::Write as _;
40                    if let Some(dir) = path.parent() {
41                        std::fs::create_dir_all(dir)?;
42                    }
43                    let mut opts = std::fs::OpenOptions::new();
44                    opts.write(true).create_new(true);
45                    #[cfg(unix)]
46                    {
47                        use std::os::unix::fs::OpenOptionsExt;
48                        opts.mode(0o600);
49                    }
50                    let mut f = opts.open(&fresh)?;
51                    f.write_all(&seed)?;
52                    f.sync_all()?;
53                }
54                let linked = std::fs::hard_link(&fresh, &path);
55                let _ = std::fs::remove_file(&fresh);
56                match linked {
57                    Ok(()) => Ok(BoxKey::from_seed(seed)),
58                    Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => BoxKey::load_or_mint(root),
59                    Err(e) => Err(e),
60                }
61            }
62            Err(e) => Err(e),
63        }
64    }
65
66    pub fn public(&self) -> [u8; 32] {
67        self.0.verifying_key().to_bytes()
68    }
69
70    /// The 64 signature bytes over `message` (the actor ticket's canonical JSON).
71    pub fn sign_bytes(&self, message: &[u8]) -> Vec<u8> {
72        self.0.sign(message).to_bytes().to_vec()
73    }
74
75    /// The 64 signature bytes over monetize-embed's [`fleet_call_message`].
76    pub fn sign(&self, c: &FleetCall) -> Vec<u8> {
77        self.0.sign(&fleet_call_message(c)).to_bytes().to_vec()
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn the_box_key_is_minted_once_owner_only_and_racing_loads_agree() {
87        let dir = tempfile::tempdir().unwrap();
88        let keys: Vec<[u8; 32]> = std::thread::scope(|s| {
89            let hs: Vec<_> = (0..8).map(|_| s.spawn(|| BoxKey::load_or_mint(dir.path()).unwrap().public())).collect();
90            hs.into_iter().map(|h| h.join().unwrap()).collect()
91        });
92        let on_disk = BoxKey::load_or_mint(dir.path()).unwrap().public();
93        assert!(keys.iter().all(|k| *k == on_disk), "every first load answers the key on disk");
94        #[cfg(unix)]
95        {
96            use std::os::unix::fs::PermissionsExt;
97            let mode = std::fs::metadata(dir.path().join(BOX_KEY_REL)).unwrap().permissions().mode();
98            assert_eq!(mode & 0o077, 0, "the box key is owner-only");
99        }
100    }
101
102    #[test]
103    fn a_call_verifies_under_monetize_embeds_own_verifier() {
104        let key = BoxKey::from_seed([3u8; 32]);
105        let c = FleetCall { product: "a-product".into(), instance: "acme".into(), role: "main".into(), call: "report".into(), body: "{}".into(), at_unix_ms: 1_000 };
106        let sig = key.sign(&c);
107        let vk = monetize_embed::VerifyingKey::from_bytes(&key.public()).unwrap();
108        monetize_embed::fleet::verify_fleet_call(&c, &sig, &vk, 1_000).expect("monetize verifies the box's call");
109    }
110}