lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README

README.md (9544B)


      1 # radroots_signing
      2 
      3 `radroots_signing` is the protocol-neutral host SPI for authorizing and signing
      4 exact Radroots authored-event plans. It owns actor provenance, signer capabilities and
      5 status, bounded signing requests, verified receipts, progress, and normalized
      6 secret-safe errors.
      7 
      8 The crate is `no_std` with `alloc`. It does not own secret keys, keyrings,
      9 network clients, NIP-46 sessions, SQL, UI prompts, or an async executor.
     10 Concrete local Nostr signing belongs in `radroots_nostr`; remote protocol state
     11 belongs in `radroots_nostr_connect`; applications compose those adapters in
     12 `radroots_sdk` or their own host layer.
     13 
     14 The authoritative package charter is the
     15 [`radroots_signing` section of the Release V1 specification](../../contracts/crates/release_v1/radroots_crates_release_v1.toml).
     16 
     17 ## Typical flow
     18 
     19 1. A host resolves public actor provenance and roles into an [`Actor`].
     20 2. Event-codec code creates an immutable `AuthoredEventPlan`.
     21 3. The host combines stable operation/artifact identity, actor, plan, deadline, and
     22    cancellation policy into a [`SignRequest`]. Construction validates the
     23    current authorization, required author role, public key, and provenance.
     24 4. A dyn-compatible [`Signer`] implementation signs locally, delegates to a
     25    remote device/service, or mediates explicit host interaction.
     26 5. The implementation creates a [`SignReceipt`] from the originating request.
     27    Receipt construction rejects plan drift and invalid Schnorr signatures.
     28 
     29 The same SPI supports a purpose-tagged, HTTP-only
     30 `BlossomAuthorizationPlan`. `SigningPurpose` keeps that BUD-11 credential
     31 distinct from registry-authored relay events, while receipt verification still
     32 binds the exact author, timestamp, kind, tags, content, event ID, deadline, and
     33 cancellation signal.
     34 
     35 [`Actor`]: crate::Actor
     36 [`Signer`]: crate::Signer
     37 [`SignRequest`]: crate::SignRequest
     38 [`SignReceipt`]: crate::SignReceipt
     39 
     40 ```rust
     41 use radroots_event::{GenericEventDraft, contract::AuthorRole};
     42 use radroots_event_codec::authoring::AuthoredEventPlan;
     43 use radroots_identity::PublicKey;
     44 use radroots_protocol::runtime::v1::OperationId;
     45 use radroots_signing::{
     46     Actor, AuthoredArtifactId, SignRequest, SigningIntentId, SigningOperationId,
     47     actor::ActorSource,
     48     request::{CancellationPolicy, SignPolicy},
     49 };
     50 
     51 let public_key = PublicKey::from_hex(
     52     "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
     53 )
     54 .expect("canonical public key");
     55 let actor = Actor::new(
     56     public_key,
     57     ActorSource::ExplicitPublicKey,
     58     [AuthorRole::Any],
     59 )
     60 .expect("validated actor");
     61 let draft = GenericEventDraft::new(
     62     "radroots.social.geochat.v1",
     63     20_000,
     64     1_700_000_000,
     65     Vec::new(),
     66     "hello from Radroots",
     67     public_key.to_hex(),
     68 )
     69 .expect("validated generic draft");
     70 let plan = AuthoredEventPlan::from_generic(draft).expect("exact plan");
     71 let intent_id = SigningIntentId::new(
     72     SigningOperationId::new([1; 16]).expect("operation ID"),
     73     AuthoredArtifactId::new([2; 16]).expect("artifact ID"),
     74 );
     75 let policy = SignPolicy::new(
     76     1_700_000_030_000,
     77     CancellationPolicy::PreservePublishedRequest,
     78 )
     79 .expect("bounded policy");
     80 let request = SignRequest::new(OperationId::SyncPush, intent_id, actor, plan, policy)
     81     .expect("authorized signing request");
     82 
     83 assert_eq!(request.operation_kind(), OperationId::SyncPush);
     84 ```
     85 
     86 The complete externally implementable SPI example is
     87 [`examples/host_signer.rs`](examples/host_signer.rs).
     88 
     89 ## Host SPI contract
     90 
     91 `Signer` is intentionally externally implementable, object-safe, `Send`, and
     92 `Sync`. Its methods return boxed `Future + Send` values so the SPI does not
     93 select an async runtime or require an async-trait macro.
     94 
     95 - `status` is observational and must not create a signing request or another
     96   durable side effect.
     97 - `sign` receives an owned, already-authorized request. Implementations must
     98   honor its deadline and cancellation policy for the entire operation and must
     99   create success only through `SignReceipt::from_signed_event`.
    100 - Implementations must not install an executor or spawn hidden workers. The
    101   composing host owns polling, scheduling, and process lifecycle.
    102 - Every concrete adapter must document the exact point at which a request
    103   becomes durable. Dropping the future before that point must leave no durable
    104   effect; dropping it afterward does not imply rollback.
    105 - Concrete backend failures are normalized to `radroots_signing::Error`.
    106   Native sources may be retained with `std`, but display, debug, and versioned
    107   protocol reports never copy source text.
    108 
    109 ## Deadlines, cancellation, and commit points
    110 
    111 `SignPolicy` carries an absolute Unix-millisecond deadline. A signer must reject work once
    112 that deadline is reached; request construction does not read a clock.
    113 
    114 `CancellationPolicy::LocalCooperative` is for local-only work that may stop
    115 when cancellation is observed. `PreservePublishedRequest` is for operations
    116 that may publish a durable remote request: cancellation before publication may
    117 stop the operation, while cancellation after publication must preserve and
    118 report the final remote state explicitly. Cancellation never silently converts
    119 an unknown or committed remote state into success or rollback.
    120 
    121 The SPI defines those rules but performs no I/O itself. Commit points belong to
    122 the concrete adapter and must be visible in that adapter's documentation and
    123 status/progress behavior.
    124 
    125 ## Retained authored evidence
    126 
    127 `AuthoredSignEvidence` records a cryptographically verified authored event even
    128 when an already-started signer finishes after the caller's deadline or
    129 cancellation. It binds exact event bytes, operation, artifact and signer request
    130 identity to a positive injected observation time. That time records observation,
    131 not proof of the instant of signing. `revalidate` checks the retained request
    132 identity and all cryptographic fields again with the caller's own clock.
    133 
    134 `Signer::sign_authored_evidence` is an optional hook. Its default delegates to
    135 `sign`, verifies receipt identity and promotes the receipt; already-cancelled
    136 requests are rejected before invoking the adapter. Existing adapters
    137 compile unchanged. Adapters that discard late output need an override to retain
    138 it. Overrides must still prevent new work after deadline or cancellation. The
    139 host owns polling, lifecycle and durable reconciliation; the SPI adds no worker.
    140 
    141 Evidence is a fact, not an active success receipt or authority to schedule new
    142 signing, admission or delivery. `SignReceipt` remains deadline/cancellation
    143 checked. The evidence constructor and default hook reject Blossom requests;
    144 expiring BUD-11 authorization continues to use the strict active receipt path.
    145 
    146 ## Serialization contract
    147 
    148 Native `Actor` and `SignRequest` values are runtime-local and are not
    149 serializable. `SignReceipt` and `AuthoredSignEvidence` can be serialized with
    150 `serde` but cannot be
    151 deserialized without the originating request; this prevents callers from
    152 bypassing authorization and exact-draft verification.
    153 
    154 The `serde` feature provides stable representations for passive policies,
    155 capabilities, status, progress, challenges, and receipts. Versioned
    156 cross-process error data is produced through `Error::to_report` and owned by
    157 `radroots_protocol`. An authentication challenge URI is deliberately
    158 host-displayable and serializable, so adapters must not embed credentials or
    159 secret material in it.
    160 
    161 ## Security and side effects
    162 
    163 - Request construction separates current authorization from historical plan integrity before role, key, and provenance
    164   authorization and never invokes a signer on failure.
    165 - A successful receipt proves exact equality of author, timestamp, kind, tags,
    166   content, and event ID with the exact request plan, plus a valid signature.
    167 - Callers at a persistence or HTTP boundary must revalidate an untrusted host
    168   signer's returned event against their retained request and locally observed
    169   completion time before committing or transmitting it.
    170 - `Error` display/debug output and protocol reports are redacted. Under `std`,
    171   a caller may explicitly inspect a preserved native error source locally.
    172 - `AuthChallenge` debug output redacts its URI; the value accepts only bounded
    173   control-free HTTPS URIs and remains untrusted navigation input for hosts.
    174 - This crate forbids unsafe code and contains no secret-key type, persistence,
    175   network transport, global state, timer, or runtime initialization.
    176 
    177 ## Features
    178 
    179 | Feature | Default | Contract |
    180 | --- | --- | --- |
    181 | `std` | yes | Uses `std` collections and permits preserved native error sources; no runtime, I/O, or global initialization is added. |
    182 | `serde` | yes | Adds serialization for explicitly passive public values and deserialization only where validated reconstruction is safe. |
    183 
    184 Features are additive. `--no-default-features` provides the `no_std + alloc`
    185 core, and `serde` is supported independently of `std`.
    186 
    187 ## Intended consumers
    188 
    189 - `radroots_nostr` implements concrete local signing.
    190 - `radroots_nostr_connect` supplies remote protocol state without owning this
    191   SPI's host composition.
    192 - `radroots_sync` and `radroots_sdk` orchestrate authorized requests and
    193   consume verified receipts.
    194 - CLI, Studio, mobile/FFI, and service hosts resolve actor provenance, choose
    195   adapters, display authentication challenges, and own cancellation/runtime
    196   behavior.
    197 
    198 Applications that only need ordinary Radroots operations should normally use
    199 `radroots` or `radroots_sdk`; implement this package directly when providing a
    200 new signer adapter or advanced host composition.