diff --git a/Cargo.lock b/Cargo.lock index d32e6ee..a2f31c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -300,7 +300,7 @@ checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "entangled-core" -version = "0.9.0" +version = "0.10.0" dependencies = [ "criterion", "data-encoding", diff --git a/README.md b/README.md index 2324c54..0b3aa2b 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ A site built with Entangled is not a web application. It is a set of signed JSON - [`entangled-core`](./entangled-core): the protocol core library. -Current crate version: `0.9.0`. +Current crate version: `0.10.0`. Implemented in `entangled-core`: diff --git a/entangled-core/Cargo.toml b/entangled-core/Cargo.toml index 2b2bcdf..b9b5fa5 100644 --- a/entangled-core/Cargo.toml +++ b/entangled-core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "entangled-core" -version = "0.9.0" +version = "0.10.0" edition = "2021" rust-version = "1.88" license = "MIT OR Apache-2.0" diff --git a/entangled-core/src/crypto/ed25519.rs b/entangled-core/src/crypto/ed25519.rs index 59048f3..c007a85 100644 --- a/entangled-core/src/crypto/ed25519.rs +++ b/entangled-core/src/crypto/ed25519.rs @@ -215,6 +215,16 @@ pub struct PublisherSigningKey(SigningKey); /// not interconvertible with it at the public API level. pub struct RuntimeSigningKey(SigningKey); +/// Origin key. Binds the manifest to a Tor v3 carrier address. +/// +/// `K_origin` (§05, §06) is never used to sign Entangled documents in v1.0: +/// it exists only so the onion address derives from it and the manifest's +/// `origin.origin_pubkey` can be checked against the address. This newtype +/// therefore exposes seed-based construction and public-key derivation but, +/// unlike [`PublisherSigningKey`] and [`RuntimeSigningKey`], no signing +/// method. A publisher holds the origin seed to derive its onion address. +pub struct OriginSigningKey(SigningKey); + impl PublisherSigningKey { /// Generate a fresh publisher keypair from OS entropy. /// @@ -276,6 +286,29 @@ impl RuntimeSigningKey { } } +impl OriginSigningKey { + /// Generate a fresh origin keypair from OS entropy. + /// + /// Test-only: gated behind `#[cfg(test)]` and the `test-utils` feature. + #[cfg(any(test, feature = "test-utils"))] + pub fn generate() -> Self { + Self(SigningKey::generate()) + } + + /// Build an [`OriginSigningKey`] from a 32-byte seed + /// (the RFC 8032 secret key). + pub fn from_seed(seed: &[u8; 32]) -> Self { + Self(SigningKey::from_seed(seed)) + } + + /// Return the [`OriginPubkey`] derived from this key. Combine with + /// [`crate::types::manifest::OnionAddress::from_origin_pubkey`] to obtain + /// the carrier address. + pub fn verifying_key(&self) -> OriginPubkey { + self.0.verifying_key().to_origin_pubkey() + } +} + impl VerifyingKey { /// Parse a [`PublisherPubkey`] into a [`VerifyingKey`]. /// diff --git a/entangled-core/src/crypto/mod.rs b/entangled-core/src/crypto/mod.rs index 894ac44..67aa4ef 100644 --- a/entangled-core/src/crypto/mod.rs +++ b/entangled-core/src/crypto/mod.rs @@ -9,8 +9,8 @@ pub mod signing; pub use ed25519::{ validate_origin_pubkey_strict, validate_pubkey_strict, validate_publisher_pubkey_strict, - validate_runtime_pubkey_strict, CryptoError, PublisherSigningKey, RuntimeSigningKey, - VerifyingKey, + validate_runtime_pubkey_strict, CryptoError, OriginSigningKey, PublisherSigningKey, + RuntimeSigningKey, VerifyingKey, }; pub use pip::{derive_pip, pip_to_pubkey, PipError}; pub use sha256::{sha256, sha256_base64url, sha256_image, sha256_request}; diff --git a/entangled-core/tests/tor/address_decode.rs b/entangled-core/tests/tor/address_decode.rs index efe1df0..fb3fc0d 100644 --- a/entangled-core/tests/tor/address_decode.rs +++ b/entangled-core/tests/tor/address_decode.rs @@ -1,7 +1,7 @@ //! `OnionAddress::decode` and `verify_strict` exercises. use data_encoding::BASE32; -use entangled_core::crypto::PublisherSigningKey; +use entangled_core::crypto::{OriginSigningKey, PublisherSigningKey}; use entangled_core::tor::TorError; use entangled_core::types::keys::OriginPubkey; use entangled_core::types::manifest::OnionAddress; @@ -157,6 +157,26 @@ fn from_origin_pubkey_corpus_fixture() { ); } +/// End-to-end origin ceremony against the corpus fixture: the origin seed +/// `ENTANGLED-v1.0-origin-test00001\0` derives, through +/// `OriginSigningKey -> OriginPubkey -> from_origin_pubkey`, to the same +/// public key and onion address the corpus records. +#[test] +fn origin_seed_to_onion_corpus_fixture() { + let seed: [u8; 32] = *b"ENTANGLED-v1.0-origin-test00001\0"; + let origin_key = OriginSigningKey::from_seed(&seed); + let pubkey = origin_key.verifying_key(); + assert_eq!( + pubkey.to_string(), + "Gp8y4JM7Qlkn8JXkJAOW8s3MSkkQNGHGC1c7-AK6Wpo" + ); + let addr = OnionAddress::from_origin_pubkey(&pubkey); + assert_eq!( + addr.as_str(), + "dkptfyethnbfsj7qsxscia4w6lg4yssjca2gdrqlk457qav2lkna4xqd.onion" + ); +} + /// Public test vector: DuckDuckGo's onion service v3 address. We can't verify /// "the right pubkey" without their key material, but the checksum and version /// byte must verify under our decoder for the implementation to be