Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

Expand Down
2 changes: 1 addition & 1 deletion entangled-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
33 changes: 33 additions & 0 deletions entangled-core/src/crypto/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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`].
///
Expand Down
4 changes: 2 additions & 2 deletions entangled-core/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
22 changes: 21 additions & 1 deletion entangled-core/tests/tor/address_decode.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
Expand Down
Loading