commit 311fac6626bfdfb14f49d50f1cd4c78b82f51e49
parent ac24ee227c4e82fd8eb1e18c252bb9fce348afff
Author: triesap <tyson@radroots.org>
Date: Sun, 14 Jun 2026 01:14:50 -0700
authority: add local nostr signer adapter
- add the local_signer feature for RadrootsNostrKeys-backed signing
- implement RadrootsLocalEventSigner for the authority signer trait
- verify signed frozen drafts through the existing Nostr verifier
- validate with cargo fmt, check, and tests for radroots_authority
Diffstat:
4 files changed, 119 insertions(+), 0 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -3966,6 +3966,7 @@ name = "radroots_authority"
version = "0.1.0-alpha.2"
dependencies = [
"radroots_events",
+ "radroots_nostr",
"thiserror 1.0.69",
]
diff --git a/crates/authority/Cargo.toml b/crates/authority/Cargo.toml
@@ -14,9 +14,16 @@ documentation = "https://docs.rs/radroots_authority"
[features]
default = ["std"]
std = ["radroots_events/std"]
+local_signer = [
+ "std",
+ "dep:radroots_nostr",
+ "radroots_nostr/events",
+ "radroots_nostr/std",
+]
[dependencies]
radroots_events = { workspace = true, default-features = false }
+radroots_nostr = { workspace = true, optional = true, default-features = false }
thiserror = { workspace = true }
[lints.rust]
diff --git a/crates/authority/src/lib.rs b/crates/authority/src/lib.rs
@@ -8,6 +8,8 @@ extern crate alloc;
pub mod actor;
pub mod authorization;
pub mod error;
+#[cfg(feature = "local_signer")]
+pub mod local_signer;
pub mod signer;
pub use actor::{
@@ -19,4 +21,6 @@ pub use authorization::{
sign_authorized_draft,
};
pub use error::{RadrootsAuthorityError, RadrootsSignerError};
+#[cfg(feature = "local_signer")]
+pub use local_signer::RadrootsLocalEventSigner;
pub use signer::{RadrootsEventSigner, RadrootsSignerIdentity};
diff --git a/crates/authority/src/local_signer.rs b/crates/authority/src/local_signer.rs
@@ -0,0 +1,107 @@
+#![forbid(unsafe_code)]
+
+use crate::{RadrootsAuthorityError, RadrootsEventSigner, RadrootsSignerError};
+use radroots_events::draft::{RadrootsFrozenEventDraft, RadrootsSignedNostrEvent};
+use radroots_events::ids::RadrootsPublicKey;
+use radroots_nostr::prelude::{RadrootsNostrKeys, radroots_nostr_sign_frozen_draft};
+
+pub struct RadrootsLocalEventSigner {
+ keys: RadrootsNostrKeys,
+ pubkey: RadrootsPublicKey,
+}
+
+impl RadrootsLocalEventSigner {
+ pub fn new(keys: RadrootsNostrKeys) -> Result<Self, RadrootsAuthorityError> {
+ let pubkey = RadrootsPublicKey::parse(keys.public_key().to_hex())
+ .map_err(|_| RadrootsAuthorityError::InvalidSignerPubkey)?;
+ Ok(Self { keys, pubkey })
+ }
+
+ pub fn keys(&self) -> &RadrootsNostrKeys {
+ &self.keys
+ }
+}
+
+impl RadrootsEventSigner for RadrootsLocalEventSigner {
+ fn pubkey(&self) -> &RadrootsPublicKey {
+ &self.pubkey
+ }
+
+ fn sign_frozen_draft(
+ &self,
+ draft: &RadrootsFrozenEventDraft,
+ ) -> Result<RadrootsSignedNostrEvent, RadrootsSignerError> {
+ radroots_nostr_sign_frozen_draft(&self.keys, draft).map_err(|error| {
+ RadrootsSignerError::SigningFailed {
+ message: error.to_string(),
+ }
+ })
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use radroots_events::RadrootsNostrEvent;
+ use radroots_events::kinds::KIND_POST;
+ use radroots_nostr::prelude::{
+ RadrootsNostrEventVerification, RadrootsNostrSecretKey, radroots_nostr_verify_event,
+ };
+
+ const FIXTURE_ALICE_SECRET_KEY_HEX: &str =
+ "10c5304d6c9ae3a1a16f7860f1cc8f5e3a76225a2663b3a989a0d775919b7df5";
+ const FIXTURE_ALICE_PUBLIC_KEY_HEX: &str =
+ "585591529da0bab31b3b1b1f986611cf5f435dca84f978c89ee8a40cca7103df";
+
+ fn fixture_keys() -> RadrootsNostrKeys {
+ let secret_key =
+ RadrootsNostrSecretKey::from_hex(FIXTURE_ALICE_SECRET_KEY_HEX).expect("secret key");
+ RadrootsNostrKeys::new(secret_key)
+ }
+
+ fn post_draft() -> RadrootsFrozenEventDraft {
+ RadrootsFrozenEventDraft::new(
+ "radroots.social.post.v1",
+ KIND_POST,
+ 1_700_000_000,
+ vec![vec!["t".to_owned(), "soil".to_owned()]],
+ "hello",
+ FIXTURE_ALICE_PUBLIC_KEY_HEX,
+ )
+ .expect("draft")
+ }
+
+ fn verification_event(signed: &RadrootsSignedNostrEvent) -> RadrootsNostrEvent {
+ RadrootsNostrEvent {
+ id: signed.id.clone(),
+ author: signed.pubkey.clone(),
+ created_at: signed.created_at,
+ kind: signed.kind,
+ tags: signed.tags.clone(),
+ content: signed.content.clone(),
+ sig: signed.sig.clone(),
+ }
+ }
+
+ #[test]
+ fn local_signer_reports_public_key() {
+ let signer = RadrootsLocalEventSigner::new(fixture_keys()).expect("signer");
+
+ assert_eq!(signer.pubkey().as_str(), FIXTURE_ALICE_PUBLIC_KEY_HEX);
+ }
+
+ #[test]
+ fn local_signer_signs_and_verifies_frozen_drafts() {
+ let signer = RadrootsLocalEventSigner::new(fixture_keys()).expect("signer");
+ let draft = post_draft();
+
+ let signed = signer.sign_frozen_draft(&draft).expect("signed");
+
+ assert_eq!(signed.id, draft.expected_event_id);
+ assert_eq!(signed.pubkey, draft.expected_pubkey);
+ assert_eq!(
+ radroots_nostr_verify_event(&verification_event(&signed)),
+ RadrootsNostrEventVerification::Verified
+ );
+ }
+}