Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

crates/monetize-estate-side/src/pump/key.rs

//! **This box's own Ed25519 key** — what the ONE monetize knows the box by. Minted once
//! under the data root, owner-only, and pinned by monetize on the box's first call
//! inside its pin window (TOFU).

use std::path::Path;

use ed25519_dalek::Signer as _;
use monetize_embed::fleet::{fleet_call_message, FleetCall};

/// Where the key lives under the data root (32 raw seed bytes, 0600).
pub const BOX_KEY_REL: &str = "fleet/box.ed25519";

pub struct BoxKey(ed25519_dalek::SigningKey);

impl BoxKey {
    pub fn from_seed(seed: [u8; 32]) -> BoxKey {
        BoxKey(ed25519_dalek::SigningKey::from_bytes(&seed))
    }

    /// `<root>/fleet/box.ed25519`, minted on first use. Minted ONCE whoever asks first: a
    /// fresh seed is written beside and LINKED into place, which fails when another caller
    /// got there first — then the one on disk is used.
    pub fn load_or_mint(root: &Path) -> std::io::Result<BoxKey> {
        let path = root.join(BOX_KEY_REL);
        match std::fs::read(&path) {
            Ok(bytes) => {
                let seed: [u8; 32] = bytes.as_slice().try_into().map_err(|_| {
                    std::io::Error::new(
                        std::io::ErrorKind::InvalidData,
                        format!(
                            "{} holds {} bytes, not a 32-byte Ed25519 seed",
                            path.display(),
                            bytes.len()
                        ),
                    )
                })?;
                Ok(BoxKey::from_seed(seed))
            }
            Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
                let mut seed = [0u8; 32];
                getrandom::fill(&mut seed).map_err(|e| {
                    std::io::Error::other(format!("no randomness for the box key: {e}"))
                })?;
                let mut tag = [0u8; 8];
                getrandom::fill(&mut tag)
                    .map_err(|e| std::io::Error::other(format!("no randomness: {e}")))?;
                let fresh = path.with_extension(format!("new-{}", crate::hex(&tag)));
                {
                    use std::io::Write as _;
                    if let Some(dir) = path.parent() {
                        std::fs::create_dir_all(dir)?;
                    }
                    let mut opts = std::fs::OpenOptions::new();
                    opts.write(true).create_new(true);
                    #[cfg(unix)]
                    {
                        use std::os::unix::fs::OpenOptionsExt;
                        opts.mode(0o600);
                    }
                    let mut f = opts.open(&fresh)?;
                    f.write_all(&seed)?;
                    f.sync_all()?;
                }
                let linked = std::fs::hard_link(&fresh, &path);
                let _ = std::fs::remove_file(&fresh);
                match linked {
                    Ok(()) => Ok(BoxKey::from_seed(seed)),
                    Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
                        BoxKey::load_or_mint(root)
                    }
                    Err(e) => Err(e),
                }
            }
            Err(e) => Err(e),
        }
    }

    pub fn public(&self) -> [u8; 32] {
        self.0.verifying_key().to_bytes()
    }

    /// The 64 signature bytes over `message` (the actor ticket's canonical JSON).
    pub fn sign_bytes(&self, message: &[u8]) -> Vec<u8> {
        self.0.sign(message).to_bytes().to_vec()
    }

    /// The 64 signature bytes over monetize-embed's [`fleet_call_message`].
    pub fn sign(&self, c: &FleetCall) -> Vec<u8> {
        self.0.sign(&fleet_call_message(c)).to_bytes().to_vec()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn the_box_key_is_minted_once_owner_only_and_racing_loads_agree() {
        let dir = tempfile::tempdir().unwrap();
        let keys: Vec<[u8; 32]> = std::thread::scope(|s| {
            let hs: Vec<_> = (0..8)
                .map(|_| s.spawn(|| BoxKey::load_or_mint(dir.path()).unwrap().public()))
                .collect();
            hs.into_iter().map(|h| h.join().unwrap()).collect()
        });
        let on_disk = BoxKey::load_or_mint(dir.path()).unwrap().public();
        assert!(
            keys.iter().all(|k| *k == on_disk),
            "every first load answers the key on disk"
        );
        #[cfg(unix)]
        {
            use std::os::unix::fs::PermissionsExt;
            let mode = std::fs::metadata(dir.path().join(BOX_KEY_REL))
                .unwrap()
                .permissions()
                .mode();
            assert_eq!(mode & 0o077, 0, "the box key is owner-only");
        }
    }

    #[test]
    fn a_call_verifies_under_monetize_embeds_own_verifier() {
        let key = BoxKey::from_seed([3u8; 32]);
        let c = FleetCall {
            product: "a-product".into(),
            instance: "acme".into(),
            role: "main".into(),
            call: "report".into(),
            body: "{}".into(),
            at_unix_ms: 1_000,
        };
        let sig = key.sign(&c);
        let vk = monetize_embed::VerifyingKey::from_bytes(&key.public()).unwrap();
        monetize_embed::fleet::verify_fleet_call(&c, &sig, &vk, 1_000)
            .expect("monetize verifies the box's call");
    }
}