1use std::sync::{Arc, Mutex, MutexGuard};
2
3use chio_core::StoreMutationFence;
4use chio_federation_authority::{FrostAuthenticatedDkgPackage, FrostCeremonySecret};
5use rusqlite::{Connection, Transaction, TransactionBehavior};
6use serde::{Deserialize, Serialize};
7
8use crate::admission_operation_store::verify_active_owner;
9use crate::encrypted_blob::TenantKey;
10use crate::serving_owner::SqliteServingOwner;
11
12mod ceremony;
13mod commit;
14mod coordinator;
15mod rotation;
16mod rotation_validation;
17mod schema;
18mod signer;
19
20use schema::FROST_STORE_SCHEMA;
21
22const FROST_STORE_SCHEMA_KEY: &str = "frost";
23pub(crate) const FROST_STORE_SUPPORTED_SCHEMA_VERSION: i32 = 0;
24const FROST_STORE_SCHEMA_ANCHORS: &[&str] = &[
25 "frost_ceremonies",
26 "chio_serving_owner",
27 "capability_grant_budgets",
28];
29
30#[derive(Debug, thiserror::Error)]
31pub enum FrostStoreError {
32 #[error("sqlite FROST store is fenced")]
33 Fenced,
34 #[error("sqlite FROST store conflict: {0}")]
35 Conflict(&'static str),
36 #[error("sqlite FROST store state is invalid: {0}")]
37 InvalidState(String),
38 #[error("sqlite FROST custody failed: {0}")]
39 Custody(&'static str),
40 #[error("sqlite FROST store unavailable: {0}")]
41 Unavailable(String),
42 #[error(transparent)]
43 Ceremony(#[from] chio_federation_authority::FrostCeremonyError),
44}
45
46pub struct FrostCustodyKey {
47 generation: String,
48 key: TenantKey,
49}
50
51impl std::fmt::Debug for FrostCustodyKey {
52 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 formatter
54 .debug_struct("FrostCustodyKey")
55 .field("generation", &self.generation)
56 .field("key", &"<redacted>")
57 .finish()
58 }
59}
60
61impl FrostCustodyKey {
62 pub fn new(
63 generation: impl Into<String>,
64 key_bytes: [u8; 32],
65 ) -> Result<Self, FrostStoreError> {
66 let generation = generation.into();
67 if generation.is_empty()
68 || generation.len() > 128
69 || generation.trim() != generation
70 || !generation.bytes().all(|byte| byte.is_ascii_graphic())
71 {
72 return Err(FrostStoreError::Custody(
73 "generation must be unpadded printable ASCII",
74 ));
75 }
76 Ok(Self {
77 generation,
78 key: TenantKey::from_bytes(key_bytes),
79 })
80 }
81
82 #[must_use]
83 pub fn generation(&self) -> &str {
84 &self.generation
85 }
86
87 pub(super) fn key(&self) -> &TenantKey {
88 &self.key
89 }
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
93#[serde(rename_all = "snake_case")]
94pub enum FrostCeremonyState {
95 Round1Ready,
96 Round2Ready,
97 Completed,
98}
99
100impl FrostCeremonyState {
101 pub(super) const fn as_str(self) -> &'static str {
102 match self {
103 Self::Round1Ready => "round1_ready",
104 Self::Round2Ready => "round2_ready",
105 Self::Completed => "completed",
106 }
107 }
108
109 pub(super) fn parse(value: &str) -> Result<Self, FrostStoreError> {
110 match value {
111 "round1_ready" => Ok(Self::Round1Ready),
112 "round2_ready" => Ok(Self::Round2Ready),
113 "completed" => Ok(Self::Completed),
114 _ => Err(FrostStoreError::InvalidState(
115 "unknown ceremony state".to_string(),
116 )),
117 }
118 }
119}
120
121#[derive(Debug, Clone, PartialEq, Eq)]
122pub struct FrostCeremonyRecord {
123 pub ceremony_id: String,
124 pub state: FrostCeremonyState,
125 pub state_version: u64,
126 pub participant_set_digest: String,
127 pub scope_id: String,
128 pub key_epoch: u64,
129 pub local_participant_id: String,
130 pub input_transcript_digest: Option<String>,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
134pub struct FrostCeremonyRound1Record {
135 pub ceremony_id: String,
136 pub state: FrostCeremonyState,
137 pub state_version: u64,
138 pub package: FrostAuthenticatedDkgPackage,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
142pub struct FrostCeremonyRound2Record {
143 pub ceremony_id: String,
144 pub state: FrostCeremonyState,
145 pub state_version: u64,
146 pub packages: Vec<FrostAuthenticatedDkgPackage>,
147 pub round1_transcript_digest: String,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq)]
151pub struct StoredFrostCeremonyCompletion {
152 pub ceremony_id: String,
153 pub state: FrostCeremonyState,
154 pub state_version: u64,
155 pub public_key_package: Vec<u8>,
156 pub group_public_key: String,
157 pub verification_shares: std::collections::BTreeMap<String, String>,
158 pub transcript_digest: String,
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
162#[serde(rename_all = "snake_case")]
163pub enum FrostRotationState {
164 Staged,
165 AnchorAdvanced,
166 Active,
167 Discarded,
168}
169
170impl FrostRotationState {
171 pub(super) const fn as_str(self) -> &'static str {
172 match self {
173 Self::Staged => "staged",
174 Self::AnchorAdvanced => "anchor_advanced",
175 Self::Active => "active",
176 Self::Discarded => "discarded",
177 }
178 }
179
180 pub(super) fn parse(value: &str) -> Result<Self, FrostStoreError> {
181 match value {
182 "staged" => Ok(Self::Staged),
183 "anchor_advanced" => Ok(Self::AnchorAdvanced),
184 "active" => Ok(Self::Active),
185 "discarded" => Ok(Self::Discarded),
186 _ => Err(FrostStoreError::InvalidState(
187 "unknown FROST rotation state".to_string(),
188 )),
189 }
190 }
191}
192
193#[derive(Debug, Clone, PartialEq, Eq)]
194pub struct FrostActiveRosterRecord {
195 pub scope_id: String,
196 pub key_epoch: u64,
197 pub roster_digest: String,
198 pub checkpoint_sequence: u64,
199 pub checkpoint_digest: String,
200 pub activation_fence: u64,
201 pub clock_high_water: u64,
202}
203
204#[derive(Debug, Clone, PartialEq, Eq)]
205pub struct FrostRotationRecord {
206 pub rotation_id: String,
207 pub scope_id: String,
208 pub state: FrostRotationState,
209 pub state_version: u64,
210 pub predecessor_checkpoint_digest: String,
211 pub target_roster_digest: String,
212 pub target_key_epoch: u64,
213 pub anchored_checkpoint_digest: Option<String>,
214}
215
216#[derive(Debug, Clone)]
217pub struct StagedFrostRotation {
218 rotation_id: String,
219 advance: chio_federation::frost::VerifiedFrostEpochAdvance,
220}
221
222pub struct FrostSignerSessionRequest<'a> {
223 pub body: &'a chio_federation::frost::FrostAuthorizationBodyV1,
224 pub active_roster: &'a chio_federation::frost::VerifiedActiveFrostRoster,
225 pub epoch_anchor: &'a dyn chio_federation::frost::FrostEpochAnchor,
226 pub slot_anchor: &'a dyn chio_federation::frost::FrostAuthorizationSlotAnchorWriter,
227 pub artifact_trust: &'a chio_federation::frost::FrostArtifactTrustStore,
228 pub ceremony_id: &'a str,
229 pub participant_id: &'a str,
230 pub coordinator_id: &'a str,
231}
232
233pub struct FrostCoordinatorSessionRequest<'a> {
234 pub body: &'a chio_federation::frost::FrostAuthorizationBodyV1,
235 pub active_roster: &'a chio_federation::frost::VerifiedActiveFrostRoster,
236 pub epoch_anchor: &'a dyn chio_federation::frost::FrostEpochAnchor,
237 pub slot_anchor: &'a dyn chio_federation::frost::FrostAuthorizationSlotAnchorWriter,
238 pub artifact_trust: &'a chio_federation::frost::FrostArtifactTrustStore,
239 pub coordinator_id: &'a str,
240}
241
242#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
243#[serde(rename_all = "camelCase", deny_unknown_fields)]
244pub struct FrostCoordinatorLease {
245 pub session_id: String,
246 pub authorization_slot_id: String,
247 pub coordinator_id: String,
248 pub worker_id: String,
249 pub lease_id: String,
250 pub owner_epoch: u64,
251 pub expires_at_unix_ms: u64,
252}
253
254#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
255#[serde(rename_all = "camelCase", deny_unknown_fields)]
256pub struct FrostCoordinatorCommitment {
257 pub participant_id: String,
258 pub signer_identifier: Vec<u8>,
259 pub commitment_bytes: Vec<u8>,
260}
261
262#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
263#[serde(rename_all = "camelCase", deny_unknown_fields)]
264pub struct FrostCoordinatorShare {
265 pub participant_id: String,
266 pub share_bytes: Vec<u8>,
267}
268
269#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
270#[serde(rename_all = "camelCase", deny_unknown_fields)]
271pub struct FrostCoordinatorSigningPackage {
272 pub session_id: String,
273 pub participant_ids: Vec<String>,
274 pub signing_package_bytes: Vec<u8>,
275}
276
277#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
278#[serde(rename_all = "camelCase", deny_unknown_fields)]
279pub struct FrostCoordinatorSessionRecord {
280 pub session_id: String,
281 pub authorization_slot_id: String,
282 pub state: FrostCoordinatorSessionState,
283 pub row_version: u64,
284 pub commitment_count: usize,
285 pub share_count: usize,
286}
287
288#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
289#[serde(rename_all = "camelCase", deny_unknown_fields)]
290pub struct FrostCoordinatorCancellation {
291 pub session: FrostCoordinatorSessionRecord,
292 pub participant_ids: Vec<String>,
293}
294
295#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
296#[serde(rename_all = "snake_case")]
297pub enum FrostCoordinatorSessionState {
298 CollectingCommitments,
299 PackageReady,
300 AuthorizationReady,
301 Completed,
302 Burned,
303}
304
305impl FrostCoordinatorSessionState {
306 #[must_use]
307 pub const fn as_str(self) -> &'static str {
308 match self {
309 Self::CollectingCommitments => "collecting_commitments",
310 Self::PackageReady => "package_ready",
311 Self::AuthorizationReady => "authorization_ready",
312 Self::Completed => "completed",
313 Self::Burned => "burned",
314 }
315 }
316
317 pub(super) fn parse(value: &str) -> Result<Self, FrostStoreError> {
318 match value {
319 "collecting_commitments" => Ok(Self::CollectingCommitments),
320 "package_ready" => Ok(Self::PackageReady),
321 "authorization_ready" => Ok(Self::AuthorizationReady),
322 "completed" => Ok(Self::Completed),
323 "burned" => Ok(Self::Burned),
324 _ => Err(FrostStoreError::InvalidState(
325 "unknown FROST coordinator state".to_string(),
326 )),
327 }
328 }
329}
330
331#[derive(Debug, Clone, PartialEq, Eq)]
332pub struct FrostSignerSessionRecord {
333 pub session_id: String,
334 pub participant_id: String,
335 pub authorization_slot_id: String,
336 pub state: FrostSignerSessionState,
337 pub state_version: u64,
338}
339
340#[derive(Debug, Clone, PartialEq, Eq)]
341pub struct FrostSignerCommitment {
342 pub session_id: String,
343 pub participant_id: String,
344 pub signer_identifier: Vec<u8>,
345 pub commitment_bytes: Vec<u8>,
346}
347
348#[derive(Debug, Clone, PartialEq, Eq)]
349pub struct FrostSignerShare {
350 pub session_id: String,
351 pub participant_id: String,
352 pub share_bytes: Vec<u8>,
353}
354
355#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
356#[serde(rename_all = "snake_case")]
357pub enum FrostSignerSessionState {
358 Prepared,
359 CommitmentPublished,
360 ShareReady,
361 Completed,
362 Burned,
363}
364
365impl FrostSignerSessionState {
366 #[must_use]
367 pub const fn as_str(self) -> &'static str {
368 match self {
369 Self::Prepared => "prepared",
370 Self::CommitmentPublished => "commitment_published",
371 Self::ShareReady => "share_ready",
372 Self::Completed => "completed",
373 Self::Burned => "burned",
374 }
375 }
376
377 pub(super) fn parse(value: &str) -> Result<Self, FrostStoreError> {
378 match value {
379 "prepared" => Ok(Self::Prepared),
380 "commitment_published" => Ok(Self::CommitmentPublished),
381 "share_ready" => Ok(Self::ShareReady),
382 "completed" => Ok(Self::Completed),
383 "burned" => Ok(Self::Burned),
384 _ => Err(FrostStoreError::InvalidState(
385 "unknown FROST signer state".to_string(),
386 )),
387 }
388 }
389}
390
391impl StagedFrostRotation {
392 #[must_use]
393 pub fn rotation_id(&self) -> &str {
394 &self.rotation_id
395 }
396
397 #[must_use]
398 pub fn advance(&self) -> &chio_federation::frost::VerifiedFrostEpochAdvance {
399 &self.advance
400 }
401}
402
403#[derive(Clone)]
404pub struct SqliteFrostStore {
405 connection: Arc<Mutex<Connection>>,
406 serving_owner: Arc<SqliteServingOwner>,
407}
408
409impl SqliteFrostStore {
410 pub(crate) fn open_alongside(
411 connection: Arc<Mutex<Connection>>,
412 serving_owner: Arc<SqliteServingOwner>,
413 ) -> Self {
414 Self {
415 connection,
416 serving_owner,
417 }
418 }
419
420 fn connection(&self) -> Result<MutexGuard<'_, Connection>, FrostStoreError> {
421 self.connection.lock().map_err(|_| {
422 FrostStoreError::Unavailable("sqlite FROST store lock is poisoned".to_string())
423 })
424 }
425
426 fn begin_read<'a>(
427 &self,
428 connection: &'a mut Connection,
429 fence: Option<&StoreMutationFence>,
430 ) -> Result<Transaction<'a>, FrostStoreError> {
431 let transaction = connection
432 .transaction_with_behavior(TransactionBehavior::Deferred)
433 .map_err(sqlite_error)?;
434 verify_active_owner(&transaction, &self.serving_owner, fence).map_err(owner_error)?;
435 self.serving_owner
436 .verify_authority_anchor(&transaction)
437 .map_err(|error| FrostStoreError::Unavailable(error.to_string()))?;
438 Ok(transaction)
439 }
440
441 fn begin_write<'a>(
442 &self,
443 connection: &'a mut Connection,
444 fence: &StoreMutationFence,
445 ) -> Result<Transaction<'a>, FrostStoreError> {
446 let transaction = connection
447 .transaction_with_behavior(TransactionBehavior::Immediate)
448 .map_err(sqlite_error)?;
449 verify_active_owner(&transaction, &self.serving_owner, Some(fence)).map_err(owner_error)?;
450 self.serving_owner
451 .verify_authority_anchor(&transaction)
452 .map_err(|error| FrostStoreError::Unavailable(error.to_string()))?;
453 Ok(transaction)
454 }
455
456 fn commit_write(&self, transaction: Transaction<'_>) -> Result<(), FrostStoreError> {
457 transaction.commit().map_err(|error| {
458 FrostStoreError::Unavailable(
459 self.serving_owner
460 .outcome_unknown(format!("sqlite FROST commit outcome is unknown: {error}"))
461 .to_string(),
462 )
463 })
464 }
465
466 fn sync_after_write(&self, connection: &Connection) -> Result<(), FrostStoreError> {
467 self.serving_owner
468 .sync_authority_anchor(connection)
469 .map_err(|error| FrostStoreError::Unavailable(error.to_string()))
470 }
471}
472
473pub(crate) fn initialize_frost_schema(connection: &mut Connection) -> Result<(), FrostStoreError> {
474 crate::check_schema_version(
475 connection,
476 FROST_STORE_SCHEMA_KEY,
477 FROST_STORE_SUPPORTED_SCHEMA_VERSION,
478 FROST_STORE_SCHEMA_ANCHORS,
479 )
480 .map_err(|error| FrostStoreError::InvalidState(error.to_string()))?;
481 let transaction = connection
482 .transaction_with_behavior(TransactionBehavior::Immediate)
483 .map_err(sqlite_error)?;
484 transaction
485 .execute_batch(FROST_STORE_SCHEMA)
486 .map_err(sqlite_error)?;
487 crate::stamp_schema_version(
488 &transaction,
489 FROST_STORE_SCHEMA_KEY,
490 FROST_STORE_SUPPORTED_SCHEMA_VERSION,
491 )
492 .map_err(|error| FrostStoreError::InvalidState(error.to_string()))?;
493 verify_frost_store_invariants(&transaction)?;
494 transaction.commit().map_err(sqlite_error)
495}
496
497pub(crate) fn verify_frost_store_invariants(
498 connection: &Connection,
499) -> Result<(), FrostStoreError> {
500 let expected = Connection::open_in_memory().map_err(sqlite_error)?;
501 expected
502 .execute_batch(FROST_STORE_SCHEMA)
503 .map_err(sqlite_error)?;
504 if frost_schema_catalog(connection)? != frost_schema_catalog(&expected)? {
505 return Err(FrostStoreError::InvalidState(
506 "FROST store schema differs from the canonical definition".to_string(),
507 ));
508 }
509 ceremony::verify_ceremony_invariants(connection)?;
510 rotation::verify_rotation_invariants(connection)?;
511 signer::verify_signer_invariants(connection)?;
512 coordinator::verify_coordinator_invariants(connection)
513}
514
515type FrostSchemaCatalogEntry = (String, String, String, Option<String>);
516
517fn frost_schema_catalog(
518 connection: &Connection,
519) -> Result<Vec<FrostSchemaCatalogEntry>, FrostStoreError> {
520 let mut statement = connection
521 .prepare(
522 r#"
523 SELECT type, name, tbl_name, sql FROM sqlite_schema
524 WHERE name GLOB 'frost_*' OR tbl_name GLOB 'frost_*'
525 ORDER BY type, name, tbl_name
526 "#,
527 )
528 .map_err(sqlite_error)?;
529 let catalog = statement
530 .query_map([], |row| {
531 Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?))
532 })
533 .map_err(sqlite_error)?
534 .collect::<Result<Vec<_>, _>>()
535 .map_err(sqlite_error)?;
536 Ok(catalog)
537}
538
539fn sqlite_error(error: rusqlite::Error) -> FrostStoreError {
540 FrostStoreError::Unavailable(error.to_string())
541}
542
543fn owner_error(
544 error: chio_kernel::admission_operation::AdmissionOperationStoreError,
545) -> FrostStoreError {
546 if matches!(
547 error,
548 chio_kernel::admission_operation::AdmissionOperationStoreError::Fenced
549 ) {
550 FrostStoreError::Fenced
551 } else {
552 FrostStoreError::Unavailable(error.to_string())
553 }
554}
555
556#[derive(Serialize, Deserialize)]
557#[serde(tag = "state", content = "output", rename_all = "snake_case")]
558enum StoredCeremonyOutput {
559 Round1(Box<FrostAuthenticatedDkgPackage>),
560 Round2(Vec<FrostAuthenticatedDkgPackage>),
561}
562
563pub(super) fn secret_kind_name(secret: &FrostCeremonySecret) -> &'static str {
564 match secret.kind() {
565 chio_federation_authority::FrostCeremonySecretKind::Round1 => "round1",
566 chio_federation_authority::FrostCeremonySecretKind::Round2 => "round2",
567 chio_federation_authority::FrostCeremonySecretKind::KeyPackage => "key_package",
568 }
569}