diff --git a/crates/gitlawb-core/src/encrypt.rs b/crates/gitlawb-core/src/encrypt.rs index 0336270..a6d3dc2 100644 --- a/crates/gitlawb-core/src/encrypt.rs +++ b/crates/gitlawb-core/src/encrypt.rs @@ -123,7 +123,7 @@ pub fn open_blob(envelope: &[u8], keypair: &Keypair) -> Result> { .context("decode header")?; let body = &envelope[p + hlen..]; - let my_x = XSecret::from(x25519_secret_from_seed(&keypair.seed_bytes())); + let my_x = XSecret::from(x25519_secret_from_seed(&keypair.to_seed())); // Identities are blinded: no entry says which recipient it belongs to, so // try each one. The ChaChaBox AEAD tag authenticates, so exactly the @@ -185,7 +185,7 @@ mod tests { // The X25519 public derived from the Ed25519 public must equal the // X25519 public of the X25519 secret derived from the same seed. let kp = Keypair::generate(); - let seed = kp.seed_bytes(); + let seed = kp.to_seed(); let xpub_from_public = x25519_public(&kp.verifying_key()).unwrap(); let xsec = x25519_secret_from_seed(&seed); let xpub_from_secret = crypto_box::SecretKey::from(xsec).public_key().to_bytes(); diff --git a/crates/gitlawb-core/src/identity.rs b/crates/gitlawb-core/src/identity.rs index 9d3fea1..14018d7 100644 --- a/crates/gitlawb-core/src/identity.rs +++ b/crates/gitlawb-core/src/identity.rs @@ -52,13 +52,9 @@ impl Keypair { URL_SAFE_NO_PAD.encode(sig.to_bytes()) } - /// The raw 32-byte Ed25519 seed. Used to derive the X25519 secret for - /// envelope decryption (see `crate::encrypt`). - pub fn seed_bytes(&self) -> [u8; 32] { - self.signing_key.to_bytes() - } - - /// Export the signing key as raw 32-byte seed (wrapped in Zeroizing). + /// The raw 32-byte Ed25519 seed, wrapped in `Zeroizing` so the copy is + /// scrubbed on drop. This is the only seed accessor; it is used to derive + /// the X25519 secret for envelope decryption (see `crate::encrypt`). pub fn to_seed(&self) -> Zeroizing<[u8; 32]> { Zeroizing::new(self.signing_key.to_bytes()) } @@ -172,6 +168,17 @@ mod tests { assert_eq!(kp.verifying_key(), kp2.verifying_key()); } + // `to_seed()` is the only seed accessor on `Keypair` (the raw, unzeroized + // `seed_bytes()` was removed in #41). It must hand out the seed + // deterministically, so the same seed reconstructs the same DID. + #[test] + fn to_seed_is_sole_seed_accessor() { + let kp = Keypair::generate(); + let seed = kp.to_seed(); + assert_eq!(*kp.to_seed(), *seed); // stable across calls + assert_eq!(Keypair::from_seed(&seed).unwrap().did(), kp.did()); + } + #[test] fn sign_b64_decodes_to_valid_signature() { use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};