From d997e0f84317c38db27bc44f20c0595963a46205 Mon Sep 17 00:00:00 2001 From: hdbg Date: Fri, 3 Apr 2026 22:43:37 +0200 Subject: [PATCH 1/2] docs: add multi-operator governance section --- ARCHITECTURE.md | 104 +++++++++++++++++++++++++++++++++++++++------- IMPLEMENTATION.md | 46 ++++++++++++++++++++ 2 files changed, 136 insertions(+), 14 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 5da078f..977d92b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -42,7 +42,49 @@ There is no bootstrap mechanism for SDK clients. They must be explicitly approve --- -## 3. Server Identity +## 3. Multi-Operator Governance + +When more than one User Agent is registered, the vault is treated as having multiple operators. In that mode, sensitive actions are governed by voting rather than by a single operator decision. + +### 3.1 Voting Rules + +Voting is based on the total number of registered operators: + +- **1 operator:** no vote is needed; the single operator decides directly. +- **2 operators:** full consensus is required; both operators must approve. +- **3 or more operators:** quorum is `floor(N / 2) + 1`. + +Examples: + +- **3 operators:** 2 approvals required +- **4 operators:** 3 approvals required + +### 3.2 Actions Requiring a Vote + +In multi-operator mode, a successful vote is required for: + +- approving new SDK clients +- granting an SDK client visibility to a wallet +- approving a one-off transaction +- approving creation of a persistent grant +- approving server updates +- updating Shamir secret-sharing parameters + +### 3.3 Special Rule for Key Rotation + +Key rotation always requires full quorum, regardless of the normal voting threshold. + +This is stricter than ordinary governance actions because rotating the root key requires every operator to participate in coordinated share refresh/update steps. The root key itself is not redistributed directly, but each operator's share material must be changed consistently. + +### 3.4 Root Key Custody + +When the vault has multiple operators, the vault root key is protected using Shamir secret sharing. + +This ensures that root-key recovery and governance-sensitive changes are aligned with the multi-operator model rather than delegated to a single operator-held secret. + +--- + +## 4. Server Identity The server proves its identity using TLS with a self-signed certificate. The TLS private key is generated on first run and is long-term; no rotation mechanism exists yet due to the complexity of multi-peer coordination. @@ -55,9 +97,9 @@ Peers verify the server by its **public key fingerprint**: --- -## 4. Key Management +## 5. Key Management -### 4.1 Key Hierarchy +### 5.1 Key Hierarchy There are three layers of keys: @@ -72,19 +114,19 @@ This layered design enables: - **Password rotation** without re-encrypting every wallet key (only the root key is re-encrypted). - **Root key rotation** without requiring the user to change their password. -### 4.2 Encryption at Rest +### 5.2 Encryption at Rest The database stores everything in encrypted form using symmetric AEAD. The encryption scheme is versioned to support transparent migration — when the vault unseals, Arbiter automatically re-encrypts any entries that are behind the current scheme version. See [IMPLEMENTATION.md](IMPLEMENTATION.md) for the specific scheme and versioning mechanism. --- -## 5. Vault Lifecycle +## 6. Vault Lifecycle -### 5.1 Sealed State +### 6.1 Sealed State On boot, the root key is encrypted and the server cannot perform any signing operations. This state is called **Sealed**. -### 5.2 Unseal Flow +### 6.2 Unseal Flow To transition to the **Unsealed** state, a User Agent must provide the password: @@ -95,7 +137,7 @@ To transition to the **Unsealed** state, a User Agent must provide the password: - **Success:** The root key is decrypted and placed into a hardened memory cell. The server transitions to `Unsealed`. Any entries pending encryption scheme migration are re-encrypted. - **Failure:** The server returns an error indicating the password is incorrect. -### 5.3 Memory Protection +### 6.3 Memory Protection Once unsealed, the root key must be protected in memory against: @@ -107,9 +149,9 @@ See [IMPLEMENTATION.md](IMPLEMENTATION.md) for the current and planned memory pr --- -## 6. Permission Engine +## 7. Permission Engine -### 6.1 Fundamental Rules +### 7.1 Fundamental Rules - SDK clients have **no access by default**. - Access is granted **explicitly** by a User Agent. @@ -119,11 +161,45 @@ Each blockchain requires its own policy system due to differences in static tran Arbiter is also responsible for ensuring that **transaction nonces are never reused**. -### 6.2 EVM Policies +### 7.2 EVM Policies Every EVM grant is scoped to a specific **wallet** and **chain ID**. -#### 6.2.1 Transaction Sub-Grants +#### 7.2.0 Transaction Signing Sequence + +The high-level interaction order is: + +```mermaid +sequenceDiagram + autonumber + actor SDK as SDK Client + participant Server + participant UA as User Agent + + SDK->>Server: SignTransactionRequest + Server->>Server: Resolve wallet and wallet visibility + alt Visibility approval required + Server->>UA: Ask for wallet visibility approval + UA-->>Server: Vote result + end + Server->>Server: Evaluate transaction + Server->>Server: Load grant and limits context + alt Grant approval required + Server->>UA: Ask for execution / grant approval + UA-->>Server: Vote result + opt Create persistent grant + Server->>Server: Create and store grant + end + Server->>Server: Retry evaluation + end + critical Final authorization path + Server->>Server: Check limits and record execution + Server-->>Server: Signature or evaluation error + end + Server-->>SDK: Signature or error +``` + +#### 7.2.1 Transaction Sub-Grants Arbiter maintains an ever-expanding database of known contracts and their ABIs. Based on contract knowledge, transaction requests fall into three categories: @@ -147,9 +223,9 @@ Available restrictions: These transactions have no `calldata` and therefore cannot interact with contracts. They can be subject to the same volume and rate restrictions as above. -#### 6.2.2 Global Limits +#### 7.2.2 Global Limits In addition to sub-grant-specific restrictions, the following limits can be applied across all grant types: - **Gas limit** — Maximum gas per transaction. -- **Time-window restrictions** — e.g., signing allowed only 08:00–20:00 on Mondays and Thursdays. \ No newline at end of file +- **Time-window restrictions** — e.g., signing allowed only 08:00–20:00 on Mondays and Thursdays. diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md index 67a820b..b410614 100644 --- a/IMPLEMENTATION.md +++ b/IMPLEMENTATION.md @@ -128,6 +128,52 @@ The central abstraction is the `Policy` trait. Each implementation handles one s 4. **Evaluate** — `Policy::evaluate` checks the decoded meaning against the grant's policy-specific constraints and returns any violations. 5. **Record** — If `RunKind::Execution` and there are no violations, the engine writes to `evm_transaction_log` and calls `Policy::record_transaction` for any policy-specific logging (e.g., token transfer volume). +The detailed branch structure is shown below: + +```mermaid +flowchart TD + A[SDK Client sends sign transaction request] --> B[Server resolves wallet] + B --> C{Wallet exists?} + + C -- No --> Z1[Return wallet not found error] + C -- Yes --> D[Check SDK client wallet visibility] + + D --> E{Wallet visible to SDK client?} + E -- No --> F[Start wallet visibility voting flow] + F --> G{Vote approved?} + G -- No --> Z2[Return wallet access denied error] + G -- Yes --> H[Persist wallet visibility] + E -- Yes --> I[Classify transaction meaning] + H --> I + + I --> J{Meaning supported?} + J -- No --> Z3[Return unsupported transaction error] + J -- Yes --> K[Find matching grant] + + K --> L{Grant exists?} + L -- Yes --> M[Check grant limits] + L -- No --> N[Start execution or grant voting flow] + + N --> O{User-agent decision} + O -- Reject --> Z4[Return no matching grant error] + O -- Allow once --> M + O -- Create grant --> P[Create grant with user-selected limits] + P --> Q[Persist grant] + Q --> M + + M --> R{Limits exceeded?} + R -- Yes --> Z5[Return evaluation error] + R -- No --> S[Record transaction in logs] + S --> T[Produce signature] + T --> U[Return signature to SDK client] + + note1[Limit checks include volume, count, and gas constraints.] + note2[Grant lookup depends on classified meaning, such as ether transfer or token transfer.] + + K -. uses .-> note2 + M -. checks .-> note1 +``` + ### Policy Trait | Method | Purpose | -- 2.49.1 From f5eb51978d02e4f8045e08c0a868551499ec858d Mon Sep 17 00:00:00 2001 From: hdbg Date: Sat, 4 Apr 2026 10:32:44 +0200 Subject: [PATCH 2/2] docs: add recovery operators and multi-operator details --- ARCHITECTURE.md | 103 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 977d92b..80119fd 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -11,6 +11,7 @@ Arbiter distinguishes two kinds of peers: - **User Agent** — A client application used by the owner to manage the vault (create wallets, approve SDK clients, configure policies). - **SDK Client** — A consumer of signing capabilities, typically an automation tool. In the future, this could include a browser-based wallet. +- **Recovery Operator** — A dormant recovery participant with narrowly scoped authority used only for custody recovery and operator replacement. --- @@ -54,6 +55,8 @@ Voting is based on the total number of registered operators: - **2 operators:** full consensus is required; both operators must approve. - **3 or more operators:** quorum is `floor(N / 2) + 1`. +For a decision to count, the operator's approval or rejection must be signed by that operator's associated key. Unsigned votes, or votes that fail signature verification, are ignored. + Examples: - **3 operators:** 2 approvals required @@ -67,6 +70,7 @@ In multi-operator mode, a successful vote is required for: - granting an SDK client visibility to a wallet - approving a one-off transaction - approving creation of a persistent grant +- approving operator replacement - approving server updates - updating Shamir secret-sharing parameters @@ -80,7 +84,104 @@ This is stricter than ordinary governance actions because rotating the root key When the vault has multiple operators, the vault root key is protected using Shamir secret sharing. -This ensures that root-key recovery and governance-sensitive changes are aligned with the multi-operator model rather than delegated to a single operator-held secret. +The vault root key is encrypted in a way that requires reconstruction from user-held shares rather than from a single shared password. + +For ordinary operators, the Shamir threshold matches the ordinary governance quorum. For example: + +- **2 operators:** `2-of-2` +- **3 operators:** `2-of-3` +- **4 operators:** `3-of-4` + +In practice, the Shamir share set also includes Recovery Operator shares. This means the effective Shamir parameters are computed over the combined share pool while keeping the same threshold. For example: + +- **3 ordinary operators + 2 recovery shares:** `2-of-5` + +This ensures that the normal custody threshold follows the ordinary operator quorum, while still allowing dormant recovery shares to exist for break-glass recovery flows. + +### 3.5 Recovery Operators + +Recovery Operators are a separate peer type from ordinary vault operators. + +Their role is intentionally narrow. They can only: + +- participate in unsealing the vault +- vote for operator replacement + +Recovery Operators do not participate in routine governance such as approving SDK clients, granting wallet visibility, approving transactions, creating grants, approving server updates, or changing Shamir parameters. + +### 3.6 Sleeping and Waking Recovery Operators + +By default, Recovery Operators are **sleeping** and do not participate in any active flow. + +Any ordinary operator may request that Recovery Operators **wake up**. + +Any ordinary operator may also cancel a pending wake-up request. + +This creates a dispute window before recovery powers become active. The default wake-up delay is **14 days**. + +Recovery Operators are therefore part of the break-glass recovery path rather than the normal operating quorum. + +The high-level recovery flow is: + +```mermaid +sequenceDiagram + autonumber + actor Op as Ordinary Operator + participant Server + actor Other as Other Operator + actor Rec as Recovery Operator + + Op->>Server: Request recovery wake-up + Server-->>Op: Wake-up pending + Note over Server: Default dispute window: 14 days + + alt Wake-up cancelled during dispute window + Other->>Server: Cancel wake-up + Server-->>Op: Recovery cancelled + Server-->>Rec: Stay sleeping + else No cancellation for 14 days + Server-->>Rec: Wake up + Rec->>Server: Join recovery flow + critical Recovery authority + Rec->>Server: Participate in unseal + Rec->>Server: Vote on operator replacement + end + Server-->>Op: Recovery mode active + end +``` + +### 3.7 Committee Formation + +There are two ways to form a multi-operator committee: + +- convert an existing single-operator vault by adding new operators +- bootstrap an unbootstrapped vault directly into multi-operator mode + +In both cases, committee formation is a coordinated process. Arbiter does not allow multi-operator custody to emerge implicitly from unrelated registrations. + +### 3.8 Bootstrapping an Unbootstrapped Vault into Multi-Operator Mode + +When an unbootstrapped vault is initialized as a multi-operator vault, the setup proceeds as follows: + +1. An operator connects to the unbootstrapped vault using a User Agent and the bootstrap token. +2. During bootstrap setup, that operator declares: + - the total number of ordinary operators + - the total number of Recovery Operators +3. The vault enters **multi-bootstrap mode**. +4. While in multi-bootstrap mode: + - every ordinary operator must connect with a User Agent using the bootstrap token + - every Recovery Operator must also connect using the bootstrap token + - each participant is registered individually + - each participant's share is created and protected with that participant's credentials +5. The vault is considered fully bootstrapped only after all declared operator and recovery-share registrations have completed successfully. + +This means the operator and recovery set is fixed at bootstrap completion time, based on the counts declared when multi-bootstrap mode was entered. + +### 3.9 Special Bootstrap Constraint for Two-Operator Vaults + +If a vault is declared with exactly **2 ordinary operators**, Arbiter requires at least **1 Recovery Operator** to be configured during bootstrap. + +This prevents the worst-case custody failure in which a `2-of-2` operator set becomes permanently unrecoverable after loss of a single operator. --- -- 2.49.1