From 8c0855b16e240fcea0d61c6ddf5e05bc6cd41744 Mon Sep 17 00:00:00 2001 From: CleverWild Date: Mon, 24 Aug 2026 14:59:05 +0200 Subject: [PATCH] feat(proto)!: Shamir re-key, governance and bootstrapping vault state Split out of feat-shamir so the wire contract can be reviewed on its own. Rebased onto main: main's UNSEAL_RESULT_LOCKED_OUT keeps tag 4, so UNSEAL_RESULT_AWAITING_CONTRIBUTIONS moved to 5. BREAKING CHANGE: `shared.VaultState` renumbers SEALED/UNSEALED/ERROR to make room for VAULT_STATE_BOOSTRAPPING = 2. Old and new peers disagree on every vault state value. - operator: new `governance` branch in OperatorRequest/Response (field 5) - vault: new `rekey` branch in vault Request/Response (field 4) - bootstrap/unseal: passphrase and recovery-passphrase contribution payloads --- protobufs/operator.proto | 19 ++-- protobufs/operator/governance.proto | 136 +++++++++++++++++++++++ protobufs/operator/vault/bootstrap.proto | 22 +++- protobufs/operator/vault/rekey.proto | 30 +++++ protobufs/operator/vault/unseal.proto | 16 ++- protobufs/operator/vault/vault.proto | 13 ++- protobufs/shared/vault.proto | 7 +- 7 files changed, 224 insertions(+), 19 deletions(-) create mode 100644 protobufs/operator/governance.proto create mode 100644 protobufs/operator/vault/rekey.proto diff --git a/protobufs/operator.proto b/protobufs/operator.proto index f780ae3..46509d2 100644 --- a/protobufs/operator.proto +++ b/protobufs/operator.proto @@ -4,25 +4,28 @@ package arbiter.operator; import "operator/auth.proto"; import "operator/evm.proto"; +import "operator/governance.proto"; import "operator/sdk_client.proto"; import "operator/vault/vault.proto"; message OperatorRequest { int32 id = 16; oneof payload { - auth.Request auth = 1; - vault.Request vault = 2; - evm.Request evm = 3; - sdk_client.Request sdk_client = 4; + auth.Request auth = 1; + vault.Request vault = 2; + evm.Request evm = 3; + sdk_client.Request sdk_client = 4; + governance.Request governance = 5; } } message OperatorResponse { optional int32 id = 16; oneof payload { - auth.Response auth = 1; - vault.Response vault = 2; - evm.Response evm = 3; - sdk_client.Response sdk_client = 4; + auth.Response auth = 1; + vault.Response vault = 2; + evm.Response evm = 3; + sdk_client.Response sdk_client = 4; + governance.Response governance = 5; } } diff --git a/protobufs/operator/governance.proto b/protobufs/operator/governance.proto new file mode 100644 index 0000000..132a69f --- /dev/null +++ b/protobufs/operator/governance.proto @@ -0,0 +1,136 @@ +syntax = "proto3"; + +package arbiter.operator.governance; + +message Request { + oneof payload { + CreateProposalRequest create = 1; + CastVoteRequest vote = 2; + QueryPendingRequest query = 3; + } +} + +message CreateProposalRequest { + oneof kind { + ApproveSdkClientPayload approve_sdk_client = 1; + GrantWalletAccessPayload grant_wallet_access = 3; + ApproveServerUpdatePayload approve_server_update = 4; + ReplaceOperatorPayload replace_operator = 5; + UpdateShamirParametersPayload update_shamir_parameters = 6; + ApprovePersistentGrantPayload approve_persistent_grant = 7; + ApproveOneOffTransactionPayload approve_one_off_transaction = 8; + } + optional uint32 ttl_secs = 2; +} + +message ReplaceOperatorPayload { + int32 old_operator_id = 1; + bytes new_pubkey = 2; +} + +message UpdateShamirParametersPayload { + uint32 new_n = 1; +} + +message ApproveServerUpdatePayload {} + +message ApproveSdkClientPayload { + int32 client_id = 1; +} + +message GrantWalletAccessPayload { + int32 wallet_id = 1; + int32 client_id = 2; +} + +message CastVoteRequest { + int32 proposal_id = 1; + bool approve = 2; + bytes signature = 3; +} + +message QueryPendingRequest {} + +message Response { + oneof payload { + CreateProposalResponse created = 1; + VoteResponse voted = 2; + QueryPendingResponse pending = 3; + } +} + +message CreateProposalResponse { + int32 proposal_id = 1; +} + +message VoteResponse { + VoteOutcome outcome = 1; +} + +enum VoteOutcome { + VOTE_OUTCOME_UNSPECIFIED = 0; + VOTE_OUTCOME_PENDING = 1; + VOTE_OUTCOME_APPROVED = 2; + VOTE_OUTCOME_REJECTED = 3; +} + +message ProposalSummary { + int32 id = 1; + string kind = 2; + int32 initiator_id = 3; + int64 expires_at = 4; + int64 approve_count = 5; + int64 reject_count = 6; +} + +message QueryPendingResponse { + repeated ProposalSummary proposals = 1; +} + +message TransactionRateLimitProto { + uint32 count = 1; + int64 window_secs = 2; +} + +message VolumeLimitProto { + bytes max_volume = 1; + int64 window_secs = 2; +} + +message EtherTransferSpecProto { + repeated bytes targets = 1; + VolumeLimitProto limit = 2; +} + +message TokenTransferSpecProto { + bytes token_contract = 1; + optional bytes target = 2; + repeated VolumeLimitProto volume_limits = 3; +} + +message ApproveOneOffTransactionPayload { + int32 client_id = 1; + bytes wallet_address = 2; + uint64 chain_id = 3; + uint64 nonce = 4; + uint64 gas_limit = 5; + bytes max_fee_per_gas = 6; + bytes max_priority_fee_per_gas = 7; + bytes to = 8; + bytes value = 9; + bytes input = 10; +} + +message ApprovePersistentGrantPayload { + int32 wallet_access_id = 1; + uint64 chain_id = 2; + optional int64 valid_from_secs = 3; + optional int64 valid_until_secs = 4; + optional bytes max_gas_fee_per_gas = 5; + optional bytes max_priority_fee_per_gas = 6; + optional TransactionRateLimitProto rate_limit = 7; + oneof specific { + EtherTransferSpecProto ether_transfer = 8; + TokenTransferSpecProto token_transfer = 9; + } +} diff --git a/protobufs/operator/vault/bootstrap.proto b/protobufs/operator/vault/bootstrap.proto index 97e376d..fc0edf8 100644 --- a/protobufs/operator/vault/bootstrap.proto +++ b/protobufs/operator/vault/bootstrap.proto @@ -8,15 +8,35 @@ message BootstrapEncryptedKey { bytes associated_data = 3; } +message DeclareCommittee { + uint32 count = 1; + uint32 recovery_count = 2; +} + +message ContributePassphrase { + bytes passphrase = 1; +} + +message ContributeRecoveryPassphrase { + int32 recovery_operator_id = 1; + bytes passphrase = 2; +} + 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; + ContributeRecoveryPassphrase contribute_recovery_passphrase = 5; + } } message Response { diff --git a/protobufs/operator/vault/rekey.proto b/protobufs/operator/vault/rekey.proto new file mode 100644 index 0000000..5a1de2c --- /dev/null +++ b/protobufs/operator/vault/rekey.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +package arbiter.operator.vault.rekey; + +message ContributePassphrase { + bytes passphrase = 1; +} + +message ContributeRecoveryPassphrase { + int32 recovery_operator_id = 1; + bytes passphrase = 2; +} + +enum RekeyResult { + REKEY_RESULT_UNSPECIFIED = 0; + REKEY_RESULT_SUCCESS = 1; + REKEY_RESULT_AWAITING_CONTRIBUTIONS = 2; + REKEY_RESULT_NOT_IN_PROGRESS = 3; +} + +message Request { + oneof payload { + ContributePassphrase contribute_passphrase = 1; + ContributeRecoveryPassphrase contribute_recovery_passphrase = 2; + } +} + +message Response { + RekeyResult result = 1; +} diff --git a/protobufs/operator/vault/unseal.proto b/protobufs/operator/vault/unseal.proto index 6f59675..447a69c 100644 --- a/protobufs/operator/vault/unseal.proto +++ b/protobufs/operator/vault/unseal.proto @@ -15,18 +15,30 @@ message UnsealEncryptedKey { bytes associated_data = 3; } +message ContributePassphrase { + bytes passphrase = 1; +} + +message ContributeRecoveryPassphrase { + int32 recovery_operator_id = 1; + bytes passphrase = 2; +} + enum UnsealResult { UNSEAL_RESULT_UNSPECIFIED = 0; UNSEAL_RESULT_SUCCESS = 1; UNSEAL_RESULT_INVALID_KEY = 2; UNSEAL_RESULT_UNBOOTSTRAPPED = 3; UNSEAL_RESULT_LOCKED_OUT = 4; + UNSEAL_RESULT_AWAITING_CONTRIBUTIONS = 5; } message Request { oneof payload { - UnsealStart start = 1; - UnsealEncryptedKey encrypted_key = 2; + UnsealStart start = 1; + UnsealEncryptedKey encrypted_key = 2; + ContributePassphrase contribute_passphrase = 3; + ContributeRecoveryPassphrase contribute_recovery_passphrase = 4; } } diff --git a/protobufs/operator/vault/vault.proto b/protobufs/operator/vault/vault.proto index 72bd940..6ab1dda 100644 --- a/protobufs/operator/vault/vault.proto +++ b/protobufs/operator/vault/vault.proto @@ -5,20 +5,23 @@ package arbiter.operator.vault; import "google/protobuf/empty.proto"; import "shared/vault.proto"; import "operator/vault/bootstrap.proto"; +import "operator/vault/rekey.proto"; import "operator/vault/unseal.proto"; message Request { oneof payload { google.protobuf.Empty query_state = 1; - unseal.Request unseal = 2; - bootstrap.Request bootstrap = 3; + unseal.Request unseal = 2; + bootstrap.Request bootstrap = 3; + rekey.Request rekey = 4; } } message Response { oneof payload { - arbiter.shared.VaultState state = 1; - unseal.Response unseal = 2; - bootstrap.Response bootstrap = 3; + arbiter.shared.VaultState state = 1; + unseal.Response unseal = 2; + bootstrap.Response bootstrap = 3; + rekey.Response rekey = 4; } } diff --git a/protobufs/shared/vault.proto b/protobufs/shared/vault.proto index 795539f..9e5c2d3 100644 --- a/protobufs/shared/vault.proto +++ b/protobufs/shared/vault.proto @@ -5,7 +5,8 @@ package arbiter.shared; enum VaultState { VAULT_STATE_UNSPECIFIED = 0; VAULT_STATE_UNBOOTSTRAPPED = 1; - VAULT_STATE_SEALED = 2; - VAULT_STATE_UNSEALED = 3; - VAULT_STATE_ERROR = 4; + VAULT_STATE_BOOSTRAPPING = 2; + VAULT_STATE_SEALED = 3; + VAULT_STATE_UNSEALED = 4; + VAULT_STATE_ERROR = 5; }