Skip to main content

ghostkey_common/
lib.rs

1use freenet_stdlib::prelude::*;
2use serde::{Deserialize, Serialize};
3
4#[cfg(feature = "crypto")]
5use ed25519_dalek::VerifyingKey;
6
7/// Compute fingerprint for a ghostkey verifying key.
8/// First 8 bytes of BLAKE3(verifying_key_bytes), base58-encoded.
9#[cfg(feature = "crypto")]
10pub fn fingerprint(verifying_key: &VerifyingKey) -> String {
11    let hash = blake3::hash(verifying_key.as_bytes());
12    bs58::encode(&hash.as_bytes()[..8]).into_string()
13}
14
15/// Serialize a value to CBOR bytes.
16pub fn to_cbor<T: Serialize>(value: &T) -> Result<Vec<u8>, String> {
17    let mut buf = Vec::new();
18    ciborium::into_writer(value, &mut buf).map_err(|e| format!("CBOR serialize: {e}"))?;
19    Ok(buf)
20}
21
22/// Deserialize a value from CBOR bytes.
23pub fn from_cbor<T: for<'de> Deserialize<'de>>(bytes: &[u8]) -> Result<T, String> {
24    ciborium::from_reader(bytes).map_err(|e| format!("CBOR deserialize: {e}"))
25}
26
27/// Who requested a ghostkey operation. Runtime-attested, can't be spoofed.
28#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
29#[non_exhaustive]
30pub enum SignatureRequestor {
31    /// A web application (UI) backed by this contract.
32    WebApp(ContractInstanceId),
33    /// Another delegate on the same node.
34    Delegate(DelegateKey),
35}
36
37/// What the ghostkey delegate actually signs. The raw payload is never signed
38/// alone -- always wrapped with the attested caller identity.
39#[derive(Serialize, Deserialize, Debug, Clone)]
40pub struct ScopedPayload {
41    pub requestor: SignatureRequestor,
42    pub payload: Vec<u8>,
43}
44
45/// Summary info about a stored ghostkey.
46#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
47pub struct GhostKeyInfo {
48    pub fingerprint: String,
49    pub label: Option<String>,
50    /// The notary certificate info field (encodes donation tier).
51    /// Historically called `delegate_info` — the wire-format key is frozen
52    /// via `#[serde(rename)]` for backward compat with stored state.
53    /// See freenet/web#24.
54    #[serde(rename = "delegate_info")]
55    pub notary_info: String,
56    /// Ed25519 verifying key bytes (32 bytes). Added in 0.2.2 for dapps
57    /// that need the raw key (e.g. Harvest store contract parameters).
58    #[serde(default)]
59    pub verifying_key_bytes: Option<Vec<u8>>,
60}
61
62/// A ghostkey exported for backup (includes private signing key).
63#[derive(Serialize, Deserialize, Debug, Clone)]
64pub struct ExportedGhostKey {
65    pub fingerprint: String,
66    pub certificate_pem: String,
67    pub signing_key_pem: String,
68    pub label: Option<String>,
69    #[serde(rename = "delegate_info")]
70    pub notary_info: String,
71}
72
73/// Requests from UI or other delegates to the ghostkey delegate.
74#[derive(Serialize, Deserialize, Debug, Clone)]
75#[non_exhaustive]
76pub enum GhostkeyRequest {
77    /// Import a ghostkey from PEM-armored certificate and signing key.
78    /// If master_verifying_key_pem is None, uses the hardcoded Freenet master key.
79    ImportGhostKey {
80        certificate_pem: String,
81        signing_key_pem: String,
82        #[serde(default)]
83        master_verifying_key_pem: Option<String>,
84    },
85    /// List all stored ghostkeys.
86    ListGhostKeys,
87    /// Get details for a specific ghostkey.
88    GetGhostKey { fingerprint: String },
89    /// Get just the public certificate (for sharing with counterparties).
90    GetCertificate { fingerprint: String },
91    /// Delete a stored ghostkey.
92    DeleteGhostKey { fingerprint: String },
93    /// Set a user-friendly label.
94    SetLabel { fingerprint: String, label: String },
95    /// Sign a message with a specific ghostkey. The delegate scopes the
96    /// signature to the requestor.
97    SignMessage {
98        fingerprint: String,
99        message: Vec<u8>,
100    },
101    /// Sign a message with the user's default ghostkey (highest-tier key,
102    /// or user-overridden via SetDefaultKey). Apps should prefer this over
103    /// SignMessage -- it avoids needing to know about specific fingerprints.
104    SignWithDefault { message: Vec<u8> },
105    /// Set which ghostkey is the default for signing.
106    SetDefaultKey { fingerprint: String },
107    /// Get the current default ghostkey fingerprint.
108    GetDefaultKey,
109    /// Verify a signed message produced by this delegate.
110    VerifySignedMessage { signed_message: Vec<u8> },
111    /// Export a ghostkey's certificate and signing key for backup.
112    /// Security-sensitive: returns the private signing key.
113    ExportGhostKey { fingerprint: String },
114    /// Export all ghostkeys for backup.
115    ExportAllGhostKeys,
116    /// Grant an application or delegate permission to use a ghostkey.
117    GrantPermission {
118        fingerprint: String,
119        requestor: SignatureRequestor,
120    },
121    /// Revoke a previously granted permission.
122    RevokePermission {
123        fingerprint: String,
124        requestor: SignatureRequestor,
125    },
126    /// List permissions for a ghostkey.
127    ListPermissions { fingerprint: String },
128    /// Debug: force a permission prompt regardless of existing permissions.
129    TestPermissionPrompt { fingerprint: String },
130}
131
132/// Responses from the ghostkey delegate.
133#[derive(Serialize, Deserialize, Debug, Clone)]
134#[non_exhaustive]
135pub enum GhostkeyResponse {
136    ImportResult {
137        fingerprint: String,
138        #[serde(rename = "delegate_info")]
139        notary_info: String,
140    },
141    GhostKeyList {
142        keys: Vec<GhostKeyInfo>,
143    },
144    GhostKeyDetail {
145        fingerprint: String,
146        certificate_pem: String,
147        label: Option<String>,
148        #[serde(rename = "delegate_info")]
149        notary_info: String,
150    },
151    Certificate {
152        fingerprint: String,
153        certificate_pem: String,
154    },
155    SignResult {
156        /// CBOR-serialized ScopedPayload
157        scoped_payload: Vec<u8>,
158        /// Ed25519 signature over the scoped_payload bytes
159        signature: Vec<u8>,
160        /// The certificate PEM, so the verifier has the full chain
161        certificate_pem: String,
162    },
163    DefaultKeyResult {
164        fingerprint: Option<String>,
165    },
166    DefaultKeySet {
167        fingerprint: String,
168    },
169    VerifyResult {
170        valid: bool,
171        signer_fingerprint: Option<String>,
172        #[serde(rename = "delegate_info")]
173        notary_info: Option<String>,
174        requestor: Option<SignatureRequestor>,
175        message: Option<Vec<u8>>,
176    },
177    Deleted {
178        fingerprint: String,
179    },
180    LabelSet {
181        fingerprint: String,
182        label: String,
183    },
184    PermissionGranted {
185        fingerprint: String,
186        requestor: SignatureRequestor,
187    },
188    PermissionRevoked {
189        fingerprint: String,
190        requestor: SignatureRequestor,
191    },
192    PermissionList {
193        fingerprint: String,
194        requestors: Vec<SignatureRequestor>,
195    },
196    ExportResult {
197        fingerprint: String,
198        certificate_pem: String,
199        signing_key_pem: String,
200        label: Option<String>,
201    },
202    ExportAllResult {
203        keys: Vec<ExportedGhostKey>,
204    },
205    PermissionDenied {
206        fingerprint: String,
207        requestor: SignatureRequestor,
208    },
209    /// The user has no ghostkeys. Apps should direct the user to
210    /// freenet.org/ghostkey to purchase one.
211    NoIdentityAvailable,
212    /// The requested ghostkey fingerprint was not found.
213    KeyNotFound {
214        fingerprint: String,
215    },
216    /// Generic error for unexpected failures.
217    Error {
218        message: String,
219    },
220}