From 0d364d195105ba8c62fe8fa1989bb4f40037ce15 Mon Sep 17 00:00:00 2001 From: CleverWild Date: Fri, 12 Jun 2026 19:43:17 +0200 Subject: [PATCH] feat(server::grpc): wire Shamir committee bootstrap and unseal proto messages Adds DeclareCommittee and ContributePassphrase variants to bootstrap.proto, ContributePassphrase to unseal.proto, and AwaitingContributions result codes to both. Implements corresponding inbound converters and outbound reply mappings. VaultGate handlers delegate to VaultCoordinator. --- protobufs/operator/vault/bootstrap.proto | 15 +++++- protobufs/operator/vault/unseal.proto | 6 +++ .../src/grpc/operator/vault_gate/inbound.rs | 40 ++++++++++++-- .../src/grpc/operator/vault_gate/outbound.rs | 35 +++++++++++- .../src/peers/operator/vault_gate/mod.rs | 53 ++++++++++++++++++- 5 files changed, 141 insertions(+), 8 deletions(-) diff --git a/protobufs/operator/vault/bootstrap.proto b/protobufs/operator/vault/bootstrap.proto index 97e376d..f1bee1d 100644 --- a/protobufs/operator/vault/bootstrap.proto +++ b/protobufs/operator/vault/bootstrap.proto @@ -8,15 +8,28 @@ message BootstrapEncryptedKey { bytes associated_data = 3; } +message DeclareCommittee { + uint32 count = 1; +} + +message ContributePassphrase { + bytes passphrase = 1; +} + enum BootstrapResult { BOOTSTRAP_RESULT_UNSPECIFIED = 0; BOOTSTRAP_RESULT_SUCCESS = 1; BOOTSTRAP_RESULT_ALREADY_BOOTSTRAPPED = 2; BOOTSTRAP_RESULT_INVALID_KEY = 3; + BOOTSTRAP_RESULT_AWAITING_CONTRIBUTIONS = 4; } message Request { - BootstrapEncryptedKey encrypted_key = 2; + oneof payload { + BootstrapEncryptedKey encrypted_key = 2; + DeclareCommittee declare_committee = 3; + ContributePassphrase contribute_passphrase = 4; + } } message Response { diff --git a/protobufs/operator/vault/unseal.proto b/protobufs/operator/vault/unseal.proto index 9378d5d..598c81c 100644 --- a/protobufs/operator/vault/unseal.proto +++ b/protobufs/operator/vault/unseal.proto @@ -15,17 +15,23 @@ message UnsealEncryptedKey { bytes associated_data = 3; } +message ContributePassphrase { + bytes passphrase = 1; +} + enum UnsealResult { UNSEAL_RESULT_UNSPECIFIED = 0; UNSEAL_RESULT_SUCCESS = 1; UNSEAL_RESULT_INVALID_KEY = 2; UNSEAL_RESULT_UNBOOTSTRAPPED = 3; + UNSEAL_RESULT_AWAITING_CONTRIBUTIONS = 4; } message Request { oneof payload { UnsealStart start = 1; UnsealEncryptedKey encrypted_key = 2; + ContributePassphrase contribute_passphrase = 3; } } diff --git a/server/crates/arbiter-server/src/grpc/operator/vault_gate/inbound.rs b/server/crates/arbiter-server/src/grpc/operator/vault_gate/inbound.rs index 6a08235..b0955eb 100644 --- a/server/crates/arbiter-server/src/grpc/operator/vault_gate/inbound.rs +++ b/server/crates/arbiter-server/src/grpc/operator/vault_gate/inbound.rs @@ -1,14 +1,16 @@ use crate::{ grpc::{Convert, TryConvert}, peers::operator::vault_gate::{ - self as vault_gate, HandleBootstrapEncryptedKey, HandleHandshake, HandleUnsealEncryptedKey, + self as vault_gate, HandleBootstrapEncryptedKey, HandleContributeBootstrapPassphrase, + HandleContributeUnsealPassphrase, HandleDeclareCommittee, HandleHandshake, + HandleUnsealEncryptedKey, }, }; use arbiter_proto::proto::operator::{ operator_request::Payload as OperatorRequestPayload, vault::{ self as proto_vault, - bootstrap::{self as proto_bootstrap}, + bootstrap::{self as proto_bootstrap, request::Payload as BootstrapRequestPayload}, request::Payload as VaultRequestPayload, unseal::{self as proto_unseal, request::Payload as UnsealRequestPayload}, }, @@ -73,6 +75,13 @@ impl TryConvert for UnsealRequestPayload { match self { Self::Start(start) => start.try_convert(), Self::EncryptedKey(key) => Ok(key.convert()), + Self::ContributePassphrase(cp) => Ok( + vault_gate::Inbound::HandleContributeUnsealPassphrase( + HandleContributeUnsealPassphrase { + passphrase: cp.passphrase, + }, + ), + ), } } } @@ -107,12 +116,35 @@ impl TryConvert for proto_bootstrap::Request { type Error = Status; fn try_convert(self) -> Result { - self.encrypted_key - .ok_or_else(|| Status::invalid_argument("Missing bootstrap encrypted key"))? + self.payload + .ok_or_else(|| Status::invalid_argument("Missing bootstrap payload"))? .try_convert() } } +impl TryConvert for BootstrapRequestPayload { + type Output = vault_gate::Inbound; + type Error = Status; + + fn try_convert(self) -> Result { + match self { + Self::EncryptedKey(key) => key.try_convert(), + Self::DeclareCommittee(dc) => Ok( + vault_gate::Inbound::HandleDeclareCommittee(HandleDeclareCommittee { + count: dc.count as usize, + }), + ), + Self::ContributePassphrase(cp) => Ok( + vault_gate::Inbound::HandleContributeBootstrapPassphrase( + HandleContributeBootstrapPassphrase { + passphrase: cp.passphrase, + }, + ), + ), + } + } +} + impl TryConvert for proto_bootstrap::BootstrapEncryptedKey { type Output = vault_gate::Inbound; type Error = Status; diff --git a/server/crates/arbiter-server/src/grpc/operator/vault_gate/outbound.rs b/server/crates/arbiter-server/src/grpc/operator/vault_gate/outbound.rs index 268b7d5..1e44ca4 100644 --- a/server/crates/arbiter-server/src/grpc/operator/vault_gate/outbound.rs +++ b/server/crates/arbiter-server/src/grpc/operator/vault_gate/outbound.rs @@ -46,7 +46,6 @@ impl Convert for VaultState { fn convert(self) -> OperatorResponsePayload { let proto_state = match self { Self::Unbootstrapped => ProtoVaultState::Unbootstrapped, - Self::Bootstrapping => ProtoVaultState::Boostrapping, Self::Sealed => ProtoVaultState::Sealed, Self::Unsealed => ProtoVaultState::Unsealed, }; @@ -111,6 +110,40 @@ impl TryConvert for vault_gate::Outbound { }; Ok(wrap_bootstrap_response(proto_result)) } + Self::HandleDeclareCommittee(result) => { + let proto_result = match result { + Ok(()) => ProtoBootstrapResult::Success, + Err(err) => { + warn!(?err, "declare committee failed"); + return Err(Status::internal("Failed to declare committee")); + } + }; + Ok(wrap_bootstrap_response(proto_result)) + } + Self::HandleContributeBootstrapPassphrase(result) => { + let proto_result = match result { + Ok(true) => ProtoBootstrapResult::Success, + Ok(false) => ProtoBootstrapResult::AwaitingContributions, + Err(err) => { + warn!(?err, "contribute bootstrap passphrase failed"); + return Err(Status::internal("Failed to contribute bootstrap passphrase")); + } + }; + Ok(wrap_bootstrap_response(proto_result)) + } + Self::HandleContributeUnsealPassphrase(result) => { + let proto_result = match result { + Ok(true) => ProtoUnsealResult::Success, + Ok(false) => ProtoUnsealResult::AwaitingContributions, + Err(err) => { + warn!(?err, "contribute unseal passphrase failed"); + return Err(Status::internal("Failed to contribute unseal passphrase")); + } + }; + Ok(wrap_unseal_response(UnsealResponsePayload::Result( + proto_result.into(), + ))) + } } } } diff --git a/server/crates/arbiter-server/src/peers/operator/vault_gate/mod.rs b/server/crates/arbiter-server/src/peers/operator/vault_gate/mod.rs index 6a8a265..b74ff3e 100644 --- a/server/crates/arbiter-server/src/peers/operator/vault_gate/mod.rs +++ b/server/crates/arbiter-server/src/peers/operator/vault_gate/mod.rs @@ -3,6 +3,7 @@ use crate::{ actors::{ GlobalActors, vault::{self, Bootstrap, GetState, TryUnseal, VaultState, events}, + vault_coordinator::{ContributeBootstrap, ContributeUnseal, StartBootstrap}, }, crypto::integrity::{self}, db::DatabasePool, @@ -17,6 +18,9 @@ use tokio::sync::oneshot; use tracing::{error, info}; use x25519_dalek::{EphemeralSecret, PublicKey, SharedSecret}; +pub use VaultGateMessage as Inbound; +pub use VaultGateMessageReply as Outbound; + pub mod state; #[derive(Debug, thiserror::Error)] @@ -118,8 +122,7 @@ impl VaultGate { } } } - -#[messages(messages = Inbound, replies = Outbound)] +#[messages(enum)] impl VaultGate { #[message] pub fn handle_handshake( @@ -234,6 +237,52 @@ impl VaultGate { Ok(answer) } + + #[message] + pub async fn handle_declare_committee(&mut self, count: usize) -> Result<(), Error> { + self.actors + .vault_coordinator + .ask(StartBootstrap { + operator_id: self.auth_creds.id, + declared_count: count, + }) + .await + .map_err(|_| Error::internal("VaultCoordinator unavailable")) + } + + #[message] + pub async fn handle_contribute_bootstrap_passphrase( + &mut self, + passphrase: Vec, + ) -> Result { + use arbiter_crypto::safecell::SafeCell; + let passphrase_cell = SafeCell::new(passphrase); + self.actors + .vault_coordinator + .ask(ContributeBootstrap { + operator_id: self.auth_creds.id, + passphrase: passphrase_cell, + }) + .await + .map_err(|_| Error::internal("VaultCoordinator unavailable")) + } + + #[message] + pub async fn handle_contribute_unseal_passphrase( + &mut self, + passphrase: Vec, + ) -> Result { + use arbiter_crypto::safecell::SafeCell; + let passphrase_cell = SafeCell::new(passphrase); + self.actors + .vault_coordinator + .ask(ContributeUnseal { + operator_id: self.auth_creds.id, + passphrase: passphrase_cell, + }) + .await + .map_err(|_| Error::internal("VaultCoordinator unavailable")) + } } impl Message for VaultGate {