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.