Compare commits
4 Commits
enforcing-
...
366a58f5eb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
366a58f5eb | ||
|
|
5141ac4f55 | ||
|
|
f6f4f81acb | ||
|
|
805d5691d6 |
@@ -24,4 +24,4 @@ steps:
|
|||||||
- mise install rust
|
- mise install rust
|
||||||
- mise install protoc
|
- mise install protoc
|
||||||
- mise install cargo:cargo-nextest
|
- mise install cargo:cargo-nextest
|
||||||
- mise exec cargo:cargo-nextest -- cargo nextest run --no-fail-fast --all-features
|
- mise exec cargo:cargo-nextest -- cargo nextest run --no-fail-fast
|
||||||
203
ARCHITECTURE.md
203
ARCHITECTURE.md
@@ -11,7 +11,6 @@ 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).
|
- **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.
|
- **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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -43,149 +42,7 @@ There is no bootstrap mechanism for SDK clients. They must be explicitly approve
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Multi-Operator Governance
|
## 3. Server Identity
|
||||||
|
|
||||||
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`.
|
|
||||||
|
|
||||||
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
|
|
||||||
- **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 operator replacement
|
|
||||||
- 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.
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 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.
|
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.
|
||||||
|
|
||||||
@@ -198,9 +55,9 @@ Peers verify the server by its **public key fingerprint**:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. Key Management
|
## 4. Key Management
|
||||||
|
|
||||||
### 5.1 Key Hierarchy
|
### 4.1 Key Hierarchy
|
||||||
|
|
||||||
There are three layers of keys:
|
There are three layers of keys:
|
||||||
|
|
||||||
@@ -215,19 +72,19 @@ This layered design enables:
|
|||||||
- **Password rotation** without re-encrypting every wallet key (only the root key is re-encrypted).
|
- **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.
|
- **Root key rotation** without requiring the user to change their password.
|
||||||
|
|
||||||
### 5.2 Encryption at Rest
|
### 4.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.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. Vault Lifecycle
|
## 5. Vault Lifecycle
|
||||||
|
|
||||||
### 6.1 Sealed State
|
### 5.1 Sealed State
|
||||||
|
|
||||||
On boot, the root key is encrypted and the server cannot perform any signing operations. This state is called **Sealed**.
|
On boot, the root key is encrypted and the server cannot perform any signing operations. This state is called **Sealed**.
|
||||||
|
|
||||||
### 6.2 Unseal Flow
|
### 5.2 Unseal Flow
|
||||||
|
|
||||||
To transition to the **Unsealed** state, a User Agent must provide the password:
|
To transition to the **Unsealed** state, a User Agent must provide the password:
|
||||||
|
|
||||||
@@ -238,7 +95,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.
|
- **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.
|
- **Failure:** The server returns an error indicating the password is incorrect.
|
||||||
|
|
||||||
### 6.3 Memory Protection
|
### 5.3 Memory Protection
|
||||||
|
|
||||||
Once unsealed, the root key must be protected in memory against:
|
Once unsealed, the root key must be protected in memory against:
|
||||||
|
|
||||||
@@ -250,9 +107,9 @@ See [IMPLEMENTATION.md](IMPLEMENTATION.md) for the current and planned memory pr
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Permission Engine
|
## 6. Permission Engine
|
||||||
|
|
||||||
### 7.1 Fundamental Rules
|
### 6.1 Fundamental Rules
|
||||||
|
|
||||||
- SDK clients have **no access by default**.
|
- SDK clients have **no access by default**.
|
||||||
- Access is granted **explicitly** by a User Agent.
|
- Access is granted **explicitly** by a User Agent.
|
||||||
@@ -262,45 +119,11 @@ 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**.
|
Arbiter is also responsible for ensuring that **transaction nonces are never reused**.
|
||||||
|
|
||||||
### 7.2 EVM Policies
|
### 6.2 EVM Policies
|
||||||
|
|
||||||
Every EVM grant is scoped to a specific **wallet** and **chain ID**.
|
Every EVM grant is scoped to a specific **wallet** and **chain ID**.
|
||||||
|
|
||||||
#### 7.2.0 Transaction Signing Sequence
|
#### 6.2.1 Transaction Sub-Grants
|
||||||
|
|
||||||
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:
|
Arbiter maintains an ever-expanding database of known contracts and their ABIs. Based on contract knowledge, transaction requests fall into three categories:
|
||||||
|
|
||||||
@@ -324,7 +147,7 @@ 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.
|
These transactions have no `calldata` and therefore cannot interact with contracts. They can be subject to the same volume and rate restrictions as above.
|
||||||
|
|
||||||
#### 7.2.2 Global Limits
|
#### 6.2.2 Global Limits
|
||||||
|
|
||||||
In addition to sub-grant-specific restrictions, the following limits can be applied across all grant types:
|
In addition to sub-grant-specific restrictions, the following limits can be applied across all grant types:
|
||||||
|
|
||||||
|
|||||||
@@ -67,18 +67,7 @@ The `program_client.nonce` column stores the **next usable nonce** — i.e. it i
|
|||||||
## Cryptography
|
## Cryptography
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
- **Client protocol:** ed25519
|
- **Signature scheme:** ed25519
|
||||||
|
|
||||||
### User-Agent Authentication
|
|
||||||
|
|
||||||
User-agent authentication supports multiple signature schemes because platform-provided "hardware-bound" keys do not expose a uniform algorithm across operating systems and hardware.
|
|
||||||
|
|
||||||
- **Supported schemes:** RSA, Ed25519, ECDSA (secp256k1)
|
|
||||||
- **Why:** the user agent authenticates with keys backed by platform facilities, and those facilities differ by platform
|
|
||||||
- **Apple Silicon Secure Enclave / Secure Element:** ECDSA-only in practice
|
|
||||||
- **Windows Hello / TPM 2.0:** currently RSA-backed in our integration
|
|
||||||
|
|
||||||
This is why the user-agent auth protocol carries an explicit `KeyType`, while the SDK client protocol remains fixed to ed25519.
|
|
||||||
|
|
||||||
### Encryption at Rest
|
### Encryption at Rest
|
||||||
- **Scheme:** Symmetric AEAD — currently **XChaCha20-Poly1305**
|
- **Scheme:** Symmetric AEAD — currently **XChaCha20-Poly1305**
|
||||||
@@ -128,52 +117,6 @@ 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.
|
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).
|
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
|
### Policy Trait
|
||||||
|
|
||||||
| Method | Purpose |
|
| Method | Purpose |
|
||||||
@@ -205,7 +148,7 @@ flowchart TD
|
|||||||
Every grant has two layers:
|
Every grant has two layers:
|
||||||
|
|
||||||
- **Shared (`evm_basic_grant`)** — wallet, chain, validity period, gas fee caps, transaction count rate limit. One row per grant regardless of type.
|
- **Shared (`evm_basic_grant`)** — wallet, chain, validity period, gas fee caps, transaction count rate limit. One row per grant regardless of type.
|
||||||
- **Specific** — policy-owned tables (`evm_ether_transfer_grant`, `evm_token_transfer_grant`) holding type-specific configuration.
|
- **Specific** — policy-owned tables (`evm_ether_transfer_grant`, `evm_token_transfer_grant`, etc.) holding type-specific configuration.
|
||||||
|
|
||||||
`find_all_grants` uses a `#[diesel::auto_type]` base join between the specific and shared tables, then batch-loads related rows (targets, volume limits) in two additional queries to avoid N+1.
|
`find_all_grants` uses a `#[diesel::auto_type]` base join between the specific and shared tables, then batch-loads related rows (targets, volume limits) in two additional queries to avoid N+1.
|
||||||
|
|
||||||
@@ -228,6 +171,7 @@ These are checked centrally in `check_shared_constraints` before policy evaluati
|
|||||||
- **Only EIP-1559 transactions are supported.** Legacy and EIP-2930 types are rejected outright.
|
- **Only EIP-1559 transactions are supported.** Legacy and EIP-2930 types are rejected outright.
|
||||||
- **No opaque-calldata (unknown contract) grant type.** The architecture describes a category for unrecognised contracts, but no policy implements it yet. Any transaction that is not a plain ETH transfer or a known ERC-20 transfer is unconditionally rejected.
|
- **No opaque-calldata (unknown contract) grant type.** The architecture describes a category for unrecognised contracts, but no policy implements it yet. Any transaction that is not a plain ETH transfer or a known ERC-20 transfer is unconditionally rejected.
|
||||||
- **Token registry is static.** Tokens are recognised only if they appear in the hard-coded `arbiter_tokens_registry` crate. There is no mechanism to register additional contracts at runtime.
|
- **Token registry is static.** Tokens are recognised only if they appear in the hard-coded `arbiter_tokens_registry` crate. There is no mechanism to register additional contracts at runtime.
|
||||||
|
- **Nonce management is not implemented.** The architecture lists nonce deduplication as a core responsibility, but no nonce tracking or enforcement exists yet.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -235,5 +179,5 @@ These are checked centrally in `check_shared_constraints` before policy evaluati
|
|||||||
|
|
||||||
The unsealed root key must be held in a hardened memory cell resistant to dumps, page swaps, and hibernation.
|
The unsealed root key must be held in a hardened memory cell resistant to dumps, page swaps, and hibernation.
|
||||||
|
|
||||||
- **Current:** A dedicated memory-protection abstraction is in place, with `memsafe` used behind that abstraction today
|
- **Current:** Using the `memsafe` crate as an interim solution
|
||||||
- **Planned:** Additional backends can be introduced behind the same abstraction, including a custom implementation based on `mlock` (Unix) and `VirtualProtect` (Windows)
|
- **Planned:** Custom implementation based on `mlock` (Unix) and `VirtualProtect` (Windows)
|
||||||
|
|||||||
73
mise.lock
73
mise.lock
@@ -8,18 +8,10 @@ backend = "aqua:ast-grep/ast-grep"
|
|||||||
checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836"
|
checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836"
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip"
|
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip"
|
||||||
|
|
||||||
[tools.ast-grep."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:5c830eae8456569e2f7212434ed9c238f58dca412d76045418ed6d394a755836"
|
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-unknown-linux-gnu.zip"
|
|
||||||
|
|
||||||
[tools.ast-grep."platforms.linux-x64"]
|
[tools.ast-grep."platforms.linux-x64"]
|
||||||
checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651"
|
checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651"
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip"
|
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip"
|
||||||
|
|
||||||
[tools.ast-grep."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:e825a05603f0bcc4cd9076c4cc8c9abd6d008b7cd07d9aa3cc323ba4b8606651"
|
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-x86_64-unknown-linux-gnu.zip"
|
|
||||||
|
|
||||||
[tools.ast-grep."platforms.macos-arm64"]
|
[tools.ast-grep."platforms.macos-arm64"]
|
||||||
checksum = "sha256:fc300d5293b1c770a5aece03a8a193b92e71e87cec726c28096990691a582620"
|
checksum = "sha256:fc300d5293b1c770a5aece03a8a193b92e71e87cec726c28096990691a582620"
|
||||||
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-apple-darwin.zip"
|
url = "https://github.com/ast-grep/ast-grep/releases/download/0.42.0/app-aarch64-apple-darwin.zip"
|
||||||
@@ -40,6 +32,10 @@ backend = "cargo:cargo-audit"
|
|||||||
version = "0.13.9"
|
version = "0.13.9"
|
||||||
backend = "cargo:cargo-edit"
|
backend = "cargo:cargo-edit"
|
||||||
|
|
||||||
|
[[tools."cargo:cargo-features"]]
|
||||||
|
version = "1.0.0"
|
||||||
|
backend = "cargo:cargo-features"
|
||||||
|
|
||||||
[[tools."cargo:cargo-features-manager"]]
|
[[tools."cargo:cargo-features-manager"]]
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
backend = "cargo:cargo-features-manager"
|
backend = "cargo:cargo-features-manager"
|
||||||
@@ -48,22 +44,26 @@ backend = "cargo:cargo-features-manager"
|
|||||||
version = "1.46.3"
|
version = "1.46.3"
|
||||||
backend = "cargo:cargo-insta"
|
backend = "cargo:cargo-insta"
|
||||||
|
|
||||||
[[tools."cargo:cargo-mutants"]]
|
|
||||||
version = "27.0.0"
|
|
||||||
backend = "cargo:cargo-mutants"
|
|
||||||
|
|
||||||
[[tools."cargo:cargo-nextest"]]
|
[[tools."cargo:cargo-nextest"]]
|
||||||
version = "0.9.126"
|
version = "0.9.126"
|
||||||
backend = "cargo:cargo-nextest"
|
backend = "cargo:cargo-nextest"
|
||||||
|
|
||||||
[[tools."cargo:cargo-shear"]]
|
[[tools."cargo:cargo-shear"]]
|
||||||
version = "1.11.2"
|
version = "1.9.1"
|
||||||
backend = "cargo:cargo-shear"
|
backend = "cargo:cargo-shear"
|
||||||
|
|
||||||
[[tools."cargo:cargo-vet"]]
|
[[tools."cargo:cargo-vet"]]
|
||||||
version = "0.10.2"
|
version = "0.10.2"
|
||||||
backend = "cargo:cargo-vet"
|
backend = "cargo:cargo-vet"
|
||||||
|
|
||||||
|
[[tools."cargo:diesel-cli"]]
|
||||||
|
version = "2.3.6"
|
||||||
|
backend = "cargo:diesel-cli"
|
||||||
|
|
||||||
|
[tools."cargo:diesel-cli".options]
|
||||||
|
default-features = "false"
|
||||||
|
features = "sqlite,sqlite-bundled"
|
||||||
|
|
||||||
[[tools."cargo:diesel_cli"]]
|
[[tools."cargo:diesel_cli"]]
|
||||||
version = "2.3.6"
|
version = "2.3.6"
|
||||||
backend = "cargo:diesel_cli"
|
backend = "cargo:diesel_cli"
|
||||||
@@ -72,6 +72,10 @@ backend = "cargo:diesel_cli"
|
|||||||
default-features = "false"
|
default-features = "false"
|
||||||
features = "sqlite,sqlite-bundled"
|
features = "sqlite,sqlite-bundled"
|
||||||
|
|
||||||
|
[[tools."cargo:rinf_cli"]]
|
||||||
|
version = "8.9.1"
|
||||||
|
backend = "cargo:rinf_cli"
|
||||||
|
|
||||||
[[tools.flutter]]
|
[[tools.flutter]]
|
||||||
version = "3.38.9-stable"
|
version = "3.38.9-stable"
|
||||||
backend = "asdf:flutter"
|
backend = "asdf:flutter"
|
||||||
@@ -84,18 +88,10 @@ backend = "aqua:protocolbuffers/protobuf/protoc"
|
|||||||
checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79"
|
checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79"
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip"
|
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip"
|
||||||
|
|
||||||
[tools.protoc."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:2594ff4fcae8cb57310d394d0961b236190ad9c5efbfdf1f597ea471d424fe79"
|
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-aarch_64.zip"
|
|
||||||
|
|
||||||
[tools.protoc."platforms.linux-x64"]
|
[tools.protoc."platforms.linux-x64"]
|
||||||
checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323"
|
checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323"
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip"
|
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip"
|
||||||
|
|
||||||
[tools.protoc."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:48785a926e73ffa3f68e2f22b14e7b849620c7a1d36809ac9249a5495e280323"
|
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-linux-x86_64.zip"
|
|
||||||
|
|
||||||
[tools.protoc."platforms.macos-arm64"]
|
[tools.protoc."platforms.macos-arm64"]
|
||||||
checksum = "sha256:b9576b5fa1a1ef3fe13a8c91d9d8204b46545759bea5ae155cd6ba2ea4cdaeed"
|
checksum = "sha256:b9576b5fa1a1ef3fe13a8c91d9d8204b46545759bea5ae155cd6ba2ea4cdaeed"
|
||||||
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-aarch_64.zip"
|
url = "https://github.com/protocolbuffers/protobuf/releases/download/v29.6/protoc-29.6-osx-aarch_64.zip"
|
||||||
@@ -113,39 +109,24 @@ version = "3.14.3"
|
|||||||
backend = "core:python"
|
backend = "core:python"
|
||||||
|
|
||||||
[tools.python."platforms.linux-arm64"]
|
[tools.python."platforms.linux-arm64"]
|
||||||
checksum = "sha256:53700338695e402a1a1fe22be4a41fbdacc70e22bb308a48eca8ed67cb7992be"
|
checksum = "sha256:be0f4dc2932f762292b27d46ea7d3e8e66ddf3969a5eb0254a229015ed402625"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.linux-arm64-musl"]
|
|
||||||
checksum = "sha256:53700338695e402a1a1fe22be4a41fbdacc70e22bb308a48eca8ed67cb7992be"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.linux-x64"]
|
[tools.python."platforms.linux-x64"]
|
||||||
checksum = "sha256:d7a9f970914bb4c88756fe3bdcc186d4feb90e9500e54f1db47dae4dc9687e39"
|
checksum = "sha256:0a73413f89efd417871876c9accaab28a9d1e3cd6358fbfff171a38ec99302f0"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.linux-x64-musl"]
|
|
||||||
checksum = "sha256:d7a9f970914bb4c88756fe3bdcc186d4feb90e9500e54f1db47dae4dc9687e39"
|
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-unknown-linux-gnu-install_only_stripped.tar.gz"
|
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.macos-arm64"]
|
[tools.python."platforms.macos-arm64"]
|
||||||
checksum = "sha256:c43aecde4a663aebff99b9b83da0efec506479f1c3f98331442f33d2c43501f9"
|
checksum = "sha256:4703cdf18b26798fde7b49b6b66149674c25f97127be6a10dbcf29309bdcdcdb"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-aarch64-apple-darwin-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-aarch64-apple-darwin-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.macos-x64"]
|
[tools.python."platforms.macos-x64"]
|
||||||
checksum = "sha256:9ab41dbc2f100a2a45d1833b9c11165f51051c558b5213eda9a9731d5948a0c0"
|
checksum = "sha256:76f1cc26e3d262eae8ca546a93e8bded10cf0323613f7e246fea2e10a8115eb7"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-apple-darwin-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-apple-darwin-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[tools.python."platforms.windows-x64"]
|
[tools.python."platforms.windows-x64"]
|
||||||
checksum = "sha256:bbe19034b35b0267176a7442575ae7dc6343480fd4d35598cb7700173d431e09"
|
checksum = "sha256:950c5f21a015c1bdd1337f233456df2470fab71e4d794407d27a84cb8b9909a0"
|
||||||
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260324/cpython-3.14.3+20260324-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
|
url = "https://github.com/astral-sh/python-build-standalone/releases/download/20260303/cpython-3.14.3+20260303-x86_64-pc-windows-msvc-install_only_stripped.tar.gz"
|
||||||
provenance = "github-attestations"
|
|
||||||
|
|
||||||
[[tools.rust]]
|
[[tools.rust]]
|
||||||
version = "1.93.0"
|
version = "1.93.0"
|
||||||
|
|||||||
@@ -12,12 +12,11 @@ protoc = "29.6"
|
|||||||
python = "3.14.3"
|
python = "3.14.3"
|
||||||
ast-grep = "0.42.0"
|
ast-grep = "0.42.0"
|
||||||
"cargo:cargo-edit" = "0.13.9"
|
"cargo:cargo-edit" = "0.13.9"
|
||||||
"cargo:cargo-mutants" = "27.0.0"
|
|
||||||
|
|
||||||
[tasks.codegen]
|
[tasks.codegen]
|
||||||
sources = ['protobufs/*.proto', 'protobufs/**/*.proto']
|
sources = ['protobufs/*.proto']
|
||||||
outputs = ['useragent/lib/proto/**']
|
outputs = ['useragent/lib/proto/*']
|
||||||
run = '''
|
run = '''
|
||||||
dart pub global activate protoc_plugin && \
|
dart pub global activate protoc_plugin && \
|
||||||
protoc --dart_out=grpc:useragent/lib/proto --proto_path=protobufs/ $(find protobufs -name '*.proto' | sort)
|
protoc --dart_out=grpc:useragent/lib/proto --proto_path=protobufs/ protobufs/*.proto
|
||||||
'''
|
'''
|
||||||
|
|||||||
@@ -36,10 +36,6 @@ message GasLimitExceededViolation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message EvalViolation {
|
message EvalViolation {
|
||||||
message ChainIdMismatch {
|
|
||||||
uint64 expected = 1;
|
|
||||||
uint64 actual = 2;
|
|
||||||
}
|
|
||||||
oneof kind {
|
oneof kind {
|
||||||
bytes invalid_target = 1; // 20-byte Ethereum address
|
bytes invalid_target = 1; // 20-byte Ethereum address
|
||||||
GasLimitExceededViolation gas_limit_exceeded = 2;
|
GasLimitExceededViolation gas_limit_exceeded = 2;
|
||||||
@@ -47,8 +43,6 @@ message EvalViolation {
|
|||||||
google.protobuf.Empty volumetric_limit_exceeded = 4;
|
google.protobuf.Empty volumetric_limit_exceeded = 4;
|
||||||
google.protobuf.Empty invalid_time = 5;
|
google.protobuf.Empty invalid_time = 5;
|
||||||
google.protobuf.Empty invalid_transaction_type = 6;
|
google.protobuf.Empty invalid_transaction_type = 6;
|
||||||
|
|
||||||
ChainIdMismatch chain_id_mismatch = 7;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,6 @@ package arbiter.user_agent.evm;
|
|||||||
import "evm.proto";
|
import "evm.proto";
|
||||||
import "google/protobuf/empty.proto";
|
import "google/protobuf/empty.proto";
|
||||||
|
|
||||||
message SignTransactionRequest {
|
|
||||||
int32 client_id = 1;
|
|
||||||
arbiter.evm.EvmSignTransactionRequest request = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Request {
|
message Request {
|
||||||
oneof payload {
|
oneof payload {
|
||||||
google.protobuf.Empty wallet_create = 1;
|
google.protobuf.Empty wallet_create = 1;
|
||||||
@@ -17,7 +12,6 @@ message Request {
|
|||||||
arbiter.evm.EvmGrantCreateRequest grant_create = 3;
|
arbiter.evm.EvmGrantCreateRequest grant_create = 3;
|
||||||
arbiter.evm.EvmGrantDeleteRequest grant_delete = 4;
|
arbiter.evm.EvmGrantDeleteRequest grant_delete = 4;
|
||||||
arbiter.evm.EvmGrantListRequest grant_list = 5;
|
arbiter.evm.EvmGrantListRequest grant_list = 5;
|
||||||
SignTransactionRequest sign_transaction = 6;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +22,5 @@ message Response {
|
|||||||
arbiter.evm.EvmGrantCreateResponse grant_create = 3;
|
arbiter.evm.EvmGrantCreateResponse grant_create = 3;
|
||||||
arbiter.evm.EvmGrantDeleteResponse grant_delete = 4;
|
arbiter.evm.EvmGrantDeleteResponse grant_delete = 4;
|
||||||
arbiter.evm.EvmGrantListResponse grant_list = 5;
|
arbiter.evm.EvmGrantListResponse grant_list = 5;
|
||||||
arbiter.evm.EvmSignTransactionResponse sign_transaction = 6;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
test_tool = "nextest"
|
|
||||||
2
server/.gitignore
vendored
2
server/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
mutants.out/
|
|
||||||
mutants.out.old/
|
|
||||||
33
server/Cargo.lock
generated
33
server/Cargo.lock
generated
@@ -724,7 +724,6 @@ name = "arbiter-server"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alloy",
|
"alloy",
|
||||||
"anyhow",
|
|
||||||
"arbiter-proto",
|
"arbiter-proto",
|
||||||
"arbiter-tokens-registry",
|
"arbiter-tokens-registry",
|
||||||
"argon2",
|
"argon2",
|
||||||
@@ -738,29 +737,23 @@ dependencies = [
|
|||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"fatality",
|
"fatality",
|
||||||
"futures",
|
"futures",
|
||||||
"hmac",
|
|
||||||
"insta",
|
"insta",
|
||||||
"k256",
|
"k256",
|
||||||
"kameo",
|
"kameo",
|
||||||
"memsafe",
|
"memsafe",
|
||||||
"mutants",
|
"miette",
|
||||||
"pem",
|
"pem",
|
||||||
"proptest",
|
|
||||||
"prost",
|
|
||||||
"prost-types",
|
"prost-types",
|
||||||
"rand 0.10.0",
|
"rand 0.10.0",
|
||||||
"rcgen",
|
"rcgen",
|
||||||
"restructed",
|
"restructed",
|
||||||
"rsa",
|
"rsa",
|
||||||
"rstest",
|
|
||||||
"rustls",
|
"rustls",
|
||||||
"secrecy",
|
"secrecy",
|
||||||
"serde_with",
|
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
"smlang",
|
"smlang",
|
||||||
"spki",
|
"spki",
|
||||||
"strum 0.28.0",
|
"strum 0.28.0",
|
||||||
"subtle",
|
|
||||||
"test-log",
|
"test-log",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -1961,7 +1954,6 @@ version = "3.0.0-rc.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890"
|
checksum = "c6e914c7c52decb085cea910552e24c63ac019e3ab8bf001ff736da9a9d9d890"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
|
||||||
"signature 3.0.0-rc.10",
|
"signature 3.0.0-rc.10",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1974,7 +1966,6 @@ dependencies = [
|
|||||||
"curve25519-dalek 5.0.0-pre.6",
|
"curve25519-dalek 5.0.0-pre.6",
|
||||||
"ed25519",
|
"ed25519",
|
||||||
"rand_core 0.10.0",
|
"rand_core 0.10.0",
|
||||||
"serde",
|
|
||||||
"sha2 0.11.0-rc.5",
|
"sha2 0.11.0-rc.5",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
@@ -2060,7 +2051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3179,12 +3170,6 @@ version = "0.10.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
|
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "mutants"
|
|
||||||
version = "0.0.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "add0ac067452ff1aca8c5002111bd6b1c895baee6e45fcbc44e0193aea17be56"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nom"
|
name = "nom"
|
||||||
version = "7.1.3"
|
version = "7.1.3"
|
||||||
@@ -3201,7 +3186,7 @@ version = "0.50.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3226,7 +3211,6 @@ dependencies = [
|
|||||||
"num-iter",
|
"num-iter",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
"serde",
|
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
@@ -3649,9 +3633,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proptest"
|
name = "proptest"
|
||||||
version = "1.11.0"
|
version = "1.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
|
checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bit-set",
|
"bit-set",
|
||||||
"bit-vec",
|
"bit-vec",
|
||||||
@@ -4166,7 +4150,6 @@ dependencies = [
|
|||||||
"pkcs1",
|
"pkcs1",
|
||||||
"pkcs8",
|
"pkcs8",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"serde",
|
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
"signature 2.2.0",
|
"signature 2.2.0",
|
||||||
"spki",
|
"spki",
|
||||||
@@ -4302,7 +4285,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4718,7 +4701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -4912,7 +4895,7 @@ dependencies = [
|
|||||||
"getrandom 0.4.2",
|
"getrandom 0.4.2",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ chrono = { version = "0.4.44", features = ["serde"] }
|
|||||||
rand = "0.10.0"
|
rand = "0.10.0"
|
||||||
rustls = { version = "0.23.37", features = ["aws-lc-rs"] }
|
rustls = { version = "0.23.37", features = ["aws-lc-rs"] }
|
||||||
smlang = "0.8.0"
|
smlang = "0.8.0"
|
||||||
|
miette = { version = "7.6.0", features = ["fancy", "serde"] }
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
async-trait = "0.1.89"
|
async-trait = "0.1.89"
|
||||||
futures = "0.3.32"
|
futures = "0.3.32"
|
||||||
@@ -42,6 +43,3 @@ k256 = { version = "0.13.4", features = ["ecdsa", "pkcs8"] }
|
|||||||
rsa = { version = "0.9", features = ["sha2"] }
|
rsa = { version = "0.9", features = ["sha2"] }
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
spki = "0.7"
|
spki = "0.7"
|
||||||
prost = "0.14.3"
|
|
||||||
miette = { version = "7.6.0", features = ["fancy", "serde"] }
|
|
||||||
mutants = "0.0.4"
|
|
||||||
|
|||||||
@@ -6,6 +6,4 @@ disallowed-methods = [
|
|||||||
{ path = "rsa::RsaPrivateKey::decrypt_blinded", reason = "RSA decryption is forbidden (RUSTSEC-2023-0071 Marvin Attack). Only PSS signing/verification is permitted." },
|
{ path = "rsa::RsaPrivateKey::decrypt_blinded", reason = "RSA decryption is forbidden (RUSTSEC-2023-0071 Marvin Attack). Only PSS signing/verification is permitted." },
|
||||||
{ path = "rsa::traits::Decryptor::decrypt", reason = "RSA decryption is forbidden (RUSTSEC-2023-0071 Marvin Attack). This blocks decrypt() on rsa::{pkcs1v15,oaep}::DecryptingKey." },
|
{ path = "rsa::traits::Decryptor::decrypt", reason = "RSA decryption is forbidden (RUSTSEC-2023-0071 Marvin Attack). This blocks decrypt() on rsa::{pkcs1v15,oaep}::DecryptingKey." },
|
||||||
{ path = "rsa::traits::RandomizedDecryptor::decrypt_with_rng", reason = "RSA decryption is forbidden (RUSTSEC-2023-0071 Marvin Attack). This blocks decrypt_with_rng() on rsa::{pkcs1v15,oaep}::DecryptingKey." },
|
{ path = "rsa::traits::RandomizedDecryptor::decrypt_with_rng", reason = "RSA decryption is forbidden (RUSTSEC-2023-0071 Marvin Attack). This blocks decrypt_with_rng() on rsa::{pkcs1v15,oaep}::DecryptingKey." },
|
||||||
|
|
||||||
{ path = "arbiter_server::crypto::integrity::v1::lookup_verified_allow_unavailable", reason = "This function allows integrity checks to be bypassed when vault key material is unavailable, which can lead to silent security failures if used incorrectly. It should only be used in specific contexts where this behavior is acceptable, and its use should be carefully audited." },
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -122,7 +122,9 @@ async fn receive_auth_confirmation(
|
|||||||
.await
|
.await
|
||||||
.map_err(|_| AuthError::UnexpectedAuthResponse)?;
|
.map_err(|_| AuthError::UnexpectedAuthResponse)?;
|
||||||
|
|
||||||
let payload = response.payload.ok_or(AuthError::UnexpectedAuthResponse)?;
|
let payload = response
|
||||||
|
.payload
|
||||||
|
.ok_or(AuthError::UnexpectedAuthResponse)?;
|
||||||
match payload {
|
match payload {
|
||||||
ClientResponsePayload::Auth(response) => match response.payload {
|
ClientResponsePayload::Auth(response) => match response.payload {
|
||||||
Some(AuthResponsePayload::Result(result))
|
Some(AuthResponsePayload::Result(result))
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
use arbiter_client::ArbiterClient;
|
use arbiter_client::ArbiterClient;
|
||||||
use arbiter_proto::{ClientMetadata, url::ArbiterUrl};
|
use arbiter_proto::{ClientMetadata, url::ArbiterUrl};
|
||||||
|
use tonic::ConnectError;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
@@ -21,6 +23,8 @@ async fn main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let url = match ArbiterUrl::try_from(input) {
|
let url = match ArbiterUrl::try_from(input) {
|
||||||
Ok(url) => url,
|
Ok(url) => url,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
use arbiter_proto::{
|
use arbiter_proto::{ClientMetadata, proto::arbiter_service_client::ArbiterServiceClient, url::ArbiterUrl};
|
||||||
ClientMetadata, proto::arbiter_service_client::ArbiterServiceClient, url::ArbiterUrl,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::{Mutex, mpsc};
|
use tokio::sync::{Mutex, mpsc};
|
||||||
use tokio_stream::wrappers::ReceiverStream;
|
use tokio_stream::wrappers::ReceiverStream;
|
||||||
use tonic::transport::ClientTlsConfig;
|
use tonic::transport::ClientTlsConfig;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
StorageError,
|
StorageError, auth::{AuthError, authenticate}, storage::{FileSigningKeyStorage, SigningKeyStorage}, transport::{BUFFER_LENGTH, ClientTransport}
|
||||||
auth::{AuthError, authenticate},
|
|
||||||
storage::{FileSigningKeyStorage, SigningKeyStorage},
|
|
||||||
transport::{BUFFER_LENGTH, ClientTransport},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "evm")]
|
#[cfg(feature = "evm")]
|
||||||
@@ -35,6 +30,7 @@ pub enum Error {
|
|||||||
|
|
||||||
#[error("Storage error")]
|
#[error("Storage error")]
|
||||||
Storage(#[from] StorageError),
|
Storage(#[from] StorageError),
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ArbiterClient {
|
pub struct ArbiterClient {
|
||||||
@@ -65,8 +61,7 @@ impl ArbiterClient {
|
|||||||
let anchor = webpki::anchor_from_trusted_cert(&url.ca_cert)?.to_owned();
|
let anchor = webpki::anchor_from_trusted_cert(&url.ca_cert)?.to_owned();
|
||||||
let tls = ClientTlsConfig::new().trust_anchor(anchor);
|
let tls = ClientTlsConfig::new().trust_anchor(anchor);
|
||||||
|
|
||||||
let channel =
|
let channel = tonic::transport::Channel::from_shared(format!("https://{}:{}", url.host, url.port))?
|
||||||
tonic::transport::Channel::from_shared(format!("https://{}:{}", url.host, url.port))?
|
|
||||||
.tls_config(tls)?
|
.tls_config(tls)?
|
||||||
.connect()
|
.connect()
|
||||||
.await?;
|
.await?;
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ pub use client::{ArbiterClient, Error};
|
|||||||
pub use storage::{FileSigningKeyStorage, SigningKeyStorage, StorageError};
|
pub use storage::{FileSigningKeyStorage, SigningKeyStorage, StorageError};
|
||||||
|
|
||||||
#[cfg(feature = "evm")]
|
#[cfg(feature = "evm")]
|
||||||
pub use wallets::evm::{ArbiterEvmSignTransactionError, ArbiterEvmWallet};
|
pub use wallets::evm::ArbiterEvmWallet;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use arbiter_proto::proto::client::{ClientRequest, ClientResponse};
|
use arbiter_proto::proto::{
|
||||||
|
client::{ClientRequest, ClientResponse},
|
||||||
|
};
|
||||||
use std::sync::atomic::{AtomicI32, Ordering};
|
use std::sync::atomic::{AtomicI32, Ordering};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
@@ -34,7 +36,9 @@ impl ClientTransport {
|
|||||||
.map_err(|_| ClientSignError::ChannelClosed)
|
.map_err(|_| ClientSignError::ChannelClosed)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn recv(&mut self) -> std::result::Result<ClientResponse, ClientSignError> {
|
pub(crate) async fn recv(
|
||||||
|
&mut self,
|
||||||
|
) -> std::result::Result<ClientResponse, ClientSignError> {
|
||||||
match self.receiver.message().await {
|
match self.receiver.message().await {
|
||||||
Ok(Some(resp)) => Ok(resp),
|
Ok(Some(resp)) => Ok(resp),
|
||||||
Ok(None) => Err(ClientSignError::ConnectionClosed),
|
Ok(None) => Err(ClientSignError::ConnectionClosed),
|
||||||
|
|||||||
@@ -8,49 +8,7 @@ use async_trait::async_trait;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
use arbiter_proto::proto::{
|
use crate::transport::ClientTransport;
|
||||||
client::{
|
|
||||||
ClientRequest,
|
|
||||||
client_request::Payload as ClientRequestPayload,
|
|
||||||
client_response::Payload as ClientResponsePayload,
|
|
||||||
evm::{
|
|
||||||
self as proto_evm, request::Payload as EvmRequestPayload,
|
|
||||||
response::Payload as EvmResponsePayload,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
evm::{
|
|
||||||
EvmSignTransactionRequest,
|
|
||||||
evm_sign_transaction_response::Result as EvmSignTransactionResult,
|
|
||||||
},
|
|
||||||
shared::evm::TransactionEvalError,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::transport::{ClientTransport, next_request_id};
|
|
||||||
|
|
||||||
/// A typed error payload returned by [`ArbiterEvmWallet`] transaction signing.
|
|
||||||
///
|
|
||||||
/// This is wrapped into `alloy::signers::Error::Other`, so consumers can downcast by [`TryFrom`] and
|
|
||||||
/// interpret the concrete policy evaluation failure instead of parsing strings.
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum ArbiterEvmSignTransactionError {
|
|
||||||
#[error("transaction rejected by policy: {0:?}")]
|
|
||||||
PolicyEval(TransactionEvalError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a Error> for &'a ArbiterEvmSignTransactionError {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn try_from(value: &'a Error) -> Result<Self, Self::Error> {
|
|
||||||
if let Error::Other(inner) = value
|
|
||||||
&& let Some(eval_error) = inner.downcast_ref()
|
|
||||||
{
|
|
||||||
Ok(eval_error)
|
|
||||||
} else {
|
|
||||||
Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ArbiterEvmWallet {
|
pub struct ArbiterEvmWallet {
|
||||||
transport: Arc<Mutex<ClientTransport>>,
|
transport: Arc<Mutex<ClientTransport>>,
|
||||||
@@ -59,10 +17,6 @@ pub struct ArbiterEvmWallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ArbiterEvmWallet {
|
impl ArbiterEvmWallet {
|
||||||
#[expect(
|
|
||||||
dead_code,
|
|
||||||
reason = "constructor may be used in future extensions, e.g. to support wallet listing"
|
|
||||||
)]
|
|
||||||
pub(crate) fn new(transport: Arc<Mutex<ClientTransport>>, address: Address) -> Self {
|
pub(crate) fn new(transport: Arc<Mutex<ClientTransport>>, address: Address) -> Self {
|
||||||
Self {
|
Self {
|
||||||
transport,
|
transport,
|
||||||
@@ -125,72 +79,11 @@ impl TxSigner<Signature> for ArbiterEvmWallet {
|
|||||||
&self,
|
&self,
|
||||||
tx: &mut dyn SignableTransaction<Signature>,
|
tx: &mut dyn SignableTransaction<Signature>,
|
||||||
) -> Result<Signature> {
|
) -> Result<Signature> {
|
||||||
|
let _transport = self.transport.lock().await;
|
||||||
self.validate_chain_id(tx)?;
|
self.validate_chain_id(tx)?;
|
||||||
|
|
||||||
let mut transport = self.transport.lock().await;
|
Err(Error::other(
|
||||||
let request_id = next_request_id();
|
"transaction signing is not supported by current arbiter.client protocol",
|
||||||
let rlp_transaction = tx.encoded_for_signing();
|
))
|
||||||
|
|
||||||
transport
|
|
||||||
.send(ClientRequest {
|
|
||||||
request_id,
|
|
||||||
payload: Some(ClientRequestPayload::Evm(proto_evm::Request {
|
|
||||||
payload: Some(EvmRequestPayload::SignTransaction(
|
|
||||||
EvmSignTransactionRequest {
|
|
||||||
wallet_address: self.address.to_vec(),
|
|
||||||
rlp_transaction,
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
})),
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::other("failed to send evm sign transaction request"))?;
|
|
||||||
|
|
||||||
let response = transport
|
|
||||||
.recv()
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::other("failed to receive evm sign transaction response"))?;
|
|
||||||
|
|
||||||
if response.request_id != Some(request_id) {
|
|
||||||
return Err(Error::other(
|
|
||||||
"received mismatched response id for evm sign transaction",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let payload = response
|
|
||||||
.payload
|
|
||||||
.ok_or_else(|| Error::other("missing evm sign transaction response payload"))?;
|
|
||||||
|
|
||||||
let ClientResponsePayload::Evm(proto_evm::Response {
|
|
||||||
payload: Some(payload),
|
|
||||||
}) = payload
|
|
||||||
else {
|
|
||||||
return Err(Error::other(
|
|
||||||
"unexpected response payload for evm sign transaction request",
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let EvmResponsePayload::SignTransaction(response) = payload else {
|
|
||||||
return Err(Error::other(
|
|
||||||
"unexpected evm response payload for sign transaction request",
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = response
|
|
||||||
.result
|
|
||||||
.ok_or_else(|| Error::other("missing evm sign transaction result"))?;
|
|
||||||
|
|
||||||
match result {
|
|
||||||
EvmSignTransactionResult::Signature(signature) => {
|
|
||||||
Signature::try_from(signature.as_slice())
|
|
||||||
.map_err(|_| Error::other("invalid signature returned by server"))
|
|
||||||
}
|
|
||||||
EvmSignTransactionResult::EvalError(eval_error) => Err(Error::other(
|
|
||||||
ArbiterEvmSignTransactionError::PolicyEval(eval_error),
|
|
||||||
)),
|
|
||||||
EvmSignTransactionResult::Error(code) => Err(Error::other(format!(
|
|
||||||
"server failed to sign transaction with error code {code}"
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ tokio.workspace = true
|
|||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
tonic-prost = "0.14.5"
|
tonic-prost = "0.14.5"
|
||||||
prost.workspace = true
|
prost = "0.14.3"
|
||||||
kameo.workspace = true
|
kameo.workspace = true
|
||||||
url = "2.5.8"
|
url = "2.5.8"
|
||||||
miette.workspace = true
|
miette.workspace = true
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ const ARBITER_URL_SCHEME: &str = "arbiter";
|
|||||||
const CERT_QUERY_KEY: &str = "cert";
|
const CERT_QUERY_KEY: &str = "cert";
|
||||||
const BOOTSTRAP_TOKEN_QUERY_KEY: &str = "bootstrap_token";
|
const BOOTSTRAP_TOKEN_QUERY_KEY: &str = "bootstrap_token";
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ArbiterUrl {
|
pub struct ArbiterUrl {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ diesel-async = { version = "0.8.0", features = [
|
|||||||
"tokio",
|
"tokio",
|
||||||
] }
|
] }
|
||||||
ed25519-dalek.workspace = true
|
ed25519-dalek.workspace = true
|
||||||
ed25519-dalek.features = ["serde"]
|
|
||||||
arbiter-proto.path = "../arbiter-proto"
|
arbiter-proto.path = "../arbiter-proto"
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
@@ -26,6 +25,7 @@ tonic.features = ["tls-aws-lc"]
|
|||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
rustls.workspace = true
|
rustls.workspace = true
|
||||||
smlang.workspace = true
|
smlang.workspace = true
|
||||||
|
miette.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
fatality = "0.1.1"
|
fatality = "0.1.1"
|
||||||
diesel_migrations = { version = "2.3.1", features = ["sqlite"] }
|
diesel_migrations = { version = "2.3.1", features = ["sqlite"] }
|
||||||
@@ -47,23 +47,13 @@ restructed = "0.2.2"
|
|||||||
strum = { version = "0.28.0", features = ["derive"] }
|
strum = { version = "0.28.0", features = ["derive"] }
|
||||||
pem = "3.0.6"
|
pem = "3.0.6"
|
||||||
k256.workspace = true
|
k256.workspace = true
|
||||||
k256.features = ["serde"]
|
|
||||||
rsa.workspace = true
|
rsa.workspace = true
|
||||||
rsa.features = ["serde"]
|
|
||||||
sha2.workspace = true
|
sha2.workspace = true
|
||||||
hmac = "0.12"
|
|
||||||
spki.workspace = true
|
spki.workspace = true
|
||||||
alloy.workspace = true
|
alloy.workspace = true
|
||||||
prost-types.workspace = true
|
prost-types.workspace = true
|
||||||
prost.workspace = true
|
|
||||||
arbiter-tokens-registry.path = "../arbiter-tokens-registry"
|
arbiter-tokens-registry.path = "../arbiter-tokens-registry"
|
||||||
anyhow = "1.0.102"
|
|
||||||
serde_with = "3.18.0"
|
|
||||||
mutants.workspace = true
|
|
||||||
subtle = "2.6.1"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
insta = "1.46.3"
|
insta = "1.46.3"
|
||||||
proptest = "1.11.0"
|
|
||||||
rstest.workspace = true
|
|
||||||
test-log = { version = "0.2", default-features = false, features = ["trace"] }
|
test-log = { version = "0.2", default-features = false, features = ["trace"] }
|
||||||
|
|||||||
@@ -191,19 +191,3 @@ create table if not exists evm_ether_transfer_grant_target (
|
|||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
create unique index if not exists uniq_ether_transfer_target on evm_ether_transfer_grant_target (grant_id, address);
|
create unique index if not exists uniq_ether_transfer_target on evm_ether_transfer_grant_target (grant_id, address);
|
||||||
|
|
||||||
-- ===============================
|
|
||||||
-- Integrity Envelopes
|
|
||||||
-- ===============================
|
|
||||||
create table if not exists integrity_envelope (
|
|
||||||
id integer not null primary key,
|
|
||||||
entity_kind text not null,
|
|
||||||
entity_id blob not null,
|
|
||||||
payload_version integer not null,
|
|
||||||
key_version integer not null,
|
|
||||||
mac blob not null, -- 20-byte recipient address
|
|
||||||
signed_at integer not null default(unixepoch ('now')),
|
|
||||||
created_at integer not null default(unixepoch ('now'))
|
|
||||||
) STRICT;
|
|
||||||
|
|
||||||
create unique index if not exists uniq_integrity_envelope_entity on integrity_envelope (entity_kind, entity_id);
|
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ use arbiter_proto::{BOOTSTRAP_PATH, home_path};
|
|||||||
use diesel::QueryDsl;
|
use diesel::QueryDsl;
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
use kameo::{Actor, messages};
|
use kameo::{Actor, messages};
|
||||||
|
use miette::Diagnostic;
|
||||||
use rand::{RngExt, distr::Alphanumeric, make_rng, rngs::StdRng};
|
use rand::{RngExt, distr::Alphanumeric, make_rng, rngs::StdRng};
|
||||||
use subtle::ConstantTimeEq as _;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::db::{self, DatabasePool, schema};
|
use crate::db::{self, DatabasePool, schema};
|
||||||
@@ -26,15 +25,18 @@ pub async fn generate_token() -> Result<String, std::io::Error> {
|
|||||||
Ok(token)
|
Ok(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Database error: {0}")]
|
#[error("Database error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::bootstrap::database))]
|
||||||
Database(#[from] db::PoolError),
|
Database(#[from] db::PoolError),
|
||||||
|
|
||||||
#[error("Database query error: {0}")]
|
#[error("Database query error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::bootstrap::database_query))]
|
||||||
Query(#[from] diesel::result::Error),
|
Query(#[from] diesel::result::Error),
|
||||||
|
|
||||||
#[error("I/O error: {0}")]
|
#[error("I/O error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::bootstrap::io))]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,14 +47,14 @@ pub struct Bootstrapper {
|
|||||||
|
|
||||||
impl Bootstrapper {
|
impl Bootstrapper {
|
||||||
pub async fn new(db: &DatabasePool) -> Result<Self, Error> {
|
pub async fn new(db: &DatabasePool) -> Result<Self, Error> {
|
||||||
let row_count: i64 = {
|
|
||||||
let mut conn = db.get().await?;
|
let mut conn = db.get().await?;
|
||||||
|
|
||||||
schema::useragent_client::table
|
let row_count: i64 = schema::useragent_client::table
|
||||||
.count()
|
.count()
|
||||||
.get_result(&mut conn)
|
.get_result(&mut conn)
|
||||||
.await?
|
.await?;
|
||||||
};
|
|
||||||
|
drop(conn);
|
||||||
|
|
||||||
let token = if row_count == 0 {
|
let token = if row_count == 0 {
|
||||||
let token = generate_token().await?;
|
let token = generate_token().await?;
|
||||||
@@ -70,13 +72,7 @@ impl Bootstrapper {
|
|||||||
#[message]
|
#[message]
|
||||||
pub fn is_correct_token(&self, token: String) -> bool {
|
pub fn is_correct_token(&self, token: String) -> bool {
|
||||||
match &self.token {
|
match &self.token {
|
||||||
Some(expected) => {
|
Some(expected) => *expected == token,
|
||||||
let expected_bytes = expected.as_bytes();
|
|
||||||
let token_bytes = token.as_bytes();
|
|
||||||
|
|
||||||
let choice = expected_bytes.ct_eq(token_bytes);
|
|
||||||
bool::from(choice)
|
|
||||||
}
|
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
ClientMetadata, format_challenge,
|
ClientMetadata, format_challenge, transport::{Bi, expect_message}
|
||||||
transport::{Bi, expect_message},
|
|
||||||
};
|
};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use diesel::{
|
use diesel::{
|
||||||
@@ -9,16 +8,14 @@ use diesel::{
|
|||||||
};
|
};
|
||||||
use diesel_async::RunQueryDsl as _;
|
use diesel_async::RunQueryDsl as _;
|
||||||
use ed25519_dalek::{Signature, VerifyingKey};
|
use ed25519_dalek::{Signature, VerifyingKey};
|
||||||
use kameo::{actor::ActorRef, error::SendError};
|
use kameo::error::SendError;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::{
|
actors::{
|
||||||
client::{ClientConnection, ClientCredentials, ClientProfile},
|
client::{ClientConnection, ClientProfile},
|
||||||
flow_coordinator::{self, RequestClientApproval},
|
flow_coordinator::{self, RequestClientApproval},
|
||||||
keyholder::KeyHolder,
|
|
||||||
},
|
},
|
||||||
crypto::integrity::{self},
|
|
||||||
db::{
|
db::{
|
||||||
self,
|
self,
|
||||||
models::{ProgramClientMetadata, SqliteTimestamp},
|
models::{ProgramClientMetadata, SqliteTimestamp},
|
||||||
@@ -32,8 +29,6 @@ pub enum Error {
|
|||||||
DatabasePoolUnavailable,
|
DatabasePoolUnavailable,
|
||||||
#[error("Database operation failed")]
|
#[error("Database operation failed")]
|
||||||
DatabaseOperationFailed,
|
DatabaseOperationFailed,
|
||||||
#[error("Integrity check failed")]
|
|
||||||
IntegrityCheckFailed,
|
|
||||||
#[error("Invalid challenge solution")]
|
#[error("Invalid challenge solution")]
|
||||||
InvalidChallengeSolution,
|
InvalidChallengeSolution,
|
||||||
#[error("Client approval request failed")]
|
#[error("Client approval request failed")]
|
||||||
@@ -42,13 +37,6 @@ pub enum Error {
|
|||||||
Transport,
|
Transport,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<diesel::result::Error> for Error {
|
|
||||||
fn from(e: diesel::result::Error) -> Self {
|
|
||||||
error!(?e, "Database error");
|
|
||||||
Self::DatabaseOperationFailed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
|
#[derive(thiserror::Error, Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum ApproveError {
|
pub enum ApproveError {
|
||||||
#[error("Internal error")]
|
#[error("Internal error")]
|
||||||
@@ -76,69 +64,17 @@ pub enum Outbound {
|
|||||||
AuthSuccess,
|
AuthSuccess,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current nonce and client ID for a registered client.
|
pub struct ClientInfo {
|
||||||
|
pub id: i32,
|
||||||
|
pub current_nonce: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Atomically reads and increments the nonce for a known client.
|
||||||
/// Returns `None` if the pubkey is not registered.
|
/// Returns `None` if the pubkey is not registered.
|
||||||
async fn get_current_nonce_and_id(
|
async fn get_client_and_nonce(
|
||||||
db: &db::DatabasePool,
|
db: &db::DatabasePool,
|
||||||
pubkey: &VerifyingKey,
|
pubkey: &VerifyingKey,
|
||||||
) -> Result<Option<(i32, i32)>, Error> {
|
) -> Result<Option<ClientInfo>, Error> {
|
||||||
let pubkey_bytes = pubkey.as_bytes().to_vec();
|
|
||||||
let mut conn = db.get().await.map_err(|e| {
|
|
||||||
error!(error = ?e, "Database pool error");
|
|
||||||
Error::DatabasePoolUnavailable
|
|
||||||
})?;
|
|
||||||
program_client::table
|
|
||||||
.filter(program_client::public_key.eq(&pubkey_bytes))
|
|
||||||
.select((program_client::id, program_client::nonce))
|
|
||||||
.first::<(i32, i32)>(&mut conn)
|
|
||||||
.await
|
|
||||||
.optional()
|
|
||||||
.map_err(|e| {
|
|
||||||
error!(error = ?e, "Database error");
|
|
||||||
Error::DatabaseOperationFailed
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn verify_integrity(
|
|
||||||
db: &db::DatabasePool,
|
|
||||||
keyholder: &ActorRef<KeyHolder>,
|
|
||||||
pubkey: &VerifyingKey,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut db_conn = db.get().await.map_err(|e| {
|
|
||||||
error!(error = ?e, "Database pool error");
|
|
||||||
Error::DatabasePoolUnavailable
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let (id, nonce) = get_current_nonce_and_id(db, pubkey).await?.ok_or_else(|| {
|
|
||||||
error!("Client not found during integrity verification");
|
|
||||||
Error::DatabaseOperationFailed
|
|
||||||
})?;
|
|
||||||
|
|
||||||
integrity::verify_entity(
|
|
||||||
&mut db_conn,
|
|
||||||
keyholder,
|
|
||||||
&ClientCredentials {
|
|
||||||
pubkey: *pubkey,
|
|
||||||
nonce,
|
|
||||||
},
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
error!(?e, "Integrity verification failed");
|
|
||||||
Error::IntegrityCheckFailed
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Atomically increments the nonce and re-signs the integrity envelope.
|
|
||||||
/// Returns the new nonce, which is used as the challenge nonce.
|
|
||||||
async fn create_nonce(
|
|
||||||
db: &db::DatabasePool,
|
|
||||||
keyholder: &ActorRef<KeyHolder>,
|
|
||||||
pubkey: &VerifyingKey,
|
|
||||||
) -> Result<i32, Error> {
|
|
||||||
let pubkey_bytes = pubkey.as_bytes().to_vec();
|
let pubkey_bytes = pubkey.as_bytes().to_vec();
|
||||||
|
|
||||||
let mut conn = db.get().await.map_err(|e| {
|
let mut conn = db.get().await.map_err(|e| {
|
||||||
@@ -147,34 +83,35 @@ async fn create_nonce(
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
conn.exclusive_transaction(|conn| {
|
conn.exclusive_transaction(|conn| {
|
||||||
let keyholder = keyholder.clone();
|
let pubkey_bytes = pubkey_bytes.clone();
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let (id, new_nonce): (i32, i32) = update(program_client::table)
|
let Some((client_id, current_nonce)) = program_client::table
|
||||||
.filter(program_client::public_key.eq(&pubkey_bytes))
|
.filter(program_client::public_key.eq(&pubkey_bytes))
|
||||||
.set(program_client::nonce.eq(program_client::nonce + 1))
|
.select((program_client::id, program_client::nonce))
|
||||||
.returning((program_client::id, program_client::nonce))
|
.first::<(i32, i32)>(conn)
|
||||||
.get_result(conn)
|
.await
|
||||||
|
.optional()?
|
||||||
|
else {
|
||||||
|
return Result::<_, diesel::result::Error>::Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
update(program_client::table)
|
||||||
|
.filter(program_client::public_key.eq(&pubkey_bytes))
|
||||||
|
.set(program_client::nonce.eq(current_nonce + 1))
|
||||||
|
.execute(conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
integrity::sign_entity(
|
Ok(Some(ClientInfo {
|
||||||
conn,
|
id: client_id,
|
||||||
&keyholder,
|
current_nonce,
|
||||||
&ClientCredentials {
|
}))
|
||||||
pubkey: *pubkey,
|
})
|
||||||
nonce: new_nonce,
|
})
|
||||||
},
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(?e, "Integrity sign failed after nonce update");
|
error!(error = ?e, "Database error");
|
||||||
Error::DatabaseOperationFailed
|
Error::DatabaseOperationFailed
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(new_nonce)
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn approve_new_client(
|
async fn approve_new_client(
|
||||||
@@ -202,23 +139,15 @@ async fn approve_new_client(
|
|||||||
|
|
||||||
async fn insert_client(
|
async fn insert_client(
|
||||||
db: &db::DatabasePool,
|
db: &db::DatabasePool,
|
||||||
keyholder: &ActorRef<KeyHolder>,
|
|
||||||
pubkey: &VerifyingKey,
|
pubkey: &VerifyingKey,
|
||||||
metadata: &ClientMetadata,
|
metadata: &ClientMetadata,
|
||||||
) -> Result<i32, Error> {
|
) -> Result<i32, Error> {
|
||||||
use crate::db::schema::{client_metadata, program_client};
|
use crate::db::schema::{client_metadata, program_client};
|
||||||
let metadata = metadata.clone();
|
|
||||||
|
|
||||||
let mut conn = db.get().await.map_err(|e| {
|
let mut conn = db.get().await.map_err(|e| {
|
||||||
error!(error = ?e, "Database pool error");
|
error!(error = ?e, "Database pool error");
|
||||||
Error::DatabasePoolUnavailable
|
Error::DatabasePoolUnavailable
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
conn.exclusive_transaction(|conn| {
|
|
||||||
let keyholder = keyholder.clone();
|
|
||||||
Box::pin(async move {
|
|
||||||
const NONCE_START: i32 = 1;
|
|
||||||
|
|
||||||
let metadata_id = insert_into(client_metadata::table)
|
let metadata_id = insert_into(client_metadata::table)
|
||||||
.values((
|
.values((
|
||||||
client_metadata::name.eq(&metadata.name),
|
client_metadata::name.eq(&metadata.name),
|
||||||
@@ -226,39 +155,29 @@ async fn insert_client(
|
|||||||
client_metadata::version.eq(&metadata.version),
|
client_metadata::version.eq(&metadata.version),
|
||||||
))
|
))
|
||||||
.returning(client_metadata::id)
|
.returning(client_metadata::id)
|
||||||
.get_result::<i32>(conn)
|
.get_result::<i32>(&mut conn)
|
||||||
.await?;
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
error!(error = ?e, "Failed to insert client metadata");
|
||||||
|
Error::DatabaseOperationFailed
|
||||||
|
})?;
|
||||||
|
|
||||||
let client_id = insert_into(program_client::table)
|
let client_id = insert_into(program_client::table)
|
||||||
.values((
|
.values((
|
||||||
program_client::public_key.eq(pubkey.as_bytes().to_vec()),
|
program_client::public_key.eq(pubkey.as_bytes().to_vec()),
|
||||||
program_client::metadata_id.eq(metadata_id),
|
program_client::metadata_id.eq(metadata_id),
|
||||||
program_client::nonce.eq(NONCE_START),
|
program_client::nonce.eq(1), // pre-incremented; challenge uses 0
|
||||||
))
|
))
|
||||||
.on_conflict_do_nothing()
|
.on_conflict_do_nothing()
|
||||||
.returning(program_client::id)
|
.returning(program_client::id)
|
||||||
.get_result::<i32>(conn)
|
.get_result::<i32>(&mut conn)
|
||||||
.await?;
|
|
||||||
|
|
||||||
integrity::sign_entity(
|
|
||||||
conn,
|
|
||||||
&keyholder,
|
|
||||||
&ClientCredentials {
|
|
||||||
pubkey: *pubkey,
|
|
||||||
nonce: NONCE_START,
|
|
||||||
},
|
|
||||||
client_id,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!(error = ?e, "Failed to sign integrity tag for new client key");
|
error!(error = ?e, "Failed to insert client metadata");
|
||||||
Error::DatabaseOperationFailed
|
Error::DatabaseOperationFailed
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(client_id)
|
Ok(client_id)
|
||||||
})
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn sync_client_metadata(
|
async fn sync_client_metadata(
|
||||||
@@ -368,7 +287,10 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn authenticate<T>(props: &mut ClientConnection, transport: &mut T) -> Result<i32, Error>
|
pub async fn authenticate<T>(
|
||||||
|
props: &mut ClientConnection,
|
||||||
|
transport: &mut T,
|
||||||
|
) -> Result<VerifyingKey, Error>
|
||||||
where
|
where
|
||||||
T: Bi<Inbound, Result<Outbound, Error>> + Send + ?Sized,
|
T: Bi<Inbound, Result<Outbound, Error>> + Send + ?Sized,
|
||||||
{
|
{
|
||||||
@@ -376,11 +298,8 @@ where
|
|||||||
return Err(Error::Transport);
|
return Err(Error::Transport);
|
||||||
};
|
};
|
||||||
|
|
||||||
let client_id = match get_current_nonce_and_id(&props.db, &pubkey).await? {
|
let info = match get_client_and_nonce(&props.db, &pubkey).await? {
|
||||||
Some((id, _)) => {
|
Some(nonce) => nonce,
|
||||||
verify_integrity(&props.db, &props.actors.key_holder, &pubkey).await?;
|
|
||||||
id
|
|
||||||
}
|
|
||||||
None => {
|
None => {
|
||||||
approve_new_client(
|
approve_new_client(
|
||||||
&props.actors,
|
&props.actors,
|
||||||
@@ -390,13 +309,17 @@ where
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
insert_client(&props.db, &props.actors.key_holder, &pubkey, &metadata).await?
|
let client_id = insert_client(&props.db, &pubkey, &metadata).await?;
|
||||||
|
ClientInfo {
|
||||||
|
id: client_id,
|
||||||
|
current_nonce: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
sync_client_metadata(&props.db, client_id, &metadata).await?;
|
sync_client_metadata(&props.db, info.id, &metadata).await?;
|
||||||
let challenge_nonce = create_nonce(&props.db, &props.actors.key_holder, &pubkey).await?;
|
|
||||||
challenge_client(transport, pubkey, challenge_nonce).await?;
|
challenge_client(transport, pubkey, info.current_nonce).await?;
|
||||||
|
|
||||||
transport
|
transport
|
||||||
.send(Ok(Outbound::AuthSuccess))
|
.send(Ok(Outbound::AuthSuccess))
|
||||||
@@ -406,5 +329,5 @@ where
|
|||||||
Error::Transport
|
Error::Transport
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(client_id)
|
Ok(pubkey)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ use kameo::actor::Spawn;
|
|||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::{GlobalActors, client::session::ClientSession},
|
actors::{GlobalActors, client::{ session::ClientSession}},
|
||||||
crypto::integrity::{Integrable, hashing::Hashable},
|
|
||||||
db,
|
db,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -14,22 +13,6 @@ pub struct ClientProfile {
|
|||||||
pub metadata: ClientMetadata,
|
pub metadata: ClientMetadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ClientCredentials {
|
|
||||||
pub pubkey: ed25519_dalek::VerifyingKey,
|
|
||||||
pub nonce: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Integrable for ClientCredentials {
|
|
||||||
const KIND: &'static str = "client_credentials";
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hashable for ClientCredentials {
|
|
||||||
fn hash<H: sha2::Digest>(&self, hasher: &mut H) {
|
|
||||||
hasher.update(self.pubkey.as_bytes());
|
|
||||||
self.nonce.hash(hasher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ClientConnection {
|
pub struct ClientConnection {
|
||||||
pub(crate) db: db::DatabasePool,
|
pub(crate) db: db::DatabasePool,
|
||||||
pub(crate) actors: GlobalActors,
|
pub(crate) actors: GlobalActors,
|
||||||
@@ -49,8 +32,8 @@ where
|
|||||||
T: Bi<auth::Inbound, Result<auth::Outbound, auth::Error>> + Send + ?Sized,
|
T: Bi<auth::Inbound, Result<auth::Outbound, auth::Error>> + Send + ?Sized,
|
||||||
{
|
{
|
||||||
match auth::authenticate(&mut props, transport).await {
|
match auth::authenticate(&mut props, transport).await {
|
||||||
Ok(client_id) => {
|
Ok(_pubkey) => {
|
||||||
ClientSession::spawn(ClientSession::new(props, client_id));
|
ClientSession::spawn(ClientSession::new(props));
|
||||||
info!("Client authenticated, session started");
|
info!("Client authenticated, session started");
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|||||||
@@ -1,28 +1,21 @@
|
|||||||
use kameo::{Actor, messages};
|
use kameo::{Actor, messages};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use alloy::{consensus::TxEip1559, primitives::Address, signers::Signature};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::{
|
actors::{
|
||||||
GlobalActors,
|
GlobalActors, client::ClientConnection, flow_coordinator::RegisterClient,
|
||||||
client::ClientConnection,
|
|
||||||
evm::{ClientSignTransaction, SignTransactionError},
|
|
||||||
flow_coordinator::RegisterClient,
|
|
||||||
keyholder::KeyHolderState,
|
keyholder::KeyHolderState,
|
||||||
},
|
},
|
||||||
db,
|
db,
|
||||||
evm::VetError,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ClientSession {
|
pub struct ClientSession {
|
||||||
props: ClientConnection,
|
props: ClientConnection,
|
||||||
client_id: i32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ClientSession {
|
impl ClientSession {
|
||||||
pub(crate) fn new(props: ClientConnection, client_id: i32) -> Self {
|
pub(crate) fn new(props: ClientConnection) -> Self {
|
||||||
Self { props, client_id }
|
Self { props }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,34 +35,6 @@ impl ClientSession {
|
|||||||
|
|
||||||
Ok(vault_state)
|
Ok(vault_state)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[message]
|
|
||||||
pub(crate) async fn handle_sign_transaction(
|
|
||||||
&mut self,
|
|
||||||
wallet_address: Address,
|
|
||||||
transaction: TxEip1559,
|
|
||||||
) -> Result<Signature, SignTransactionRpcError> {
|
|
||||||
match self
|
|
||||||
.props
|
|
||||||
.actors
|
|
||||||
.evm
|
|
||||||
.ask(ClientSignTransaction {
|
|
||||||
client_id: self.client_id,
|
|
||||||
wallet_address,
|
|
||||||
transaction,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(signature) => Ok(signature),
|
|
||||||
Err(kameo::error::SendError::HandlerError(SignTransactionError::Vet(vet_error))) => {
|
|
||||||
Err(SignTransactionRpcError::Vet(vet_error))
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!(?err, "Failed to sign EVM transaction in client session");
|
|
||||||
Err(SignTransactionRpcError::Internal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Actor for ClientSession {
|
impl Actor for ClientSession {
|
||||||
@@ -94,10 +59,7 @@ impl Actor for ClientSession {
|
|||||||
impl ClientSession {
|
impl ClientSession {
|
||||||
pub fn new_test(db: db::DatabasePool, actors: GlobalActors) -> Self {
|
pub fn new_test(db: db::DatabasePool, actors: GlobalActors) -> Self {
|
||||||
let props = ClientConnection::new(db, actors);
|
let props = ClientConnection::new(db, actors);
|
||||||
Self {
|
Self { props }
|
||||||
props,
|
|
||||||
client_id: 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,12 +70,3 @@ pub enum Error {
|
|||||||
#[error("Internal error")]
|
#[error("Internal error")]
|
||||||
Internal,
|
Internal,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum SignTransactionRpcError {
|
|
||||||
#[error("Policy evaluation failed")]
|
|
||||||
Vet(#[from] VetError),
|
|
||||||
|
|
||||||
#[error("Internal error")]
|
|
||||||
Internal,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,16 +8,15 @@ use rand::{SeedableRng, rng, rngs::StdRng};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::keyholder::{CreateNew, Decrypt, KeyHolder},
|
actors::keyholder::{CreateNew, Decrypt, KeyHolder},
|
||||||
crypto::integrity,
|
|
||||||
db::{
|
db::{
|
||||||
DatabaseError, DatabasePool,
|
self, DatabaseError, DatabasePool,
|
||||||
models::{self},
|
models::{self, SqliteTimestamp},
|
||||||
schema,
|
schema,
|
||||||
},
|
},
|
||||||
evm::{
|
evm::{
|
||||||
self, ListError, RunKind,
|
self, RunKind,
|
||||||
policies::{
|
policies::{
|
||||||
CombinedSettings, Grant, SharedGrantSettings, SpecificGrant, SpecificMeaning,
|
FullGrant, Grant, SharedGrantSettings, SpecificGrant, SpecificMeaning,
|
||||||
ether_transfer::EtherTransfer, token_transfers::TokenTransfer,
|
ether_transfer::EtherTransfer, token_transfers::TokenTransfer,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -26,40 +25,46 @@ use crate::{
|
|||||||
|
|
||||||
pub use crate::evm::safe_signer;
|
pub use crate::evm::safe_signer;
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
|
||||||
pub enum SignTransactionError {
|
pub enum SignTransactionError {
|
||||||
#[error("Wallet not found")]
|
#[error("Wallet not found")]
|
||||||
|
#[diagnostic(code(arbiter::evm::sign::wallet_not_found))]
|
||||||
WalletNotFound,
|
WalletNotFound,
|
||||||
|
|
||||||
#[error("Database error: {0}")]
|
#[error("Database error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter::evm::sign::database))]
|
||||||
Database(#[from] DatabaseError),
|
Database(#[from] DatabaseError),
|
||||||
|
|
||||||
#[error("Keyholder error: {0}")]
|
#[error("Keyholder error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter::evm::sign::keyholder))]
|
||||||
Keyholder(#[from] crate::actors::keyholder::Error),
|
Keyholder(#[from] crate::actors::keyholder::Error),
|
||||||
|
|
||||||
#[error("Keyholder mailbox error")]
|
#[error("Keyholder mailbox error")]
|
||||||
|
#[diagnostic(code(arbiter::evm::sign::keyholder_send))]
|
||||||
KeyholderSend,
|
KeyholderSend,
|
||||||
|
|
||||||
#[error("Signing error: {0}")]
|
#[error("Signing error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter::evm::sign::signing))]
|
||||||
Signing(#[from] alloy::signers::Error),
|
Signing(#[from] alloy::signers::Error),
|
||||||
|
|
||||||
#[error("Policy error: {0}")]
|
#[error("Policy error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter::evm::sign::vet))]
|
||||||
Vet(#[from] evm::VetError),
|
Vet(#[from] evm::VetError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Keyholder error: {0}")]
|
#[error("Keyholder error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter::evm::keyholder))]
|
||||||
Keyholder(#[from] crate::actors::keyholder::Error),
|
Keyholder(#[from] crate::actors::keyholder::Error),
|
||||||
|
|
||||||
#[error("Keyholder mailbox error")]
|
#[error("Keyholder mailbox error")]
|
||||||
|
#[diagnostic(code(arbiter::evm::keyholder_send))]
|
||||||
KeyholderSend,
|
KeyholderSend,
|
||||||
|
|
||||||
#[error("Database error: {0}")]
|
#[error("Database error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter::evm::database))]
|
||||||
Database(#[from] DatabaseError),
|
Database(#[from] DatabaseError),
|
||||||
|
|
||||||
#[error("Integrity violation: {0}")]
|
|
||||||
Integrity(#[from] integrity::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Actor)]
|
#[derive(Actor)]
|
||||||
@@ -75,7 +80,7 @@ impl EvmActor {
|
|||||||
// is it safe to seed rng from system once?
|
// is it safe to seed rng from system once?
|
||||||
// todo: audit
|
// todo: audit
|
||||||
let rng = StdRng::from_rng(&mut rng());
|
let rng = StdRng::from_rng(&mut rng());
|
||||||
let engine = evm::Engine::new(db.clone(), keyholder.clone());
|
let engine = evm::Engine::new(db.clone());
|
||||||
Self {
|
Self {
|
||||||
keyholder,
|
keyholder,
|
||||||
db,
|
db,
|
||||||
@@ -136,59 +141,46 @@ impl EvmActor {
|
|||||||
&mut self,
|
&mut self,
|
||||||
basic: SharedGrantSettings,
|
basic: SharedGrantSettings,
|
||||||
grant: SpecificGrant,
|
grant: SpecificGrant,
|
||||||
) -> Result<integrity::Verified<i32>, Error> {
|
) -> Result<i32, DatabaseError> {
|
||||||
match grant {
|
match grant {
|
||||||
SpecificGrant::EtherTransfer(settings) => self
|
SpecificGrant::EtherTransfer(settings) => {
|
||||||
.engine
|
self.engine
|
||||||
.create_grant::<EtherTransfer>(CombinedSettings {
|
.create_grant::<EtherTransfer>(FullGrant {
|
||||||
shared: basic,
|
basic,
|
||||||
specific: settings,
|
specific: settings,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(Error::from),
|
}
|
||||||
SpecificGrant::TokenTransfer(settings) => self
|
SpecificGrant::TokenTransfer(settings) => {
|
||||||
.engine
|
self.engine
|
||||||
.create_grant::<TokenTransfer>(CombinedSettings {
|
.create_grant::<TokenTransfer>(FullGrant {
|
||||||
shared: basic,
|
basic,
|
||||||
specific: settings,
|
specific: settings,
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(Error::from),
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[message]
|
#[message]
|
||||||
pub async fn useragent_delete_grant(&mut self, _grant_id: i32) -> Result<(), Error> {
|
pub async fn useragent_delete_grant(&mut self, grant_id: i32) -> Result<(), Error> {
|
||||||
// let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
|
let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
|
||||||
// let keyholder = self.keyholder.clone();
|
diesel::update(schema::evm_basic_grant::table)
|
||||||
|
.filter(schema::evm_basic_grant::id.eq(grant_id))
|
||||||
// diesel_async::AsyncConnection::transaction(&mut conn, |conn| {
|
.set(schema::evm_basic_grant::revoked_at.eq(SqliteTimestamp::now()))
|
||||||
// Box::pin(async move {
|
.execute(&mut conn)
|
||||||
// diesel::update(schema::evm_basic_grant::table)
|
.await
|
||||||
// .filter(schema::evm_basic_grant::id.eq(grant_id))
|
.map_err(DatabaseError::from)?;
|
||||||
// .set(schema::evm_basic_grant::revoked_at.eq(SqliteTimestamp::now()))
|
Ok(())
|
||||||
// .execute(conn)
|
|
||||||
// .await?;
|
|
||||||
|
|
||||||
// let signed = integrity::evm::load_signed_grant_by_basic_id(conn, grant_id).await?;
|
|
||||||
|
|
||||||
// diesel::result::QueryResult::Ok(())
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// .await
|
|
||||||
// .map_err(DatabaseError::from)?;
|
|
||||||
|
|
||||||
// Ok(())
|
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[message]
|
#[message]
|
||||||
pub async fn useragent_list_grants(&mut self) -> Result<Vec<Grant<SpecificGrant>>, Error> {
|
pub async fn useragent_list_grants(&mut self) -> Result<Vec<Grant<SpecificGrant>>, Error> {
|
||||||
match self.engine.list_all_grants().await {
|
Ok(self
|
||||||
Ok(grants) => Ok(grants),
|
.engine
|
||||||
Err(ListError::Database(db_err)) => Err(Error::Database(db_err)),
|
.list_all_grants()
|
||||||
Err(ListError::Integrity(integrity_err)) => Err(Error::Integrity(integrity_err)),
|
.await
|
||||||
}
|
.map_err(DatabaseError::from)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[message]
|
#[message]
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use crate::actors::{
|
|||||||
pub struct Args {
|
pub struct Args {
|
||||||
pub client: ClientProfile,
|
pub client: ClientProfile,
|
||||||
pub user_agents: Vec<ActorRef<UserAgentSession>>,
|
pub user_agents: Vec<ActorRef<UserAgentSession>>,
|
||||||
pub reply: ReplySender<Result<bool, ApprovalError>>,
|
pub reply: ReplySender<Result<bool, ApprovalError>>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ClientApprovalController {
|
pub struct ClientApprovalController {
|
||||||
@@ -39,11 +39,7 @@ impl Actor for ClientApprovalController {
|
|||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
async fn on_start(
|
async fn on_start(
|
||||||
Args {
|
Args { client, mut user_agents, reply }: Self::Args,
|
||||||
client,
|
|
||||||
mut user_agents,
|
|
||||||
reply,
|
|
||||||
}: Self::Args,
|
|
||||||
actor_ref: ActorRef<Self>,
|
actor_ref: ActorRef<Self>,
|
||||||
) -> Result<Self, Self::Error> {
|
) -> Result<Self, Self::Error> {
|
||||||
let this = Self {
|
let this = Self {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
pub mod v1;
|
||||||
@@ -1,21 +1,52 @@
|
|||||||
use std::ops::Deref as _;
|
use std::ops::Deref as _;
|
||||||
|
|
||||||
use argon2::{Algorithm, Argon2};
|
use argon2::{Algorithm, Argon2, password_hash::Salt as ArgonSalt};
|
||||||
use chacha20poly1305::{
|
use chacha20poly1305::{
|
||||||
AeadInPlace, Key, KeyInit as _, XChaCha20Poly1305, XNonce,
|
AeadInPlace, Key, KeyInit as _, XChaCha20Poly1305, XNonce,
|
||||||
aead::{AeadMut, Error, Payload},
|
aead::{AeadMut, Error, Payload},
|
||||||
};
|
};
|
||||||
use rand::{
|
use rand::{
|
||||||
Rng as _, SeedableRng as _,
|
Rng as _, SeedableRng,
|
||||||
rngs::{StdRng, SysRng},
|
rngs::{StdRng, SysRng},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::safe_cell::{SafeCell, SafeCellHandle as _};
|
use crate::safe_cell::{SafeCell, SafeCellHandle as _};
|
||||||
|
|
||||||
pub mod encryption;
|
pub const ROOT_KEY_TAG: &[u8] = "arbiter/seal/v1".as_bytes();
|
||||||
pub mod integrity;
|
pub const TAG: &[u8] = "arbiter/private-key/v1".as_bytes();
|
||||||
|
|
||||||
use encryption::v1::{Nonce, Salt};
|
pub const NONCE_LENGTH: usize = 24;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct Nonce([u8; NONCE_LENGTH]);
|
||||||
|
impl Nonce {
|
||||||
|
pub fn increment(&mut self) {
|
||||||
|
for i in (0..self.0.len()).rev() {
|
||||||
|
if self.0[i] == 0xFF {
|
||||||
|
self.0[i] = 0;
|
||||||
|
} else {
|
||||||
|
self.0[i] += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_vec(&self) -> Vec<u8> {
|
||||||
|
self.0.to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> TryFrom<&'a [u8]> for Nonce {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
|
||||||
|
if value.len() != NONCE_LENGTH {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
let mut nonce = [0u8; NONCE_LENGTH];
|
||||||
|
nonce.copy_from_slice(value);
|
||||||
|
Ok(Self(nonce))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct KeyCell(pub SafeCell<Key>);
|
pub struct KeyCell(pub SafeCell<Key>);
|
||||||
impl From<SafeCell<Key>> for KeyCell {
|
impl From<SafeCell<Key>> for KeyCell {
|
||||||
@@ -102,21 +133,24 @@ impl KeyCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type Salt = [u8; ArgonSalt::RECOMMENDED_LENGTH];
|
||||||
|
|
||||||
|
pub fn generate_salt() -> Salt {
|
||||||
|
let mut salt = Salt::default();
|
||||||
|
#[allow(
|
||||||
|
clippy::unwrap_used,
|
||||||
|
reason = "Rng failure is unrecoverable and should panic"
|
||||||
|
)]
|
||||||
|
let mut rng = StdRng::try_from_rng(&mut SysRng).unwrap();
|
||||||
|
rng.fill_bytes(&mut salt);
|
||||||
|
salt
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User password might be of different length, have not enough entropy, etc...
|
||||||
/// Derive a fixed-length key from the password using Argon2id, which is designed for password hashing and key derivation.
|
/// Derive a fixed-length key from the password using Argon2id, which is designed for password hashing and key derivation.
|
||||||
pub fn derive_key(mut password: SafeCell<Vec<u8>>, salt: &Salt) -> KeyCell {
|
pub fn derive_seal_key(mut password: SafeCell<Vec<u8>>, salt: &Salt) -> KeyCell {
|
||||||
let params = {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
{
|
|
||||||
argon2::Params::new(8, 1, 1, None).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
{
|
|
||||||
argon2::Params::new(262_144, 3, 4, None).unwrap()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#[allow(clippy::unwrap_used)]
|
#[allow(clippy::unwrap_used)]
|
||||||
|
let params = argon2::Params::new(262_144, 3, 4, None).unwrap();
|
||||||
let hasher = Argon2::new(Algorithm::Argon2id, argon2::Version::V0x13, params);
|
let hasher = Argon2::new(Algorithm::Argon2id, argon2::Version::V0x13, params);
|
||||||
let mut key = SafeCell::new(Key::default());
|
let mut key = SafeCell::new(Key::default());
|
||||||
password.read_inline(|password_source| {
|
password.read_inline(|password_source| {
|
||||||
@@ -137,11 +171,37 @@ pub fn derive_key(mut password: SafeCell<Vec<u8>>, salt: &Salt) -> KeyCell {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::*;
|
||||||
derive_key,
|
use crate::safe_cell::SafeCell;
|
||||||
encryption::v1::{Nonce, generate_salt},
|
|
||||||
};
|
#[test]
|
||||||
use crate::safe_cell::{SafeCell, SafeCellHandle as _};
|
pub fn derive_seal_key_deterministic() {
|
||||||
|
static PASSWORD: &[u8] = b"password";
|
||||||
|
let password = SafeCell::new(PASSWORD.to_vec());
|
||||||
|
let password2 = SafeCell::new(PASSWORD.to_vec());
|
||||||
|
let salt = generate_salt();
|
||||||
|
|
||||||
|
let mut key1 = derive_seal_key(password, &salt);
|
||||||
|
let mut key2 = derive_seal_key(password2, &salt);
|
||||||
|
|
||||||
|
let key1_reader = key1.0.read();
|
||||||
|
let key2_reader = key2.0.read();
|
||||||
|
|
||||||
|
assert_eq!(key1_reader.deref(), key2_reader.deref());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn successful_derive() {
|
||||||
|
static PASSWORD: &[u8] = b"password";
|
||||||
|
let password = SafeCell::new(PASSWORD.to_vec());
|
||||||
|
let salt = generate_salt();
|
||||||
|
|
||||||
|
let mut key = derive_seal_key(password, &salt);
|
||||||
|
let key_reader = key.0.read();
|
||||||
|
let key_ref = key_reader.deref();
|
||||||
|
|
||||||
|
assert_ne!(key_ref.as_slice(), &[0u8; 32][..]);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn encrypt_decrypt() {
|
pub fn encrypt_decrypt() {
|
||||||
@@ -149,7 +209,7 @@ mod tests {
|
|||||||
let password = SafeCell::new(PASSWORD.to_vec());
|
let password = SafeCell::new(PASSWORD.to_vec());
|
||||||
let salt = generate_salt();
|
let salt = generate_salt();
|
||||||
|
|
||||||
let mut key = derive_key(password, &salt);
|
let mut key = derive_seal_key(password, &salt);
|
||||||
let nonce = Nonce(*b"unique nonce 123 1231233"); // 24 bytes for XChaCha20Poly1305
|
let nonce = Nonce(*b"unique nonce 123 1231233"); // 24 bytes for XChaCha20Poly1305
|
||||||
let associated_data = b"associated data";
|
let associated_data = b"associated data";
|
||||||
let mut buffer = b"secret data".to_vec();
|
let mut buffer = b"secret data".to_vec();
|
||||||
@@ -166,4 +226,18 @@ mod tests {
|
|||||||
let buffer = buffer.read();
|
let buffer = buffer.read();
|
||||||
assert_eq!(*buffer, b"secret data");
|
assert_eq!(*buffer, b"secret data");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
// We should fuzz this
|
||||||
|
pub fn test_nonce_increment() {
|
||||||
|
let mut nonce = Nonce([0u8; NONCE_LENGTH]);
|
||||||
|
nonce.increment();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
nonce.0,
|
||||||
|
[
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,19 +4,11 @@ use diesel::{
|
|||||||
dsl::{insert_into, update},
|
dsl::{insert_into, update},
|
||||||
};
|
};
|
||||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||||
use hmac::Mac as _;
|
|
||||||
use kameo::{Actor, Reply, messages};
|
use kameo::{Actor, Reply, messages};
|
||||||
use strum::{EnumDiscriminants, IntoDiscriminant};
|
use strum::{EnumDiscriminants, IntoDiscriminant};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
use crate::{
|
use crate::safe_cell::SafeCell;
|
||||||
crypto::{
|
|
||||||
KeyCell, derive_key,
|
|
||||||
encryption::v1::{self, Nonce},
|
|
||||||
integrity::v1::HmacSha256,
|
|
||||||
},
|
|
||||||
safe_cell::SafeCell,
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{
|
db::{
|
||||||
self,
|
self,
|
||||||
@@ -25,6 +17,9 @@ use crate::{
|
|||||||
},
|
},
|
||||||
safe_cell::SafeCellHandle as _,
|
safe_cell::SafeCellHandle as _,
|
||||||
};
|
};
|
||||||
|
use encryption::v1::{self, KeyCell, Nonce};
|
||||||
|
|
||||||
|
pub mod encryption;
|
||||||
|
|
||||||
#[derive(Default, EnumDiscriminants)]
|
#[derive(Default, EnumDiscriminants)]
|
||||||
#[strum_discriminants(derive(Reply), vis(pub), name(KeyHolderState))]
|
#[strum_discriminants(derive(Reply), vis(pub), name(KeyHolderState))]
|
||||||
@@ -40,28 +35,36 @@ enum State {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("Keyholder is already bootstrapped")]
|
#[error("Keyholder is already bootstrapped")]
|
||||||
|
#[diagnostic(code(arbiter::keyholder::already_bootstrapped))]
|
||||||
AlreadyBootstrapped,
|
AlreadyBootstrapped,
|
||||||
#[error("Keyholder is not bootstrapped")]
|
#[error("Keyholder is not bootstrapped")]
|
||||||
|
#[diagnostic(code(arbiter::keyholder::not_bootstrapped))]
|
||||||
NotBootstrapped,
|
NotBootstrapped,
|
||||||
#[error("Invalid key provided")]
|
#[error("Invalid key provided")]
|
||||||
|
#[diagnostic(code(arbiter::keyholder::invalid_key))]
|
||||||
InvalidKey,
|
InvalidKey,
|
||||||
|
|
||||||
#[error("Requested aead entry not found")]
|
#[error("Requested aead entry not found")]
|
||||||
|
#[diagnostic(code(arbiter::keyholder::aead_not_found))]
|
||||||
NotFound,
|
NotFound,
|
||||||
|
|
||||||
#[error("Encryption error: {0}")]
|
#[error("Encryption error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter::keyholder::encryption_error))]
|
||||||
Encryption(#[from] chacha20poly1305::aead::Error),
|
Encryption(#[from] chacha20poly1305::aead::Error),
|
||||||
|
|
||||||
#[error("Database error: {0}")]
|
#[error("Database error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter::keyholder::database_error))]
|
||||||
DatabaseConnection(#[from] db::PoolError),
|
DatabaseConnection(#[from] db::PoolError),
|
||||||
|
|
||||||
#[error("Database transaction error: {0}")]
|
#[error("Database transaction error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter::keyholder::database_transaction_error))]
|
||||||
DatabaseTransaction(#[from] diesel::result::Error),
|
DatabaseTransaction(#[from] diesel::result::Error),
|
||||||
|
|
||||||
#[error("Broken database")]
|
#[error("Broken database")]
|
||||||
|
#[diagnostic(code(arbiter::keyholder::broken_database))]
|
||||||
BrokenDatabase,
|
BrokenDatabase,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +114,8 @@ impl KeyHolder {
|
|||||||
.first(conn)
|
.first(conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut nonce = Nonce::try_from(current_nonce.as_slice()).map_err(|_| {
|
let mut nonce =
|
||||||
|
v1::Nonce::try_from(current_nonce.as_slice()).map_err(|_| {
|
||||||
error!(
|
error!(
|
||||||
"Broken database: invalid nonce for root key history id={}",
|
"Broken database: invalid nonce for root key history id={}",
|
||||||
root_key_id
|
root_key_id
|
||||||
@@ -140,12 +144,12 @@ impl KeyHolder {
|
|||||||
return Err(Error::AlreadyBootstrapped);
|
return Err(Error::AlreadyBootstrapped);
|
||||||
}
|
}
|
||||||
let salt = v1::generate_salt();
|
let salt = v1::generate_salt();
|
||||||
let mut seal_key = derive_key(seal_key_raw, &salt);
|
let mut seal_key = v1::derive_seal_key(seal_key_raw, &salt);
|
||||||
let mut root_key = KeyCell::new_secure_random();
|
let mut root_key = KeyCell::new_secure_random();
|
||||||
|
|
||||||
// Zero nonces are fine because they are one-time
|
// Zero nonces are fine because they are one-time
|
||||||
let root_key_nonce = Nonce::default();
|
let root_key_nonce = v1::Nonce::default();
|
||||||
let data_encryption_nonce = Nonce::default();
|
let data_encryption_nonce = v1::Nonce::default();
|
||||||
|
|
||||||
let root_key_ciphertext: Vec<u8> = root_key.0.read_inline(|reader| {
|
let root_key_ciphertext: Vec<u8> = root_key.0.read_inline(|reader| {
|
||||||
let root_key_reader = reader.as_slice();
|
let root_key_reader = reader.as_slice();
|
||||||
@@ -210,6 +214,7 @@ impl KeyHolder {
|
|||||||
let mut conn = self.db.get().await?;
|
let mut conn = self.db.get().await?;
|
||||||
schema::root_key_history::table
|
schema::root_key_history::table
|
||||||
.filter(schema::root_key_history::id.eq(*root_key_history_id))
|
.filter(schema::root_key_history::id.eq(*root_key_history_id))
|
||||||
|
.select(schema::root_key_history::data_encryption_nonce)
|
||||||
.select(RootKeyHistory::as_select())
|
.select(RootKeyHistory::as_select())
|
||||||
.first(&mut conn)
|
.first(&mut conn)
|
||||||
.await?
|
.await?
|
||||||
@@ -220,7 +225,7 @@ impl KeyHolder {
|
|||||||
error!("Broken database: invalid salt for root key");
|
error!("Broken database: invalid salt for root key");
|
||||||
Error::BrokenDatabase
|
Error::BrokenDatabase
|
||||||
})?;
|
})?;
|
||||||
let mut seal_key = derive_key(seal_key_raw, &salt);
|
let mut seal_key = v1::derive_seal_key(seal_key_raw, &salt);
|
||||||
|
|
||||||
let mut root_key = SafeCell::new(current_key.ciphertext.clone());
|
let mut root_key = SafeCell::new(current_key.ciphertext.clone());
|
||||||
|
|
||||||
@@ -240,7 +245,7 @@ impl KeyHolder {
|
|||||||
|
|
||||||
self.state = State::Unsealed {
|
self.state = State::Unsealed {
|
||||||
root_key_history_id: current_key.id,
|
root_key_history_id: current_key.id,
|
||||||
root_key: KeyCell::try_from(root_key).map_err(|err| {
|
root_key: v1::KeyCell::try_from(root_key).map_err(|err| {
|
||||||
error!(?err, "Broken database: invalid encryption key size");
|
error!(?err, "Broken database: invalid encryption key size");
|
||||||
Error::BrokenDatabase
|
Error::BrokenDatabase
|
||||||
})?,
|
})?,
|
||||||
@@ -251,6 +256,7 @@ impl KeyHolder {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decrypts the `aead_encrypted` entry with the given ID and returns the plaintext
|
||||||
#[message]
|
#[message]
|
||||||
pub async fn decrypt(&mut self, aead_id: i32) -> Result<SafeCell<Vec<u8>>, Error> {
|
pub async fn decrypt(&mut self, aead_id: i32) -> Result<SafeCell<Vec<u8>>, Error> {
|
||||||
let State::Unsealed { root_key, .. } = &mut self.state else {
|
let State::Unsealed { root_key, .. } = &mut self.state else {
|
||||||
@@ -286,7 +292,6 @@ impl KeyHolder {
|
|||||||
let State::Unsealed {
|
let State::Unsealed {
|
||||||
root_key,
|
root_key,
|
||||||
root_key_history_id,
|
root_key_history_id,
|
||||||
..
|
|
||||||
} = &mut self.state
|
} = &mut self.state
|
||||||
else {
|
else {
|
||||||
return Err(Error::NotBootstrapped);
|
return Err(Error::NotBootstrapped);
|
||||||
@@ -324,60 +329,6 @@ impl KeyHolder {
|
|||||||
self.state.discriminant()
|
self.state.discriminant()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[message]
|
|
||||||
pub fn sign_integrity(&mut self, mac_input: Vec<u8>) -> Result<(i32, Vec<u8>), Error> {
|
|
||||||
let State::Unsealed {
|
|
||||||
root_key,
|
|
||||||
root_key_history_id,
|
|
||||||
} = &mut self.state
|
|
||||||
else {
|
|
||||||
return Err(Error::NotBootstrapped);
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut hmac = root_key
|
|
||||||
.0
|
|
||||||
.read_inline(|k| match HmacSha256::new_from_slice(k) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => unreachable!("HMAC accepts keys of any size"),
|
|
||||||
});
|
|
||||||
hmac.update(&root_key_history_id.to_be_bytes());
|
|
||||||
hmac.update(&mac_input);
|
|
||||||
|
|
||||||
let mac = hmac.finalize().into_bytes().to_vec();
|
|
||||||
Ok((*root_key_history_id, mac))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[message]
|
|
||||||
pub fn verify_integrity(
|
|
||||||
&mut self,
|
|
||||||
mac_input: Vec<u8>,
|
|
||||||
expected_mac: Vec<u8>,
|
|
||||||
key_version: i32,
|
|
||||||
) -> Result<bool, Error> {
|
|
||||||
let State::Unsealed {
|
|
||||||
root_key,
|
|
||||||
root_key_history_id,
|
|
||||||
} = &mut self.state
|
|
||||||
else {
|
|
||||||
return Err(Error::NotBootstrapped);
|
|
||||||
};
|
|
||||||
|
|
||||||
if *root_key_history_id != key_version {
|
|
||||||
return Ok(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut hmac = root_key
|
|
||||||
.0
|
|
||||||
.read_inline(|k| match HmacSha256::new_from_slice(k) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(_) => unreachable!("HMAC accepts keys of any size"),
|
|
||||||
});
|
|
||||||
hmac.update(&key_version.to_be_bytes());
|
|
||||||
hmac.update(&mac_input);
|
|
||||||
|
|
||||||
Ok(hmac.verify_slice(&expected_mac).is_ok())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[message]
|
#[message]
|
||||||
pub fn seal(&mut self) -> Result<(), Error> {
|
pub fn seal(&mut self) -> Result<(), Error> {
|
||||||
let State::Unsealed {
|
let State::Unsealed {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use kameo::actor::{ActorRef, Spawn};
|
use kameo::actor::{ActorRef, Spawn};
|
||||||
|
use miette::Diagnostic;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -16,12 +17,14 @@ pub mod flow_coordinator;
|
|||||||
pub mod keyholder;
|
pub mod keyholder;
|
||||||
pub mod user_agent;
|
pub mod user_agent;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
pub enum SpawnError {
|
pub enum SpawnError {
|
||||||
#[error("Failed to spawn Bootstrapper actor")]
|
#[error("Failed to spawn Bootstrapper actor")]
|
||||||
|
#[diagnostic(code(SpawnError::Bootstrapper))]
|
||||||
Bootstrapper(#[from] bootstrap::Error),
|
Bootstrapper(#[from] bootstrap::Error),
|
||||||
|
|
||||||
#[error("Failed to spawn KeyHolder actor")]
|
#[error("Failed to spawn KeyHolder actor")]
|
||||||
|
#[diagnostic(code(SpawnError::KeyHolder))]
|
||||||
KeyHolder(#[from] keyholder::Error),
|
KeyHolder(#[from] keyholder::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,27 +30,11 @@ pub enum Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
#[track_caller]
|
fn internal(details: impl Into<String>) -> Self {
|
||||||
pub(super) fn internal(details: impl Into<String>, err: &impl std::fmt::Debug) -> Self {
|
Self::Internal {
|
||||||
let details = details.into();
|
details: details.into(),
|
||||||
let caller = std::panic::Location::caller();
|
|
||||||
error!(
|
|
||||||
caller_file = %caller.file(),
|
|
||||||
caller_line = caller.line(),
|
|
||||||
caller_column = caller.column(),
|
|
||||||
details = %details,
|
|
||||||
error = ?err,
|
|
||||||
"Internal error"
|
|
||||||
);
|
|
||||||
|
|
||||||
Self::Internal { details }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<diesel::result::Error> for Error {
|
|
||||||
fn from(e: diesel::result::Error) -> Self {
|
|
||||||
Self::internal("Database error", &e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
use arbiter_proto::transport::Bi;
|
use arbiter_proto::transport::Bi;
|
||||||
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, update};
|
use diesel::{ExpressionMethods as _, OptionalExtension as _, QueryDsl, update};
|
||||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
use diesel_async::RunQueryDsl;
|
||||||
use kameo::actor::ActorRef;
|
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
use super::Error;
|
use super::Error;
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::{
|
actors::{
|
||||||
bootstrap::ConsumeToken,
|
bootstrap::ConsumeToken,
|
||||||
keyholder::KeyHolder,
|
user_agent::{AuthPublicKey, UserAgentConnection, auth::Outbound},
|
||||||
user_agent::{AuthPublicKey, UserAgentConnection, UserAgentCredentials, auth::Outbound},
|
|
||||||
},
|
},
|
||||||
crypto::integrity,
|
db::schema,
|
||||||
db::{DatabasePool, schema::useragent_client},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ChallengeRequest {
|
pub struct ChallengeRequest {
|
||||||
@@ -43,166 +40,61 @@ smlang::statemachine!(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Returns the current nonce, ready to use for the challenge nonce.
|
async fn create_nonce(db: &crate::db::DatabasePool, pubkey_bytes: &[u8]) -> Result<i32, Error> {
|
||||||
async fn get_current_nonce_and_id(
|
let mut db_conn = db.get().await.map_err(|e| {
|
||||||
db: &DatabasePool,
|
error!(error = ?e, "Database pool error");
|
||||||
key: &AuthPublicKey,
|
Error::internal("Database unavailable")
|
||||||
) -> Result<(i32, i32), Error> {
|
})?;
|
||||||
let mut db_conn = db
|
|
||||||
.get()
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::internal("Database unavailable", &e))?;
|
|
||||||
db_conn
|
db_conn
|
||||||
.exclusive_transaction(|conn| {
|
.exclusive_transaction(|conn| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
useragent_client::table
|
let current_nonce = schema::useragent_client::table
|
||||||
.filter(useragent_client::public_key.eq(key.to_stored_bytes()))
|
.filter(schema::useragent_client::public_key.eq(pubkey_bytes.to_vec()))
|
||||||
.filter(useragent_client::key_type.eq(key.key_type()))
|
.select(schema::useragent_client::nonce)
|
||||||
.select((useragent_client::id, useragent_client::nonce))
|
.first::<i32>(conn)
|
||||||
.first::<(i32, i32)>(conn)
|
.await?;
|
||||||
.await
|
|
||||||
|
update(schema::useragent_client::table)
|
||||||
|
.filter(schema::useragent_client::public_key.eq(pubkey_bytes.to_vec()))
|
||||||
|
.set(schema::useragent_client::nonce.eq(current_nonce + 1))
|
||||||
|
.execute(conn)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Result::<_, diesel::result::Error>::Ok(current_nonce)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.optional()
|
.optional()
|
||||||
.map_err(|e| Error::internal("Database operation failed", &e))?
|
.map_err(|e| {
|
||||||
|
error!(error = ?e, "Database error");
|
||||||
|
Error::internal("Database operation failed")
|
||||||
|
})?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
error!(?key, "Public key not found in database");
|
error!(?pubkey_bytes, "Public key not found in database");
|
||||||
Error::UnregisteredPublicKey
|
Error::UnregisteredPublicKey
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify_integrity(
|
async fn register_key(db: &crate::db::DatabasePool, pubkey: &AuthPublicKey) -> Result<(), Error> {
|
||||||
db: &DatabasePool,
|
|
||||||
keyholder: &ActorRef<KeyHolder>,
|
|
||||||
pubkey: &AuthPublicKey,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let mut db_conn = db
|
|
||||||
.get()
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::internal("Database unavailable", &e))?;
|
|
||||||
|
|
||||||
let (id, nonce) = get_current_nonce_and_id(db, pubkey).await?;
|
|
||||||
|
|
||||||
let attestation_status = integrity::check_entity_attestation(
|
|
||||||
&mut db_conn,
|
|
||||||
keyholder,
|
|
||||||
&UserAgentCredentials {
|
|
||||||
pubkey: pubkey.clone(),
|
|
||||||
nonce,
|
|
||||||
},
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::internal("Integrity verification failed", &e))?;
|
|
||||||
|
|
||||||
use integrity::AttestationStatus as AS;
|
|
||||||
// SAFETY (policy): challenge auth must work in both vault states.
|
|
||||||
// While sealed, integrity checks can only report `Unavailable` because key material is not
|
|
||||||
// accessible. While unsealed, the same check can report `Attested`.
|
|
||||||
// This path intentionally accepts both outcomes to keep challenge auth available across state
|
|
||||||
// transitions; stricter verification is enforced in sensitive post-auth flows.
|
|
||||||
match attestation_status {
|
|
||||||
AS::Attested | AS::Unavailable => Ok(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_nonce(
|
|
||||||
db: &DatabasePool,
|
|
||||||
keyholder: &ActorRef<KeyHolder>,
|
|
||||||
pubkey: &AuthPublicKey,
|
|
||||||
) -> Result<i32, Error> {
|
|
||||||
let mut db_conn = db
|
|
||||||
.get()
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::internal("Database unavailable", &e))?;
|
|
||||||
let new_nonce = db_conn
|
|
||||||
.exclusive_transaction(|conn| {
|
|
||||||
Box::pin(async move {
|
|
||||||
let (id, new_nonce): (i32, i32) = update(useragent_client::table)
|
|
||||||
.filter(useragent_client::public_key.eq(pubkey.to_stored_bytes()))
|
|
||||||
.filter(useragent_client::key_type.eq(pubkey.key_type()))
|
|
||||||
.set(useragent_client::nonce.eq(useragent_client::nonce + 1))
|
|
||||||
.returning((useragent_client::id, useragent_client::nonce))
|
|
||||||
.get_result(conn)
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::internal("Database operation failed", &e))?;
|
|
||||||
|
|
||||||
integrity::sign_entity(
|
|
||||||
conn,
|
|
||||||
keyholder,
|
|
||||||
&UserAgentCredentials {
|
|
||||||
pubkey: pubkey.clone(),
|
|
||||||
nonce: new_nonce,
|
|
||||||
},
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|e| Error::internal("Database error", &e))?;
|
|
||||||
|
|
||||||
Result::<_, Error>::Ok(new_nonce)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
Ok(new_nonce)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn register_key(
|
|
||||||
db: &DatabasePool,
|
|
||||||
keyholder: &ActorRef<KeyHolder>,
|
|
||||||
pubkey: &AuthPublicKey,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let pubkey_bytes = pubkey.to_stored_bytes();
|
let pubkey_bytes = pubkey.to_stored_bytes();
|
||||||
let key_type = pubkey.key_type();
|
let key_type = pubkey.key_type();
|
||||||
let mut conn = db
|
let mut conn = db.get().await.map_err(|e| {
|
||||||
.get()
|
error!(error = ?e, "Database pool error");
|
||||||
.await
|
Error::internal("Database unavailable")
|
||||||
.map_err(|e| Error::internal("Database unavailable", &e))?;
|
})?;
|
||||||
|
|
||||||
conn.transaction(|conn| {
|
diesel::insert_into(schema::useragent_client::table)
|
||||||
Box::pin(async move {
|
|
||||||
const NONCE_START: i32 = 1;
|
|
||||||
|
|
||||||
let id: i32 = diesel::insert_into(useragent_client::table)
|
|
||||||
.values((
|
.values((
|
||||||
useragent_client::public_key.eq(pubkey_bytes),
|
schema::useragent_client::public_key.eq(pubkey_bytes),
|
||||||
useragent_client::nonce.eq(NONCE_START),
|
schema::useragent_client::nonce.eq(1),
|
||||||
useragent_client::key_type.eq(key_type),
|
schema::useragent_client::key_type.eq(key_type),
|
||||||
))
|
))
|
||||||
.returning(useragent_client::id)
|
.execute(&mut conn)
|
||||||
.get_result(conn)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Error::internal("Database operation failed", &e))?;
|
.map_err(|e| {
|
||||||
|
error!(error = ?e, "Database error");
|
||||||
if let Err(e) = integrity::sign_entity(
|
Error::internal("Database operation failed")
|
||||||
conn,
|
})?;
|
||||||
keyholder,
|
|
||||||
&UserAgentCredentials {
|
|
||||||
pubkey: pubkey.clone(),
|
|
||||||
nonce: NONCE_START,
|
|
||||||
},
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
match e {
|
|
||||||
integrity::Error::Keyholder(
|
|
||||||
crate::actors::keyholder::Error::NotBootstrapped,
|
|
||||||
) => {
|
|
||||||
// IMPORTANT: bootstrap-token auth must work before the vault has a root key.
|
|
||||||
// We intentionally allow creating the DB row first and backfill envelopes
|
|
||||||
// after bootstrap/unseal to keep the bootstrap flow possible.
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
return Err(Error::internal("Failed to register public key", &other));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Result::<_, Error>::Ok(())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -228,9 +120,8 @@ where
|
|||||||
&mut self,
|
&mut self,
|
||||||
ChallengeRequest { pubkey }: ChallengeRequest,
|
ChallengeRequest { pubkey }: ChallengeRequest,
|
||||||
) -> Result<ChallengeContext, Self::Error> {
|
) -> Result<ChallengeContext, Self::Error> {
|
||||||
verify_integrity(&self.conn.db, &self.conn.actors.key_holder, &pubkey).await?;
|
let stored_bytes = pubkey.to_stored_bytes();
|
||||||
|
let nonce = create_nonce(&self.conn.db, &stored_bytes).await?;
|
||||||
let nonce = create_nonce(&self.conn.db, &self.conn.actors.key_holder, &pubkey).await?;
|
|
||||||
|
|
||||||
self.transport
|
self.transport
|
||||||
.send(Ok(Outbound::AuthChallenge { nonce }))
|
.send(Ok(Outbound::AuthChallenge { nonce }))
|
||||||
@@ -260,32 +151,25 @@ where
|
|||||||
token: token.clone(),
|
token: token.clone(),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(|e| Error::internal("Failed to consume bootstrap token", &e))?;
|
.map_err(|e| {
|
||||||
|
error!(?e, "Failed to consume bootstrap token");
|
||||||
|
Error::internal("Failed to consume bootstrap token")
|
||||||
|
})?;
|
||||||
|
|
||||||
if !token_ok {
|
if !token_ok {
|
||||||
error!("Invalid bootstrap token provided");
|
error!("Invalid bootstrap token provided");
|
||||||
return Err(Error::InvalidBootstrapToken);
|
return Err(Error::InvalidBootstrapToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
match token_ok {
|
register_key(&self.conn.db, &pubkey).await?;
|
||||||
true => {
|
|
||||||
register_key(&self.conn.db, &self.conn.actors.key_holder, &pubkey).await?;
|
|
||||||
self.transport
|
self.transport
|
||||||
.send(Ok(Outbound::AuthSuccess))
|
.send(Ok(Outbound::AuthSuccess))
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::Transport)?;
|
.map_err(|_| Error::Transport)?;
|
||||||
|
|
||||||
Ok(pubkey)
|
Ok(pubkey)
|
||||||
}
|
}
|
||||||
false => {
|
|
||||||
error!("Invalid bootstrap token provided");
|
|
||||||
self.transport
|
|
||||||
.send(Err(Error::InvalidBootstrapToken))
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::Transport)?;
|
|
||||||
Err(Error::InvalidBootstrapToken)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(missing_docs)]
|
#[allow(missing_docs)]
|
||||||
#[allow(clippy::unused_unit)]
|
#[allow(clippy::unused_unit)]
|
||||||
@@ -326,21 +210,13 @@ where
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match valid {
|
if valid {
|
||||||
true => {
|
|
||||||
self.transport
|
self.transport
|
||||||
.send(Ok(Outbound::AuthSuccess))
|
.send(Ok(Outbound::AuthSuccess))
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::Transport)?;
|
.map_err(|_| Error::Transport)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(key.clone())
|
Ok(key.clone())
|
||||||
}
|
}
|
||||||
false => {
|
|
||||||
self.transport
|
|
||||||
.send(Err(Error::InvalidChallengeSolution))
|
|
||||||
.await
|
|
||||||
.map_err(|_| Error::Transport)?;
|
|
||||||
Err(Error::InvalidChallengeSolution)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
actors::{GlobalActors, client::ClientProfile},
|
actors::{GlobalActors, client::ClientProfile},
|
||||||
crypto::integrity::Integrable,
|
|
||||||
db::{self, models::KeyType},
|
db::{self, models::KeyType},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -14,16 +13,6 @@ pub enum AuthPublicKey {
|
|||||||
Rsa(rsa::RsaPublicKey),
|
Rsa(rsa::RsaPublicKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct UserAgentCredentials {
|
|
||||||
pub pubkey: AuthPublicKey,
|
|
||||||
pub nonce: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Integrable for UserAgentCredentials {
|
|
||||||
const KIND: &'static str = "useragent_credentials";
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AuthPublicKey {
|
impl AuthPublicKey {
|
||||||
/// Canonical bytes stored in DB and echoed back in the challenge.
|
/// Canonical bytes stored in DB and echoed back in the challenge.
|
||||||
/// Ed25519: raw 32 bytes. ECDSA: SEC1 compressed 33 bytes. RSA: DER-encoded SPKI.
|
/// Ed25519: raw 32 bytes. ECDSA: SEC1 compressed 33 bytes. RSA: DER-encoded SPKI.
|
||||||
@@ -103,18 +92,3 @@ pub mod session;
|
|||||||
|
|
||||||
pub use auth::authenticate;
|
pub use auth::authenticate;
|
||||||
pub use session::UserAgentSession;
|
pub use session::UserAgentSession;
|
||||||
|
|
||||||
use crate::crypto::integrity::hashing::Hashable;
|
|
||||||
|
|
||||||
impl Hashable for AuthPublicKey {
|
|
||||||
fn hash<H: sha2::Digest>(&self, hasher: &mut H) {
|
|
||||||
hasher.update(self.to_stored_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hashable for UserAgentCredentials {
|
|
||||||
fn hash<H: sha2::Digest>(&self, hasher: &mut H) {
|
|
||||||
self.pubkey.hash(hasher);
|
|
||||||
self.nonce.hash(hasher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,97 +1,40 @@
|
|||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use alloy::{consensus::TxEip1559, primitives::Address, signers::Signature};
|
use alloy::primitives::Address;
|
||||||
use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
|
use chacha20poly1305::{AeadInPlace, XChaCha20Poly1305, XNonce, aead::KeyInit};
|
||||||
use diesel::{ExpressionMethods as _, QueryDsl as _, SelectableHelper};
|
use diesel::sql_types::ops::Add;
|
||||||
|
use diesel::{BoolExpressionMethods as _, ExpressionMethods as _, QueryDsl as _, SelectableHelper};
|
||||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||||
use kameo::error::SendError;
|
use kameo::error::SendError;
|
||||||
use kameo::messages;
|
|
||||||
use kameo::prelude::Context;
|
use kameo::prelude::Context;
|
||||||
|
use kameo::{message, messages};
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
use x25519_dalek::{EphemeralSecret, PublicKey};
|
use x25519_dalek::{EphemeralSecret, PublicKey};
|
||||||
|
|
||||||
|
use crate::actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer;
|
||||||
use crate::actors::keyholder::KeyHolderState;
|
use crate::actors::keyholder::KeyHolderState;
|
||||||
use crate::actors::user_agent::session::Error;
|
use crate::actors::user_agent::session::Error;
|
||||||
use crate::db::models::{
|
use crate::db::models::{
|
||||||
EvmWalletAccess, NewEvmWalletAccess, ProgramClient, ProgramClientMetadata,
|
CoreEvmWalletAccess, EvmWalletAccess, NewEvmWalletAccess, ProgramClient, ProgramClientMetadata,
|
||||||
};
|
};
|
||||||
|
use crate::db::schema::evm_wallet_access;
|
||||||
use crate::evm::policies::{Grant, SpecificGrant};
|
use crate::evm::policies::{Grant, SpecificGrant};
|
||||||
use crate::safe_cell::SafeCell;
|
use crate::safe_cell::SafeCell;
|
||||||
use crate::{
|
|
||||||
actors::flow_coordinator::client_connect_approval::ClientApprovalAnswer,
|
|
||||||
crypto::integrity::{self, Verified},
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::{
|
actors::{
|
||||||
evm::{
|
evm::{
|
||||||
ClientSignTransaction, Generate, ListWallets, SignTransactionError as EvmSignError,
|
Generate, ListWallets, UseragentCreateGrant, UseragentDeleteGrant, UseragentListGrants,
|
||||||
UseragentCreateGrant, UseragentDeleteGrant, UseragentListGrants,
|
|
||||||
},
|
},
|
||||||
keyholder::{self, Bootstrap, TryUnseal},
|
keyholder::{self, Bootstrap, TryUnseal},
|
||||||
user_agent::session::{
|
user_agent::session::{
|
||||||
UserAgentSession,
|
UserAgentSession,
|
||||||
state::{UnsealContext, UserAgentEvents, UserAgentStates},
|
state::{UnsealContext, UserAgentEvents, UserAgentStates},
|
||||||
},
|
},
|
||||||
user_agent::{AuthPublicKey, UserAgentCredentials},
|
|
||||||
},
|
},
|
||||||
db::schema::useragent_client,
|
|
||||||
safe_cell::SafeCellHandle as _,
|
safe_cell::SafeCellHandle as _,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn is_vault_sealed_from_evm<M>(err: &SendError<M, crate::actors::evm::Error>) -> bool {
|
|
||||||
matches!(
|
|
||||||
err,
|
|
||||||
SendError::HandlerError(crate::actors::evm::Error::Keyholder(
|
|
||||||
keyholder::Error::NotBootstrapped
|
|
||||||
)) | SendError::HandlerError(crate::actors::evm::Error::Integrity(
|
|
||||||
crate::crypto::integrity::Error::Keyholder(keyholder::Error::NotBootstrapped)
|
|
||||||
))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UserAgentSession {
|
impl UserAgentSession {
|
||||||
async fn backfill_useragent_integrity(&self) -> Result<(), Error> {
|
|
||||||
let mut conn = self.props.db.get().await?;
|
|
||||||
let keyholder = self.props.actors.key_holder.clone();
|
|
||||||
|
|
||||||
conn.transaction(|conn| {
|
|
||||||
Box::pin(async move {
|
|
||||||
let rows: Vec<(i32, i32, Vec<u8>, crate::db::models::KeyType)> =
|
|
||||||
useragent_client::table
|
|
||||||
.select((
|
|
||||||
useragent_client::id,
|
|
||||||
useragent_client::nonce,
|
|
||||||
useragent_client::public_key,
|
|
||||||
useragent_client::key_type,
|
|
||||||
))
|
|
||||||
.load(conn)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
for (id, nonce, public_key, key_type) in rows {
|
|
||||||
let pubkey = AuthPublicKey::try_from((key_type, public_key)).map_err(|e| {
|
|
||||||
Error::internal(format!("Invalid user-agent key in db: {e}"))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
integrity::sign_entity(
|
|
||||||
conn,
|
|
||||||
&keyholder,
|
|
||||||
&UserAgentCredentials { pubkey, nonce },
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
Error::internal(format!("Failed to backfill user-agent integrity: {e}"))
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Result::<_, Error>::Ok(())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn take_unseal_secret(&mut self) -> Result<(EphemeralSecret, PublicKey), Error> {
|
fn take_unseal_secret(&mut self) -> Result<(EphemeralSecret, PublicKey), Error> {
|
||||||
let UserAgentStates::WaitingForUnsealKey(unseal_context) = self.state.state() else {
|
let UserAgentStates::WaitingForUnsealKey(unseal_context) = self.state.state() else {
|
||||||
error!("Received encrypted key in invalid state");
|
error!("Received encrypted key in invalid state");
|
||||||
@@ -169,24 +112,6 @@ pub enum BootstrapError {
|
|||||||
General(#[from] super::Error),
|
General(#[from] super::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum SignTransactionError {
|
|
||||||
#[error("Policy evaluation failed")]
|
|
||||||
Vet(#[from] crate::evm::VetError),
|
|
||||||
|
|
||||||
#[error("Internal signing error")]
|
|
||||||
Internal,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum GrantMutationError {
|
|
||||||
#[error("Vault is sealed")]
|
|
||||||
VaultSealed,
|
|
||||||
|
|
||||||
#[error("Internal grant mutation error")]
|
|
||||||
Internal,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[messages]
|
#[messages]
|
||||||
impl UserAgentSession {
|
impl UserAgentSession {
|
||||||
#[message]
|
#[message]
|
||||||
@@ -249,7 +174,6 @@ impl UserAgentSession {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
self.backfill_useragent_integrity().await?;
|
|
||||||
info!("Successfully unsealed key with client-provided key");
|
info!("Successfully unsealed key with client-provided key");
|
||||||
self.transition(UserAgentEvents::ReceivedValidKey)?;
|
self.transition(UserAgentEvents::ReceivedValidKey)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -311,7 +235,6 @@ impl UserAgentSession {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
self.backfill_useragent_integrity().await?;
|
|
||||||
info!("Successfully bootstrapped vault with client-provided key");
|
info!("Successfully bootstrapped vault with client-provided key");
|
||||||
self.transition(UserAgentEvents::ReceivedValidKey)?;
|
self.transition(UserAgentEvents::ReceivedValidKey)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -385,15 +308,12 @@ impl UserAgentSession {
|
|||||||
#[messages]
|
#[messages]
|
||||||
impl UserAgentSession {
|
impl UserAgentSession {
|
||||||
#[message]
|
#[message]
|
||||||
pub(crate) async fn handle_grant_list(
|
pub(crate) async fn handle_grant_list(&mut self) -> Result<Vec<Grant<SpecificGrant>>, Error> {
|
||||||
&mut self,
|
|
||||||
) -> Result<Vec<Grant<SpecificGrant>>, GrantMutationError> {
|
|
||||||
match self.props.actors.evm.ask(UseragentListGrants {}).await {
|
match self.props.actors.evm.ask(UseragentListGrants {}).await {
|
||||||
Ok(grants) => Ok(grants),
|
Ok(grants) => Ok(grants),
|
||||||
Err(err) if is_vault_sealed_from_evm(&err) => Err(GrantMutationError::VaultSealed),
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, "EVM grant list failed");
|
error!(?err, "EVM grant list failed");
|
||||||
Err(GrantMutationError::Internal)
|
Err(Error::internal("Failed to list EVM grants"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -403,7 +323,7 @@ impl UserAgentSession {
|
|||||||
&mut self,
|
&mut self,
|
||||||
basic: crate::evm::policies::SharedGrantSettings,
|
basic: crate::evm::policies::SharedGrantSettings,
|
||||||
grant: crate::evm::policies::SpecificGrant,
|
grant: crate::evm::policies::SpecificGrant,
|
||||||
) -> Result<Verified<i32>, GrantMutationError> {
|
) -> Result<i32, Error> {
|
||||||
match self
|
match self
|
||||||
.props
|
.props
|
||||||
.actors
|
.actors
|
||||||
@@ -412,62 +332,26 @@ impl UserAgentSession {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(grant_id) => Ok(grant_id),
|
Ok(grant_id) => Ok(grant_id),
|
||||||
Err(err) if is_vault_sealed_from_evm(&err) => Err(GrantMutationError::VaultSealed),
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, "EVM grant create failed");
|
error!(?err, "EVM grant create failed");
|
||||||
Err(GrantMutationError::Internal)
|
Err(Error::internal("Failed to create EVM grant"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[message]
|
#[message]
|
||||||
pub(crate) async fn handle_grant_delete(
|
pub(crate) async fn handle_grant_delete(&mut self, grant_id: i32) -> Result<(), Error> {
|
||||||
&mut self,
|
|
||||||
grant_id: i32,
|
|
||||||
) -> Result<(), GrantMutationError> {
|
|
||||||
match self
|
match self
|
||||||
.props
|
.props
|
||||||
.actors
|
.actors
|
||||||
.evm
|
.evm
|
||||||
.ask(UseragentDeleteGrant {
|
.ask(UseragentDeleteGrant { grant_id })
|
||||||
_grant_id: grant_id,
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(()) => Ok(()),
|
Ok(()) => Ok(()),
|
||||||
Err(err) if is_vault_sealed_from_evm(&err) => Err(GrantMutationError::VaultSealed),
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(?err, "EVM grant delete failed");
|
error!(?err, "EVM grant delete failed");
|
||||||
Err(GrantMutationError::Internal)
|
Err(Error::internal("Failed to delete EVM grant"))
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[message]
|
|
||||||
pub(crate) async fn handle_sign_transaction(
|
|
||||||
&mut self,
|
|
||||||
client_id: i32,
|
|
||||||
wallet_address: Address,
|
|
||||||
transaction: TxEip1559,
|
|
||||||
) -> Result<Signature, SignTransactionError> {
|
|
||||||
match self
|
|
||||||
.props
|
|
||||||
.actors
|
|
||||||
.evm
|
|
||||||
.ask(ClientSignTransaction {
|
|
||||||
client_id,
|
|
||||||
wallet_address,
|
|
||||||
transaction,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(signature) => Ok(signature),
|
|
||||||
Err(SendError::HandlerError(EvmSignError::Vet(vet_error))) => {
|
|
||||||
Err(SignTransactionError::Vet(vet_error))
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
error!(?err, "EVM sign transaction failed in user-agent session");
|
|
||||||
Err(SignTransactionError::Internal)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use miette::Diagnostic;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -10,24 +11,30 @@ use crate::{
|
|||||||
|
|
||||||
pub mod tls;
|
pub mod tls;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
pub enum InitError {
|
pub enum InitError {
|
||||||
#[error("Database setup failed: {0}")]
|
#[error("Database setup failed: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::init::database_setup))]
|
||||||
DatabaseSetup(#[from] db::DatabaseSetupError),
|
DatabaseSetup(#[from] db::DatabaseSetupError),
|
||||||
|
|
||||||
#[error("Connection acquire failed: {0}")]
|
#[error("Connection acquire failed: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::init::database_pool))]
|
||||||
DatabasePool(#[from] db::PoolError),
|
DatabasePool(#[from] db::PoolError),
|
||||||
|
|
||||||
#[error("Database query error: {0}")]
|
#[error("Database query error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::init::database_query))]
|
||||||
DatabaseQuery(#[from] diesel::result::Error),
|
DatabaseQuery(#[from] diesel::result::Error),
|
||||||
|
|
||||||
#[error("TLS initialization failed: {0}")]
|
#[error("TLS initialization failed: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::init::tls_init))]
|
||||||
Tls(#[from] tls::InitError),
|
Tls(#[from] tls::InitError),
|
||||||
|
|
||||||
#[error("Actor spawn failed: {0}")]
|
#[error("Actor spawn failed: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::init::actor_spawn))]
|
||||||
ActorSpawn(#[from] crate::actors::SpawnError),
|
ActorSpawn(#[from] crate::actors::SpawnError),
|
||||||
|
|
||||||
#[error("I/O Error: {0}")]
|
#[error("I/O Error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::init::io))]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::{net::Ipv4Addr, string::FromUtf8Error};
|
use std::{net::IpAddr, string::FromUtf8Error};
|
||||||
|
|
||||||
use diesel::{ExpressionMethods as _, QueryDsl, SelectableHelper as _};
|
use diesel::{ExpressionMethods as _, QueryDsl, SelectableHelper as _};
|
||||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||||
|
use miette::Diagnostic;
|
||||||
use pem::Pem;
|
use pem::Pem;
|
||||||
use rcgen::{
|
use rcgen::{
|
||||||
BasicConstraints, Certificate, CertificateParams, CertifiedIssuer, DistinguishedName, DnType,
|
BasicConstraints, Certificate, CertificateParams, CertifiedIssuer, DistinguishedName, DnType,
|
||||||
@@ -29,24 +29,30 @@ const ENCODE_CONFIG: pem::EncodeConfig = {
|
|||||||
pem::EncodeConfig::new().set_line_ending(line_ending)
|
pem::EncodeConfig::new().set_line_ending(line_ending)
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug, Diagnostic)]
|
||||||
pub enum InitError {
|
pub enum InitError {
|
||||||
#[error("Key generation error during TLS initialization: {0}")]
|
#[error("Key generation error during TLS initialization: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::tls_init::key_generation))]
|
||||||
KeyGeneration(#[from] rcgen::Error),
|
KeyGeneration(#[from] rcgen::Error),
|
||||||
|
|
||||||
#[error("Key invalid format: {0}")]
|
#[error("Key invalid format: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::tls_init::key_invalid_format))]
|
||||||
KeyInvalidFormat(#[from] FromUtf8Error),
|
KeyInvalidFormat(#[from] FromUtf8Error),
|
||||||
|
|
||||||
#[error("Key deserialization error: {0}")]
|
#[error("Key deserialization error: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::tls_init::key_deserialization))]
|
||||||
KeyDeserializationError(rcgen::Error),
|
KeyDeserializationError(rcgen::Error),
|
||||||
|
|
||||||
#[error("Database error during TLS initialization: {0}")]
|
#[error("Database error during TLS initialization: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::tls_init::database_error))]
|
||||||
DatabaseError(#[from] diesel::result::Error),
|
DatabaseError(#[from] diesel::result::Error),
|
||||||
|
|
||||||
#[error("Pem deserialization error during TLS initialization: {0}")]
|
#[error("Pem deserialization error during TLS initialization: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::tls_init::pem_deserialization))]
|
||||||
PemDeserializationError(#[from] rustls::pki_types::pem::Error),
|
PemDeserializationError(#[from] rustls::pki_types::pem::Error),
|
||||||
|
|
||||||
#[error("Database pool acquire error during TLS initialization: {0}")]
|
#[error("Database pool acquire error during TLS initialization: {0}")]
|
||||||
|
#[diagnostic(code(arbiter_server::tls_init::database_pool_acquire))]
|
||||||
DatabasePoolAcquire(#[from] db::PoolError),
|
DatabasePoolAcquire(#[from] db::PoolError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +116,9 @@ impl TlsCa {
|
|||||||
];
|
];
|
||||||
params
|
params
|
||||||
.subject_alt_names
|
.subject_alt_names
|
||||||
.push(SanType::IpAddress(Ipv4Addr::LOCALHOST.into()));
|
.push(SanType::IpAddress(IpAddr::from([
|
||||||
|
127, 0, 0, 1,
|
||||||
|
])));
|
||||||
|
|
||||||
let mut dn = DistinguishedName::new();
|
let mut dn = DistinguishedName::new();
|
||||||
dn.push(DnType::CommonName, "Arbiter Instance Leaf");
|
dn.push(DnType::CommonName, "Arbiter Instance Leaf");
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
pub mod v1;
|
|
||||||
|
|
||||||
pub use v1::*;
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
use argon2::password_hash::Salt as ArgonSalt;
|
|
||||||
|
|
||||||
use rand::{
|
|
||||||
Rng as _, SeedableRng,
|
|
||||||
rngs::{StdRng, SysRng},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ROOT_KEY_TAG: &[u8] = "arbiter/seal/v1".as_bytes();
|
|
||||||
pub const TAG: &[u8] = "arbiter/private-key/v1".as_bytes();
|
|
||||||
|
|
||||||
pub const NONCE_LENGTH: usize = 24;
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Nonce(pub [u8; NONCE_LENGTH]);
|
|
||||||
impl Nonce {
|
|
||||||
pub fn increment(&mut self) {
|
|
||||||
for i in (0..self.0.len()).rev() {
|
|
||||||
if self.0[i] == 0xFF {
|
|
||||||
self.0[i] = 0;
|
|
||||||
} else {
|
|
||||||
self.0[i] += 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_vec(&self) -> Vec<u8> {
|
|
||||||
self.0.to_vec()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<'a> TryFrom<&'a [u8]> for Nonce {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
|
|
||||||
if value.len() != NONCE_LENGTH {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
let mut nonce = [0u8; NONCE_LENGTH];
|
|
||||||
nonce.copy_from_slice(value);
|
|
||||||
Ok(Self(nonce))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Salt = [u8; ArgonSalt::RECOMMENDED_LENGTH];
|
|
||||||
|
|
||||||
pub fn generate_salt() -> Salt {
|
|
||||||
let mut salt = Salt::default();
|
|
||||||
#[allow(
|
|
||||||
clippy::unwrap_used,
|
|
||||||
reason = "Rng failure is unrecoverable and should panic"
|
|
||||||
)]
|
|
||||||
let mut rng = StdRng::try_from_rng(&mut SysRng).unwrap();
|
|
||||||
rng.fill_bytes(&mut salt);
|
|
||||||
salt
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::ops::Deref as _;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use crate::{
|
|
||||||
crypto::derive_key,
|
|
||||||
safe_cell::{SafeCell, SafeCellHandle as _},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn derive_seal_key_deterministic() {
|
|
||||||
static PASSWORD: &[u8] = b"password";
|
|
||||||
let password = SafeCell::new(PASSWORD.to_vec());
|
|
||||||
let password2 = SafeCell::new(PASSWORD.to_vec());
|
|
||||||
let salt = generate_salt();
|
|
||||||
|
|
||||||
let mut key1 = derive_key(password, &salt);
|
|
||||||
let mut key2 = derive_key(password2, &salt);
|
|
||||||
|
|
||||||
let key1_reader = key1.0.read();
|
|
||||||
let key2_reader = key2.0.read();
|
|
||||||
|
|
||||||
assert_eq!(key1_reader.deref(), key2_reader.deref());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
pub fn successful_derive() {
|
|
||||||
static PASSWORD: &[u8] = b"password";
|
|
||||||
let password = SafeCell::new(PASSWORD.to_vec());
|
|
||||||
let salt = generate_salt();
|
|
||||||
|
|
||||||
let mut key = derive_key(password, &salt);
|
|
||||||
let key_reader = key.0.read();
|
|
||||||
let key_ref = key_reader.deref();
|
|
||||||
|
|
||||||
assert_ne!(key_ref.as_slice(), &[0u8; 32][..]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
// We should fuzz this
|
|
||||||
pub fn test_nonce_increment() {
|
|
||||||
let mut nonce = Nonce([0u8; NONCE_LENGTH]);
|
|
||||||
nonce.increment();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
nonce.0,
|
|
||||||
[
|
|
||||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
pub mod v1;
|
|
||||||
|
|
||||||
pub use v1::*;
|
|
||||||
@@ -1,681 +0,0 @@
|
|||||||
use crate::actors::keyholder;
|
|
||||||
use hmac::Hmac;
|
|
||||||
use sha2::Sha256;
|
|
||||||
use std::future::Future;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
use diesel::{ExpressionMethods as _, QueryDsl, dsl::insert_into, sqlite::Sqlite};
|
|
||||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
|
||||||
use kameo::{actor::ActorRef, error::SendError};
|
|
||||||
use sha2::Digest as _;
|
|
||||||
|
|
||||||
pub mod hashing;
|
|
||||||
use self::hashing::Hashable;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
actors::keyholder::{KeyHolder, SignIntegrity, VerifyIntegrity},
|
|
||||||
db::{
|
|
||||||
self,
|
|
||||||
models::{IntegrityEnvelope as IntegrityEnvelopeRow, NewIntegrityEnvelope},
|
|
||||||
schema::integrity_envelope,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error("Database error: {0}")]
|
|
||||||
Database(#[from] db::DatabaseError),
|
|
||||||
|
|
||||||
#[error("KeyHolder error: {0}")]
|
|
||||||
Keyholder(#[from] keyholder::Error),
|
|
||||||
|
|
||||||
#[error("KeyHolder mailbox error")]
|
|
||||||
KeyholderSend,
|
|
||||||
|
|
||||||
#[error("Integrity envelope is missing for entity {entity_kind}")]
|
|
||||||
MissingEnvelope { entity_kind: &'static str },
|
|
||||||
|
|
||||||
#[error(
|
|
||||||
"Integrity payload version mismatch for entity {entity_kind}: expected {expected}, found {found}"
|
|
||||||
)]
|
|
||||||
PayloadVersionMismatch {
|
|
||||||
entity_kind: &'static str,
|
|
||||||
expected: i32,
|
|
||||||
found: i32,
|
|
||||||
},
|
|
||||||
|
|
||||||
#[error("Integrity MAC mismatch for entity {entity_kind}")]
|
|
||||||
MacMismatch { entity_kind: &'static str },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[must_use]
|
|
||||||
pub enum AttestationStatus {
|
|
||||||
Attested,
|
|
||||||
Unavailable,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Verified<T>(T);
|
|
||||||
|
|
||||||
impl<T> AsRef<T> for Verified<T> {
|
|
||||||
fn as_ref(&self) -> &T {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Verified<T> {
|
|
||||||
pub fn into_inner(self) -> T {
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Deref for Verified<T> {
|
|
||||||
type Target = T;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const CURRENT_PAYLOAD_VERSION: i32 = 1;
|
|
||||||
pub const INTEGRITY_SUBKEY_TAG: &[u8] = b"arbiter/db-integrity-key/v1";
|
|
||||||
|
|
||||||
pub type HmacSha256 = Hmac<Sha256>;
|
|
||||||
|
|
||||||
pub trait Integrable: Hashable {
|
|
||||||
const KIND: &'static str;
|
|
||||||
const VERSION: i32 = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn payload_hash(payload: &impl Hashable) -> [u8; 32] {
|
|
||||||
let mut hasher = Sha256::new();
|
|
||||||
payload.hash(&mut hasher);
|
|
||||||
hasher.finalize().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_len_prefixed(out: &mut Vec<u8>, bytes: &[u8]) {
|
|
||||||
out.extend_from_slice(&(bytes.len() as u32).to_be_bytes());
|
|
||||||
out.extend_from_slice(bytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_mac_input(
|
|
||||||
entity_kind: &str,
|
|
||||||
entity_id: &[u8],
|
|
||||||
payload_version: i32,
|
|
||||||
payload_hash: &[u8; 32],
|
|
||||||
) -> Vec<u8> {
|
|
||||||
let mut out = Vec::with_capacity(8 + entity_kind.len() + entity_id.len() + 32);
|
|
||||||
push_len_prefixed(&mut out, entity_kind.as_bytes());
|
|
||||||
push_len_prefixed(&mut out, entity_id);
|
|
||||||
out.extend_from_slice(&payload_version.to_be_bytes());
|
|
||||||
out.extend_from_slice(payload_hash);
|
|
||||||
out
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct EntityId(Vec<u8>);
|
|
||||||
|
|
||||||
impl Deref for EntityId {
|
|
||||||
type Target = [u8];
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<i32> for EntityId {
|
|
||||||
fn from(value: i32) -> Self {
|
|
||||||
Self(value.to_be_bytes().to_vec())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&'_ [u8]> for EntityId {
|
|
||||||
fn from(bytes: &'_ [u8]) -> Self {
|
|
||||||
Self(bytes.to_vec())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn lookup_verified<E, C, F, Fut>(
|
|
||||||
conn: &mut C,
|
|
||||||
keyholder: &ActorRef<KeyHolder>,
|
|
||||||
entity_id: impl Into<EntityId>,
|
|
||||||
load: F,
|
|
||||||
) -> Result<Verified<E>, Error>
|
|
||||||
where
|
|
||||||
C: AsyncConnection<Backend = Sqlite>,
|
|
||||||
E: Integrable,
|
|
||||||
F: FnOnce(&mut C) -> Fut,
|
|
||||||
Fut: Future<Output = Result<E, db::DatabaseError>>,
|
|
||||||
{
|
|
||||||
let entity = load(conn).await?;
|
|
||||||
verify_entity(conn, keyholder, &entity, entity_id).await?;
|
|
||||||
Ok(Verified(entity))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn lookup_verified_allow_unavailable<E, C, F, Fut>(
|
|
||||||
conn: &mut C,
|
|
||||||
keyholder: &ActorRef<KeyHolder>,
|
|
||||||
entity_id: impl Into<EntityId>,
|
|
||||||
load: F,
|
|
||||||
) -> Result<Verified<E>, Error>
|
|
||||||
where
|
|
||||||
C: AsyncConnection<Backend = Sqlite>,
|
|
||||||
E: Integrable+ 'static,
|
|
||||||
F: FnOnce(&mut C) -> Fut,
|
|
||||||
Fut: Future<Output = Result<E, db::DatabaseError>>,
|
|
||||||
{
|
|
||||||
let entity = load(conn).await?;
|
|
||||||
match check_entity_attestation(conn, keyholder, &entity, entity_id.into()).await? {
|
|
||||||
// IMPORTANT: allow_unavailable mode must succeed with an unattested result when vault key
|
|
||||||
// material is unavailable, otherwise integrity checks can be silently bypassed while sealed.
|
|
||||||
AttestationStatus::Attested | AttestationStatus::Unavailable => Ok(Verified(entity)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn lookup_verified_from_query<E, Id, C, F>(
|
|
||||||
conn: &mut C,
|
|
||||||
keyholder: &ActorRef<KeyHolder>,
|
|
||||||
load: F,
|
|
||||||
) -> Result<Verified<E>, Error>
|
|
||||||
where
|
|
||||||
C: AsyncConnection<Backend = Sqlite> + Send,
|
|
||||||
E: Integrable,
|
|
||||||
Id: Into<EntityId>,
|
|
||||||
F: for<'a> FnOnce(
|
|
||||||
&'a mut C,
|
|
||||||
) -> Pin<
|
|
||||||
Box<dyn Future<Output = Result<(Id, E), db::DatabaseError>> + Send + 'a>,
|
|
||||||
>,
|
|
||||||
{
|
|
||||||
let (entity_id, entity) = load(conn).await?;
|
|
||||||
verify_entity(conn, keyholder, &entity, entity_id).await?;
|
|
||||||
Ok(Verified(entity))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn sign_entity<E: Integrable, Id: Into<EntityId> + Clone>(
|
|
||||||
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
|
||||||
keyholder: &ActorRef<KeyHolder>,
|
|
||||||
entity: &E,
|
|
||||||
as_entity_id: Id,
|
|
||||||
) -> Result<Verified<Id>, Error> {
|
|
||||||
let payload_hash = payload_hash(entity);
|
|
||||||
|
|
||||||
let entity_id = as_entity_id.clone().into();
|
|
||||||
|
|
||||||
let mac_input = build_mac_input(E::KIND, &entity_id, E::VERSION, &payload_hash);
|
|
||||||
|
|
||||||
let (key_version, mac) = keyholder
|
|
||||||
.ask(SignIntegrity { mac_input })
|
|
||||||
.await
|
|
||||||
.map_err(|err| match err {
|
|
||||||
kameo::error::SendError::HandlerError(inner) => Error::Keyholder(inner),
|
|
||||||
_ => Error::KeyholderSend,
|
|
||||||
})?;
|
|
||||||
|
|
||||||
insert_into(integrity_envelope::table)
|
|
||||||
.values(NewIntegrityEnvelope {
|
|
||||||
entity_kind: E::KIND.to_owned(),
|
|
||||||
entity_id: entity_id.to_vec(),
|
|
||||||
payload_version: E::VERSION,
|
|
||||||
key_version,
|
|
||||||
mac: mac.to_vec(),
|
|
||||||
})
|
|
||||||
.on_conflict((
|
|
||||||
integrity_envelope::entity_id,
|
|
||||||
integrity_envelope::entity_kind,
|
|
||||||
))
|
|
||||||
.do_update()
|
|
||||||
.set((
|
|
||||||
integrity_envelope::payload_version.eq(E::VERSION),
|
|
||||||
integrity_envelope::key_version.eq(key_version),
|
|
||||||
integrity_envelope::mac.eq(mac),
|
|
||||||
))
|
|
||||||
.execute(conn)
|
|
||||||
.await
|
|
||||||
.map_err(db::DatabaseError::from)?;
|
|
||||||
|
|
||||||
Ok(Verified(as_entity_id))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn check_entity_attestation<E: Integrable>(
|
|
||||||
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
|
||||||
keyholder: &ActorRef<KeyHolder>,
|
|
||||||
entity: &E,
|
|
||||||
entity_id: impl Into<EntityId>,
|
|
||||||
) -> Result<AttestationStatus, Error> {
|
|
||||||
let entity_id = entity_id.into();
|
|
||||||
let envelope: IntegrityEnvelopeRow = integrity_envelope::table
|
|
||||||
.filter(integrity_envelope::entity_kind.eq(E::KIND))
|
|
||||||
.filter(integrity_envelope::entity_id.eq(&*entity_id))
|
|
||||||
.first(conn)
|
|
||||||
.await
|
|
||||||
.map_err(|err| match err {
|
|
||||||
diesel::result::Error::NotFound => Error::MissingEnvelope {
|
|
||||||
entity_kind: E::KIND,
|
|
||||||
},
|
|
||||||
other => Error::Database(db::DatabaseError::from(other)),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if envelope.payload_version != E::VERSION {
|
|
||||||
return Err(Error::PayloadVersionMismatch {
|
|
||||||
entity_kind: E::KIND,
|
|
||||||
expected: E::VERSION,
|
|
||||||
found: envelope.payload_version,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let payload_hash = payload_hash(entity);
|
|
||||||
let mac_input = build_mac_input(E::KIND, &entity_id, envelope.payload_version, &payload_hash);
|
|
||||||
|
|
||||||
let result = keyholder
|
|
||||||
.ask(VerifyIntegrity {
|
|
||||||
mac_input,
|
|
||||||
expected_mac: envelope.mac,
|
|
||||||
key_version: envelope.key_version,
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(true) => Ok(AttestationStatus::Attested),
|
|
||||||
Ok(false) => Err(Error::MacMismatch {
|
|
||||||
entity_kind: E::KIND,
|
|
||||||
}),
|
|
||||||
Err(SendError::HandlerError(keyholder::Error::NotBootstrapped)) => {
|
|
||||||
Ok(AttestationStatus::Unavailable)
|
|
||||||
}
|
|
||||||
Err(_) => Err(Error::KeyholderSend),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn verify_entity<'a, E: Integrable>(
|
|
||||||
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
|
||||||
keyholder: &ActorRef<KeyHolder>,
|
|
||||||
entity: &'a E,
|
|
||||||
entity_id: impl Into<EntityId>,
|
|
||||||
) -> Result<Verified<&'a E>, Error> {
|
|
||||||
match check_entity_attestation::<E>(conn, keyholder, entity, entity_id).await? {
|
|
||||||
AttestationStatus::Attested => Ok(Verified(entity)),
|
|
||||||
AttestationStatus::Unavailable => Err(Error::Keyholder(keyholder::Error::NotBootstrapped)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn delete_envelope<E: Integrable>(
|
|
||||||
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
|
||||||
entity_id: impl Into<EntityId>,
|
|
||||||
) -> Result<usize, Error> {
|
|
||||||
let entity_id = entity_id.into();
|
|
||||||
|
|
||||||
let affected = diesel::delete(
|
|
||||||
integrity_envelope::table
|
|
||||||
.filter(integrity_envelope::entity_kind.eq(E::KIND))
|
|
||||||
.filter(integrity_envelope::entity_id.eq(&*entity_id)),
|
|
||||||
)
|
|
||||||
.execute(conn)
|
|
||||||
.await
|
|
||||||
.map_err(db::DatabaseError::from)?;
|
|
||||||
|
|
||||||
Ok(affected)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use diesel::{ExpressionMethods as _, QueryDsl};
|
|
||||||
use diesel_async::RunQueryDsl;
|
|
||||||
use kameo::{actor::ActorRef, prelude::Spawn};
|
|
||||||
use sha2::Digest;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
actors::keyholder::{Bootstrap, KeyHolder},
|
|
||||||
db::{self, schema},
|
|
||||||
safe_cell::{SafeCell, SafeCellHandle as _},
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::hashing::Hashable;
|
|
||||||
use super::{
|
|
||||||
check_entity_attestation, AttestationStatus, Error, Integrable, lookup_verified,
|
|
||||||
lookup_verified_allow_unavailable, lookup_verified_from_query, sign_entity, verify_entity,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct DummyEntity {
|
|
||||||
payload_version: i32,
|
|
||||||
payload: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hashable for DummyEntity {
|
|
||||||
fn hash<H: Digest>(&self, hasher: &mut H) {
|
|
||||||
self.payload_version.hash(hasher);
|
|
||||||
self.payload.hash(hasher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Integrable for DummyEntity {
|
|
||||||
const KIND: &'static str = "dummy_entity";
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn bootstrapped_keyholder(db: &db::DatabasePool) -> ActorRef<KeyHolder> {
|
|
||||||
let actor = KeyHolder::spawn(KeyHolder::new(db.clone()).await.unwrap());
|
|
||||||
actor
|
|
||||||
.ask(Bootstrap {
|
|
||||||
seal_key_raw: SafeCell::new(b"integrity-test-seal-key".to_vec()),
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
actor
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn sign_writes_envelope_and_verify_passes() {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let keyholder = bootstrapped_keyholder(&db).await;
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
|
|
||||||
const ENTITY_ID: &[u8] = b"entity-id-7";
|
|
||||||
|
|
||||||
let entity = DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let count: i64 = schema::integrity_envelope::table
|
|
||||||
.filter(schema::integrity_envelope::entity_kind.eq("dummy_entity"))
|
|
||||||
.filter(schema::integrity_envelope::entity_id.eq(ENTITY_ID))
|
|
||||||
.count()
|
|
||||||
.get_result(&mut conn)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(count, 1, "envelope row must be created exactly once");
|
|
||||||
let _ = check_entity_attestation(&mut conn, &keyholder, &entity, ENTITY_ID)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn tampered_mac_fails_verification() {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let keyholder = bootstrapped_keyholder(&db).await;
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
|
|
||||||
const ENTITY_ID: &[u8] = b"entity-id-11";
|
|
||||||
|
|
||||||
let entity = DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
diesel::update(schema::integrity_envelope::table)
|
|
||||||
.filter(schema::integrity_envelope::entity_kind.eq("dummy_entity"))
|
|
||||||
.filter(schema::integrity_envelope::entity_id.eq(ENTITY_ID))
|
|
||||||
.set(schema::integrity_envelope::mac.eq(vec![0u8; 32]))
|
|
||||||
.execute(&mut conn)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let err = check_entity_attestation(&mut conn, &keyholder, &entity, ENTITY_ID)
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
assert!(matches!(err, Error::MacMismatch { .. }));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn changed_payload_fails_verification() {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let keyholder = bootstrapped_keyholder(&db).await;
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
|
|
||||||
const ENTITY_ID: &[u8] = b"entity-id-21";
|
|
||||||
|
|
||||||
let entity = DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let tampered = DummyEntity {
|
|
||||||
payload: b"payload-v1-but-tampered".to_vec(),
|
|
||||||
..entity
|
|
||||||
};
|
|
||||||
|
|
||||||
let err = check_entity_attestation(&mut conn, &keyholder, &tampered, ENTITY_ID)
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
assert!(matches!(err, Error::MacMismatch { .. }));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn allow_unavailable_lookup_passes_while_sealed() {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let keyholder = bootstrapped_keyholder(&db).await;
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
|
|
||||||
const ENTITY_ID: &[u8] = b"entity-id-31";
|
|
||||||
|
|
||||||
let entity = DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
drop(keyholder);
|
|
||||||
|
|
||||||
let sealed_keyholder = KeyHolder::spawn(KeyHolder::new(db.clone()).await.unwrap());
|
|
||||||
let status = check_entity_attestation(&mut conn, &sealed_keyholder, &entity, ENTITY_ID)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(status, AttestationStatus::Unavailable);
|
|
||||||
|
|
||||||
#[expect(clippy::disallowed_methods, reason = "test only")]
|
|
||||||
lookup_verified_allow_unavailable(&mut conn, &sealed_keyholder, ENTITY_ID, |_| async {
|
|
||||||
Ok::<_, db::DatabaseError>(DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn strict_verify_fails_closed_while_sealed() {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let keyholder = bootstrapped_keyholder(&db).await;
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
|
|
||||||
const ENTITY_ID: &[u8] = b"entity-id-41";
|
|
||||||
|
|
||||||
let entity = DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
drop(keyholder);
|
|
||||||
|
|
||||||
let sealed_keyholder = KeyHolder::spawn(KeyHolder::new(db.clone()).await.unwrap());
|
|
||||||
|
|
||||||
let err = verify_entity(&mut conn, &sealed_keyholder, &entity, ENTITY_ID)
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
assert!(matches!(
|
|
||||||
err,
|
|
||||||
Error::Keyholder(crate::actors::keyholder::Error::NotBootstrapped)
|
|
||||||
));
|
|
||||||
|
|
||||||
let err = lookup_verified(&mut conn, &sealed_keyholder, ENTITY_ID, |_| async {
|
|
||||||
Ok::<_, db::DatabaseError>(DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
assert!(matches!(
|
|
||||||
err,
|
|
||||||
Error::Keyholder(crate::actors::keyholder::Error::NotBootstrapped)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn lookup_verified_supports_loaded_aggregate() {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let keyholder = bootstrapped_keyholder(&db).await;
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
|
|
||||||
const ENTITY_ID: i32 = 77;
|
|
||||||
|
|
||||||
let entity = DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let verified = lookup_verified(&mut conn, &keyholder, ENTITY_ID, |_| async {
|
|
||||||
Ok::<_, db::DatabaseError>(DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(verified.payload, b"payload-v1".to_vec());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn lookup_verified_allow_unavailable_works_while_sealed() {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let keyholder = bootstrapped_keyholder(&db).await;
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
|
|
||||||
const ENTITY_ID: i32 = 78;
|
|
||||||
|
|
||||||
let entity = DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
drop(keyholder);
|
|
||||||
|
|
||||||
let sealed_keyholder = KeyHolder::spawn(KeyHolder::new(db.clone()).await.unwrap());
|
|
||||||
|
|
||||||
#[expect(clippy::disallowed_methods, reason = "test only")]
|
|
||||||
lookup_verified_allow_unavailable(&mut conn, &sealed_keyholder, ENTITY_ID, |_| async {
|
|
||||||
Ok::<_, db::DatabaseError>(DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn extension_trait_lookup_verified_required_works() {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let keyholder = bootstrapped_keyholder(&db).await;
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
|
|
||||||
const ENTITY_ID: i32 = 79;
|
|
||||||
|
|
||||||
let entity = DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let verified = lookup_verified(&mut conn, &keyholder, ENTITY_ID, |_| {
|
|
||||||
Box::pin(async {
|
|
||||||
Ok::<_, db::DatabaseError>(DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(verified.payload, b"payload-v1".to_vec());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn lookup_verified_from_query_helpers_work() {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let keyholder = bootstrapped_keyholder(&db).await;
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
|
|
||||||
const ENTITY_ID: i32 = 80;
|
|
||||||
|
|
||||||
let entity = DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
sign_entity(&mut conn, &keyholder, &entity, ENTITY_ID)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let verified = lookup_verified_from_query(&mut conn, &keyholder, |_| {
|
|
||||||
Box::pin(async {
|
|
||||||
Ok::<_, db::DatabaseError>((
|
|
||||||
ENTITY_ID,
|
|
||||||
DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(verified.payload, b"payload-v1".to_vec());
|
|
||||||
|
|
||||||
drop(keyholder);
|
|
||||||
let sealed_keyholder = KeyHolder::spawn(KeyHolder::new(db.clone()).await.unwrap());
|
|
||||||
|
|
||||||
let err = lookup_verified_from_query(&mut conn, &sealed_keyholder, |_| {
|
|
||||||
Box::pin(async {
|
|
||||||
Ok::<_, db::DatabaseError>((
|
|
||||||
ENTITY_ID,
|
|
||||||
DummyEntity {
|
|
||||||
payload_version: 1,
|
|
||||||
payload: b"payload-v1".to_vec(),
|
|
||||||
},
|
|
||||||
))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap_err();
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
err,
|
|
||||||
Error::Keyholder(crate::actors::keyholder::Error::NotBootstrapped)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
use hmac::digest::Digest;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
/// Deterministically hash a value by feeding its fields into the hasher in a consistent order.
|
|
||||||
pub trait Hashable {
|
|
||||||
fn hash<H: Digest>(&self, hasher: &mut H);
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! impl_numeric {
|
|
||||||
($($t:ty),*) => {
|
|
||||||
$(
|
|
||||||
impl Hashable for $t {
|
|
||||||
fn hash<H: Digest>(&self, hasher: &mut H) {
|
|
||||||
hasher.update(&self.to_be_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_numeric!(u8, u16, u32, u64, i8, i16, i32, i64);
|
|
||||||
|
|
||||||
impl Hashable for &[u8] {
|
|
||||||
fn hash<H: Digest>(&self, hasher: &mut H) {
|
|
||||||
hasher.update(self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hashable for String {
|
|
||||||
fn hash<H: Digest>(&self, hasher: &mut H) {
|
|
||||||
hasher.update(self.as_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Hashable + PartialOrd> Hashable for Vec<T> {
|
|
||||||
fn hash<H: Digest>(&self, hasher: &mut H) {
|
|
||||||
let ref_sorted = {
|
|
||||||
let mut sorted = self.iter().collect::<Vec<_>>();
|
|
||||||
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
|
||||||
sorted
|
|
||||||
};
|
|
||||||
for item in ref_sorted {
|
|
||||||
item.hash(hasher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Hashable + PartialOrd> Hashable for HashSet<T> {
|
|
||||||
fn hash<H: Digest>(&self, hasher: &mut H) {
|
|
||||||
let ref_sorted = {
|
|
||||||
let mut sorted = self.iter().collect::<Vec<_>>();
|
|
||||||
sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
|
||||||
sorted
|
|
||||||
};
|
|
||||||
for item in ref_sorted {
|
|
||||||
item.hash(hasher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Hashable> Hashable for Option<T> {
|
|
||||||
fn hash<H: Digest>(&self, hasher: &mut H) {
|
|
||||||
match self {
|
|
||||||
Some(value) => {
|
|
||||||
hasher.update([1]);
|
|
||||||
value.hash(hasher);
|
|
||||||
}
|
|
||||||
None => hasher.update([0]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Hashable> Hashable for Box<T> {
|
|
||||||
fn hash<H: Digest>(&self, hasher: &mut H) {
|
|
||||||
self.as_ref().hash(hasher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T: Hashable> Hashable for &T {
|
|
||||||
fn hash<H: Digest>(&self, hasher: &mut H) {
|
|
||||||
(*self).hash(hasher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hashable for alloy::primitives::Address {
|
|
||||||
fn hash<H: Digest>(&self, hasher: &mut H) {
|
|
||||||
hasher.update(self.as_slice());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hashable for alloy::primitives::U256 {
|
|
||||||
fn hash<H: Digest>(&self, hasher: &mut H) {
|
|
||||||
hasher.update(self.to_be_bytes::<32>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hashable for chrono::Duration {
|
|
||||||
fn hash<H: Digest>(&self, hasher: &mut H) {
|
|
||||||
hasher.update(self.num_seconds().to_be_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hashable for chrono::DateTime<chrono::Utc> {
|
|
||||||
fn hash<H: Digest>(&self, hasher: &mut H) {
|
|
||||||
hasher.update(self.timestamp_millis().to_be_bytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@ use diesel_async::{
|
|||||||
sync_connection_wrapper::SyncConnectionWrapper,
|
sync_connection_wrapper::SyncConnectionWrapper,
|
||||||
};
|
};
|
||||||
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
|
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
|
||||||
|
use miette::Diagnostic;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
@@ -21,21 +21,26 @@ static DB_FILE: &str = "arbiter.sqlite";
|
|||||||
|
|
||||||
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
|
const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Diagnostic, Debug)]
|
||||||
pub enum DatabaseSetupError {
|
pub enum DatabaseSetupError {
|
||||||
#[error("Failed to determine home directory")]
|
#[error("Failed to determine home directory")]
|
||||||
|
#[diagnostic(code(arbiter::db::home_dir))]
|
||||||
HomeDir(std::io::Error),
|
HomeDir(std::io::Error),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
#[diagnostic(code(arbiter::db::connection))]
|
||||||
Connection(diesel::ConnectionError),
|
Connection(diesel::ConnectionError),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
#[diagnostic(code(arbiter::db::concurrency))]
|
||||||
ConcurrencySetup(diesel::result::Error),
|
ConcurrencySetup(diesel::result::Error),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
#[diagnostic(code(arbiter::db::migration))]
|
||||||
Migration(Box<dyn std::error::Error + Send + Sync>),
|
Migration(Box<dyn std::error::Error + Send + Sync>),
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
#[diagnostic(code(arbiter::db::pool))]
|
||||||
Pool(#[from] PoolInitError),
|
Pool(#[from] PoolInitError),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +138,6 @@ pub async fn create_pool(url: Option<&str>) -> Result<DatabasePool, DatabaseSetu
|
|||||||
Ok(pool)
|
Ok(pool)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[mutants::skip]
|
|
||||||
pub async fn create_test_pool() -> DatabasePool {
|
pub async fn create_test_pool() -> DatabasePool {
|
||||||
use rand::distr::{Alphanumeric, SampleString as _};
|
use rand::distr::{Alphanumeric, SampleString as _};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::db::schema::{
|
|||||||
self, aead_encrypted, arbiter_settings, evm_basic_grant, evm_ether_transfer_grant,
|
self, aead_encrypted, arbiter_settings, evm_basic_grant, evm_ether_transfer_grant,
|
||||||
evm_ether_transfer_grant_target, evm_ether_transfer_limit, evm_token_transfer_grant,
|
evm_ether_transfer_grant_target, evm_ether_transfer_limit, evm_token_transfer_grant,
|
||||||
evm_token_transfer_log, evm_token_transfer_volume_limit, evm_transaction_log, evm_wallet,
|
evm_token_transfer_log, evm_token_transfer_volume_limit, evm_transaction_log, evm_wallet,
|
||||||
integrity_envelope, root_key_history, tls_history,
|
root_key_history, tls_history,
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use diesel::{prelude::*, sqlite::Sqlite};
|
use diesel::{prelude::*, sqlite::Sqlite};
|
||||||
@@ -376,22 +376,3 @@ pub struct EvmTokenTransferLog {
|
|||||||
pub value: Vec<u8>,
|
pub value: Vec<u8>,
|
||||||
pub created_at: SqliteTimestamp,
|
pub created_at: SqliteTimestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Models, Queryable, Debug, Insertable, Selectable)]
|
|
||||||
#[diesel(table_name = integrity_envelope, check_for_backend(Sqlite))]
|
|
||||||
#[view(
|
|
||||||
NewIntegrityEnvelope,
|
|
||||||
derive(Insertable),
|
|
||||||
omit(id, signed_at, created_at),
|
|
||||||
attributes_with = "deriveless"
|
|
||||||
)]
|
|
||||||
pub struct IntegrityEnvelope {
|
|
||||||
pub id: i32,
|
|
||||||
pub entity_kind: String,
|
|
||||||
pub entity_id: Vec<u8>,
|
|
||||||
pub payload_version: i32,
|
|
||||||
pub key_version: i32,
|
|
||||||
pub mac: Vec<u8>,
|
|
||||||
pub signed_at: SqliteTimestamp,
|
|
||||||
pub created_at: SqliteTimestamp,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -139,19 +139,6 @@ diesel::table! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
diesel::table! {
|
|
||||||
integrity_envelope (id) {
|
|
||||||
id -> Integer,
|
|
||||||
entity_kind -> Text,
|
|
||||||
entity_id -> Binary,
|
|
||||||
payload_version -> Integer,
|
|
||||||
key_version -> Integer,
|
|
||||||
mac -> Binary,
|
|
||||||
signed_at -> Integer,
|
|
||||||
created_at -> Integer,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
diesel::table! {
|
diesel::table! {
|
||||||
program_client (id) {
|
program_client (id) {
|
||||||
id -> Integer,
|
id -> Integer,
|
||||||
@@ -232,7 +219,6 @@ diesel::allow_tables_to_appear_in_same_query!(
|
|||||||
evm_transaction_log,
|
evm_transaction_log,
|
||||||
evm_wallet,
|
evm_wallet,
|
||||||
evm_wallet_access,
|
evm_wallet_access,
|
||||||
integrity_envelope,
|
|
||||||
program_client,
|
program_client,
|
||||||
root_key_history,
|
root_key_history,
|
||||||
tls_history,
|
tls_history,
|
||||||
|
|||||||
@@ -8,11 +8,9 @@ use alloy::{
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use diesel::{ExpressionMethods as _, QueryDsl as _, QueryResult, insert_into, sqlite::Sqlite};
|
use diesel::{ExpressionMethods as _, QueryDsl as _, QueryResult, insert_into, sqlite::Sqlite};
|
||||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||||
use kameo::actor::ActorRef;
|
use tracing_subscriber::registry::Data;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::keyholder::KeyHolder,
|
|
||||||
crypto::integrity::{self, Verified},
|
|
||||||
db::{
|
db::{
|
||||||
self, DatabaseError,
|
self, DatabaseError,
|
||||||
models::{
|
models::{
|
||||||
@@ -21,8 +19,8 @@ use crate::{
|
|||||||
schema::{self, evm_transaction_log},
|
schema::{self, evm_transaction_log},
|
||||||
},
|
},
|
||||||
evm::policies::{
|
evm::policies::{
|
||||||
CombinedSettings, DatabaseID, EvalContext, EvalViolation, Grant, Policy,
|
DatabaseID, EvalContext, EvalViolation, FullGrant, Grant, Policy, SharedGrantSettings,
|
||||||
SharedGrantSettings, SpecificGrant, SpecificMeaning, ether_transfer::EtherTransfer,
|
SpecificGrant, SpecificMeaning, ether_transfer::EtherTransfer,
|
||||||
token_transfers::TokenTransfer,
|
token_transfers::TokenTransfer,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -31,47 +29,42 @@ pub mod policies;
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
/// Errors that can only occur once the transaction meaning is known (during policy evaluation)
|
/// Errors that can only occur once the transaction meaning is known (during policy evaluation)
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
|
||||||
pub enum PolicyError {
|
pub enum PolicyError {
|
||||||
#[error("Database error")]
|
#[error("Database error")]
|
||||||
Database(#[from] crate::db::DatabaseError),
|
Error(#[from] crate::db::DatabaseError),
|
||||||
#[error("Transaction violates policy: {0:?}")]
|
#[error("Transaction violates policy: {0:?}")]
|
||||||
|
#[diagnostic(code(arbiter_server::evm::policy_error::violation))]
|
||||||
Violations(Vec<EvalViolation>),
|
Violations(Vec<EvalViolation>),
|
||||||
#[error("No matching grant found")]
|
#[error("No matching grant found")]
|
||||||
|
#[diagnostic(code(arbiter_server::evm::policy_error::no_matching_grant))]
|
||||||
NoMatchingGrant,
|
NoMatchingGrant,
|
||||||
|
|
||||||
#[error("Integrity error: {0}")]
|
|
||||||
Integrity(#[from] integrity::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
|
||||||
pub enum VetError {
|
pub enum VetError {
|
||||||
#[error("Contract creation transactions are not supported")]
|
#[error("Contract creation transactions are not supported")]
|
||||||
|
#[diagnostic(code(arbiter_server::evm::vet_error::contract_creation_unsupported))]
|
||||||
ContractCreationNotSupported,
|
ContractCreationNotSupported,
|
||||||
#[error("Engine can't classify this transaction")]
|
#[error("Engine can't classify this transaction")]
|
||||||
|
#[diagnostic(code(arbiter_server::evm::vet_error::unsupported))]
|
||||||
UnsupportedTransactionType,
|
UnsupportedTransactionType,
|
||||||
#[error("Policy evaluation failed: {1}")]
|
#[error("Policy evaluation failed: {1}")]
|
||||||
|
#[diagnostic(code(arbiter_server::evm::vet_error::evaluated))]
|
||||||
Evaluated(SpecificMeaning, #[source] PolicyError),
|
Evaluated(SpecificMeaning, #[source] PolicyError),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
|
||||||
pub enum AnalyzeError {
|
pub enum AnalyzeError {
|
||||||
#[error("Engine doesn't support granting permissions for contract creation")]
|
#[error("Engine doesn't support granting permissions for contract creation")]
|
||||||
|
#[diagnostic(code(arbiter_server::evm::analyze_error::contract_creation_not_supported))]
|
||||||
ContractCreationNotSupported,
|
ContractCreationNotSupported,
|
||||||
|
|
||||||
#[error("Unsupported transaction type")]
|
#[error("Unsupported transaction type")]
|
||||||
|
#[diagnostic(code(arbiter_server::evm::analyze_error::unsupported_transaction_type))]
|
||||||
UnsupportedTransactionType,
|
UnsupportedTransactionType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum ListError {
|
|
||||||
#[error("Database error")]
|
|
||||||
Database(#[from] crate::db::DatabaseError),
|
|
||||||
|
|
||||||
#[error("Integrity verification failed for grant")]
|
|
||||||
Integrity(#[from] integrity::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Controls whether a transaction should be executed or only validated
|
/// Controls whether a transaction should be executed or only validated
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum RunKind {
|
pub enum RunKind {
|
||||||
@@ -90,14 +83,6 @@ async fn check_shared_constraints(
|
|||||||
let mut violations = Vec::new();
|
let mut violations = Vec::new();
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
|
|
||||||
if shared.chain != context.chain {
|
|
||||||
violations.push(EvalViolation::MismatchingChainId {
|
|
||||||
expected: shared.chain,
|
|
||||||
actual: context.chain,
|
|
||||||
});
|
|
||||||
return Ok(violations);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validity window
|
// Validity window
|
||||||
if shared.valid_from.is_some_and(|t| now < t) || shared.valid_until.is_some_and(|t| now > t) {
|
if shared.valid_from.is_some_and(|t| now < t) || shared.valid_until.is_some_and(|t| now > t) {
|
||||||
violations.push(EvalViolation::InvalidTime);
|
violations.push(EvalViolation::InvalidTime);
|
||||||
@@ -138,7 +123,6 @@ async fn check_shared_constraints(
|
|||||||
// Supporting only EIP-1559 transactions for now, but we can easily extend this to support legacy transactions if needed
|
// Supporting only EIP-1559 transactions for now, but we can easily extend this to support legacy transactions if needed
|
||||||
pub struct Engine {
|
pub struct Engine {
|
||||||
db: db::DatabasePool,
|
db: db::DatabasePool,
|
||||||
keyholder: ActorRef<KeyHolder>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
@@ -147,49 +131,16 @@ impl Engine {
|
|||||||
context: EvalContext,
|
context: EvalContext,
|
||||||
meaning: &P::Meaning,
|
meaning: &P::Meaning,
|
||||||
run_kind: RunKind,
|
run_kind: RunKind,
|
||||||
) -> Result<(), PolicyError>
|
) -> Result<(), PolicyError> {
|
||||||
where
|
|
||||||
P::Settings: Clone,
|
|
||||||
{
|
|
||||||
let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
|
let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
|
||||||
|
|
||||||
let verified_settings =
|
let grant = P::try_find_grant(&context, &mut conn)
|
||||||
match integrity::lookup_verified_from_query(&mut conn, &self.keyholder, |conn| {
|
|
||||||
let context = context.clone();
|
|
||||||
Box::pin(async move {
|
|
||||||
let grant = P::try_find_grant(&context, conn)
|
|
||||||
.await
|
|
||||||
.map_err(DatabaseError::from)?
|
|
||||||
.ok_or_else(|| DatabaseError::from(diesel::result::Error::NotFound))?;
|
|
||||||
|
|
||||||
Ok::<_, DatabaseError>((grant.common_settings_id, grant.settings))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(verified) => verified,
|
|
||||||
Err(integrity::Error::Database(DatabaseError::Connection(
|
|
||||||
diesel::result::Error::NotFound,
|
|
||||||
))) => return Err(PolicyError::NoMatchingGrant),
|
|
||||||
Err(err) => return Err(PolicyError::Integrity(err)),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut grant = P::try_find_grant(&context, &mut conn)
|
|
||||||
.await
|
.await
|
||||||
.map_err(DatabaseError::from)?
|
.map_err(DatabaseError::from)?
|
||||||
.ok_or(PolicyError::NoMatchingGrant)?;
|
.ok_or(PolicyError::NoMatchingGrant)?;
|
||||||
|
|
||||||
// IMPORTANT: policy evaluation uses extra non-integrity fields from Grant
|
let mut violations =
|
||||||
// (e.g., per-policy ids), so we currently reload Grant after the query-native
|
check_shared_constraints(&context, &grant.shared, grant.shared_grant_id, &mut conn)
|
||||||
// integrity check over canonicalized settings.
|
|
||||||
grant.settings = verified_settings.into_inner();
|
|
||||||
|
|
||||||
let mut violations = check_shared_constraints(
|
|
||||||
&context,
|
|
||||||
&grant.settings.shared,
|
|
||||||
grant.common_settings_id,
|
|
||||||
&mut conn,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.map_err(DatabaseError::from)?;
|
.map_err(DatabaseError::from)?;
|
||||||
violations.extend(
|
violations.extend(
|
||||||
@@ -200,14 +151,12 @@ impl Engine {
|
|||||||
|
|
||||||
if !violations.is_empty() {
|
if !violations.is_empty() {
|
||||||
return Err(PolicyError::Violations(violations));
|
return Err(PolicyError::Violations(violations));
|
||||||
}
|
} else if run_kind == RunKind::Execution {
|
||||||
|
|
||||||
if run_kind == RunKind::Execution {
|
|
||||||
conn.transaction(|conn| {
|
conn.transaction(|conn| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let log_id: i32 = insert_into(evm_transaction_log::table)
|
let log_id: i32 = insert_into(evm_transaction_log::table)
|
||||||
.values(&NewEvmTransactionLog {
|
.values(&NewEvmTransactionLog {
|
||||||
grant_id: grant.common_settings_id,
|
grant_id: grant.shared_grant_id,
|
||||||
wallet_access_id: context.target.id,
|
wallet_access_id: context.target.id,
|
||||||
chain_id: context.chain as i32,
|
chain_id: context.chain as i32,
|
||||||
eth_value: utils::u256_to_bytes(context.value).to_vec(),
|
eth_value: utils::u256_to_bytes(context.value).to_vec(),
|
||||||
@@ -231,19 +180,15 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
pub fn new(db: db::DatabasePool, keyholder: ActorRef<KeyHolder>) -> Self {
|
pub fn new(db: db::DatabasePool) -> Self {
|
||||||
Self { db, keyholder }
|
Self { db }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_grant<P: Policy>(
|
pub async fn create_grant<P: Policy>(
|
||||||
&self,
|
&self,
|
||||||
full_grant: CombinedSettings<P::Settings>,
|
full_grant: FullGrant<P::Settings>,
|
||||||
) -> Result<Verified<i32>, DatabaseError>
|
) -> Result<i32, DatabaseError> {
|
||||||
where
|
|
||||||
P::Settings: Clone,
|
|
||||||
{
|
|
||||||
let mut conn = self.db.get().await?;
|
let mut conn = self.db.get().await?;
|
||||||
let keyholder = self.keyholder.clone();
|
|
||||||
|
|
||||||
let id = conn
|
let id = conn
|
||||||
.transaction(|conn| {
|
.transaction(|conn| {
|
||||||
@@ -252,25 +197,25 @@ impl Engine {
|
|||||||
|
|
||||||
let basic_grant: EvmBasicGrant = insert_into(evm_basic_grant::table)
|
let basic_grant: EvmBasicGrant = insert_into(evm_basic_grant::table)
|
||||||
.values(&NewEvmBasicGrant {
|
.values(&NewEvmBasicGrant {
|
||||||
chain_id: full_grant.shared.chain as i32,
|
chain_id: full_grant.basic.chain as i32,
|
||||||
wallet_access_id: full_grant.shared.wallet_access_id,
|
wallet_access_id: full_grant.basic.wallet_access_id,
|
||||||
valid_from: full_grant.shared.valid_from.map(SqliteTimestamp),
|
valid_from: full_grant.basic.valid_from.map(SqliteTimestamp),
|
||||||
valid_until: full_grant.shared.valid_until.map(SqliteTimestamp),
|
valid_until: full_grant.basic.valid_until.map(SqliteTimestamp),
|
||||||
max_gas_fee_per_gas: full_grant
|
max_gas_fee_per_gas: full_grant
|
||||||
.shared
|
.basic
|
||||||
.max_gas_fee_per_gas
|
.max_gas_fee_per_gas
|
||||||
.map(|fee| utils::u256_to_bytes(fee).to_vec()),
|
.map(|fee| utils::u256_to_bytes(fee).to_vec()),
|
||||||
max_priority_fee_per_gas: full_grant
|
max_priority_fee_per_gas: full_grant
|
||||||
.shared
|
.basic
|
||||||
.max_priority_fee_per_gas
|
.max_priority_fee_per_gas
|
||||||
.map(|fee| utils::u256_to_bytes(fee).to_vec()),
|
.map(|fee| utils::u256_to_bytes(fee).to_vec()),
|
||||||
rate_limit_count: full_grant
|
rate_limit_count: full_grant
|
||||||
.shared
|
.basic
|
||||||
.rate_limit
|
.rate_limit
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|rl| rl.count as i32),
|
.map(|rl| rl.count as i32),
|
||||||
rate_limit_window_secs: full_grant
|
rate_limit_window_secs: full_grant
|
||||||
.shared
|
.basic
|
||||||
.rate_limit
|
.rate_limit
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|rl| rl.window.num_seconds() as i32),
|
.map(|rl| rl.window.num_seconds() as i32),
|
||||||
@@ -280,14 +225,7 @@ impl Engine {
|
|||||||
.get_result(conn)
|
.get_result(conn)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
P::create_grant(&basic_grant, &full_grant.specific, conn).await?;
|
P::create_grant(&basic_grant, &full_grant.specific, conn).await
|
||||||
|
|
||||||
let verified_entity_id =
|
|
||||||
integrity::sign_entity(conn, &keyholder, &full_grant, basic_grant.id)
|
|
||||||
.await
|
|
||||||
.map_err(|_| diesel::result::Error::RollbackTransaction)?;
|
|
||||||
|
|
||||||
QueryResult::Ok(verified_entity_id)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
@@ -295,46 +233,33 @@ impl Engine {
|
|||||||
Ok(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn list_one_kind<Kind: Policy, Y>(
|
pub async fn list_all_grants(&self) -> Result<Vec<Grant<SpecificGrant>>, DatabaseError> {
|
||||||
&self,
|
let mut conn = self.db.get().await?;
|
||||||
conn: &mut impl AsyncConnection<Backend = Sqlite>,
|
|
||||||
) -> Result<Vec<Grant<Y>>, ListError>
|
|
||||||
where
|
|
||||||
Y: From<Kind::Settings>,
|
|
||||||
{
|
|
||||||
let all_grants = Kind::find_all_grants(conn)
|
|
||||||
.await
|
|
||||||
.map_err(DatabaseError::from)?;
|
|
||||||
|
|
||||||
let mut verified_grants = Vec::with_capacity(all_grants.len());
|
|
||||||
|
|
||||||
// Verify integrity of all grants before returning any results.
|
|
||||||
for grant in all_grants {
|
|
||||||
integrity::verify_entity(
|
|
||||||
conn,
|
|
||||||
&self.keyholder,
|
|
||||||
&grant.settings,
|
|
||||||
grant.common_settings_id,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
verified_grants.push(Grant {
|
|
||||||
id: grant.id,
|
|
||||||
common_settings_id: grant.common_settings_id,
|
|
||||||
settings: grant.settings.generalize(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(verified_grants)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn list_all_grants(&self) -> Result<Vec<Grant<SpecificGrant>>, ListError> {
|
|
||||||
let mut conn = self.db.get().await.map_err(DatabaseError::from)?;
|
|
||||||
|
|
||||||
let mut grants: Vec<Grant<SpecificGrant>> = Vec::new();
|
let mut grants: Vec<Grant<SpecificGrant>> = Vec::new();
|
||||||
|
|
||||||
grants.extend(self.list_one_kind::<EtherTransfer, _>(&mut conn).await?);
|
grants.extend(
|
||||||
grants.extend(self.list_one_kind::<TokenTransfer, _>(&mut conn).await?);
|
EtherTransfer::find_all_grants(&mut conn)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|g| Grant {
|
||||||
|
id: g.id,
|
||||||
|
shared_grant_id: g.shared_grant_id,
|
||||||
|
shared: g.shared,
|
||||||
|
settings: SpecificGrant::EtherTransfer(g.settings),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
grants.extend(
|
||||||
|
TokenTransfer::find_all_grants(&mut conn)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|g| Grant {
|
||||||
|
id: g.id,
|
||||||
|
shared_grant_id: g.shared_grant_id,
|
||||||
|
shared: g.shared,
|
||||||
|
settings: SpecificGrant::TokenTransfer(g.settings),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
Ok(grants)
|
Ok(grants)
|
||||||
}
|
}
|
||||||
@@ -380,255 +305,3 @@ impl Engine {
|
|||||||
Err(VetError::UnsupportedTransactionType)
|
Err(VetError::UnsupportedTransactionType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use alloy::primitives::{Address, Bytes, U256, address};
|
|
||||||
use chrono::{Duration, Utc};
|
|
||||||
use diesel::{SelectableHelper, insert_into};
|
|
||||||
use diesel_async::RunQueryDsl;
|
|
||||||
use rstest::rstest;
|
|
||||||
|
|
||||||
use crate::db::{
|
|
||||||
self, DatabaseConnection,
|
|
||||||
models::{
|
|
||||||
EvmBasicGrant, EvmWalletAccess, NewEvmBasicGrant, NewEvmTransactionLog, SqliteTimestamp,
|
|
||||||
},
|
|
||||||
schema::{evm_basic_grant, evm_transaction_log},
|
|
||||||
};
|
|
||||||
use crate::evm::policies::{
|
|
||||||
EvalContext, EvalViolation, SharedGrantSettings, TransactionRateLimit,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::check_shared_constraints;
|
|
||||||
|
|
||||||
const WALLET_ACCESS_ID: i32 = 1;
|
|
||||||
const CHAIN_ID: u64 = 1;
|
|
||||||
const RECIPIENT: Address = address!("1111111111111111111111111111111111111111");
|
|
||||||
|
|
||||||
fn context() -> EvalContext {
|
|
||||||
EvalContext {
|
|
||||||
target: EvmWalletAccess {
|
|
||||||
id: WALLET_ACCESS_ID,
|
|
||||||
wallet_id: 10,
|
|
||||||
client_id: 20,
|
|
||||||
created_at: SqliteTimestamp(Utc::now()),
|
|
||||||
},
|
|
||||||
chain: CHAIN_ID,
|
|
||||||
to: RECIPIENT,
|
|
||||||
value: U256::ZERO,
|
|
||||||
calldata: Bytes::new(),
|
|
||||||
max_fee_per_gas: 100,
|
|
||||||
max_priority_fee_per_gas: 10,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn shared_settings() -> SharedGrantSettings {
|
|
||||||
SharedGrantSettings {
|
|
||||||
wallet_access_id: WALLET_ACCESS_ID,
|
|
||||||
chain: CHAIN_ID,
|
|
||||||
valid_from: None,
|
|
||||||
valid_until: None,
|
|
||||||
max_gas_fee_per_gas: None,
|
|
||||||
max_priority_fee_per_gas: None,
|
|
||||||
rate_limit: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn insert_basic_grant(
|
|
||||||
conn: &mut DatabaseConnection,
|
|
||||||
shared: &SharedGrantSettings,
|
|
||||||
) -> EvmBasicGrant {
|
|
||||||
insert_into(evm_basic_grant::table)
|
|
||||||
.values(NewEvmBasicGrant {
|
|
||||||
wallet_access_id: shared.wallet_access_id,
|
|
||||||
chain_id: shared.chain as i32,
|
|
||||||
valid_from: shared.valid_from.map(SqliteTimestamp),
|
|
||||||
valid_until: shared.valid_until.map(SqliteTimestamp),
|
|
||||||
max_gas_fee_per_gas: shared
|
|
||||||
.max_gas_fee_per_gas
|
|
||||||
.map(|fee| super::utils::u256_to_bytes(fee).to_vec()),
|
|
||||||
max_priority_fee_per_gas: shared
|
|
||||||
.max_priority_fee_per_gas
|
|
||||||
.map(|fee| super::utils::u256_to_bytes(fee).to_vec()),
|
|
||||||
rate_limit_count: shared.rate_limit.as_ref().map(|limit| limit.count as i32),
|
|
||||||
rate_limit_window_secs: shared
|
|
||||||
.rate_limit
|
|
||||||
.as_ref()
|
|
||||||
.map(|limit| limit.window.num_seconds() as i32),
|
|
||||||
revoked_at: None,
|
|
||||||
})
|
|
||||||
.returning(EvmBasicGrant::as_select())
|
|
||||||
.get_result(conn)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case::matching_chain(CHAIN_ID, false)]
|
|
||||||
#[case::mismatching_chain(CHAIN_ID + 1, true)]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn check_shared_constraints_enforces_chain_id(
|
|
||||||
#[case] context_chain: u64,
|
|
||||||
#[case] expect_mismatch: bool,
|
|
||||||
) {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
|
|
||||||
let context = EvalContext {
|
|
||||||
chain: context_chain,
|
|
||||||
..context()
|
|
||||||
};
|
|
||||||
|
|
||||||
let violations = check_shared_constraints(&context, &shared_settings(), 999, &mut *conn)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
violations
|
|
||||||
.iter()
|
|
||||||
.any(|violation| matches!(violation, EvalViolation::MismatchingChainId { .. })),
|
|
||||||
expect_mismatch
|
|
||||||
);
|
|
||||||
|
|
||||||
if expect_mismatch {
|
|
||||||
assert_eq!(violations.len(), 1);
|
|
||||||
} else {
|
|
||||||
assert!(violations.is_empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case::valid_from_in_bounds(Some(Utc::now() - Duration::hours(1)), None, false)]
|
|
||||||
#[case::valid_from_out_of_bounds(Some(Utc::now() + Duration::hours(1)), None, true)]
|
|
||||||
#[case::valid_until_in_bounds(None, Some(Utc::now() + Duration::hours(1)), false)]
|
|
||||||
#[case::valid_until_out_of_bounds(None, Some(Utc::now() - Duration::hours(1)), true)]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn check_shared_constraints_enforces_validity_window(
|
|
||||||
#[case] valid_from: Option<chrono::DateTime<Utc>>,
|
|
||||||
#[case] valid_until: Option<chrono::DateTime<Utc>>,
|
|
||||||
#[case] expect_invalid_time: bool,
|
|
||||||
) {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
|
|
||||||
let shared = SharedGrantSettings {
|
|
||||||
valid_from,
|
|
||||||
valid_until,
|
|
||||||
..shared_settings()
|
|
||||||
};
|
|
||||||
|
|
||||||
let violations = check_shared_constraints(&context(), &shared, 999, &mut *conn)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
violations
|
|
||||||
.iter()
|
|
||||||
.any(|violation| matches!(violation, EvalViolation::InvalidTime)),
|
|
||||||
expect_invalid_time
|
|
||||||
);
|
|
||||||
|
|
||||||
if expect_invalid_time {
|
|
||||||
assert_eq!(violations.len(), 1);
|
|
||||||
} else {
|
|
||||||
assert!(violations.is_empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case::max_fee_within_limit(Some(U256::from(100u64)), None, 100, 10, false)]
|
|
||||||
#[case::max_fee_exceeded(Some(U256::from(99u64)), None, 100, 10, true)]
|
|
||||||
#[case::priority_fee_within_limit(None, Some(U256::from(10u64)), 100, 10, false)]
|
|
||||||
#[case::priority_fee_exceeded(None, Some(U256::from(9u64)), 100, 10, true)]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn check_shared_constraints_enforces_gas_fee_caps(
|
|
||||||
#[case] max_gas_fee_per_gas: Option<U256>,
|
|
||||||
#[case] max_priority_fee_per_gas: Option<U256>,
|
|
||||||
#[case] actual_max_fee_per_gas: u128,
|
|
||||||
#[case] actual_max_priority_fee_per_gas: u128,
|
|
||||||
#[case] expect_gas_limit_violation: bool,
|
|
||||||
) {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
|
|
||||||
let context = EvalContext {
|
|
||||||
max_fee_per_gas: actual_max_fee_per_gas,
|
|
||||||
max_priority_fee_per_gas: actual_max_priority_fee_per_gas,
|
|
||||||
..context()
|
|
||||||
};
|
|
||||||
|
|
||||||
let shared = SharedGrantSettings {
|
|
||||||
max_gas_fee_per_gas,
|
|
||||||
max_priority_fee_per_gas,
|
|
||||||
..shared_settings()
|
|
||||||
};
|
|
||||||
let violations = check_shared_constraints(&context, &shared, 999, &mut *conn)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
violations
|
|
||||||
.iter()
|
|
||||||
.any(|violation| matches!(violation, EvalViolation::GasLimitExceeded { .. })),
|
|
||||||
expect_gas_limit_violation
|
|
||||||
);
|
|
||||||
|
|
||||||
if expect_gas_limit_violation {
|
|
||||||
assert_eq!(violations.len(), 1);
|
|
||||||
} else {
|
|
||||||
assert!(violations.is_empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case::under_rate_limit(2, false)]
|
|
||||||
#[case::at_rate_limit(1, true)]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn check_shared_constraints_enforces_rate_limit(
|
|
||||||
#[case] rate_limit_count: u32,
|
|
||||||
#[case] expect_rate_limit_violation: bool,
|
|
||||||
) {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
|
|
||||||
let shared = SharedGrantSettings {
|
|
||||||
rate_limit: Some(TransactionRateLimit {
|
|
||||||
count: rate_limit_count,
|
|
||||||
window: Duration::hours(1),
|
|
||||||
}),
|
|
||||||
..shared_settings()
|
|
||||||
};
|
|
||||||
|
|
||||||
let basic_grant = insert_basic_grant(&mut conn, &shared).await;
|
|
||||||
|
|
||||||
insert_into(evm_transaction_log::table)
|
|
||||||
.values(NewEvmTransactionLog {
|
|
||||||
grant_id: basic_grant.id,
|
|
||||||
wallet_access_id: WALLET_ACCESS_ID,
|
|
||||||
chain_id: CHAIN_ID as i32,
|
|
||||||
eth_value: super::utils::u256_to_bytes(U256::ZERO).to_vec(),
|
|
||||||
signed_at: SqliteTimestamp(Utc::now()),
|
|
||||||
})
|
|
||||||
.execute(&mut *conn)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let violations = check_shared_constraints(&context(), &shared, basic_grant.id, &mut *conn)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
violations
|
|
||||||
.iter()
|
|
||||||
.any(|violation| matches!(violation, EvalViolation::RateLimitExceeded)),
|
|
||||||
expect_rate_limit_violation
|
|
||||||
);
|
|
||||||
|
|
||||||
if expect_rate_limit_violation {
|
|
||||||
assert_eq!(violations.len(), 1);
|
|
||||||
} else {
|
|
||||||
assert!(violations.is_empty());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,11 +6,10 @@ use diesel::{
|
|||||||
ExpressionMethods as _, QueryDsl, SelectableHelper, result::QueryResult, sqlite::Sqlite,
|
ExpressionMethods as _, QueryDsl, SelectableHelper, result::QueryResult, sqlite::Sqlite,
|
||||||
};
|
};
|
||||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||||
|
use miette::Diagnostic;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
crypto::integrity::v1::Integrable,
|
|
||||||
db::models::{self, EvmBasicGrant, EvmWalletAccess},
|
db::models::{self, EvmBasicGrant, EvmWalletAccess},
|
||||||
evm::utils,
|
evm::utils,
|
||||||
};
|
};
|
||||||
@@ -34,31 +33,34 @@ pub struct EvalContext {
|
|||||||
pub max_priority_fee_per_gas: u128,
|
pub max_priority_fee_per_gas: u128,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error, Diagnostic)]
|
||||||
pub enum EvalViolation {
|
pub enum EvalViolation {
|
||||||
#[error("This grant doesn't allow transactions to the target address {target}")]
|
#[error("This grant doesn't allow transactions to the target address {target}")]
|
||||||
|
#[diagnostic(code(arbiter_server::evm::eval_violation::invalid_target))]
|
||||||
InvalidTarget { target: Address },
|
InvalidTarget { target: Address },
|
||||||
|
|
||||||
#[error("Gas limit exceeded for this grant")]
|
#[error("Gas limit exceeded for this grant")]
|
||||||
|
#[diagnostic(code(arbiter_server::evm::eval_violation::gas_limit_exceeded))]
|
||||||
GasLimitExceeded {
|
GasLimitExceeded {
|
||||||
max_gas_fee_per_gas: Option<U256>,
|
max_gas_fee_per_gas: Option<U256>,
|
||||||
max_priority_fee_per_gas: Option<U256>,
|
max_priority_fee_per_gas: Option<U256>,
|
||||||
},
|
},
|
||||||
|
|
||||||
#[error("Rate limit exceeded for this grant")]
|
#[error("Rate limit exceeded for this grant")]
|
||||||
|
#[diagnostic(code(arbiter_server::evm::eval_violation::rate_limit_exceeded))]
|
||||||
RateLimitExceeded,
|
RateLimitExceeded,
|
||||||
|
|
||||||
#[error("Transaction exceeds volumetric limits of the grant")]
|
#[error("Transaction exceeds volumetric limits of the grant")]
|
||||||
|
#[diagnostic(code(arbiter_server::evm::eval_violation::volumetric_limit_exceeded))]
|
||||||
VolumetricLimitExceeded,
|
VolumetricLimitExceeded,
|
||||||
|
|
||||||
#[error("Transaction is outside of the grant's validity period")]
|
#[error("Transaction is outside of the grant's validity period")]
|
||||||
|
#[diagnostic(code(arbiter_server::evm::eval_violation::invalid_time))]
|
||||||
InvalidTime,
|
InvalidTime,
|
||||||
|
|
||||||
#[error("Transaction type is not allowed by this grant")]
|
#[error("Transaction type is not allowed by this grant")]
|
||||||
|
#[diagnostic(code(arbiter_server::evm::eval_violation::invalid_transaction_type))]
|
||||||
InvalidTransactionType,
|
InvalidTransactionType,
|
||||||
|
|
||||||
#[error("Mismatching chain ID")]
|
|
||||||
MismatchingChainId { expected: ChainId, actual: ChainId },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type DatabaseID = i32;
|
pub type DatabaseID = i32;
|
||||||
@@ -66,12 +68,13 @@ pub type DatabaseID = i32;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Grant<PolicySettings> {
|
pub struct Grant<PolicySettings> {
|
||||||
pub id: DatabaseID,
|
pub id: DatabaseID,
|
||||||
pub common_settings_id: DatabaseID, // ID of the basic grant for shared-logic checks like rate limits and validity periods
|
pub shared_grant_id: DatabaseID, // ID of the basic grant for shared-logic checks like rate limits and validity periods
|
||||||
pub settings: CombinedSettings<PolicySettings>,
|
pub shared: SharedGrantSettings,
|
||||||
|
pub settings: PolicySettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Policy: Sized {
|
pub trait Policy: Sized {
|
||||||
type Settings: Send + Sync + 'static + Into<SpecificGrant> + Integrable;
|
type Settings: Send + Sync + 'static + Into<SpecificGrant>;
|
||||||
type Meaning: Display + std::fmt::Debug + Send + Sync + 'static + Into<SpecificMeaning>;
|
type Meaning: Display + std::fmt::Debug + Send + Sync + 'static + Into<SpecificMeaning>;
|
||||||
|
|
||||||
fn analyze(context: &EvalContext) -> Option<Self::Meaning>;
|
fn analyze(context: &EvalContext) -> Option<Self::Meaning>;
|
||||||
@@ -127,13 +130,13 @@ pub enum SpecificMeaning {
|
|||||||
TokenTransfer(token_transfers::Meaning),
|
TokenTransfer(token_transfers::Meaning),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct TransactionRateLimit {
|
pub struct TransactionRateLimit {
|
||||||
pub count: u32,
|
pub count: u32,
|
||||||
pub window: Duration,
|
pub window: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct VolumeRateLimit {
|
pub struct VolumeRateLimit {
|
||||||
pub max_volume: U256,
|
pub max_volume: U256,
|
||||||
pub window: Duration,
|
pub window: Duration,
|
||||||
@@ -154,7 +157,7 @@ pub struct SharedGrantSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SharedGrantSettings {
|
impl SharedGrantSettings {
|
||||||
pub(crate) fn try_from_model(model: EvmBasicGrant) -> QueryResult<Self> {
|
fn try_from_model(model: EvmBasicGrant) -> QueryResult<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
wallet_access_id: model.wallet_access_id,
|
wallet_access_id: model.wallet_access_id,
|
||||||
chain: model.chain_id as u64, // safe because chain_id is stored as i32 but is guaranteed to be a valid ChainId by the API when creating grants
|
chain: model.chain_id as u64, // safe because chain_id is stored as i32 but is guaranteed to be a valid ChainId by the API when creating grants
|
||||||
@@ -200,57 +203,7 @@ pub enum SpecificGrant {
|
|||||||
TokenTransfer(token_transfers::Settings),
|
TokenTransfer(token_transfers::Settings),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
pub struct FullGrant<PolicyGrant> {
|
||||||
pub struct CombinedSettings<PolicyGrant> {
|
pub basic: SharedGrantSettings,
|
||||||
pub shared: SharedGrantSettings,
|
|
||||||
pub specific: PolicyGrant,
|
pub specific: PolicyGrant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P> CombinedSettings<P> {
|
|
||||||
pub fn generalize<Y: From<P>>(self) -> CombinedSettings<Y> {
|
|
||||||
CombinedSettings {
|
|
||||||
shared: self.shared,
|
|
||||||
specific: self.specific.into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Integrable> Integrable for CombinedSettings<P> {
|
|
||||||
const KIND: &'static str = P::KIND;
|
|
||||||
const VERSION: i32 = P::VERSION;
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::crypto::integrity::hashing::Hashable;
|
|
||||||
|
|
||||||
impl Hashable for TransactionRateLimit {
|
|
||||||
fn hash<H: sha2::Digest>(&self, hasher: &mut H) {
|
|
||||||
self.count.hash(hasher);
|
|
||||||
self.window.hash(hasher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hashable for VolumeRateLimit {
|
|
||||||
fn hash<H: sha2::Digest>(&self, hasher: &mut H) {
|
|
||||||
self.max_volume.hash(hasher);
|
|
||||||
self.window.hash(hasher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Hashable for SharedGrantSettings {
|
|
||||||
fn hash<H: sha2::Digest>(&self, hasher: &mut H) {
|
|
||||||
self.wallet_access_id.hash(hasher);
|
|
||||||
self.chain.hash(hasher);
|
|
||||||
self.valid_from.hash(hasher);
|
|
||||||
self.valid_until.hash(hasher);
|
|
||||||
self.max_gas_fee_per_gas.hash(hasher);
|
|
||||||
self.max_priority_fee_per_gas.hash(hasher);
|
|
||||||
self.rate_limit.hash(hasher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<P: Hashable> Hashable for CombinedSettings<P> {
|
|
||||||
fn hash<H: sha2::Digest>(&self, hasher: &mut H) {
|
|
||||||
self.shared.hash(hasher);
|
|
||||||
self.specific.hash(hasher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,14 +8,13 @@ use diesel::sqlite::Sqlite;
|
|||||||
use diesel::{ExpressionMethods, JoinOnDsl, prelude::*};
|
use diesel::{ExpressionMethods, JoinOnDsl, prelude::*};
|
||||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||||
|
|
||||||
use crate::crypto::integrity::v1::Integrable;
|
|
||||||
use crate::db::models::{
|
use crate::db::models::{
|
||||||
EvmBasicGrant, EvmEtherTransferGrant, EvmEtherTransferGrantTarget, EvmEtherTransferLimit,
|
EvmBasicGrant, EvmEtherTransferGrant, EvmEtherTransferGrantTarget, EvmEtherTransferLimit,
|
||||||
NewEvmEtherTransferLimit, SqliteTimestamp,
|
NewEvmEtherTransferLimit, SqliteTimestamp,
|
||||||
};
|
};
|
||||||
use crate::db::schema::{evm_basic_grant, evm_ether_transfer_limit, evm_transaction_log};
|
use crate::db::schema::{evm_basic_grant, evm_ether_transfer_limit, evm_transaction_log};
|
||||||
use crate::evm::policies::{
|
use crate::evm::policies::{
|
||||||
CombinedSettings, Grant, SharedGrantSettings, SpecificGrant, SpecificMeaning, VolumeRateLimit,
|
Grant, SharedGrantSettings, SpecificGrant, SpecificMeaning, VolumeRateLimit,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{
|
db::{
|
||||||
@@ -37,8 +36,8 @@ use super::{DatabaseID, EvalContext, EvalViolation};
|
|||||||
// Plain ether transfer
|
// Plain ether transfer
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct Meaning {
|
pub struct Meaning {
|
||||||
pub(crate) to: Address,
|
to: Address,
|
||||||
pub(crate) value: U256,
|
value: U256,
|
||||||
}
|
}
|
||||||
impl Display for Meaning {
|
impl Display for Meaning {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
@@ -57,18 +56,6 @@ pub struct Settings {
|
|||||||
pub target: Vec<Address>,
|
pub target: Vec<Address>,
|
||||||
pub limit: VolumeRateLimit,
|
pub limit: VolumeRateLimit,
|
||||||
}
|
}
|
||||||
impl Integrable for Settings {
|
|
||||||
const KIND: &'static str = "EtherTransfer";
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::crypto::integrity::hashing::Hashable;
|
|
||||||
|
|
||||||
impl Hashable for Settings {
|
|
||||||
fn hash<H: sha2::Digest>(&self, hasher: &mut H) {
|
|
||||||
self.target.hash(hasher);
|
|
||||||
self.limit.hash(hasher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Settings> for SpecificGrant {
|
impl From<Settings> for SpecificGrant {
|
||||||
fn from(val: Settings) -> SpecificGrant {
|
fn from(val: Settings) -> SpecificGrant {
|
||||||
@@ -104,22 +91,20 @@ async fn query_relevant_past_transaction(
|
|||||||
|
|
||||||
async fn check_rate_limits(
|
async fn check_rate_limits(
|
||||||
grant: &Grant<Settings>,
|
grant: &Grant<Settings>,
|
||||||
current_transfer_value: U256,
|
|
||||||
db: &mut impl AsyncConnection<Backend = Sqlite>,
|
db: &mut impl AsyncConnection<Backend = Sqlite>,
|
||||||
) -> QueryResult<Vec<EvalViolation>> {
|
) -> QueryResult<Vec<EvalViolation>> {
|
||||||
let mut violations = Vec::new();
|
let mut violations = Vec::new();
|
||||||
let window = grant.settings.specific.limit.window;
|
let window = grant.settings.limit.window;
|
||||||
|
|
||||||
let past_transaction =
|
let past_transaction = query_relevant_past_transaction(grant.id, window, db).await?;
|
||||||
query_relevant_past_transaction(grant.common_settings_id, window, db).await?;
|
|
||||||
|
|
||||||
let window_start = chrono::Utc::now() - grant.settings.specific.limit.window;
|
let window_start = chrono::Utc::now() - grant.settings.limit.window;
|
||||||
let prospective_cumulative_volume: U256 = past_transaction
|
let cumulative_volume: U256 = past_transaction
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, timestamp)| timestamp >= &window_start)
|
.filter(|(_, timestamp)| timestamp >= &window_start)
|
||||||
.fold(current_transfer_value, |acc, (value, _)| acc + *value);
|
.fold(U256::default(), |acc, (value, _)| acc + *value);
|
||||||
|
|
||||||
if prospective_cumulative_volume > grant.settings.specific.limit.max_volume {
|
if cumulative_volume > grant.settings.limit.max_volume {
|
||||||
violations.push(EvalViolation::VolumetricLimitExceeded);
|
violations.push(EvalViolation::VolumetricLimitExceeded);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,11 +137,11 @@ impl Policy for EtherTransfer {
|
|||||||
let mut violations = Vec::new();
|
let mut violations = Vec::new();
|
||||||
|
|
||||||
// Check if the target address is within the grant's allowed targets
|
// Check if the target address is within the grant's allowed targets
|
||||||
if !grant.settings.specific.target.contains(&meaning.to) {
|
if !grant.settings.target.contains(&meaning.to) {
|
||||||
violations.push(EvalViolation::InvalidTarget { target: meaning.to });
|
violations.push(EvalViolation::InvalidTarget { target: meaning.to });
|
||||||
}
|
}
|
||||||
|
|
||||||
let rate_violations = check_rate_limits(grant, meaning.value, db).await?;
|
let rate_violations = check_rate_limits(grant, db).await?;
|
||||||
violations.extend(rate_violations);
|
violations.extend(rate_violations);
|
||||||
|
|
||||||
Ok(violations)
|
Ok(violations)
|
||||||
@@ -250,21 +235,20 @@ impl Policy for EtherTransfer {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(Some(Grant {
|
let settings = Settings {
|
||||||
id: grant.id,
|
|
||||||
common_settings_id: grant.basic_grant_id,
|
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: SharedGrantSettings::try_from_model(basic_grant)?,
|
|
||||||
specific: Settings {
|
|
||||||
target: targets,
|
target: targets,
|
||||||
limit: VolumeRateLimit {
|
limit: VolumeRateLimit {
|
||||||
max_volume: utils::try_bytes_to_u256(&limit.max_volume).map_err(|err| {
|
max_volume: utils::try_bytes_to_u256(&limit.max_volume)
|
||||||
diesel::result::Error::DeserializationError(Box::new(err))
|
.map_err(|err| diesel::result::Error::DeserializationError(Box::new(err)))?,
|
||||||
})?,
|
|
||||||
window: chrono::Duration::seconds(limit.window_secs as i64),
|
window: chrono::Duration::seconds(limit.window_secs as i64),
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
},
|
|
||||||
|
Ok(Some(Grant {
|
||||||
|
id: grant.id,
|
||||||
|
shared_grant_id: grant.basic_grant_id,
|
||||||
|
shared: SharedGrantSettings::try_from_model(basic_grant)?,
|
||||||
|
settings,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,10 +326,9 @@ impl Policy for EtherTransfer {
|
|||||||
|
|
||||||
Ok(Grant {
|
Ok(Grant {
|
||||||
id: specific.id,
|
id: specific.id,
|
||||||
common_settings_id: specific.basic_grant_id,
|
shared_grant_id: specific.basic_grant_id,
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: SharedGrantSettings::try_from_model(basic)?,
|
shared: SharedGrantSettings::try_from_model(basic)?,
|
||||||
specific: Settings {
|
settings: Settings {
|
||||||
target: targets,
|
target: targets,
|
||||||
limit: VolumeRateLimit {
|
limit: VolumeRateLimit {
|
||||||
max_volume: utils::try_bytes_to_u256(&limit.max_volume).map_err(
|
max_volume: utils::try_bytes_to_u256(&limit.max_volume).map_err(
|
||||||
@@ -354,7 +337,6 @@ impl Policy for EtherTransfer {
|
|||||||
window: Duration::seconds(limit.window_secs as i64),
|
window: Duration::seconds(limit.window_secs as i64),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ use crate::db::{
|
|||||||
schema::{evm_basic_grant, evm_transaction_log},
|
schema::{evm_basic_grant, evm_transaction_log},
|
||||||
};
|
};
|
||||||
use crate::evm::{
|
use crate::evm::{
|
||||||
policies::{
|
policies::{EvalContext, EvalViolation, Grant, Policy, SharedGrantSettings, VolumeRateLimit},
|
||||||
CombinedSettings, EvalContext, EvalViolation, Grant, Policy, SharedGrantSettings,
|
|
||||||
VolumeRateLimit,
|
|
||||||
},
|
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -84,6 +81,8 @@ fn shared() -> SharedGrantSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── analyze ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn analyze_matches_empty_calldata() {
|
fn analyze_matches_empty_calldata() {
|
||||||
let m = EtherTransfer::analyze(&ctx(ALLOWED, U256::from(1_000u64))).unwrap();
|
let m = EtherTransfer::analyze(&ctx(ALLOWED, U256::from(1_000u64))).unwrap();
|
||||||
@@ -100,6 +99,8 @@ fn analyze_rejects_nonempty_calldata() {
|
|||||||
assert!(EtherTransfer::analyze(&context).is_none());
|
assert!(EtherTransfer::analyze(&context).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── evaluate ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn evaluate_passes_for_allowed_target() {
|
async fn evaluate_passes_for_allowed_target() {
|
||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
@@ -107,11 +108,9 @@ async fn evaluate_passes_for_allowed_target() {
|
|||||||
|
|
||||||
let grant = Grant {
|
let grant = Grant {
|
||||||
id: 999,
|
id: 999,
|
||||||
common_settings_id: 999,
|
shared_grant_id: 999,
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: shared(),
|
shared: shared(),
|
||||||
specific: make_settings(vec![ALLOWED], 1_000_000),
|
settings: make_settings(vec![ALLOWED], 1_000_000),
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let context = ctx(ALLOWED, U256::from(100u64));
|
let context = ctx(ALLOWED, U256::from(100u64));
|
||||||
let m = EtherTransfer::analyze(&context).unwrap();
|
let m = EtherTransfer::analyze(&context).unwrap();
|
||||||
@@ -128,11 +127,9 @@ async fn evaluate_rejects_disallowed_target() {
|
|||||||
|
|
||||||
let grant = Grant {
|
let grant = Grant {
|
||||||
id: 999,
|
id: 999,
|
||||||
common_settings_id: 999,
|
shared_grant_id: 999,
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: shared(),
|
shared: shared(),
|
||||||
specific: make_settings(vec![ALLOWED], 1_000_000),
|
settings: make_settings(vec![ALLOWED], 1_000_000),
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let context = ctx(OTHER, U256::from(100u64));
|
let context = ctx(OTHER, U256::from(100u64));
|
||||||
let m = EtherTransfer::analyze(&context).unwrap();
|
let m = EtherTransfer::analyze(&context).unwrap();
|
||||||
@@ -170,11 +167,9 @@ async fn evaluate_passes_when_volume_within_limit() {
|
|||||||
|
|
||||||
let grant = Grant {
|
let grant = Grant {
|
||||||
id: grant_id,
|
id: grant_id,
|
||||||
common_settings_id: basic.id,
|
shared_grant_id: basic.id,
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: shared(),
|
shared: shared(),
|
||||||
specific: settings,
|
settings,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let context = ctx(ALLOWED, U256::from(100u64));
|
let context = ctx(ALLOWED, U256::from(100u64));
|
||||||
let m = EtherTransfer::analyze(&context).unwrap();
|
let m = EtherTransfer::analyze(&context).unwrap();
|
||||||
@@ -203,7 +198,7 @@ async fn evaluate_rejects_volume_over_limit() {
|
|||||||
grant_id,
|
grant_id,
|
||||||
wallet_access_id: WALLET_ACCESS_ID,
|
wallet_access_id: WALLET_ACCESS_ID,
|
||||||
chain_id: CHAIN_ID as i32,
|
chain_id: CHAIN_ID as i32,
|
||||||
eth_value: utils::u256_to_bytes(U256::from(1_000u64)).to_vec(),
|
eth_value: utils::u256_to_bytes(U256::from(1_001u64)).to_vec(),
|
||||||
signed_at: SqliteTimestamp(Utc::now()),
|
signed_at: SqliteTimestamp(Utc::now()),
|
||||||
})
|
})
|
||||||
.execute(&mut *conn)
|
.execute(&mut *conn)
|
||||||
@@ -212,13 +207,11 @@ async fn evaluate_rejects_volume_over_limit() {
|
|||||||
|
|
||||||
let grant = Grant {
|
let grant = Grant {
|
||||||
id: grant_id,
|
id: grant_id,
|
||||||
common_settings_id: basic.id,
|
shared_grant_id: basic.id,
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: shared(),
|
shared: shared(),
|
||||||
specific: settings,
|
settings,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let context = ctx(ALLOWED, U256::from(1u64));
|
let context = ctx(ALLOWED, U256::from(100u64));
|
||||||
let m = EtherTransfer::analyze(&context).unwrap();
|
let m = EtherTransfer::analyze(&context).unwrap();
|
||||||
let v = EtherTransfer::evaluate(&context, &m, &grant, &mut *conn)
|
let v = EtherTransfer::evaluate(&context, &m, &grant, &mut *conn)
|
||||||
.await
|
.await
|
||||||
@@ -240,13 +233,13 @@ async fn evaluate_passes_at_exactly_volume_limit() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Exactly at the limit including current transfer — check is `>`, so this should not violate
|
// Exactly at the limit — the check is `>`, so this should not violate
|
||||||
insert_into(evm_transaction_log::table)
|
insert_into(evm_transaction_log::table)
|
||||||
.values(NewEvmTransactionLog {
|
.values(NewEvmTransactionLog {
|
||||||
grant_id,
|
grant_id,
|
||||||
wallet_access_id: WALLET_ACCESS_ID,
|
wallet_access_id: WALLET_ACCESS_ID,
|
||||||
chain_id: CHAIN_ID as i32,
|
chain_id: CHAIN_ID as i32,
|
||||||
eth_value: utils::u256_to_bytes(U256::from(900u64)).to_vec(),
|
eth_value: utils::u256_to_bytes(U256::from(1_000u64)).to_vec(),
|
||||||
signed_at: SqliteTimestamp(Utc::now()),
|
signed_at: SqliteTimestamp(Utc::now()),
|
||||||
})
|
})
|
||||||
.execute(&mut *conn)
|
.execute(&mut *conn)
|
||||||
@@ -255,11 +248,9 @@ async fn evaluate_passes_at_exactly_volume_limit() {
|
|||||||
|
|
||||||
let grant = Grant {
|
let grant = Grant {
|
||||||
id: grant_id,
|
id: grant_id,
|
||||||
common_settings_id: basic.id,
|
shared_grant_id: basic.id,
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: shared(),
|
shared: shared(),
|
||||||
specific: settings,
|
settings,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let context = ctx(ALLOWED, U256::from(100u64));
|
let context = ctx(ALLOWED, U256::from(100u64));
|
||||||
let m = EtherTransfer::analyze(&context).unwrap();
|
let m = EtherTransfer::analyze(&context).unwrap();
|
||||||
@@ -272,6 +263,8 @@ async fn evaluate_passes_at_exactly_volume_limit() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── try_find_grant ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn try_find_grant_roundtrip() {
|
async fn try_find_grant_roundtrip() {
|
||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
@@ -289,11 +282,8 @@ async fn try_find_grant_roundtrip() {
|
|||||||
|
|
||||||
assert!(found.is_some());
|
assert!(found.is_some());
|
||||||
let g = found.unwrap();
|
let g = found.unwrap();
|
||||||
assert_eq!(g.settings.specific.target, vec![ALLOWED]);
|
assert_eq!(g.settings.target, vec![ALLOWED]);
|
||||||
assert_eq!(
|
assert_eq!(g.settings.limit.max_volume, U256::from(1_000_000u64));
|
||||||
g.settings.specific.limit.max_volume,
|
|
||||||
U256::from(1_000_000u64)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -330,36 +320,7 @@ async fn try_find_grant_wrong_target_returns_none() {
|
|||||||
assert!(found.is_none());
|
assert!(found.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
proptest::proptest! {
|
// ── find_all_grants ──────────────────────────────────────────────────────
|
||||||
#[test]
|
|
||||||
fn target_order_does_not_affect_hash(
|
|
||||||
raw_addrs in proptest::collection::vec(proptest::prelude::any::<[u8; 20]>(), 0..8),
|
|
||||||
seed in proptest::prelude::any::<u64>(),
|
|
||||||
max_volume in proptest::prelude::any::<u64>(),
|
|
||||||
window_secs in 1i64..=86400,
|
|
||||||
) {
|
|
||||||
use rand::{SeedableRng, seq::SliceRandom};
|
|
||||||
use sha2::Digest;
|
|
||||||
use crate::crypto::integrity::hashing::Hashable;
|
|
||||||
|
|
||||||
let addrs: Vec<Address> = raw_addrs.iter().map(|b| Address::from(*b)).collect();
|
|
||||||
let mut shuffled = addrs.clone();
|
|
||||||
shuffled.shuffle(&mut rand::rngs::StdRng::seed_from_u64(seed));
|
|
||||||
|
|
||||||
let limit = VolumeRateLimit {
|
|
||||||
max_volume: U256::from(max_volume),
|
|
||||||
window: Duration::seconds(window_secs),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut h1 = sha2::Sha256::new();
|
|
||||||
Settings { target: addrs, limit: limit.clone() }.hash(&mut h1);
|
|
||||||
|
|
||||||
let mut h2 = sha2::Sha256::new();
|
|
||||||
Settings { target: shuffled, limit }.hash(&mut h2);
|
|
||||||
|
|
||||||
proptest::prop_assert_eq!(h1.finalize(), h2.finalize());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn find_all_grants_empty_db() {
|
async fn find_all_grants_empty_db() {
|
||||||
@@ -386,7 +347,7 @@ async fn find_all_grants_excludes_revoked() {
|
|||||||
|
|
||||||
let all = EtherTransfer::find_all_grants(&mut *conn).await.unwrap();
|
let all = EtherTransfer::find_all_grants(&mut *conn).await.unwrap();
|
||||||
assert_eq!(all.len(), 1);
|
assert_eq!(all.len(), 1);
|
||||||
assert_eq!(all[0].settings.specific.target, vec![ALLOWED]);
|
assert_eq!(all[0].settings.target, vec![ALLOWED]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -402,11 +363,8 @@ async fn find_all_grants_multiple_targets() {
|
|||||||
|
|
||||||
let all = EtherTransfer::find_all_grants(&mut *conn).await.unwrap();
|
let all = EtherTransfer::find_all_grants(&mut *conn).await.unwrap();
|
||||||
assert_eq!(all.len(), 1);
|
assert_eq!(all.len(), 1);
|
||||||
assert_eq!(all[0].settings.specific.target.len(), 2);
|
assert_eq!(all[0].settings.target.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(all[0].settings.limit.max_volume, U256::from(1_000_000u64));
|
||||||
all[0].settings.specific.limit.max_volume,
|
|
||||||
U256::from(1_000_000u64)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use alloy::{
|
||||||
|
primitives::{Address, U256},
|
||||||
|
sol_types::SolCall,
|
||||||
|
};
|
||||||
|
use arbiter_tokens_registry::evm::nonfungible::{self, TokenInfo};
|
||||||
|
use chrono::{DateTime, Duration, Utc};
|
||||||
|
use diesel::dsl::{auto_type, insert_into};
|
||||||
|
use diesel::sqlite::Sqlite;
|
||||||
|
use diesel::{ExpressionMethods, prelude::*};
|
||||||
|
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||||
|
|
||||||
|
use crate::db::models::{
|
||||||
|
EvmBasicGrant, EvmTokenTransferGrant, EvmTokenTransferVolumeLimit, NewEvmTokenTransferGrant,
|
||||||
|
NewEvmTokenTransferLog, NewEvmTokenTransferVolumeLimit, SqliteTimestamp,
|
||||||
|
};
|
||||||
use crate::db::schema::{
|
use crate::db::schema::{
|
||||||
evm_basic_grant, evm_token_transfer_grant, evm_token_transfer_log,
|
evm_basic_grant, evm_token_transfer_grant, evm_token_transfer_log,
|
||||||
evm_token_transfer_volume_limit,
|
evm_token_transfer_volume_limit,
|
||||||
@@ -11,25 +26,6 @@ use crate::evm::{
|
|||||||
},
|
},
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
use crate::{
|
|
||||||
crypto::integrity::Integrable,
|
|
||||||
db::models::{
|
|
||||||
EvmBasicGrant, EvmTokenTransferGrant, EvmTokenTransferVolumeLimit,
|
|
||||||
NewEvmTokenTransferGrant, NewEvmTokenTransferLog, NewEvmTokenTransferVolumeLimit,
|
|
||||||
SqliteTimestamp,
|
|
||||||
},
|
|
||||||
evm::policies::CombinedSettings,
|
|
||||||
};
|
|
||||||
use alloy::{
|
|
||||||
primitives::{Address, U256},
|
|
||||||
sol_types::SolCall,
|
|
||||||
};
|
|
||||||
use arbiter_tokens_registry::evm::nonfungible::{self, TokenInfo};
|
|
||||||
use chrono::{DateTime, Duration, Utc};
|
|
||||||
use diesel::dsl::{auto_type, insert_into};
|
|
||||||
use diesel::sqlite::Sqlite;
|
|
||||||
use diesel::{ExpressionMethods, prelude::*};
|
|
||||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
|
||||||
|
|
||||||
use super::{DatabaseID, EvalContext, EvalViolation};
|
use super::{DatabaseID, EvalContext, EvalViolation};
|
||||||
|
|
||||||
@@ -42,9 +38,9 @@ fn grant_join() -> _ {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct Meaning {
|
pub struct Meaning {
|
||||||
pub token: &'static TokenInfo,
|
token: &'static TokenInfo,
|
||||||
pub to: Address,
|
to: Address,
|
||||||
pub value: U256,
|
value: U256,
|
||||||
}
|
}
|
||||||
impl std::fmt::Display for Meaning {
|
impl std::fmt::Display for Meaning {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
@@ -68,20 +64,6 @@ pub struct Settings {
|
|||||||
pub target: Option<Address>,
|
pub target: Option<Address>,
|
||||||
pub volume_limits: Vec<VolumeRateLimit>,
|
pub volume_limits: Vec<VolumeRateLimit>,
|
||||||
}
|
}
|
||||||
impl Integrable for Settings {
|
|
||||||
const KIND: &'static str = "TokenTransfer";
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::crypto::integrity::hashing::Hashable;
|
|
||||||
|
|
||||||
impl Hashable for Settings {
|
|
||||||
fn hash<H: sha2::Digest>(&self, hasher: &mut H) {
|
|
||||||
self.token_contract.hash(hasher);
|
|
||||||
self.target.hash(hasher);
|
|
||||||
self.volume_limits.hash(hasher);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Settings> for SpecificGrant {
|
impl From<Settings> for SpecificGrant {
|
||||||
fn from(val: Settings) -> SpecificGrant {
|
fn from(val: Settings) -> SpecificGrant {
|
||||||
SpecificGrant::TokenTransfer(val)
|
SpecificGrant::TokenTransfer(val)
|
||||||
@@ -119,32 +101,24 @@ async fn query_relevant_past_transfers(
|
|||||||
|
|
||||||
async fn check_volume_rate_limits(
|
async fn check_volume_rate_limits(
|
||||||
grant: &Grant<Settings>,
|
grant: &Grant<Settings>,
|
||||||
current_transfer_value: U256,
|
|
||||||
db: &mut impl AsyncConnection<Backend = Sqlite>,
|
db: &mut impl AsyncConnection<Backend = Sqlite>,
|
||||||
) -> QueryResult<Vec<EvalViolation>> {
|
) -> QueryResult<Vec<EvalViolation>> {
|
||||||
let mut violations = Vec::new();
|
let mut violations = Vec::new();
|
||||||
|
|
||||||
let Some(longest_window) = grant
|
let Some(longest_window) = grant.settings.volume_limits.iter().map(|l| l.window).max() else {
|
||||||
.settings
|
|
||||||
.specific
|
|
||||||
.volume_limits
|
|
||||||
.iter()
|
|
||||||
.map(|l| l.window)
|
|
||||||
.max()
|
|
||||||
else {
|
|
||||||
return Ok(violations);
|
return Ok(violations);
|
||||||
};
|
};
|
||||||
|
|
||||||
let past_transfers = query_relevant_past_transfers(grant.id, longest_window, db).await?;
|
let past_transfers = query_relevant_past_transfers(grant.id, longest_window, db).await?;
|
||||||
|
|
||||||
for limit in &grant.settings.specific.volume_limits {
|
for limit in &grant.settings.volume_limits {
|
||||||
let window_start = chrono::Utc::now() - limit.window;
|
let window_start = chrono::Utc::now() - limit.window;
|
||||||
let prospective_cumulative_volume: U256 = past_transfers
|
let cumulative_volume: U256 = past_transfers
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, timestamp)| timestamp >= &window_start)
|
.filter(|(_, timestamp)| timestamp >= &window_start)
|
||||||
.fold(current_transfer_value, |acc, (value, _)| acc + *value);
|
.fold(U256::default(), |acc, (value, _)| acc + *value);
|
||||||
|
|
||||||
if prospective_cumulative_volume > limit.max_volume {
|
if cumulative_volume > limit.max_volume {
|
||||||
violations.push(EvalViolation::VolumetricLimitExceeded);
|
violations.push(EvalViolation::VolumetricLimitExceeded);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -183,13 +157,13 @@ impl Policy for TokenTransfer {
|
|||||||
return Ok(violations);
|
return Ok(violations);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(allowed) = grant.settings.specific.target
|
if let Some(allowed) = grant.settings.target
|
||||||
&& allowed != meaning.to
|
&& allowed != meaning.to
|
||||||
{
|
{
|
||||||
violations.push(EvalViolation::InvalidTarget { target: meaning.to });
|
violations.push(EvalViolation::InvalidTarget { target: meaning.to });
|
||||||
}
|
}
|
||||||
|
|
||||||
let rate_violations = check_volume_rate_limits(grant, meaning.value, db).await?;
|
let rate_violations = check_volume_rate_limits(grant, db).await?;
|
||||||
violations.extend(rate_violations);
|
violations.extend(rate_violations);
|
||||||
|
|
||||||
Ok(violations)
|
Ok(violations)
|
||||||
@@ -286,17 +260,17 @@ impl Policy for TokenTransfer {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Some(Grant {
|
let settings = Settings {
|
||||||
id: token_grant.id,
|
|
||||||
common_settings_id: token_grant.basic_grant_id,
|
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: SharedGrantSettings::try_from_model(basic_grant)?,
|
|
||||||
specific: Settings {
|
|
||||||
token_contract: Address::from(token_contract),
|
token_contract: Address::from(token_contract),
|
||||||
target,
|
target,
|
||||||
volume_limits,
|
volume_limits,
|
||||||
},
|
};
|
||||||
},
|
|
||||||
|
Ok(Some(Grant {
|
||||||
|
id: token_grant.id,
|
||||||
|
shared_grant_id: token_grant.basic_grant_id,
|
||||||
|
shared: SharedGrantSettings::try_from_model(basic_grant)?,
|
||||||
|
settings,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,15 +368,13 @@ impl Policy for TokenTransfer {
|
|||||||
|
|
||||||
Ok(Grant {
|
Ok(Grant {
|
||||||
id: specific.id,
|
id: specific.id,
|
||||||
common_settings_id: specific.basic_grant_id,
|
shared_grant_id: specific.basic_grant_id,
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: SharedGrantSettings::try_from_model(basic)?,
|
shared: SharedGrantSettings::try_from_model(basic)?,
|
||||||
specific: Settings {
|
settings: Settings {
|
||||||
token_contract: Address::from(token_contract),
|
token_contract: Address::from(token_contract),
|
||||||
target,
|
target,
|
||||||
volume_limits,
|
volume_limits,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ use crate::db::{
|
|||||||
};
|
};
|
||||||
use crate::evm::{
|
use crate::evm::{
|
||||||
abi::IERC20::transferCall,
|
abi::IERC20::transferCall,
|
||||||
policies::{
|
policies::{EvalContext, EvalViolation, Grant, Policy, SharedGrantSettings, VolumeRateLimit},
|
||||||
CombinedSettings, EvalContext, EvalViolation, Grant, Policy, SharedGrantSettings,
|
|
||||||
VolumeRateLimit,
|
|
||||||
},
|
|
||||||
utils,
|
utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -101,6 +98,8 @@ fn shared() -> SharedGrantSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── analyze ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn analyze_known_token_valid_calldata() {
|
fn analyze_known_token_valid_calldata() {
|
||||||
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
|
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
|
||||||
@@ -126,6 +125,8 @@ fn analyze_empty_calldata_returns_none() {
|
|||||||
assert!(TokenTransfer::analyze(&ctx(DAI, Bytes::new())).is_none());
|
assert!(TokenTransfer::analyze(&ctx(DAI, Bytes::new())).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── evaluate ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn evaluate_rejects_nonzero_eth_value() {
|
async fn evaluate_rejects_nonzero_eth_value() {
|
||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
@@ -133,11 +134,9 @@ async fn evaluate_rejects_nonzero_eth_value() {
|
|||||||
|
|
||||||
let grant = Grant {
|
let grant = Grant {
|
||||||
id: 999,
|
id: 999,
|
||||||
common_settings_id: 999,
|
shared_grant_id: 999,
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: shared(),
|
shared: shared(),
|
||||||
specific: make_settings(None, None),
|
settings: make_settings(None, None),
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
|
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
|
||||||
let mut context = ctx(DAI, calldata);
|
let mut context = ctx(DAI, calldata);
|
||||||
@@ -164,11 +163,9 @@ async fn evaluate_passes_any_recipient_when_no_restriction() {
|
|||||||
|
|
||||||
let grant = Grant {
|
let grant = Grant {
|
||||||
id: 999,
|
id: 999,
|
||||||
common_settings_id: 999,
|
shared_grant_id: 999,
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: shared(),
|
shared: shared(),
|
||||||
specific: make_settings(None, None),
|
settings: make_settings(None, None),
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
|
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
|
||||||
let context = ctx(DAI, calldata);
|
let context = ctx(DAI, calldata);
|
||||||
@@ -186,11 +183,9 @@ async fn evaluate_passes_matching_restricted_recipient() {
|
|||||||
|
|
||||||
let grant = Grant {
|
let grant = Grant {
|
||||||
id: 999,
|
id: 999,
|
||||||
common_settings_id: 999,
|
shared_grant_id: 999,
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: shared(),
|
shared: shared(),
|
||||||
specific: make_settings(Some(RECIPIENT), None),
|
settings: make_settings(Some(RECIPIENT), None),
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
|
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
|
||||||
let context = ctx(DAI, calldata);
|
let context = ctx(DAI, calldata);
|
||||||
@@ -208,11 +203,9 @@ async fn evaluate_rejects_wrong_restricted_recipient() {
|
|||||||
|
|
||||||
let grant = Grant {
|
let grant = Grant {
|
||||||
id: 999,
|
id: 999,
|
||||||
common_settings_id: 999,
|
shared_grant_id: 999,
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: shared(),
|
shared: shared(),
|
||||||
specific: make_settings(Some(RECIPIENT), None),
|
settings: make_settings(Some(RECIPIENT), None),
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let calldata = transfer_calldata(OTHER, U256::from(100u64));
|
let calldata = transfer_calldata(OTHER, U256::from(100u64));
|
||||||
let context = ctx(DAI, calldata);
|
let context = ctx(DAI, calldata);
|
||||||
@@ -227,7 +220,7 @@ async fn evaluate_rejects_wrong_restricted_recipient() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn evaluate_passes_volume_at_exact_limit() {
|
async fn evaluate_passes_volume_within_limit() {
|
||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
|
|
||||||
@@ -237,7 +230,7 @@ async fn evaluate_passes_volume_at_exact_limit() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Record a past transfer of 900, with current transfer 100 => exactly 1000 limit
|
// Record a past transfer of 500 (within 1000 limit)
|
||||||
use crate::db::{models::NewEvmTokenTransferLog, schema::evm_token_transfer_log};
|
use crate::db::{models::NewEvmTokenTransferLog, schema::evm_token_transfer_log};
|
||||||
insert_into(evm_token_transfer_log::table)
|
insert_into(evm_token_transfer_log::table)
|
||||||
.values(NewEvmTokenTransferLog {
|
.values(NewEvmTokenTransferLog {
|
||||||
@@ -246,7 +239,7 @@ async fn evaluate_passes_volume_at_exact_limit() {
|
|||||||
chain_id: CHAIN_ID as i32,
|
chain_id: CHAIN_ID as i32,
|
||||||
token_contract: DAI.to_vec(),
|
token_contract: DAI.to_vec(),
|
||||||
recipient_address: RECIPIENT.to_vec(),
|
recipient_address: RECIPIENT.to_vec(),
|
||||||
value: utils::u256_to_bytes(U256::from(900u64)).to_vec(),
|
value: utils::u256_to_bytes(U256::from(500u64)).to_vec(),
|
||||||
})
|
})
|
||||||
.execute(&mut *conn)
|
.execute(&mut *conn)
|
||||||
.await
|
.await
|
||||||
@@ -254,11 +247,9 @@ async fn evaluate_passes_volume_at_exact_limit() {
|
|||||||
|
|
||||||
let grant = Grant {
|
let grant = Grant {
|
||||||
id: grant_id,
|
id: grant_id,
|
||||||
common_settings_id: basic.id,
|
shared_grant_id: basic.id,
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: shared(),
|
shared: shared(),
|
||||||
specific: settings,
|
settings,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
|
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
|
||||||
let context = ctx(DAI, calldata);
|
let context = ctx(DAI, calldata);
|
||||||
@@ -291,7 +282,7 @@ async fn evaluate_rejects_volume_over_limit() {
|
|||||||
chain_id: CHAIN_ID as i32,
|
chain_id: CHAIN_ID as i32,
|
||||||
token_contract: DAI.to_vec(),
|
token_contract: DAI.to_vec(),
|
||||||
recipient_address: RECIPIENT.to_vec(),
|
recipient_address: RECIPIENT.to_vec(),
|
||||||
value: utils::u256_to_bytes(U256::from(1_000u64)).to_vec(),
|
value: utils::u256_to_bytes(U256::from(1_001u64)).to_vec(),
|
||||||
})
|
})
|
||||||
.execute(&mut *conn)
|
.execute(&mut *conn)
|
||||||
.await
|
.await
|
||||||
@@ -299,13 +290,11 @@ async fn evaluate_rejects_volume_over_limit() {
|
|||||||
|
|
||||||
let grant = Grant {
|
let grant = Grant {
|
||||||
id: grant_id,
|
id: grant_id,
|
||||||
common_settings_id: basic.id,
|
shared_grant_id: basic.id,
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: shared(),
|
shared: shared(),
|
||||||
specific: settings,
|
settings,
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let calldata = transfer_calldata(RECIPIENT, U256::from(1u64));
|
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
|
||||||
let context = ctx(DAI, calldata);
|
let context = ctx(DAI, calldata);
|
||||||
let m = TokenTransfer::analyze(&context).unwrap();
|
let m = TokenTransfer::analyze(&context).unwrap();
|
||||||
let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn)
|
let v = TokenTransfer::evaluate(&context, &m, &grant, &mut *conn)
|
||||||
@@ -324,11 +313,9 @@ async fn evaluate_no_volume_limits_always_passes() {
|
|||||||
|
|
||||||
let grant = Grant {
|
let grant = Grant {
|
||||||
id: 999,
|
id: 999,
|
||||||
common_settings_id: 999,
|
shared_grant_id: 999,
|
||||||
settings: CombinedSettings {
|
|
||||||
shared: shared(),
|
shared: shared(),
|
||||||
specific: make_settings(None, None), // no volume limits
|
settings: make_settings(None, None), // no volume limits
|
||||||
},
|
|
||||||
};
|
};
|
||||||
let calldata = transfer_calldata(RECIPIENT, U256::from(u64::MAX));
|
let calldata = transfer_calldata(RECIPIENT, U256::from(u64::MAX));
|
||||||
let context = ctx(DAI, calldata);
|
let context = ctx(DAI, calldata);
|
||||||
@@ -362,13 +349,10 @@ async fn try_find_grant_roundtrip() {
|
|||||||
|
|
||||||
assert!(found.is_some());
|
assert!(found.is_some());
|
||||||
let g = found.unwrap();
|
let g = found.unwrap();
|
||||||
assert_eq!(g.settings.specific.token_contract, DAI);
|
assert_eq!(g.settings.token_contract, DAI);
|
||||||
assert_eq!(g.settings.specific.target, Some(RECIPIENT));
|
assert_eq!(g.settings.target, Some(RECIPIENT));
|
||||||
assert_eq!(g.settings.specific.volume_limits.len(), 1);
|
assert_eq!(g.settings.volume_limits.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(g.settings.volume_limits[0].max_volume, U256::from(5_000u64));
|
||||||
g.settings.specific.volume_limits[0].max_volume,
|
|
||||||
U256::from(5_000u64)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
@@ -408,39 +392,7 @@ async fn try_find_grant_unknown_token_returns_none() {
|
|||||||
assert!(found.is_none());
|
assert!(found.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
proptest::proptest! {
|
// ── find_all_grants ──────────────────────────────────────────────────────
|
||||||
#[test]
|
|
||||||
fn volume_limits_order_does_not_affect_hash(
|
|
||||||
raw_limits in proptest::collection::vec(
|
|
||||||
(proptest::prelude::any::<u64>(), 1i64..=86400),
|
|
||||||
0..8,
|
|
||||||
),
|
|
||||||
seed in proptest::prelude::any::<u64>(),
|
|
||||||
) {
|
|
||||||
use rand::{SeedableRng, seq::SliceRandom};
|
|
||||||
use sha2::Digest;
|
|
||||||
use crate::crypto::integrity::hashing::Hashable;
|
|
||||||
|
|
||||||
let limits: Vec<VolumeRateLimit> = raw_limits
|
|
||||||
.iter()
|
|
||||||
.map(|(max_vol, window_secs)| VolumeRateLimit {
|
|
||||||
max_volume: U256::from(*max_vol),
|
|
||||||
window: Duration::seconds(*window_secs),
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut shuffled = limits.clone();
|
|
||||||
shuffled.shuffle(&mut rand::rngs::StdRng::seed_from_u64(seed));
|
|
||||||
|
|
||||||
let mut h1 = sha2::Sha256::new();
|
|
||||||
Settings { token_contract: DAI, target: None, volume_limits: limits }.hash(&mut h1);
|
|
||||||
|
|
||||||
let mut h2 = sha2::Sha256::new();
|
|
||||||
Settings { token_contract: DAI, target: None, volume_limits: shuffled }.hash(&mut h2);
|
|
||||||
|
|
||||||
proptest::prop_assert_eq!(h1.finalize(), h2.finalize());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn find_all_grants_empty_db() {
|
async fn find_all_grants_empty_db() {
|
||||||
@@ -482,9 +434,9 @@ async fn find_all_grants_loads_volume_limits() {
|
|||||||
|
|
||||||
let all = TokenTransfer::find_all_grants(&mut *conn).await.unwrap();
|
let all = TokenTransfer::find_all_grants(&mut *conn).await.unwrap();
|
||||||
assert_eq!(all.len(), 1);
|
assert_eq!(all.len(), 1);
|
||||||
assert_eq!(all[0].settings.specific.volume_limits.len(), 1);
|
assert_eq!(all[0].settings.volume_limits.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
all[0].settings.specific.volume_limits[0].max_volume,
|
all[0].settings.volume_limits[0].max_volume,
|
||||||
U256::from(9_999u64)
|
U256::from(9_999u64)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,36 @@
|
|||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
proto::client::{
|
proto::{
|
||||||
ClientRequest, ClientResponse, client_request::Payload as ClientRequestPayload,
|
client::{
|
||||||
|
ClientRequest, ClientResponse,
|
||||||
|
client_request::Payload as ClientRequestPayload,
|
||||||
client_response::Payload as ClientResponsePayload,
|
client_response::Payload as ClientResponsePayload,
|
||||||
|
vault::{self as proto_vault, request::Payload as VaultRequestPayload, response::Payload as VaultResponsePayload},
|
||||||
|
},
|
||||||
|
shared::VaultState as ProtoVaultState,
|
||||||
},
|
},
|
||||||
transport::{Receiver, Sender, grpc::GrpcBi},
|
transport::{Receiver, Sender, grpc::GrpcBi},
|
||||||
};
|
};
|
||||||
use kameo::actor::{ActorRef, Spawn as _};
|
use kameo::{
|
||||||
|
actor::{ActorRef, Spawn as _},
|
||||||
|
error::SendError,
|
||||||
|
};
|
||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
use tracing::{info, warn};
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actors::client::{ClientConnection, session::ClientSession},
|
actors::{
|
||||||
|
client::{
|
||||||
|
self, ClientConnection,
|
||||||
|
session::{ClientSession, Error, HandleQueryVaultState},
|
||||||
|
},
|
||||||
|
keyholder::KeyHolderState,
|
||||||
|
},
|
||||||
grpc::request_tracker::RequestTracker,
|
grpc::request_tracker::RequestTracker,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod auth;
|
mod auth;
|
||||||
mod evm;
|
|
||||||
mod inbound;
|
mod inbound;
|
||||||
mod outbound;
|
mod outbound;
|
||||||
mod vault;
|
|
||||||
|
|
||||||
async fn dispatch_loop(
|
async fn dispatch_loop(
|
||||||
mut bi: GrpcBi<ClientRequest, ClientResponse>,
|
mut bi: GrpcBi<ClientRequest, ClientResponse>,
|
||||||
@@ -26,9 +38,7 @@ async fn dispatch_loop(
|
|||||||
mut request_tracker: RequestTracker,
|
mut request_tracker: RequestTracker,
|
||||||
) {
|
) {
|
||||||
loop {
|
loop {
|
||||||
let Some(message) = bi.recv().await else {
|
let Some(message) = bi.recv().await else { return };
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
let conn = match message {
|
let conn = match message {
|
||||||
Ok(conn) => conn,
|
Ok(conn) => conn,
|
||||||
@@ -47,24 +57,16 @@ async fn dispatch_loop(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let Some(payload) = conn.payload else {
|
let Some(payload) = conn.payload else {
|
||||||
let _ = bi
|
let _ = bi.send(Err(Status::invalid_argument("Missing client request payload"))).await;
|
||||||
.send(Err(Status::invalid_argument(
|
|
||||||
"Missing client request payload",
|
|
||||||
)))
|
|
||||||
.await;
|
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
match dispatch_inner(&actor, payload).await {
|
match dispatch_inner(&actor, payload).await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
if bi
|
if bi.send(Ok(ClientResponse {
|
||||||
.send(Ok(ClientResponse {
|
|
||||||
request_id: Some(request_id),
|
request_id: Some(request_id),
|
||||||
payload: Some(response),
|
payload: Some(response),
|
||||||
}))
|
})).await.is_err() {
|
||||||
.await
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,33 +83,52 @@ async fn dispatch_inner(
|
|||||||
payload: ClientRequestPayload,
|
payload: ClientRequestPayload,
|
||||||
) -> Result<ClientResponsePayload, Status> {
|
) -> Result<ClientResponsePayload, Status> {
|
||||||
match payload {
|
match payload {
|
||||||
ClientRequestPayload::Vault(req) => vault::dispatch(actor, req).await,
|
ClientRequestPayload::Vault(req) => dispatch_vault_request(actor, req).await,
|
||||||
ClientRequestPayload::Evm(req) => evm::dispatch(actor, req).await,
|
payload => {
|
||||||
ClientRequestPayload::Auth(..) => {
|
warn!(?payload, "Unsupported post-auth client request");
|
||||||
warn!("Unsupported post-auth client auth request");
|
|
||||||
Err(Status::invalid_argument("Unsupported client request"))
|
Err(Status::invalid_argument("Unsupported client request"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn dispatch_vault_request(
|
||||||
|
actor: &ActorRef<ClientSession>,
|
||||||
|
req: proto_vault::Request,
|
||||||
|
) -> Result<ClientResponsePayload, Status> {
|
||||||
|
let Some(payload) = req.payload else {
|
||||||
|
return Err(Status::invalid_argument("Missing client vault request payload"));
|
||||||
|
};
|
||||||
|
|
||||||
|
match payload {
|
||||||
|
VaultRequestPayload::QueryState(_) => {
|
||||||
|
let state = match actor.ask(HandleQueryVaultState {}).await {
|
||||||
|
Ok(KeyHolderState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
|
||||||
|
Ok(KeyHolderState::Sealed) => ProtoVaultState::Sealed,
|
||||||
|
Ok(KeyHolderState::Unsealed) => ProtoVaultState::Unsealed,
|
||||||
|
Err(SendError::HandlerError(Error::Internal)) => ProtoVaultState::Error,
|
||||||
|
Err(err) => {
|
||||||
|
warn!(error = ?err, "Failed to query vault state");
|
||||||
|
ProtoVaultState::Error
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(ClientResponsePayload::Vault(proto_vault::Response {
|
||||||
|
payload: Some(VaultResponsePayload::State(state.into())),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn start(mut conn: ClientConnection, mut bi: GrpcBi<ClientRequest, ClientResponse>) {
|
pub async fn start(mut conn: ClientConnection, mut bi: GrpcBi<ClientRequest, ClientResponse>) {
|
||||||
let mut request_tracker = RequestTracker::default();
|
let mut request_tracker = RequestTracker::default();
|
||||||
|
|
||||||
let client_id = match auth::start(&mut conn, &mut bi, &mut request_tracker).await {
|
if let Err(e) = auth::start(&mut conn, &mut bi, &mut request_tracker).await {
|
||||||
Ok(id) => id,
|
let mut transport = auth::AuthTransportAdapter::new(&mut bi, &mut request_tracker);
|
||||||
Err(err) => {
|
let _ = transport.send(Err(e.clone())).await;
|
||||||
let _ = bi
|
warn!(error = ?e, "Client authentication failed");
|
||||||
.send(Err(Status::unauthenticated(format!(
|
|
||||||
"Authentication failed: {}",
|
|
||||||
err
|
|
||||||
))))
|
|
||||||
.await;
|
|
||||||
warn!(error = ?err, "Client authentication failed");
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let actor = ClientSession::spawn(ClientSession::new(conn, client_id));
|
let actor = client::session::ClientSession::spawn(client::session::ClientSession::new(conn));
|
||||||
let actor_for_cleanup = actor.clone();
|
let actor_for_cleanup = actor.clone();
|
||||||
|
|
||||||
info!("Client authenticated successfully");
|
info!("Client authenticated successfully");
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use arbiter_proto::{
|
|||||||
},
|
},
|
||||||
shared::ClientInfo as ProtoClientInfo,
|
shared::ClientInfo as ProtoClientInfo,
|
||||||
},
|
},
|
||||||
transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi}
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
@@ -49,9 +49,7 @@ impl<'a> AuthTransportAdapter<'a> {
|
|||||||
nonce,
|
nonce,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
auth::Outbound::AuthSuccess => {
|
auth::Outbound::AuthSuccess => AuthResponsePayload::Result(ProtoAuthResult::Success.into()),
|
||||||
AuthResponsePayload::Result(ProtoAuthResult::Success.into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +66,6 @@ impl<'a> AuthTransportAdapter<'a> {
|
|||||||
auth::Error::ApproveError(auth::ApproveError::Internal)
|
auth::Error::ApproveError(auth::ApproveError::Internal)
|
||||||
| auth::Error::DatabasePoolUnavailable
|
| auth::Error::DatabasePoolUnavailable
|
||||||
| auth::Error::DatabaseOperationFailed
|
| auth::Error::DatabaseOperationFailed
|
||||||
| auth::Error::IntegrityCheckFailed
|
|
||||||
| auth::Error::Transport => ProtoAuthResult::Internal,
|
| auth::Error::Transport => ProtoAuthResult::Internal,
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
@@ -141,9 +138,7 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
let Some(payload) = auth_request.payload else {
|
let Some(payload) = auth_request.payload else {
|
||||||
let _ = self
|
let _ = self
|
||||||
.bi
|
.bi
|
||||||
.send(Err(Status::invalid_argument(
|
.send(Err(Status::invalid_argument("Missing client auth request payload")))
|
||||||
"Missing client auth request payload",
|
|
||||||
)))
|
|
||||||
.await;
|
.await;
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
@@ -173,7 +168,9 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
metadata: client_metadata_from_proto(client_info),
|
metadata: client_metadata_from_proto(client_info),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
AuthRequestPayload::ChallengeSolution(ProtoAuthChallengeSolution { signature }) => {
|
AuthRequestPayload::ChallengeSolution(ProtoAuthChallengeSolution {
|
||||||
|
signature,
|
||||||
|
}) => {
|
||||||
let Ok(signature) = ed25519_dalek::Signature::try_from(signature.as_slice()) else {
|
let Ok(signature) = ed25519_dalek::Signature::try_from(signature.as_slice()) else {
|
||||||
let _ = self
|
let _ = self
|
||||||
.send_auth_result(ProtoAuthResult::InvalidSignature)
|
.send_auth_result(ProtoAuthResult::InvalidSignature)
|
||||||
@@ -200,7 +197,8 @@ pub async fn start(
|
|||||||
conn: &mut ClientConnection,
|
conn: &mut ClientConnection,
|
||||||
bi: &mut GrpcBi<ClientRequest, ClientResponse>,
|
bi: &mut GrpcBi<ClientRequest, ClientResponse>,
|
||||||
request_tracker: &mut RequestTracker,
|
request_tracker: &mut RequestTracker,
|
||||||
) -> Result<i32, auth::Error> {
|
) -> Result<(), auth::Error> {
|
||||||
let mut transport = AuthTransportAdapter::new(bi, request_tracker);
|
let mut transport = AuthTransportAdapter::new(bi, request_tracker);
|
||||||
client::auth::authenticate(conn, &mut transport).await
|
client::auth::authenticate(conn, &mut transport).await?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
use arbiter_proto::proto::{
|
|
||||||
client::{
|
|
||||||
client_response::Payload as ClientResponsePayload,
|
|
||||||
evm::{
|
|
||||||
self as proto_evm, request::Payload as EvmRequestPayload,
|
|
||||||
response::Payload as EvmResponsePayload,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
evm::{
|
|
||||||
EvmError as ProtoEvmError, EvmSignTransactionResponse,
|
|
||||||
evm_sign_transaction_response::Result as EvmSignTransactionResult,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use kameo::actor::ActorRef;
|
|
||||||
use tonic::Status;
|
|
||||||
use tracing::warn;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
actors::client::session::{ClientSession, HandleSignTransaction, SignTransactionRpcError},
|
|
||||||
grpc::{
|
|
||||||
Convert, TryConvert,
|
|
||||||
common::inbound::{RawEvmAddress, RawEvmTransaction},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn wrap_response(payload: EvmResponsePayload) -> ClientResponsePayload {
|
|
||||||
ClientResponsePayload::Evm(proto_evm::Response {
|
|
||||||
payload: Some(payload),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) async fn dispatch(
|
|
||||||
actor: &ActorRef<ClientSession>,
|
|
||||||
req: proto_evm::Request,
|
|
||||||
) -> Result<ClientResponsePayload, Status> {
|
|
||||||
let Some(payload) = req.payload else {
|
|
||||||
return Err(Status::invalid_argument(
|
|
||||||
"Missing client EVM request payload",
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
match payload {
|
|
||||||
EvmRequestPayload::SignTransaction(request) => {
|
|
||||||
let address = RawEvmAddress(request.wallet_address).try_convert()?;
|
|
||||||
let transaction = RawEvmTransaction(request.rlp_transaction).try_convert()?;
|
|
||||||
|
|
||||||
let response = match actor
|
|
||||||
.ask(HandleSignTransaction {
|
|
||||||
wallet_address: address,
|
|
||||||
transaction,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(signature) => EvmSignTransactionResponse {
|
|
||||||
result: Some(EvmSignTransactionResult::Signature(
|
|
||||||
signature.as_bytes().to_vec(),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
Err(kameo::error::SendError::HandlerError(SignTransactionRpcError::Vet(
|
|
||||||
vet_error,
|
|
||||||
))) => EvmSignTransactionResponse {
|
|
||||||
result: Some(vet_error.convert()),
|
|
||||||
},
|
|
||||||
Err(kameo::error::SendError::HandlerError(SignTransactionRpcError::Internal)) => {
|
|
||||||
EvmSignTransactionResponse {
|
|
||||||
result: Some(EvmSignTransactionResult::Error(
|
|
||||||
ProtoEvmError::Internal.into(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to sign EVM transaction");
|
|
||||||
EvmSignTransactionResponse {
|
|
||||||
result: Some(EvmSignTransactionResult::Error(
|
|
||||||
ProtoEvmError::Internal.into(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(wrap_response(EvmResponsePayload::SignTransaction(response)))
|
|
||||||
}
|
|
||||||
EvmRequestPayload::AnalyzeTransaction(_) => Err(Status::unimplemented(
|
|
||||||
"EVM transaction analysis is not yet implemented",
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
use arbiter_proto::proto::{
|
|
||||||
client::{
|
|
||||||
client_response::Payload as ClientResponsePayload,
|
|
||||||
vault::{
|
|
||||||
self as proto_vault, request::Payload as VaultRequestPayload,
|
|
||||||
response::Payload as VaultResponsePayload,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
shared::VaultState as ProtoVaultState,
|
|
||||||
};
|
|
||||||
use kameo::{actor::ActorRef, error::SendError};
|
|
||||||
use tonic::Status;
|
|
||||||
use tracing::warn;
|
|
||||||
|
|
||||||
use crate::actors::{
|
|
||||||
client::session::{ClientSession, Error, HandleQueryVaultState},
|
|
||||||
keyholder::KeyHolderState,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub(super) async fn dispatch(
|
|
||||||
actor: &ActorRef<ClientSession>,
|
|
||||||
req: proto_vault::Request,
|
|
||||||
) -> Result<ClientResponsePayload, Status> {
|
|
||||||
let Some(payload) = req.payload else {
|
|
||||||
return Err(Status::invalid_argument(
|
|
||||||
"Missing client vault request payload",
|
|
||||||
));
|
|
||||||
};
|
|
||||||
|
|
||||||
match payload {
|
|
||||||
VaultRequestPayload::QueryState(_) => {
|
|
||||||
let state = match actor.ask(HandleQueryVaultState {}).await {
|
|
||||||
Ok(KeyHolderState::Unbootstrapped) => ProtoVaultState::Unbootstrapped,
|
|
||||||
Ok(KeyHolderState::Sealed) => ProtoVaultState::Sealed,
|
|
||||||
Ok(KeyHolderState::Unsealed) => ProtoVaultState::Unsealed,
|
|
||||||
Err(SendError::HandlerError(Error::Internal)) => ProtoVaultState::Error,
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to query vault state");
|
|
||||||
ProtoVaultState::Error
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(ClientResponsePayload::Vault(proto_vault::Response {
|
|
||||||
payload: Some(VaultResponsePayload::State(state.into())),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
pub mod inbound;
|
|
||||||
pub mod outbound;
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
use alloy::{consensus::TxEip1559, primitives::Address, rlp::Decodable as _};
|
|
||||||
|
|
||||||
use crate::grpc::TryConvert;
|
|
||||||
|
|
||||||
pub struct RawEvmAddress(pub Vec<u8>);
|
|
||||||
impl TryConvert for RawEvmAddress {
|
|
||||||
type Output = Address;
|
|
||||||
|
|
||||||
type Error = tonic::Status;
|
|
||||||
|
|
||||||
fn try_convert(self) -> Result<Self::Output, Self::Error> {
|
|
||||||
let wallet_address = match <[u8; 20]>::try_from(self.0.as_slice()) {
|
|
||||||
Ok(address) => Address::from(address),
|
|
||||||
Err(_) => {
|
|
||||||
return Err(tonic::Status::invalid_argument(
|
|
||||||
"Invalid EVM wallet address",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Ok(wallet_address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RawEvmTransaction(pub Vec<u8>);
|
|
||||||
impl TryConvert for RawEvmTransaction {
|
|
||||||
type Output = TxEip1559;
|
|
||||||
|
|
||||||
type Error = tonic::Status;
|
|
||||||
|
|
||||||
fn try_convert(self) -> Result<Self::Output, Self::Error> {
|
|
||||||
let tx = TxEip1559::decode(&mut self.0.as_slice())
|
|
||||||
.map_err(|_| tonic::Status::invalid_argument("Invalid EVM transaction format"))?;
|
|
||||||
Ok(tx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
use alloy::primitives::U256;
|
|
||||||
use arbiter_proto::proto::{
|
|
||||||
evm::{
|
|
||||||
EvmError as ProtoEvmError,
|
|
||||||
evm_sign_transaction_response::Result as EvmSignTransactionResult,
|
|
||||||
},
|
|
||||||
shared::evm::{
|
|
||||||
EvalViolation as ProtoEvalViolation, GasLimitExceededViolation, NoMatchingGrantError,
|
|
||||||
PolicyViolationsError, SpecificMeaning as ProtoSpecificMeaning,
|
|
||||||
TokenInfo as ProtoTokenInfo, TransactionEvalError as ProtoTransactionEvalError,
|
|
||||||
eval_violation as proto_eval_violation, eval_violation::Kind as ProtoEvalViolationKind,
|
|
||||||
specific_meaning::Meaning as ProtoSpecificMeaningKind,
|
|
||||||
transaction_eval_error::Kind as ProtoTransactionEvalErrorKind,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
evm::{
|
|
||||||
PolicyError, VetError,
|
|
||||||
policies::{EvalViolation, SpecificMeaning},
|
|
||||||
},
|
|
||||||
grpc::Convert,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn u256_to_proto_bytes(value: U256) -> Vec<u8> {
|
|
||||||
value.to_be_bytes::<32>().to_vec()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Convert for SpecificMeaning {
|
|
||||||
type Output = ProtoSpecificMeaning;
|
|
||||||
|
|
||||||
fn convert(self) -> Self::Output {
|
|
||||||
let kind = match self {
|
|
||||||
SpecificMeaning::EtherTransfer(meaning) => ProtoSpecificMeaningKind::EtherTransfer(
|
|
||||||
arbiter_proto::proto::shared::evm::EtherTransferMeaning {
|
|
||||||
to: meaning.to.to_vec(),
|
|
||||||
value: u256_to_proto_bytes(meaning.value),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
SpecificMeaning::TokenTransfer(meaning) => ProtoSpecificMeaningKind::TokenTransfer(
|
|
||||||
arbiter_proto::proto::shared::evm::TokenTransferMeaning {
|
|
||||||
token: Some(ProtoTokenInfo {
|
|
||||||
symbol: meaning.token.symbol.to_string(),
|
|
||||||
address: meaning.token.contract.to_vec(),
|
|
||||||
chain_id: meaning.token.chain,
|
|
||||||
}),
|
|
||||||
to: meaning.to.to_vec(),
|
|
||||||
value: u256_to_proto_bytes(meaning.value),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
ProtoSpecificMeaning {
|
|
||||||
meaning: Some(kind),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Convert for EvalViolation {
|
|
||||||
type Output = ProtoEvalViolation;
|
|
||||||
|
|
||||||
fn convert(self) -> Self::Output {
|
|
||||||
let kind = match self {
|
|
||||||
EvalViolation::InvalidTarget { target } => {
|
|
||||||
ProtoEvalViolationKind::InvalidTarget(target.to_vec())
|
|
||||||
}
|
|
||||||
EvalViolation::GasLimitExceeded {
|
|
||||||
max_gas_fee_per_gas,
|
|
||||||
max_priority_fee_per_gas,
|
|
||||||
} => ProtoEvalViolationKind::GasLimitExceeded(GasLimitExceededViolation {
|
|
||||||
max_gas_fee_per_gas: max_gas_fee_per_gas.map(u256_to_proto_bytes),
|
|
||||||
max_priority_fee_per_gas: max_priority_fee_per_gas.map(u256_to_proto_bytes),
|
|
||||||
}),
|
|
||||||
EvalViolation::RateLimitExceeded => ProtoEvalViolationKind::RateLimitExceeded(()),
|
|
||||||
EvalViolation::VolumetricLimitExceeded => {
|
|
||||||
ProtoEvalViolationKind::VolumetricLimitExceeded(())
|
|
||||||
}
|
|
||||||
EvalViolation::InvalidTime => ProtoEvalViolationKind::InvalidTime(()),
|
|
||||||
EvalViolation::InvalidTransactionType => {
|
|
||||||
ProtoEvalViolationKind::InvalidTransactionType(())
|
|
||||||
}
|
|
||||||
EvalViolation::MismatchingChainId { expected, actual } => {
|
|
||||||
ProtoEvalViolationKind::ChainIdMismatch(proto_eval_violation::ChainIdMismatch {
|
|
||||||
expected,
|
|
||||||
actual,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
ProtoEvalViolation { kind: Some(kind) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Convert for VetError {
|
|
||||||
type Output = EvmSignTransactionResult;
|
|
||||||
|
|
||||||
fn convert(self) -> Self::Output {
|
|
||||||
let kind = match self {
|
|
||||||
VetError::ContractCreationNotSupported => {
|
|
||||||
ProtoTransactionEvalErrorKind::ContractCreationNotSupported(())
|
|
||||||
}
|
|
||||||
VetError::UnsupportedTransactionType => {
|
|
||||||
ProtoTransactionEvalErrorKind::UnsupportedTransactionType(())
|
|
||||||
}
|
|
||||||
VetError::Evaluated(meaning, policy_error) => match policy_error {
|
|
||||||
PolicyError::NoMatchingGrant => {
|
|
||||||
ProtoTransactionEvalErrorKind::NoMatchingGrant(NoMatchingGrantError {
|
|
||||||
meaning: Some(meaning.convert()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
PolicyError::Violations(violations) => {
|
|
||||||
ProtoTransactionEvalErrorKind::PolicyViolations(PolicyViolationsError {
|
|
||||||
meaning: Some(meaning.convert()),
|
|
||||||
violations: violations.into_iter().map(Convert::convert).collect(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
PolicyError::Database(_) | PolicyError::Integrity(_) => {
|
|
||||||
return EvmSignTransactionResult::Error(ProtoEvmError::Internal.into());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
EvmSignTransactionResult::EvalError(ProtoTransactionEvalError { kind: Some(kind) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,13 +14,10 @@ use crate::{
|
|||||||
grpc::user_agent::start,
|
grpc::user_agent::start,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod request_tracker;
|
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
mod request_tracker;
|
||||||
pub mod user_agent;
|
pub mod user_agent;
|
||||||
|
|
||||||
mod common;
|
|
||||||
|
|
||||||
pub trait Convert {
|
pub trait Convert {
|
||||||
type Output;
|
type Output;
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
proto::user_agent::{
|
proto::{
|
||||||
|
user_agent::{
|
||||||
UserAgentRequest, UserAgentResponse,
|
UserAgentRequest, UserAgentResponse,
|
||||||
user_agent_request::Payload as UserAgentRequestPayload,
|
user_agent_request::Payload as UserAgentRequestPayload,
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
transport::{Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
transport::{Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
@@ -17,7 +19,6 @@ use crate::{
|
|||||||
actors::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession},
|
actors::user_agent::{OutOfBand, UserAgentConnection, UserAgentSession},
|
||||||
grpc::request_tracker::RequestTracker,
|
grpc::request_tracker::RequestTracker,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod auth;
|
mod auth;
|
||||||
mod evm;
|
mod evm;
|
||||||
mod inbound;
|
mod inbound;
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
use arbiter_proto::{
|
use arbiter_proto::{
|
||||||
proto::user_agent::{
|
proto::user_agent::{
|
||||||
UserAgentRequest, UserAgentResponse,
|
UserAgentRequest, UserAgentResponse, auth::{
|
||||||
auth::{
|
|
||||||
self as proto_auth, AuthChallenge as ProtoAuthChallenge,
|
self as proto_auth, AuthChallenge as ProtoAuthChallenge,
|
||||||
AuthChallengeRequest as ProtoAuthChallengeRequest,
|
AuthChallengeRequest as ProtoAuthChallengeRequest,
|
||||||
AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult,
|
AuthChallengeSolution as ProtoAuthChallengeSolution, AuthResult as ProtoAuthResult,
|
||||||
KeyType as ProtoKeyType, request::Payload as AuthRequestPayload,
|
KeyType as ProtoKeyType, request::Payload as AuthRequestPayload,
|
||||||
response::Payload as AuthResponsePayload,
|
response::Payload as AuthResponsePayload,
|
||||||
},
|
}, user_agent_request::Payload as UserAgentRequestPayload,
|
||||||
user_agent_request::Payload as UserAgentRequestPayload,
|
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
},
|
},
|
||||||
transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
transport::{Bi, Error as TransportError, Receiver, Sender, grpc::GrpcBi},
|
||||||
@@ -65,9 +63,7 @@ impl Sender<Result<auth::Outbound, auth::Error>> for AuthTransportAdapter<'_> {
|
|||||||
Ok(Outbound::AuthChallenge { nonce }) => {
|
Ok(Outbound::AuthChallenge { nonce }) => {
|
||||||
AuthResponsePayload::Challenge(ProtoAuthChallenge { nonce })
|
AuthResponsePayload::Challenge(ProtoAuthChallenge { nonce })
|
||||||
}
|
}
|
||||||
Ok(Outbound::AuthSuccess) => {
|
Ok(Outbound::AuthSuccess) => AuthResponsePayload::Result(ProtoAuthResult::Success.into()),
|
||||||
AuthResponsePayload::Result(ProtoAuthResult::Success.into())
|
|
||||||
}
|
|
||||||
Err(Error::UnregisteredPublicKey) => {
|
Err(Error::UnregisteredPublicKey) => {
|
||||||
AuthResponsePayload::Result(ProtoAuthResult::InvalidKey.into())
|
AuthResponsePayload::Result(ProtoAuthResult::InvalidKey.into())
|
||||||
}
|
}
|
||||||
@@ -175,9 +171,9 @@ impl Receiver<auth::Inbound> for AuthTransportAdapter<'_> {
|
|||||||
bootstrap_token,
|
bootstrap_token,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
AuthRequestPayload::ChallengeSolution(ProtoAuthChallengeSolution { signature }) => {
|
AuthRequestPayload::ChallengeSolution(ProtoAuthChallengeSolution {
|
||||||
Some(auth::Inbound::AuthChallengeSolution { signature })
|
signature,
|
||||||
}
|
}) => Some(auth::Inbound::AuthChallengeSolution { signature }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,15 @@ use arbiter_proto::proto::{
|
|||||||
evm::{
|
evm::{
|
||||||
EvmError as ProtoEvmError, EvmGrantCreateRequest, EvmGrantCreateResponse,
|
EvmError as ProtoEvmError, EvmGrantCreateRequest, EvmGrantCreateResponse,
|
||||||
EvmGrantDeleteRequest, EvmGrantDeleteResponse, EvmGrantList, EvmGrantListResponse,
|
EvmGrantDeleteRequest, EvmGrantDeleteResponse, EvmGrantList, EvmGrantListResponse,
|
||||||
EvmSignTransactionResponse, GrantEntry, WalletCreateResponse, WalletEntry, WalletList,
|
GrantEntry, WalletCreateResponse, WalletEntry, WalletList, WalletListResponse,
|
||||||
WalletListResponse, evm_grant_create_response::Result as EvmGrantCreateResult,
|
evm_grant_create_response::Result as EvmGrantCreateResult,
|
||||||
evm_grant_delete_response::Result as EvmGrantDeleteResult,
|
evm_grant_delete_response::Result as EvmGrantDeleteResult,
|
||||||
evm_grant_list_response::Result as EvmGrantListResult,
|
evm_grant_list_response::Result as EvmGrantListResult,
|
||||||
evm_sign_transaction_response::Result as EvmSignTransactionResult,
|
|
||||||
wallet_create_response::Result as WalletCreateResult,
|
wallet_create_response::Result as WalletCreateResult,
|
||||||
wallet_list_response::Result as WalletListResult,
|
wallet_list_response::Result as WalletListResult,
|
||||||
},
|
},
|
||||||
user_agent::{
|
user_agent::{
|
||||||
evm::{
|
evm::{self as proto_evm, request::Payload as EvmRequestPayload, response::Payload as EvmResponsePayload},
|
||||||
self as proto_evm, SignTransactionRequest as ProtoSignTransactionRequest,
|
|
||||||
request::Payload as EvmRequestPayload, response::Payload as EvmResponsePayload,
|
|
||||||
},
|
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -26,15 +22,11 @@ use crate::{
|
|||||||
actors::user_agent::{
|
actors::user_agent::{
|
||||||
UserAgentSession,
|
UserAgentSession,
|
||||||
session::connection::{
|
session::connection::{
|
||||||
GrantMutationError, HandleEvmWalletCreate, HandleEvmWalletList, HandleGrantCreate,
|
HandleEvmWalletCreate, HandleEvmWalletList, HandleGrantCreate, HandleGrantDelete,
|
||||||
HandleGrantDelete, HandleGrantList, HandleSignTransaction,
|
HandleGrantList,
|
||||||
SignTransactionError as SessionSignTransactionError,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
grpc::{
|
grpc::{Convert, TryConvert},
|
||||||
Convert, TryConvert,
|
|
||||||
common::inbound::{RawEvmAddress, RawEvmTransaction},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fn wrap_evm_response(payload: EvmResponsePayload) -> UserAgentResponsePayload {
|
fn wrap_evm_response(payload: EvmResponsePayload) -> UserAgentResponsePayload {
|
||||||
@@ -57,7 +49,6 @@ pub(super) async fn dispatch(
|
|||||||
EvmRequestPayload::GrantCreate(req) => handle_grant_create(actor, req).await,
|
EvmRequestPayload::GrantCreate(req) => handle_grant_create(actor, req).await,
|
||||||
EvmRequestPayload::GrantDelete(req) => handle_grant_delete(actor, req).await,
|
EvmRequestPayload::GrantDelete(req) => handle_grant_delete(actor, req).await,
|
||||||
EvmRequestPayload::GrantList(_) => handle_grant_list(actor).await,
|
EvmRequestPayload::GrantList(_) => handle_grant_list(actor).await,
|
||||||
EvmRequestPayload::SignTransaction(req) => handle_sign_transaction(actor, req).await,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,16 +105,13 @@ async fn handle_grant_list(
|
|||||||
grants: grants
|
grants: grants
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|grant| GrantEntry {
|
.map(|grant| GrantEntry {
|
||||||
id: grant.common_settings_id,
|
id: grant.id,
|
||||||
wallet_access_id: grant.settings.shared.wallet_access_id,
|
wallet_access_id: grant.shared.wallet_access_id,
|
||||||
shared: Some(grant.settings.shared.convert()),
|
shared: Some(grant.shared.convert()),
|
||||||
specific: Some(grant.settings.specific.convert()),
|
specific: Some(grant.settings.convert()),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
}),
|
}),
|
||||||
Err(kameo::error::SendError::HandlerError(GrantMutationError::VaultSealed)) => {
|
|
||||||
EvmGrantListResult::Error(ProtoEvmError::VaultSealed.into())
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(error = ?err, "Failed to list EVM grants");
|
warn!(error = ?err, "Failed to list EVM grants");
|
||||||
EvmGrantListResult::Error(ProtoEvmError::Internal.into())
|
EvmGrantListResult::Error(ProtoEvmError::Internal.into())
|
||||||
@@ -150,10 +138,7 @@ async fn handle_grant_create(
|
|||||||
.try_convert()?;
|
.try_convert()?;
|
||||||
|
|
||||||
let result = match actor.ask(HandleGrantCreate { basic, grant }).await {
|
let result = match actor.ask(HandleGrantCreate { basic, grant }).await {
|
||||||
Ok(grant_id) => EvmGrantCreateResult::GrantId(grant_id.into_inner()),
|
Ok(grant_id) => EvmGrantCreateResult::GrantId(grant_id),
|
||||||
Err(kameo::error::SendError::HandlerError(GrantMutationError::VaultSealed)) => {
|
|
||||||
EvmGrantCreateResult::Error(ProtoEvmError::VaultSealed.into())
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(error = ?err, "Failed to create EVM grant");
|
warn!(error = ?err, "Failed to create EVM grant");
|
||||||
EvmGrantCreateResult::Error(ProtoEvmError::Internal.into())
|
EvmGrantCreateResult::Error(ProtoEvmError::Internal.into())
|
||||||
@@ -170,16 +155,8 @@ async fn handle_grant_delete(
|
|||||||
actor: &ActorRef<UserAgentSession>,
|
actor: &ActorRef<UserAgentSession>,
|
||||||
req: EvmGrantDeleteRequest,
|
req: EvmGrantDeleteRequest,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
let result = match actor
|
let result = match actor.ask(HandleGrantDelete { grant_id: req.grant_id }).await {
|
||||||
.ask(HandleGrantDelete {
|
|
||||||
grant_id: req.grant_id,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(()) => EvmGrantDeleteResult::Ok(()),
|
Ok(()) => EvmGrantDeleteResult::Ok(()),
|
||||||
Err(kameo::error::SendError::HandlerError(GrantMutationError::VaultSealed)) => {
|
|
||||||
EvmGrantDeleteResult::Error(ProtoEvmError::VaultSealed.into())
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
warn!(error = ?err, "Failed to delete EVM grant");
|
warn!(error = ?err, "Failed to delete EVM grant");
|
||||||
EvmGrantDeleteResult::Error(ProtoEvmError::Internal.into())
|
EvmGrantDeleteResult::Error(ProtoEvmError::Internal.into())
|
||||||
@@ -191,53 +168,3 @@ async fn handle_grant_delete(
|
|||||||
},
|
},
|
||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_sign_transaction(
|
|
||||||
actor: &ActorRef<UserAgentSession>,
|
|
||||||
req: ProtoSignTransactionRequest,
|
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
|
||||||
let request = req
|
|
||||||
.request
|
|
||||||
.ok_or_else(|| Status::invalid_argument("Missing sign transaction request"))?;
|
|
||||||
let wallet_address = RawEvmAddress(request.wallet_address).try_convert()?;
|
|
||||||
let transaction = RawEvmTransaction(request.rlp_transaction).try_convert()?;
|
|
||||||
|
|
||||||
let response = match actor
|
|
||||||
.ask(HandleSignTransaction {
|
|
||||||
client_id: req.client_id,
|
|
||||||
wallet_address,
|
|
||||||
transaction,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(signature) => EvmSignTransactionResponse {
|
|
||||||
result: Some(EvmSignTransactionResult::Signature(
|
|
||||||
signature.as_bytes().to_vec(),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
Err(kameo::error::SendError::HandlerError(SessionSignTransactionError::Vet(vet_error))) => {
|
|
||||||
EvmSignTransactionResponse {
|
|
||||||
result: Some(vet_error.convert()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(kameo::error::SendError::HandlerError(SessionSignTransactionError::Internal)) => {
|
|
||||||
EvmSignTransactionResponse {
|
|
||||||
result: Some(EvmSignTransactionResult::Error(
|
|
||||||
ProtoEvmError::Internal.into(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
warn!(error = ?err, "Failed to sign EVM transaction");
|
|
||||||
EvmSignTransactionResponse {
|
|
||||||
result: Some(EvmSignTransactionResult::Error(
|
|
||||||
ProtoEvmError::Internal.into(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(wrap_evm_response(
|
|
||||||
EvmResponsePayload::SignTransaction(response),
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ use arbiter_proto::proto::{
|
|||||||
TransactionRateLimit as ProtoTransactionRateLimit, VolumeRateLimit as ProtoVolumeRateLimit,
|
TransactionRateLimit as ProtoTransactionRateLimit, VolumeRateLimit as ProtoVolumeRateLimit,
|
||||||
specific_grant::Grant as ProtoSpecificGrantType,
|
specific_grant::Grant as ProtoSpecificGrantType,
|
||||||
},
|
},
|
||||||
user_agent::sdk_client::{WalletAccess, WalletAccessEntry as ProtoSdkClientWalletAccess},
|
user_agent::sdk_client::{
|
||||||
|
WalletAccess, WalletAccessEntry as ProtoSdkClientWalletAccess,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use prost_types::Timestamp as ProtoTimestamp;
|
use prost_types::Timestamp as ProtoTimestamp;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use arbiter_proto::proto::{
|
use arbiter_proto::proto::{
|
||||||
shared::ClientInfo as ProtoClientMetadata,
|
|
||||||
user_agent::{
|
user_agent::{
|
||||||
sdk_client::{
|
sdk_client::{
|
||||||
self as proto_sdk_client, ConnectionCancel as ProtoSdkClientConnectionCancel,
|
self as proto_sdk_client, ConnectionCancel as ProtoSdkClientConnectionCancel,
|
||||||
@@ -14,6 +13,7 @@ use arbiter_proto::proto::{
|
|||||||
},
|
},
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
},
|
},
|
||||||
|
shared::ClientInfo as ProtoClientMetadata,
|
||||||
};
|
};
|
||||||
use kameo::actor::ActorRef;
|
use kameo::actor::ActorRef;
|
||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
@@ -62,22 +62,18 @@ pub(super) async fn dispatch(
|
|||||||
req: proto_sdk_client::Request,
|
req: proto_sdk_client::Request,
|
||||||
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
) -> Result<Option<UserAgentResponsePayload>, Status> {
|
||||||
let Some(payload) = req.payload else {
|
let Some(payload) = req.payload else {
|
||||||
return Err(Status::invalid_argument(
|
return Err(Status::invalid_argument("Missing SDK client request payload"));
|
||||||
"Missing SDK client request payload",
|
|
||||||
));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match payload {
|
match payload {
|
||||||
SdkClientRequestPayload::ConnectionResponse(resp) => {
|
SdkClientRequestPayload::ConnectionResponse(resp) => {
|
||||||
handle_connection_response(actor, resp).await
|
handle_connection_response(actor, resp).await
|
||||||
}
|
}
|
||||||
SdkClientRequestPayload::Revoke(_) => Err(Status::unimplemented(
|
SdkClientRequestPayload::Revoke(_) => {
|
||||||
"SdkClientRevoke is not yet implemented",
|
Err(Status::unimplemented("SdkClientRevoke is not yet implemented"))
|
||||||
)),
|
|
||||||
SdkClientRequestPayload::List(_) => handle_list(actor).await,
|
|
||||||
SdkClientRequestPayload::GrantWalletAccess(req) => {
|
|
||||||
handle_grant_wallet_access(actor, req).await
|
|
||||||
}
|
}
|
||||||
|
SdkClientRequestPayload::List(_) => handle_list(actor).await,
|
||||||
|
SdkClientRequestPayload::GrantWalletAccess(req) => handle_grant_wallet_access(actor, req).await,
|
||||||
SdkClientRequestPayload::RevokeWalletAccess(req) => {
|
SdkClientRequestPayload::RevokeWalletAccess(req) => {
|
||||||
handle_revoke_wallet_access(actor, req).await
|
handle_revoke_wallet_access(actor, req).await
|
||||||
}
|
}
|
||||||
@@ -132,11 +128,11 @@ async fn handle_list(
|
|||||||
ProtoSdkClientListResult::Error(ProtoSdkClientError::Internal.into())
|
ProtoSdkClientListResult::Error(ProtoSdkClientError::Internal.into())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(Some(wrap_sdk_client_response(
|
Ok(Some(wrap_sdk_client_response(SdkClientResponsePayload::List(
|
||||||
SdkClientResponsePayload::List(ProtoSdkClientListResponse {
|
ProtoSdkClientListResponse {
|
||||||
result: Some(result),
|
result: Some(result),
|
||||||
}),
|
},
|
||||||
)))
|
))))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_grant_wallet_access(
|
async fn handle_grant_wallet_access(
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use arbiter_proto::proto::shared::VaultState as ProtoVaultState;
|
|
||||||
use arbiter_proto::proto::user_agent::{
|
use arbiter_proto::proto::user_agent::{
|
||||||
user_agent_response::Payload as UserAgentResponsePayload,
|
user_agent_response::Payload as UserAgentResponsePayload,
|
||||||
vault::{
|
vault::{
|
||||||
@@ -12,15 +11,18 @@ use arbiter_proto::proto::user_agent::{
|
|||||||
unseal::{
|
unseal::{
|
||||||
self as proto_unseal, UnsealEncryptedKey as ProtoUnsealEncryptedKey,
|
self as proto_unseal, UnsealEncryptedKey as ProtoUnsealEncryptedKey,
|
||||||
UnsealResult as ProtoUnsealResult, UnsealStart,
|
UnsealResult as ProtoUnsealResult, UnsealStart,
|
||||||
request::Payload as UnsealRequestPayload, response::Payload as UnsealResponsePayload,
|
request::Payload as UnsealRequestPayload,
|
||||||
|
response::Payload as UnsealResponsePayload,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
use arbiter_proto::proto::shared::VaultState as ProtoVaultState;
|
||||||
use kameo::{actor::ActorRef, error::SendError};
|
use kameo::{actor::ActorRef, error::SendError};
|
||||||
use tonic::Status;
|
use tonic::Status;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
|
|
||||||
use crate::actors::{
|
use crate::{
|
||||||
|
actors::{
|
||||||
keyholder::KeyHolderState,
|
keyholder::KeyHolderState,
|
||||||
user_agent::{
|
user_agent::{
|
||||||
UserAgentSession,
|
UserAgentSession,
|
||||||
@@ -29,6 +31,7 @@ use crate::actors::{
|
|||||||
HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError,
|
HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayload {
|
fn wrap_vault_response(payload: VaultResponsePayload) -> UserAgentResponsePayload {
|
||||||
@@ -148,9 +151,7 @@ async fn handle_bootstrap_encrypted_key(
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(()) => ProtoBootstrapResult::Success,
|
Ok(()) => ProtoBootstrapResult::Success,
|
||||||
Err(SendError::HandlerError(BootstrapError::InvalidKey)) => {
|
Err(SendError::HandlerError(BootstrapError::InvalidKey)) => ProtoBootstrapResult::InvalidKey,
|
||||||
ProtoBootstrapResult::InvalidKey
|
|
||||||
}
|
|
||||||
Err(SendError::HandlerError(BootstrapError::AlreadyBootstrapped)) => {
|
Err(SendError::HandlerError(BootstrapError::AlreadyBootstrapped)) => {
|
||||||
ProtoBootstrapResult::AlreadyBootstrapped
|
ProtoBootstrapResult::AlreadyBootstrapped
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ use crate::context::ServerContext;
|
|||||||
|
|
||||||
pub mod actors;
|
pub mod actors;
|
||||||
pub mod context;
|
pub mod context;
|
||||||
pub mod crypto;
|
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod evm;
|
pub mod evm;
|
||||||
pub mod grpc;
|
pub mod grpc;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use anyhow::anyhow;
|
|
||||||
use arbiter_proto::{proto::arbiter_service_server::ArbiterServiceServer, url::ArbiterUrl};
|
use arbiter_proto::{proto::arbiter_service_server::ArbiterServiceServer, url::ArbiterUrl};
|
||||||
use arbiter_server::{Server, actors::bootstrap::GetToken, context::ServerContext, db};
|
use arbiter_server::{Server, actors::bootstrap::GetToken, context::ServerContext, db};
|
||||||
|
use miette::miette;
|
||||||
use rustls::crypto::aws_lc_rs;
|
use rustls::crypto::aws_lc_rs;
|
||||||
use tonic::transport::{Identity, ServerTlsConfig};
|
use tonic::transport::{Identity, ServerTlsConfig};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
@@ -10,8 +10,7 @@ use tracing::info;
|
|||||||
const PORT: u16 = 50051;
|
const PORT: u16 = 50051;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
#[mutants::skip]
|
async fn main() -> miette::Result<()> {
|
||||||
async fn main() -> anyhow::Result<()> {
|
|
||||||
aws_lc_rs::default_provider().install_default().unwrap();
|
aws_lc_rs::default_provider().install_default().unwrap();
|
||||||
|
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
@@ -47,11 +46,11 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
tonic::transport::Server::builder()
|
tonic::transport::Server::builder()
|
||||||
.tls_config(tls)
|
.tls_config(tls)
|
||||||
.map_err(|err| anyhow!("Failed to setup TLS: {err}"))?
|
.map_err(|err| miette!("Faild to setup TLS: {err}"))?
|
||||||
.add_service(ArbiterServiceServer::new(Server::new(context)))
|
.add_service(ArbiterServiceServer::new(Server::new(context)))
|
||||||
.serve(addr)
|
.serve(addr)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow!("gRPC server error: {e}"))?;
|
.map_err(|e| miette::miette!("gRPC server error: {e}"))?;
|
||||||
|
|
||||||
unreachable!("gRPC server should run indefinitely");
|
unreachable!("gRPC server should run indefinitely");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
use arbiter_proto::ClientMetadata;
|
use arbiter_proto::ClientMetadata;
|
||||||
use arbiter_proto::transport::{Receiver, Sender};
|
use arbiter_proto::transport::{Receiver, Sender};
|
||||||
|
use arbiter_server::actors::GlobalActors;
|
||||||
use arbiter_server::{
|
use arbiter_server::{
|
||||||
actors::{
|
actors::client::{ClientConnection, auth, connect_client},
|
||||||
GlobalActors,
|
db,
|
||||||
client::{ClientConnection, ClientCredentials, auth, connect_client},
|
|
||||||
keyholder::Bootstrap,
|
|
||||||
},
|
|
||||||
crypto::integrity,
|
|
||||||
db::{self, schema},
|
|
||||||
safe_cell::{SafeCell, SafeCellHandle as _},
|
|
||||||
};
|
};
|
||||||
use diesel::{ExpressionMethods as _, NullableExpressionMethods as _, QueryDsl as _, insert_into};
|
use diesel::{ExpressionMethods as _, NullableExpressionMethods as _, QueryDsl as _, insert_into};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
@@ -26,8 +21,7 @@ fn metadata(name: &str, description: Option<&str>, version: Option<&str>) -> Cli
|
|||||||
|
|
||||||
async fn insert_registered_client(
|
async fn insert_registered_client(
|
||||||
db: &db::DatabasePool,
|
db: &db::DatabasePool,
|
||||||
actors: &GlobalActors,
|
pubkey: Vec<u8>,
|
||||||
pubkey: ed25519_dalek::VerifyingKey,
|
|
||||||
metadata: &ClientMetadata,
|
metadata: &ClientMetadata,
|
||||||
) {
|
) {
|
||||||
use arbiter_server::db::schema::{client_metadata, program_client};
|
use arbiter_server::db::schema::{client_metadata, program_client};
|
||||||
@@ -43,64 +37,23 @@ async fn insert_registered_client(
|
|||||||
.get_result(&mut conn)
|
.get_result(&mut conn)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let client_id: i32 = insert_into(program_client::table)
|
insert_into(program_client::table)
|
||||||
.values((
|
.values((
|
||||||
program_client::public_key.eq(pubkey.to_bytes().to_vec()),
|
program_client::public_key.eq(pubkey),
|
||||||
program_client::metadata_id.eq(metadata_id),
|
program_client::metadata_id.eq(metadata_id),
|
||||||
))
|
))
|
||||||
.returning(program_client::id)
|
|
||||||
.get_result(&mut conn)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
integrity::sign_entity(
|
|
||||||
&mut conn,
|
|
||||||
&actors.key_holder,
|
|
||||||
&ClientCredentials { pubkey, nonce: 1 },
|
|
||||||
client_id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn insert_bootstrap_sentinel_useragent(db: &db::DatabasePool) {
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
let sentinel_key = ed25519_dalek::SigningKey::generate(&mut rand::rng())
|
|
||||||
.verifying_key()
|
|
||||||
.to_bytes()
|
|
||||||
.to_vec();
|
|
||||||
|
|
||||||
insert_into(schema::useragent_client::table)
|
|
||||||
.values((
|
|
||||||
schema::useragent_client::public_key.eq(sentinel_key),
|
|
||||||
schema::useragent_client::key_type.eq(1i32),
|
|
||||||
))
|
|
||||||
.execute(&mut conn)
|
.execute(&mut conn)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_test_actors(db: &db::DatabasePool) -> GlobalActors {
|
|
||||||
insert_bootstrap_sentinel_useragent(db).await;
|
|
||||||
|
|
||||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
|
||||||
actors
|
|
||||||
.key_holder
|
|
||||||
.ask(Bootstrap {
|
|
||||||
seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()),
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
actors
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
pub async fn test_unregistered_pubkey_rejected() {
|
pub async fn test_unregistered_pubkey_rejected() {
|
||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
|
|
||||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
let (server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
let actors = spawn_test_actors(&db).await;
|
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||||
let props = ClientConnection::new(db.clone(), actors);
|
let props = ClientConnection::new(db.clone(), actors);
|
||||||
let task = tokio::spawn(async move {
|
let task = tokio::spawn(async move {
|
||||||
let mut server_transport = server_transport;
|
let mut server_transport = server_transport;
|
||||||
@@ -125,19 +78,20 @@ pub async fn test_unregistered_pubkey_rejected() {
|
|||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
pub async fn test_challenge_auth() {
|
pub async fn test_challenge_auth() {
|
||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
let actors = spawn_test_actors(&db).await;
|
|
||||||
|
|
||||||
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
||||||
|
let pubkey_bytes = new_key.verifying_key().to_bytes().to_vec();
|
||||||
|
|
||||||
insert_registered_client(
|
insert_registered_client(
|
||||||
&db,
|
&db,
|
||||||
&actors,
|
pubkey_bytes.clone(),
|
||||||
new_key.verifying_key(),
|
|
||||||
&metadata("client", Some("desc"), Some("1.0.0")),
|
&metadata("client", Some("desc"), Some("1.0.0")),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
let (server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
|
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||||
|
|
||||||
let props = ClientConnection::new(db.clone(), actors);
|
let props = ClientConnection::new(db.clone(), actors);
|
||||||
let task = tokio::spawn(async move {
|
let task = tokio::spawn(async move {
|
||||||
let mut server_transport = server_transport;
|
let mut server_transport = server_transport;
|
||||||
@@ -193,13 +147,34 @@ pub async fn test_challenge_auth() {
|
|||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
pub async fn test_metadata_unchanged_does_not_append_history() {
|
pub async fn test_metadata_unchanged_does_not_append_history() {
|
||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
let actors = spawn_test_actors(&db).await;
|
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||||
|
let props = ClientConnection::new(db.clone(), actors);
|
||||||
|
|
||||||
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
||||||
let requested = metadata("client", Some("desc"), Some("1.0.0"));
|
let requested = metadata("client", Some("desc"), Some("1.0.0"));
|
||||||
|
|
||||||
insert_registered_client(&db, &actors, new_key.verifying_key(), &requested).await;
|
{
|
||||||
|
use arbiter_server::db::schema::{client_metadata, program_client};
|
||||||
let props = ClientConnection::new(db.clone(), actors);
|
let mut conn = db.get().await.unwrap();
|
||||||
|
let metadata_id: i32 = insert_into(client_metadata::table)
|
||||||
|
.values((
|
||||||
|
client_metadata::name.eq(&requested.name),
|
||||||
|
client_metadata::description.eq(&requested.description),
|
||||||
|
client_metadata::version.eq(&requested.version),
|
||||||
|
))
|
||||||
|
.returning(client_metadata::id)
|
||||||
|
.get_result(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
insert_into(program_client::table)
|
||||||
|
.values((
|
||||||
|
program_client::public_key.eq(new_key.verifying_key().to_bytes().to_vec()),
|
||||||
|
program_client::metadata_id.eq(metadata_id),
|
||||||
|
))
|
||||||
|
.execute(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
let (server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
let task = tokio::spawn(async move {
|
let task = tokio::spawn(async move {
|
||||||
@@ -250,18 +225,33 @@ pub async fn test_metadata_unchanged_does_not_append_history() {
|
|||||||
#[test_log::test]
|
#[test_log::test]
|
||||||
pub async fn test_metadata_change_appends_history_and_repoints_binding() {
|
pub async fn test_metadata_change_appends_history_and_repoints_binding() {
|
||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
let actors = spawn_test_actors(&db).await;
|
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||||
|
let props = ClientConnection::new(db.clone(), actors);
|
||||||
|
|
||||||
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
||||||
|
|
||||||
insert_registered_client(
|
{
|
||||||
&db,
|
use arbiter_server::db::schema::{client_metadata, program_client};
|
||||||
&actors,
|
let mut conn = db.get().await.unwrap();
|
||||||
new_key.verifying_key(),
|
let metadata_id: i32 = insert_into(client_metadata::table)
|
||||||
&metadata("client", Some("old"), Some("1.0.0")),
|
.values((
|
||||||
)
|
client_metadata::name.eq("client"),
|
||||||
.await;
|
client_metadata::description.eq(Some("old")),
|
||||||
|
client_metadata::version.eq(Some("1.0.0")),
|
||||||
let props = ClientConnection::new(db.clone(), actors);
|
))
|
||||||
|
.returning(client_metadata::id)
|
||||||
|
.get_result(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
insert_into(program_client::table)
|
||||||
|
.values((
|
||||||
|
program_client::public_key.eq(new_key.verifying_key().to_bytes().to_vec()),
|
||||||
|
program_client::metadata_id.eq(metadata_id),
|
||||||
|
))
|
||||||
|
.execute(&mut conn)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
let (server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
let task = tokio::spawn(async move {
|
let task = tokio::spawn(async move {
|
||||||
@@ -332,59 +322,3 @@ pub async fn test_metadata_change_appends_history_and_repoints_binding() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[test_log::test]
|
|
||||||
pub async fn test_challenge_auth_rejects_integrity_tag_mismatch() {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let actors = spawn_test_actors(&db).await;
|
|
||||||
|
|
||||||
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
|
||||||
let requested = metadata("client", Some("desc"), Some("1.0.0"));
|
|
||||||
|
|
||||||
{
|
|
||||||
use arbiter_server::db::schema::{client_metadata, program_client};
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
let metadata_id: i32 = insert_into(client_metadata::table)
|
|
||||||
.values((
|
|
||||||
client_metadata::name.eq(&requested.name),
|
|
||||||
client_metadata::description.eq(&requested.description),
|
|
||||||
client_metadata::version.eq(&requested.version),
|
|
||||||
))
|
|
||||||
.returning(client_metadata::id)
|
|
||||||
.get_result(&mut conn)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
insert_into(program_client::table)
|
|
||||||
.values((
|
|
||||||
program_client::public_key.eq(new_key.verifying_key().to_bytes().to_vec()),
|
|
||||||
program_client::metadata_id.eq(metadata_id),
|
|
||||||
))
|
|
||||||
.execute(&mut conn)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
|
||||||
let props = ClientConnection::new(db.clone(), actors);
|
|
||||||
let task = tokio::spawn(async move {
|
|
||||||
let mut server_transport = server_transport;
|
|
||||||
connect_client(props, &mut server_transport).await;
|
|
||||||
});
|
|
||||||
|
|
||||||
test_transport
|
|
||||||
.send(auth::Inbound::AuthChallengeRequest {
|
|
||||||
pubkey: new_key.verifying_key(),
|
|
||||||
metadata: requested,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let response = test_transport
|
|
||||||
.recv()
|
|
||||||
.await
|
|
||||||
.expect("should receive auth rejection");
|
|
||||||
assert!(matches!(response, Err(auth::Error::IntegrityCheckFailed)));
|
|
||||||
|
|
||||||
task.await.unwrap();
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use arbiter_server::{
|
use arbiter_server::{
|
||||||
actors::keyholder::{Error, KeyHolder},
|
actors::keyholder::{Error, KeyHolder},
|
||||||
crypto::encryption::v1::{Nonce, ROOT_KEY_TAG},
|
|
||||||
db::{self, models, schema},
|
db::{self, models, schema},
|
||||||
safe_cell::{SafeCell, SafeCellHandle as _},
|
safe_cell::{SafeCell, SafeCellHandle as _},
|
||||||
};
|
};
|
||||||
@@ -26,10 +25,16 @@ async fn test_bootstrap() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(row.schema_version, 1);
|
assert_eq!(row.schema_version, 1);
|
||||||
assert_eq!(row.tag, ROOT_KEY_TAG);
|
assert_eq!(
|
||||||
|
row.tag,
|
||||||
|
arbiter_server::actors::keyholder::encryption::v1::ROOT_KEY_TAG
|
||||||
|
);
|
||||||
assert!(!row.ciphertext.is_empty());
|
assert!(!row.ciphertext.is_empty());
|
||||||
assert!(!row.salt.is_empty());
|
assert!(!row.salt.is_empty());
|
||||||
assert_eq!(row.data_encryption_nonce, Nonce::default().to_vec());
|
assert_eq!(
|
||||||
|
row.data_encryption_nonce,
|
||||||
|
arbiter_server::actors::keyholder::encryption::v1::Nonce::default().to_vec()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use arbiter_server::{
|
use arbiter_server::{
|
||||||
actors::keyholder::Error,
|
actors::keyholder::{Error, encryption::v1},
|
||||||
crypto::encryption::v1::Nonce,
|
|
||||||
db::{self, models, schema},
|
db::{self, models, schema},
|
||||||
safe_cell::{SafeCell, SafeCellHandle as _},
|
safe_cell::{SafeCell, SafeCellHandle as _},
|
||||||
};
|
};
|
||||||
@@ -103,7 +102,7 @@ async fn test_nonce_never_reused() {
|
|||||||
assert_eq!(nonces.len(), unique.len(), "all nonces must be unique");
|
assert_eq!(nonces.len(), unique.len(), "all nonces must be unique");
|
||||||
|
|
||||||
for (i, row) in rows.iter().enumerate() {
|
for (i, row) in rows.iter().enumerate() {
|
||||||
let mut expected = Nonce::default();
|
let mut expected = v1::Nonce::default();
|
||||||
for _ in 0..=i {
|
for _ in 0..=i {
|
||||||
expected.increment();
|
expected.increment();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,9 @@ use arbiter_server::{
|
|||||||
actors::{
|
actors::{
|
||||||
GlobalActors,
|
GlobalActors,
|
||||||
bootstrap::GetToken,
|
bootstrap::GetToken,
|
||||||
keyholder::Bootstrap,
|
user_agent::{AuthPublicKey, UserAgentConnection, auth},
|
||||||
user_agent::{AuthPublicKey, UserAgentConnection, UserAgentCredentials, auth},
|
|
||||||
},
|
},
|
||||||
crypto::integrity,
|
|
||||||
db::{self, schema},
|
db::{self, schema},
|
||||||
safe_cell::{SafeCell, SafeCellHandle as _},
|
|
||||||
};
|
};
|
||||||
use diesel::{ExpressionMethods as _, QueryDsl, insert_into};
|
use diesel::{ExpressionMethods as _, QueryDsl, insert_into};
|
||||||
use diesel_async::RunQueryDsl;
|
use diesel_async::RunQueryDsl;
|
||||||
@@ -21,13 +18,6 @@ use super::common::ChannelTransport;
|
|||||||
pub async fn test_bootstrap_token_auth() {
|
pub async fn test_bootstrap_token_auth() {
|
||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||||
actors
|
|
||||||
.key_holder
|
|
||||||
.ask(Bootstrap {
|
|
||||||
seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()),
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let token = actors.bootstrapper.ask(GetToken).await.unwrap().unwrap();
|
let token = actors.bootstrapper.ask(GetToken).await.unwrap().unwrap();
|
||||||
|
|
||||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
let (server_transport, mut test_transport) = ChannelTransport::new();
|
||||||
@@ -93,6 +83,7 @@ pub async fn test_bootstrap_invalid_token_auth() {
|
|||||||
Err(auth::Error::InvalidBootstrapToken)
|
Err(auth::Error::InvalidBootstrapToken)
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Verify no key was registered
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
let count: i64 = schema::useragent_client::table
|
let count: i64 = schema::useragent_client::table
|
||||||
.count()
|
.count()
|
||||||
@@ -107,37 +98,19 @@ pub async fn test_bootstrap_invalid_token_auth() {
|
|||||||
pub async fn test_challenge_auth() {
|
pub async fn test_challenge_auth() {
|
||||||
let db = db::create_test_pool().await;
|
let db = db::create_test_pool().await;
|
||||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
||||||
actors
|
|
||||||
.key_holder
|
|
||||||
.ask(Bootstrap {
|
|
||||||
seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()),
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
||||||
let pubkey_bytes = new_key.verifying_key().to_bytes().to_vec();
|
let pubkey_bytes = new_key.verifying_key().to_bytes().to_vec();
|
||||||
|
|
||||||
|
// Pre-register key with key_type
|
||||||
{
|
{
|
||||||
let mut conn = db.get().await.unwrap();
|
let mut conn = db.get().await.unwrap();
|
||||||
let id: i32 = insert_into(schema::useragent_client::table)
|
insert_into(schema::useragent_client::table)
|
||||||
.values((
|
.values((
|
||||||
schema::useragent_client::public_key.eq(pubkey_bytes.clone()),
|
schema::useragent_client::public_key.eq(pubkey_bytes.clone()),
|
||||||
schema::useragent_client::key_type.eq(1i32),
|
schema::useragent_client::key_type.eq(1i32),
|
||||||
))
|
))
|
||||||
.returning(schema::useragent_client::id)
|
.execute(&mut conn)
|
||||||
.get_result(&mut conn)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
integrity::sign_entity(
|
|
||||||
&mut conn,
|
|
||||||
&actors.key_holder,
|
|
||||||
&UserAgentCredentials {
|
|
||||||
pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()),
|
|
||||||
nonce: 1,
|
|
||||||
},
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
@@ -149,6 +122,7 @@ pub async fn test_challenge_auth() {
|
|||||||
auth::authenticate(&mut props, server_transport).await
|
auth::authenticate(&mut props, server_transport).await
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Send challenge request
|
||||||
test_transport
|
test_transport
|
||||||
.send(auth::Inbound::AuthChallengeRequest {
|
.send(auth::Inbound::AuthChallengeRequest {
|
||||||
pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()),
|
pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()),
|
||||||
@@ -157,6 +131,7 @@ pub async fn test_challenge_auth() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
// Read the challenge response
|
||||||
let response = test_transport
|
let response = test_transport
|
||||||
.recv()
|
.recv()
|
||||||
.await
|
.await
|
||||||
@@ -190,138 +165,3 @@ pub async fn test_challenge_auth() {
|
|||||||
|
|
||||||
task.await.unwrap().unwrap();
|
task.await.unwrap().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[test_log::test]
|
|
||||||
pub async fn test_challenge_auth_rejects_integrity_tag_mismatch_when_unsealed() {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
|
||||||
|
|
||||||
actors
|
|
||||||
.key_holder
|
|
||||||
.ask(Bootstrap {
|
|
||||||
seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()),
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
|
||||||
let pubkey_bytes = new_key.verifying_key().to_bytes().to_vec();
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
insert_into(schema::useragent_client::table)
|
|
||||||
.values((
|
|
||||||
schema::useragent_client::public_key.eq(pubkey_bytes.clone()),
|
|
||||||
schema::useragent_client::key_type.eq(1i32),
|
|
||||||
))
|
|
||||||
.execute(&mut conn)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
|
||||||
let db_for_task = db.clone();
|
|
||||||
let task = tokio::spawn(async move {
|
|
||||||
let mut props = UserAgentConnection::new(db_for_task, actors);
|
|
||||||
auth::authenticate(&mut props, server_transport).await
|
|
||||||
});
|
|
||||||
|
|
||||||
test_transport
|
|
||||||
.send(auth::Inbound::AuthChallengeRequest {
|
|
||||||
pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()),
|
|
||||||
bootstrap_token: None,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
task.await.unwrap(),
|
|
||||||
Err(auth::Error::Internal { .. })
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
#[test_log::test]
|
|
||||||
pub async fn test_challenge_auth_rejects_invalid_signature() {
|
|
||||||
let db = db::create_test_pool().await;
|
|
||||||
let actors = GlobalActors::spawn(db.clone()).await.unwrap();
|
|
||||||
actors
|
|
||||||
.key_holder
|
|
||||||
.ask(Bootstrap {
|
|
||||||
seal_key_raw: SafeCell::new(b"test-seal-key".to_vec()),
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let new_key = ed25519_dalek::SigningKey::generate(&mut rand::rng());
|
|
||||||
let pubkey_bytes = new_key.verifying_key().to_bytes().to_vec();
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut conn = db.get().await.unwrap();
|
|
||||||
let id: i32 = insert_into(schema::useragent_client::table)
|
|
||||||
.values((
|
|
||||||
schema::useragent_client::public_key.eq(pubkey_bytes.clone()),
|
|
||||||
schema::useragent_client::key_type.eq(1i32),
|
|
||||||
))
|
|
||||||
.returning(schema::useragent_client::id)
|
|
||||||
.get_result(&mut conn)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
integrity::sign_entity(
|
|
||||||
&mut conn,
|
|
||||||
&actors.key_holder,
|
|
||||||
&UserAgentCredentials {
|
|
||||||
pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()),
|
|
||||||
nonce: 1,
|
|
||||||
},
|
|
||||||
id,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
let (server_transport, mut test_transport) = ChannelTransport::new();
|
|
||||||
let db_for_task = db.clone();
|
|
||||||
let task = tokio::spawn(async move {
|
|
||||||
let mut props = UserAgentConnection::new(db_for_task, actors);
|
|
||||||
auth::authenticate(&mut props, server_transport).await
|
|
||||||
});
|
|
||||||
|
|
||||||
test_transport
|
|
||||||
.send(auth::Inbound::AuthChallengeRequest {
|
|
||||||
pubkey: AuthPublicKey::Ed25519(new_key.verifying_key()),
|
|
||||||
bootstrap_token: None,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let response = test_transport
|
|
||||||
.recv()
|
|
||||||
.await
|
|
||||||
.expect("should receive challenge");
|
|
||||||
let challenge = match response {
|
|
||||||
Ok(resp) => match resp {
|
|
||||||
auth::Outbound::AuthChallenge { nonce } => nonce,
|
|
||||||
other => panic!("Expected AuthChallenge, got {other:?}"),
|
|
||||||
},
|
|
||||||
Err(err) => panic!("Expected Ok response, got Err({err:?})"),
|
|
||||||
};
|
|
||||||
|
|
||||||
let wrong_challenge = arbiter_proto::format_challenge(challenge + 1, &pubkey_bytes);
|
|
||||||
let signature = new_key.sign(&wrong_challenge);
|
|
||||||
|
|
||||||
test_transport
|
|
||||||
.send(auth::Inbound::AuthChallengeSolution {
|
|
||||||
signature: signature.to_bytes().to_vec(),
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let expected_err = task.await.unwrap();
|
|
||||||
println!("Received expected error: {expected_err:#?}");
|
|
||||||
assert!(matches!(
|
|
||||||
expected_err,
|
|
||||||
Err(auth::Error::InvalidChallengeSolution)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,10 +2,9 @@ use arbiter_server::{
|
|||||||
actors::{
|
actors::{
|
||||||
GlobalActors,
|
GlobalActors,
|
||||||
keyholder::{Bootstrap, Seal},
|
keyholder::{Bootstrap, Seal},
|
||||||
user_agent::{
|
user_agent::{UserAgentSession, session::connection::{
|
||||||
UserAgentSession,
|
HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError,
|
||||||
session::connection::{HandleUnsealEncryptedKey, HandleUnsealRequest, UnsealError},
|
}},
|
||||||
},
|
|
||||||
},
|
},
|
||||||
db,
|
db,
|
||||||
safe_cell::{SafeCell, SafeCellHandle as _},
|
safe_cell::{SafeCell, SafeCellHandle as _},
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:arbiter/proto/shared/client.pb.dart';
|
import 'package:arbiter/proto/client.pb.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:hooks_riverpod/experimental/mutation.dart';
|
||||||
|
|
||||||
part 'callout_event.freezed.dart';
|
part 'callout_event.freezed.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:arbiter/features/callouts/active_callout.dart';
|
|||||||
import 'package:arbiter/features/callouts/callout_event.dart';
|
import 'package:arbiter/features/callouts/callout_event.dart';
|
||||||
import 'package:arbiter/features/callouts/types/sdk_connect_approve.dart'
|
import 'package:arbiter/features/callouts/types/sdk_connect_approve.dart'
|
||||||
as connect_approve;
|
as connect_approve;
|
||||||
import 'package:arbiter/proto/shared/client.pb.dart';
|
import 'package:arbiter/proto/client.pb.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'callout_manager.g.dart';
|
part 'callout_manager.g.dart';
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:arbiter/features/callouts/callout_event.dart';
|
import 'package:arbiter/features/callouts/callout_event.dart';
|
||||||
import 'package:arbiter/proto/user_agent/sdk_client.pb.dart' as ua_sdk;
|
|
||||||
import 'package:arbiter/proto/user_agent.pb.dart';
|
import 'package:arbiter/proto/user_agent.pb.dart';
|
||||||
import 'package:arbiter/providers/connection/connection_manager.dart';
|
import 'package:arbiter/providers/connection/connection_manager.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
@@ -15,11 +14,8 @@ Stream<CalloutEvent> connectApproveEvents(Ref ref) async* {
|
|||||||
|
|
||||||
await for (final message in connection.outOfBandMessages) {
|
await for (final message in connection.outOfBandMessages) {
|
||||||
switch (message.whichPayload()) {
|
switch (message.whichPayload()) {
|
||||||
case UserAgentResponse_Payload.sdkClient:
|
case UserAgentResponse_Payload.sdkClientConnectionRequest:
|
||||||
final sdkClientMessage = message.sdkClient;
|
final body = message.sdkClientConnectionRequest;
|
||||||
switch (sdkClientMessage.whichPayload()) {
|
|
||||||
case ua_sdk.Response_Payload.connectionRequest:
|
|
||||||
final body = sdkClientMessage.connectionRequest;
|
|
||||||
final id = base64Encode(body.pubkey);
|
final id = base64Encode(body.pubkey);
|
||||||
yield CalloutEvent.added(
|
yield CalloutEvent.added(
|
||||||
id: 'connect_approve:$id',
|
id: 'connect_approve:$id',
|
||||||
@@ -29,17 +25,13 @@ Stream<CalloutEvent> connectApproveEvents(Ref ref) async* {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
case ua_sdk.Response_Payload.connectionCancel:
|
case UserAgentResponse_Payload.sdkClientConnectionCancel:
|
||||||
final id = base64Encode(sdkClientMessage.connectionCancel.pubkey);
|
final id = base64Encode(message.sdkClientConnectionCancel.pubkey);
|
||||||
yield CalloutEvent.cancelled(id: 'connect_approve:$id');
|
yield CalloutEvent.cancelled(id: 'connect_approve:$id');
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,14 +41,11 @@ Future<void> sendDecision(Ref ref, String pubkey, bool approved) async {
|
|||||||
|
|
||||||
final bytes = base64Decode(pubkey);
|
final bytes = base64Decode(pubkey);
|
||||||
|
|
||||||
final req = UserAgentRequest(
|
final req = UserAgentRequest(sdkClientConnectionResponse: SdkClientConnectionResponse(
|
||||||
sdkClient: ua_sdk.Request(
|
|
||||||
connectionResponse: ua_sdk.ConnectionResponse(
|
|
||||||
approved: approved,
|
approved: approved,
|
||||||
pubkey: bytes,
|
pubkey: bytes
|
||||||
),
|
));
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await connection.tell(req);
|
await connection.tell(req);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -47,4 +47,4 @@ final class ConnectApproveEventsProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$connectApproveEventsHash() =>
|
String _$connectApproveEventsHash() =>
|
||||||
r'abab87cc875a9a4834f836c2c0eba4aa7671d82e';
|
r'6a0998288afc0836a7c1701a983f64c33d318fd6';
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import 'package:arbiter/features/connection/connection.dart';
|
|||||||
import 'package:arbiter/features/connection/server_info_storage.dart';
|
import 'package:arbiter/features/connection/server_info_storage.dart';
|
||||||
import 'package:arbiter/features/identity/pk_manager.dart';
|
import 'package:arbiter/features/identity/pk_manager.dart';
|
||||||
import 'package:arbiter/proto/arbiter.pbgrpc.dart';
|
import 'package:arbiter/proto/arbiter.pbgrpc.dart';
|
||||||
import 'package:arbiter/proto/user_agent/auth.pb.dart' as ua_auth;
|
|
||||||
import 'package:arbiter/proto/user_agent.pb.dart';
|
import 'package:arbiter/proto/user_agent.pb.dart';
|
||||||
import 'package:grpc/grpc.dart';
|
import 'package:grpc/grpc.dart';
|
||||||
import 'package:mtcore/markettakers.dart';
|
import 'package:mtcore/markettakers.dart';
|
||||||
@@ -13,22 +12,22 @@ import 'package:mtcore/markettakers.dart';
|
|||||||
class AuthorizationException implements Exception {
|
class AuthorizationException implements Exception {
|
||||||
const AuthorizationException(this.result);
|
const AuthorizationException(this.result);
|
||||||
|
|
||||||
final ua_auth.AuthResult result;
|
final AuthResult result;
|
||||||
|
|
||||||
String get message => switch (result) {
|
String get message => switch (result) {
|
||||||
ua_auth.AuthResult.AUTH_RESULT_INVALID_KEY =>
|
AuthResult.AUTH_RESULT_INVALID_KEY =>
|
||||||
'Authentication failed: this device key is not registered on the server.',
|
'Authentication failed: this device key is not registered on the server.',
|
||||||
ua_auth.AuthResult.AUTH_RESULT_INVALID_SIGNATURE =>
|
AuthResult.AUTH_RESULT_INVALID_SIGNATURE =>
|
||||||
'Authentication failed: the server rejected the signature for this device key.',
|
'Authentication failed: the server rejected the signature for this device key.',
|
||||||
ua_auth.AuthResult.AUTH_RESULT_BOOTSTRAP_REQUIRED =>
|
AuthResult.AUTH_RESULT_BOOTSTRAP_REQUIRED =>
|
||||||
'Authentication failed: the server requires bootstrap before this device can connect.',
|
'Authentication failed: the server requires bootstrap before this device can connect.',
|
||||||
ua_auth.AuthResult.AUTH_RESULT_TOKEN_INVALID =>
|
AuthResult.AUTH_RESULT_TOKEN_INVALID =>
|
||||||
'Authentication failed: the bootstrap token is invalid.',
|
'Authentication failed: the bootstrap token is invalid.',
|
||||||
ua_auth.AuthResult.AUTH_RESULT_INTERNAL =>
|
AuthResult.AUTH_RESULT_INTERNAL =>
|
||||||
'Authentication failed: the server hit an internal error.',
|
'Authentication failed: the server hit an internal error.',
|
||||||
ua_auth.AuthResult.AUTH_RESULT_UNSPECIFIED =>
|
AuthResult.AUTH_RESULT_UNSPECIFIED =>
|
||||||
'Authentication failed: the server returned an unspecified auth error.',
|
'Authentication failed: the server returned an unspecified auth error.',
|
||||||
ua_auth.AuthResult.AUTH_RESULT_SUCCESS => 'Authentication succeeded.',
|
AuthResult.AUTH_RESULT_SUCCESS => 'Authentication succeeded.',
|
||||||
_ => 'Authentication failed: ${result.name}.',
|
_ => 'Authentication failed: ${result.name}.',
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -58,76 +57,56 @@ Future<Connection> connectAndAuthorize(
|
|||||||
);
|
);
|
||||||
final pubkey = await key.getPublicKey();
|
final pubkey = await key.getPublicKey();
|
||||||
|
|
||||||
final req = ua_auth.AuthChallengeRequest(
|
final req = AuthChallengeRequest(
|
||||||
pubkey: pubkey,
|
pubkey: pubkey,
|
||||||
bootstrapToken: bootstrapToken,
|
bootstrapToken: bootstrapToken,
|
||||||
keyType: switch (key.alg) {
|
keyType: switch (key.alg) {
|
||||||
KeyAlgorithm.rsa => ua_auth.KeyType.KEY_TYPE_RSA,
|
KeyAlgorithm.rsa => KeyType.KEY_TYPE_RSA,
|
||||||
KeyAlgorithm.ecdsa => ua_auth.KeyType.KEY_TYPE_ECDSA_SECP256K1,
|
KeyAlgorithm.ecdsa => KeyType.KEY_TYPE_ECDSA_SECP256K1,
|
||||||
KeyAlgorithm.ed25519 => ua_auth.KeyType.KEY_TYPE_ED25519,
|
KeyAlgorithm.ed25519 => KeyType.KEY_TYPE_ED25519,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
final response = await connection.ask(
|
final response = await connection.ask(
|
||||||
UserAgentRequest(auth: ua_auth.Request(challengeRequest: req)),
|
UserAgentRequest(authChallengeRequest: req),
|
||||||
);
|
);
|
||||||
talker.info(
|
talker.info(
|
||||||
"Sent auth challenge request with pubkey ${base64Encode(pubkey)}",
|
"Sent auth challenge request with pubkey ${base64Encode(pubkey)}",
|
||||||
);
|
);
|
||||||
talker.info('Received response from server, checking auth flow...');
|
talker.info('Received response from server, checking auth flow...');
|
||||||
|
|
||||||
if (!response.hasAuth()) {
|
if (response.hasAuthResult()) {
|
||||||
throw ConnectionException(
|
if (response.authResult != AuthResult.AUTH_RESULT_SUCCESS) {
|
||||||
'Expected auth response, got ${response.whichPayload()}',
|
throw AuthorizationException(response.authResult);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final authResponse = response.auth;
|
|
||||||
|
|
||||||
if (authResponse.hasResult()) {
|
|
||||||
if (authResponse.result != ua_auth.AuthResult.AUTH_RESULT_SUCCESS) {
|
|
||||||
throw AuthorizationException(authResponse.result);
|
|
||||||
}
|
}
|
||||||
talker.info('Authentication successful, connection established');
|
talker.info('Authentication successful, connection established');
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!authResponse.hasChallenge()) {
|
if (!response.hasAuthChallenge()) {
|
||||||
throw ConnectionException(
|
throw ConnectionException(
|
||||||
'Expected auth challenge response, got ${authResponse.whichPayload()}',
|
'Expected AuthChallengeResponse, got ${response.whichPayload()}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final challenge = _formatChallenge(authResponse.challenge, pubkey);
|
final challenge = _formatChallenge(response.authChallenge, pubkey);
|
||||||
talker.info(
|
talker.info(
|
||||||
'Received auth challenge, signing with key ${base64Encode(pubkey)}',
|
'Received auth challenge, signing with key ${base64Encode(pubkey)}',
|
||||||
);
|
);
|
||||||
|
|
||||||
final signature = await key.sign(challenge);
|
final signature = await key.sign(challenge);
|
||||||
final solutionResponse = await connection.ask(
|
final solutionResponse = await connection.ask(
|
||||||
UserAgentRequest(
|
UserAgentRequest(authChallengeSolution: AuthChallengeSolution(signature: signature)),
|
||||||
auth: ua_auth.Request(
|
|
||||||
challengeSolution: ua_auth.AuthChallengeSolution(signature: signature),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
talker.info('Sent auth challenge solution, waiting for server response...');
|
talker.info('Sent auth challenge solution, waiting for server response...');
|
||||||
|
|
||||||
if (!solutionResponse.hasAuth()) {
|
if (!solutionResponse.hasAuthResult()) {
|
||||||
throw ConnectionException(
|
throw ConnectionException(
|
||||||
'Expected auth solution response, got ${solutionResponse.whichPayload()}',
|
'Expected AuthChallengeSolutionResponse, got ${solutionResponse.whichPayload()}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (solutionResponse.authResult != AuthResult.AUTH_RESULT_SUCCESS) {
|
||||||
final authSolutionResponse = solutionResponse.auth;
|
throw AuthorizationException(solutionResponse.authResult);
|
||||||
|
|
||||||
if (!authSolutionResponse.hasResult()) {
|
|
||||||
throw ConnectionException(
|
|
||||||
'Expected auth solution result, got ${authSolutionResponse.whichPayload()}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (authSolutionResponse.result != ua_auth.AuthResult.AUTH_RESULT_SUCCESS) {
|
|
||||||
throw AuthorizationException(authSolutionResponse.result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
talker.info('Authentication successful, connection established');
|
talker.info('Authentication successful, connection established');
|
||||||
@@ -168,7 +147,7 @@ Future<Connection> _connect(StoredServerInfo serverInfo) async {
|
|||||||
return Connection(channel: channel, tx: tx, rx: rx);
|
return Connection(channel: channel, tx: tx, rx: rx);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<int> _formatChallenge(ua_auth.AuthChallenge challenge, List<int> pubkey) {
|
List<int> _formatChallenge(AuthChallenge challenge, List<int> pubkey) {
|
||||||
final encodedPubkey = base64Encode(pubkey);
|
final encodedPubkey = base64Encode(pubkey);
|
||||||
final payload = "${challenge.nonce}:$encodedPubkey";
|
final payload = "${challenge.nonce}:$encodedPubkey";
|
||||||
return utf8.encode(payload);
|
return utf8.encode(payload);
|
||||||
|
|||||||
@@ -1,27 +1,19 @@
|
|||||||
import 'package:arbiter/features/connection/connection.dart';
|
import 'package:arbiter/features/connection/connection.dart';
|
||||||
import 'package:arbiter/proto/evm.pb.dart';
|
import 'package:arbiter/proto/evm.pb.dart';
|
||||||
import 'package:arbiter/proto/user_agent/evm.pb.dart' as ua_evm;
|
|
||||||
import 'package:arbiter/proto/user_agent.pb.dart';
|
import 'package:arbiter/proto/user_agent.pb.dart';
|
||||||
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart';
|
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart';
|
||||||
|
|
||||||
Future<List<WalletEntry>> listEvmWallets(Connection connection) async {
|
Future<List<WalletEntry>> listEvmWallets(Connection connection) async {
|
||||||
final response = await connection.ask(
|
final response = await connection.ask(
|
||||||
UserAgentRequest(evm: ua_evm.Request(walletList: Empty())),
|
UserAgentRequest(evmWalletList: Empty()),
|
||||||
);
|
);
|
||||||
if (!response.hasEvm()) {
|
if (!response.hasEvmWalletList()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected EVM response, got ${response.whichPayload()}',
|
'Expected EVM wallet list response, got ${response.whichPayload()}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final evmResponse = response.evm;
|
final result = response.evmWalletList;
|
||||||
if (!evmResponse.hasWalletList()) {
|
|
||||||
throw Exception(
|
|
||||||
'Expected EVM wallet list response, got ${evmResponse.whichPayload()}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = evmResponse.walletList;
|
|
||||||
switch (result.whichResult()) {
|
switch (result.whichResult()) {
|
||||||
case WalletListResponse_Result.wallets:
|
case WalletListResponse_Result.wallets:
|
||||||
return result.wallets.wallets.toList(growable: false);
|
return result.wallets.wallets.toList(growable: false);
|
||||||
@@ -34,22 +26,15 @@ Future<List<WalletEntry>> listEvmWallets(Connection connection) async {
|
|||||||
|
|
||||||
Future<void> createEvmWallet(Connection connection) async {
|
Future<void> createEvmWallet(Connection connection) async {
|
||||||
final response = await connection.ask(
|
final response = await connection.ask(
|
||||||
UserAgentRequest(evm: ua_evm.Request(walletCreate: Empty())),
|
UserAgentRequest(evmWalletCreate: Empty()),
|
||||||
);
|
);
|
||||||
if (!response.hasEvm()) {
|
if (!response.hasEvmWalletCreate()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected EVM response, got ${response.whichPayload()}',
|
'Expected EVM wallet create response, got ${response.whichPayload()}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final evmResponse = response.evm;
|
final result = response.evmWalletCreate;
|
||||||
if (!evmResponse.hasWalletCreate()) {
|
|
||||||
throw Exception(
|
|
||||||
'Expected EVM wallet create response, got ${evmResponse.whichPayload()}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = evmResponse.walletCreate;
|
|
||||||
switch (result.whichResult()) {
|
switch (result.whichResult()) {
|
||||||
case WalletCreateResponse_Result.wallet:
|
case WalletCreateResponse_Result.wallet:
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,28 +1,22 @@
|
|||||||
import 'package:arbiter/features/connection/connection.dart';
|
import 'package:arbiter/features/connection/connection.dart';
|
||||||
import 'package:arbiter/proto/evm.pb.dart';
|
import 'package:arbiter/proto/evm.pb.dart';
|
||||||
import 'package:arbiter/proto/user_agent/evm.pb.dart' as ua_evm;
|
|
||||||
import 'package:arbiter/proto/user_agent.pb.dart';
|
import 'package:arbiter/proto/user_agent.pb.dart';
|
||||||
|
import 'package:fixnum/fixnum.dart';
|
||||||
|
import 'package:protobuf/well_known_types/google/protobuf/timestamp.pb.dart';
|
||||||
|
|
||||||
Future<List<GrantEntry>> listEvmGrants(Connection connection) async {
|
Future<List<GrantEntry>> listEvmGrants(Connection connection) async {
|
||||||
final request = EvmGrantListRequest();
|
final request = EvmGrantListRequest();
|
||||||
|
|
||||||
final response = await connection.ask(
|
final response = await connection.ask(
|
||||||
UserAgentRequest(evm: ua_evm.Request(grantList: request)),
|
UserAgentRequest(evmGrantList: request),
|
||||||
);
|
);
|
||||||
if (!response.hasEvm()) {
|
if (!response.hasEvmGrantList()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected EVM response, got ${response.whichPayload()}',
|
'Expected EVM grant list response, got ${response.whichPayload()}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final evmResponse = response.evm;
|
final result = response.evmGrantList;
|
||||||
if (!evmResponse.hasGrantList()) {
|
|
||||||
throw Exception(
|
|
||||||
'Expected EVM grant list response, got ${evmResponse.whichPayload()}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = evmResponse.grantList;
|
|
||||||
switch (result.whichResult()) {
|
switch (result.whichResult()) {
|
||||||
case EvmGrantListResponse_Result.grants:
|
case EvmGrantListResponse_Result.grants:
|
||||||
return result.grants.grants.toList(growable: false);
|
return result.grants.grants.toList(growable: false);
|
||||||
@@ -39,56 +33,36 @@ Future<int> createEvmGrant(
|
|||||||
required SpecificGrant specific,
|
required SpecificGrant specific,
|
||||||
}) async {
|
}) async {
|
||||||
final request = UserAgentRequest(
|
final request = UserAgentRequest(
|
||||||
evm: ua_evm.Request(
|
evmGrantCreate: EvmGrantCreateRequest(
|
||||||
grantCreate: EvmGrantCreateRequest(
|
|
||||||
shared: sharedSettings,
|
shared: sharedSettings,
|
||||||
specific: specific,
|
specific: specific,
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final resp = await connection.ask(request);
|
final resp = await connection.ask(request);
|
||||||
|
|
||||||
if (!resp.hasEvm()) {
|
if (!resp.hasEvmGrantCreate()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected EVM response, got ${resp.whichPayload()}',
|
'Expected EVM grant create response, got ${resp.whichPayload()}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final evmResponse = resp.evm;
|
final result = resp.evmGrantCreate;
|
||||||
if (!evmResponse.hasGrantCreate()) {
|
|
||||||
throw Exception(
|
|
||||||
'Expected EVM grant create response, got ${evmResponse.whichPayload()}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = evmResponse.grantCreate;
|
|
||||||
|
|
||||||
return result.grantId;
|
return result.grantId;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteEvmGrant(Connection connection, int grantId) async {
|
Future<void> deleteEvmGrant(Connection connection, int grantId) async {
|
||||||
final response = await connection.ask(
|
final response = await connection.ask(
|
||||||
UserAgentRequest(
|
UserAgentRequest(evmGrantDelete: EvmGrantDeleteRequest(grantId: grantId)),
|
||||||
evm: ua_evm.Request(
|
|
||||||
grantDelete: EvmGrantDeleteRequest(grantId: grantId),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (!response.hasEvm()) {
|
if (!response.hasEvmGrantDelete()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected EVM response, got ${response.whichPayload()}',
|
'Expected EVM grant delete response, got ${response.whichPayload()}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final evmResponse = response.evm;
|
final result = response.evmGrantDelete;
|
||||||
if (!evmResponse.hasGrantDelete()) {
|
|
||||||
throw Exception(
|
|
||||||
'Expected EVM grant delete response, got ${evmResponse.whichPayload()}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = evmResponse.grantDelete;
|
|
||||||
switch (result.whichResult()) {
|
switch (result.whichResult()) {
|
||||||
case EvmGrantDeleteResponse_Result.ok:
|
case EvmGrantDeleteResponse_Result.ok:
|
||||||
return;
|
return;
|
||||||
@@ -99,6 +73,13 @@ Future<void> deleteEvmGrant(Connection connection, int grantId) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timestamp _toTimestamp(DateTime value) {
|
||||||
|
final utc = value.toUtc();
|
||||||
|
return Timestamp()
|
||||||
|
..seconds = Int64(utc.millisecondsSinceEpoch ~/ 1000)
|
||||||
|
..nanos = (utc.microsecondsSinceEpoch % 1000000) * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
String _describeGrantError(EvmError error) {
|
String _describeGrantError(EvmError error) {
|
||||||
return switch (error) {
|
return switch (error) {
|
||||||
EvmError.EVM_ERROR_VAULT_SEALED =>
|
EvmError.EVM_ERROR_VAULT_SEALED =>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:arbiter/features/connection/connection.dart';
|
import 'package:arbiter/features/connection/connection.dart';
|
||||||
import 'package:arbiter/proto/user_agent/sdk_client.pb.dart' as ua_sdk;
|
|
||||||
import 'package:arbiter/proto/user_agent.pb.dart';
|
import 'package:arbiter/proto/user_agent.pb.dart';
|
||||||
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart';
|
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart';
|
||||||
|
|
||||||
@@ -8,47 +7,31 @@ Future<Set<int>> readClientWalletAccess(
|
|||||||
required int clientId,
|
required int clientId,
|
||||||
}) async {
|
}) async {
|
||||||
final response = await connection.ask(
|
final response = await connection.ask(
|
||||||
UserAgentRequest(
|
UserAgentRequest(listWalletAccess: Empty()),
|
||||||
sdkClient: ua_sdk.Request(listWalletAccess: Empty()),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (!response.hasSdkClient()) {
|
if (!response.hasListWalletAccessResponse()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected SDK client response, got ${response.whichPayload()}',
|
'Expected list wallet access response, got ${response.whichPayload()}',
|
||||||
);
|
|
||||||
}
|
|
||||||
final sdkClientResponse = response.sdkClient;
|
|
||||||
if (!sdkClientResponse.hasListWalletAccess()) {
|
|
||||||
throw Exception(
|
|
||||||
'Expected list wallet access response, got ${sdkClientResponse.whichPayload()}',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
for (final entry in sdkClientResponse.listWalletAccess.accesses)
|
for (final entry in response.listWalletAccessResponse.accesses)
|
||||||
if (entry.access.sdkClientId == clientId) entry.access.walletId,
|
if (entry.access.sdkClientId == clientId) entry.access.walletId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ua_sdk.WalletAccessEntry>> listAllWalletAccesses(
|
Future<List<SdkClientWalletAccess>> listAllWalletAccesses(
|
||||||
Connection connection,
|
Connection connection,
|
||||||
) async {
|
) async {
|
||||||
final response = await connection.ask(
|
final response = await connection.ask(
|
||||||
UserAgentRequest(
|
UserAgentRequest(listWalletAccess: Empty()),
|
||||||
sdkClient: ua_sdk.Request(listWalletAccess: Empty()),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (!response.hasSdkClient()) {
|
if (!response.hasListWalletAccessResponse()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected SDK client response, got ${response.whichPayload()}',
|
'Expected list wallet access response, got ${response.whichPayload()}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final sdkClientResponse = response.sdkClient;
|
return response.listWalletAccessResponse.accesses.toList(growable: false);
|
||||||
if (!sdkClientResponse.hasListWalletAccess()) {
|
|
||||||
throw Exception(
|
|
||||||
'Expected list wallet access response, got ${sdkClientResponse.whichPayload()}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return sdkClientResponse.listWalletAccess.accesses.toList(growable: false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> writeClientWalletAccess(
|
Future<void> writeClientWalletAccess(
|
||||||
@@ -64,29 +47,26 @@ Future<void> writeClientWalletAccess(
|
|||||||
if (toGrant.isNotEmpty) {
|
if (toGrant.isNotEmpty) {
|
||||||
await connection.tell(
|
await connection.tell(
|
||||||
UserAgentRequest(
|
UserAgentRequest(
|
||||||
sdkClient: ua_sdk.Request(
|
grantWalletAccess: SdkClientGrantWalletAccess(
|
||||||
grantWalletAccess: ua_sdk.GrantWalletAccess(
|
|
||||||
accesses: [
|
accesses: [
|
||||||
for (final walletId in toGrant)
|
for (final walletId in toGrant)
|
||||||
ua_sdk.WalletAccess(sdkClientId: clientId, walletId: walletId),
|
WalletAccess(sdkClientId: clientId, walletId: walletId),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (toRevoke.isNotEmpty) {
|
if (toRevoke.isNotEmpty) {
|
||||||
await connection.tell(
|
await connection.tell(
|
||||||
UserAgentRequest(
|
UserAgentRequest(
|
||||||
sdkClient: ua_sdk.Request(
|
revokeWalletAccess: SdkClientRevokeWalletAccess(
|
||||||
revokeWalletAccess: ua_sdk.RevokeWalletAccess(
|
|
||||||
accesses: [
|
accesses: [
|
||||||
for (final walletId in toRevoke) walletId,
|
for (final walletId in toRevoke)
|
||||||
|
walletId
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,10 @@
|
|||||||
import 'package:arbiter/features/connection/connection.dart';
|
import 'package:arbiter/features/connection/connection.dart';
|
||||||
import 'package:arbiter/proto/user_agent/vault/bootstrap.pb.dart' as ua_bootstrap;
|
|
||||||
import 'package:arbiter/proto/user_agent/vault/unseal.pb.dart' as ua_unseal;
|
|
||||||
import 'package:arbiter/proto/user_agent/vault/vault.pb.dart' as ua_vault;
|
|
||||||
import 'package:arbiter/proto/user_agent.pb.dart';
|
import 'package:arbiter/proto/user_agent.pb.dart';
|
||||||
import 'package:cryptography/cryptography.dart';
|
import 'package:cryptography/cryptography.dart';
|
||||||
|
|
||||||
const _vaultKeyAssociatedData = 'arbiter.vault.password';
|
const _vaultKeyAssociatedData = 'arbiter.vault.password';
|
||||||
|
|
||||||
Future<ua_bootstrap.BootstrapResult> bootstrapVault(
|
Future<BootstrapResult> bootstrapVault(
|
||||||
Connection connection,
|
Connection connection,
|
||||||
String password,
|
String password,
|
||||||
) async {
|
) async {
|
||||||
@@ -15,76 +12,39 @@ Future<ua_bootstrap.BootstrapResult> bootstrapVault(
|
|||||||
|
|
||||||
final response = await connection.ask(
|
final response = await connection.ask(
|
||||||
UserAgentRequest(
|
UserAgentRequest(
|
||||||
vault: ua_vault.Request(
|
bootstrapEncryptedKey: BootstrapEncryptedKey(
|
||||||
bootstrap: ua_bootstrap.Request(
|
|
||||||
encryptedKey: ua_bootstrap.BootstrapEncryptedKey(
|
|
||||||
nonce: encryptedKey.nonce,
|
nonce: encryptedKey.nonce,
|
||||||
ciphertext: encryptedKey.ciphertext,
|
ciphertext: encryptedKey.ciphertext,
|
||||||
associatedData: encryptedKey.associatedData,
|
associatedData: encryptedKey.associatedData,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (!response.hasVault()) {
|
if (!response.hasBootstrapResult()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected vault response, got ${response.whichPayload()}',
|
'Expected bootstrap result, got ${response.whichPayload()}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final vaultResponse = response.vault;
|
return response.bootstrapResult;
|
||||||
if (!vaultResponse.hasBootstrap()) {
|
|
||||||
throw Exception(
|
|
||||||
'Expected bootstrap result, got ${vaultResponse.whichPayload()}',
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final bootstrapResponse = vaultResponse.bootstrap;
|
Future<UnsealResult> unsealVault(Connection connection, String password) async {
|
||||||
if (!bootstrapResponse.hasResult()) {
|
|
||||||
throw Exception('Expected bootstrap result payload.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return bootstrapResponse.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ua_unseal.UnsealResult> unsealVault(
|
|
||||||
Connection connection,
|
|
||||||
String password,
|
|
||||||
) async {
|
|
||||||
final encryptedKey = await _encryptVaultKeyMaterial(connection, password);
|
final encryptedKey = await _encryptVaultKeyMaterial(connection, password);
|
||||||
|
|
||||||
final response = await connection.ask(
|
final response = await connection.ask(
|
||||||
UserAgentRequest(
|
UserAgentRequest(
|
||||||
vault: ua_vault.Request(
|
unsealEncryptedKey: UnsealEncryptedKey(
|
||||||
unseal: ua_unseal.Request(
|
|
||||||
encryptedKey: ua_unseal.UnsealEncryptedKey(
|
|
||||||
nonce: encryptedKey.nonce,
|
nonce: encryptedKey.nonce,
|
||||||
ciphertext: encryptedKey.ciphertext,
|
ciphertext: encryptedKey.ciphertext,
|
||||||
associatedData: encryptedKey.associatedData,
|
associatedData: encryptedKey.associatedData,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (!response.hasVault()) {
|
if (!response.hasUnsealResult()) {
|
||||||
throw Exception('Expected vault response, got ${response.whichPayload()}');
|
throw Exception('Expected unseal result, got ${response.whichPayload()}');
|
||||||
}
|
}
|
||||||
|
|
||||||
final vaultResponse = response.vault;
|
return response.unsealResult;
|
||||||
if (!vaultResponse.hasUnseal()) {
|
|
||||||
throw Exception(
|
|
||||||
'Expected unseal result, got ${vaultResponse.whichPayload()}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final unsealResponse = vaultResponse.unseal;
|
|
||||||
if (!unsealResponse.hasResult()) {
|
|
||||||
throw Exception(
|
|
||||||
'Expected unseal result payload, got ${unsealResponse.whichPayload()}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return unsealResponse.result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<_EncryptedVaultKey> _encryptVaultKeyMaterial(
|
Future<_EncryptedVaultKey> _encryptVaultKeyMaterial(
|
||||||
@@ -97,36 +57,16 @@ Future<_EncryptedVaultKey> _encryptVaultKeyMaterial(
|
|||||||
final clientPublicKey = await clientKeyPair.extractPublicKey();
|
final clientPublicKey = await clientKeyPair.extractPublicKey();
|
||||||
|
|
||||||
final handshakeResponse = await connection.ask(
|
final handshakeResponse = await connection.ask(
|
||||||
UserAgentRequest(
|
UserAgentRequest(unsealStart: UnsealStart(clientPubkey: clientPublicKey.bytes)),
|
||||||
vault: ua_vault.Request(
|
|
||||||
unseal: ua_unseal.Request(
|
|
||||||
start: ua_unseal.UnsealStart(clientPubkey: clientPublicKey.bytes),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (!handshakeResponse.hasVault()) {
|
if (!handshakeResponse.hasUnsealStartResponse()) {
|
||||||
throw Exception(
|
throw Exception(
|
||||||
'Expected vault response, got ${handshakeResponse.whichPayload()}',
|
'Expected unseal handshake response, got ${handshakeResponse.whichPayload()}',
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final vaultResponse = handshakeResponse.vault;
|
|
||||||
if (!vaultResponse.hasUnseal()) {
|
|
||||||
throw Exception(
|
|
||||||
'Expected unseal handshake response, got ${vaultResponse.whichPayload()}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final unsealResponse = vaultResponse.unseal;
|
|
||||||
if (!unsealResponse.hasStart()) {
|
|
||||||
throw Exception(
|
|
||||||
'Expected unseal handshake payload, got ${unsealResponse.whichPayload()}',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final serverPublicKey = SimplePublicKey(
|
final serverPublicKey = SimplePublicKey(
|
||||||
unsealResponse.start.serverPubkey,
|
handshakeResponse.unsealStartResponse.serverPubkey,
|
||||||
type: KeyPairType.x25519,
|
type: KeyPairType.x25519,
|
||||||
);
|
);
|
||||||
final sharedSecret = await keyExchange.sharedSecretKey(
|
final sharedSecret = await keyExchange.sharedSecretKey(
|
||||||
|
|||||||
@@ -13,26 +13,305 @@
|
|||||||
import 'dart:core' as $core;
|
import 'dart:core' as $core;
|
||||||
|
|
||||||
import 'package:protobuf/protobuf.dart' as $pb;
|
import 'package:protobuf/protobuf.dart' as $pb;
|
||||||
|
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart' as $0;
|
||||||
|
|
||||||
import 'client/auth.pb.dart' as $0;
|
import 'client.pbenum.dart';
|
||||||
import 'client/evm.pb.dart' as $2;
|
import 'evm.pb.dart' as $1;
|
||||||
import 'client/vault.pb.dart' as $1;
|
|
||||||
|
|
||||||
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
|
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
|
||||||
|
|
||||||
enum ClientRequest_Payload { auth, vault, evm, notSet }
|
export 'client.pbenum.dart';
|
||||||
|
|
||||||
|
class ClientInfo extends $pb.GeneratedMessage {
|
||||||
|
factory ClientInfo({
|
||||||
|
$core.String? name,
|
||||||
|
$core.String? description,
|
||||||
|
$core.String? version,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (name != null) result.name = name;
|
||||||
|
if (description != null) result.description = description;
|
||||||
|
if (version != null) result.version = version;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientInfo._();
|
||||||
|
|
||||||
|
factory ClientInfo.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory ClientInfo.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'ClientInfo',
|
||||||
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..aOS(1, _omitFieldNames ? '' : 'name')
|
||||||
|
..aOS(2, _omitFieldNames ? '' : 'description')
|
||||||
|
..aOS(3, _omitFieldNames ? '' : 'version')
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
ClientInfo clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
ClientInfo copyWith(void Function(ClientInfo) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as ClientInfo)) as ClientInfo;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static ClientInfo create() => ClientInfo._();
|
||||||
|
@$core.override
|
||||||
|
ClientInfo createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static ClientInfo getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<ClientInfo>(create);
|
||||||
|
static ClientInfo? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.String get name => $_getSZ(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set name($core.String value) => $_setString(0, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasName() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearName() => $_clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.String get description => $_getSZ(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set description($core.String value) => $_setString(1, value);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasDescription() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearDescription() => $_clearField(2);
|
||||||
|
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.String get version => $_getSZ(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
set version($core.String value) => $_setString(2, value);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.bool hasVersion() => $_has(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
void clearVersion() => $_clearField(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthChallengeRequest extends $pb.GeneratedMessage {
|
||||||
|
factory AuthChallengeRequest({
|
||||||
|
$core.List<$core.int>? pubkey,
|
||||||
|
ClientInfo? clientInfo,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (pubkey != null) result.pubkey = pubkey;
|
||||||
|
if (clientInfo != null) result.clientInfo = clientInfo;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthChallengeRequest._();
|
||||||
|
|
||||||
|
factory AuthChallengeRequest.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory AuthChallengeRequest.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'AuthChallengeRequest',
|
||||||
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY)
|
||||||
|
..aOM<ClientInfo>(2, _omitFieldNames ? '' : 'clientInfo',
|
||||||
|
subBuilder: ClientInfo.create)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
AuthChallengeRequest clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
AuthChallengeRequest copyWith(void Function(AuthChallengeRequest) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as AuthChallengeRequest))
|
||||||
|
as AuthChallengeRequest;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static AuthChallengeRequest create() => AuthChallengeRequest._();
|
||||||
|
@$core.override
|
||||||
|
AuthChallengeRequest createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static AuthChallengeRequest getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<AuthChallengeRequest>(create);
|
||||||
|
static AuthChallengeRequest? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.List<$core.int> get pubkey => $_getN(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set pubkey($core.List<$core.int> value) => $_setBytes(0, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasPubkey() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearPubkey() => $_clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
ClientInfo get clientInfo => $_getN(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set clientInfo(ClientInfo value) => $_setField(2, value);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasClientInfo() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearClientInfo() => $_clearField(2);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
ClientInfo ensureClientInfo() => $_ensure(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthChallenge extends $pb.GeneratedMessage {
|
||||||
|
factory AuthChallenge({
|
||||||
|
$core.List<$core.int>? pubkey,
|
||||||
|
$core.int? nonce,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (pubkey != null) result.pubkey = pubkey;
|
||||||
|
if (nonce != null) result.nonce = nonce;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthChallenge._();
|
||||||
|
|
||||||
|
factory AuthChallenge.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory AuthChallenge.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'AuthChallenge',
|
||||||
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY)
|
||||||
|
..aI(2, _omitFieldNames ? '' : 'nonce')
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
AuthChallenge clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
AuthChallenge copyWith(void Function(AuthChallenge) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as AuthChallenge))
|
||||||
|
as AuthChallenge;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static AuthChallenge create() => AuthChallenge._();
|
||||||
|
@$core.override
|
||||||
|
AuthChallenge createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static AuthChallenge getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<AuthChallenge>(create);
|
||||||
|
static AuthChallenge? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.List<$core.int> get pubkey => $_getN(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set pubkey($core.List<$core.int> value) => $_setBytes(0, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasPubkey() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearPubkey() => $_clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.int get nonce => $_getIZ(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set nonce($core.int value) => $_setSignedInt32(1, value);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasNonce() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearNonce() => $_clearField(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
class AuthChallengeSolution extends $pb.GeneratedMessage {
|
||||||
|
factory AuthChallengeSolution({
|
||||||
|
$core.List<$core.int>? signature,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (signature != null) result.signature = signature;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthChallengeSolution._();
|
||||||
|
|
||||||
|
factory AuthChallengeSolution.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory AuthChallengeSolution.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'AuthChallengeSolution',
|
||||||
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
1, _omitFieldNames ? '' : 'signature', $pb.PbFieldType.OY)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
AuthChallengeSolution clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
AuthChallengeSolution copyWith(
|
||||||
|
void Function(AuthChallengeSolution) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as AuthChallengeSolution))
|
||||||
|
as AuthChallengeSolution;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static AuthChallengeSolution create() => AuthChallengeSolution._();
|
||||||
|
@$core.override
|
||||||
|
AuthChallengeSolution createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static AuthChallengeSolution getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<AuthChallengeSolution>(create);
|
||||||
|
static AuthChallengeSolution? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.List<$core.int> get signature => $_getN(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set signature($core.List<$core.int> value) => $_setBytes(0, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasSignature() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearSignature() => $_clearField(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ClientRequest_Payload {
|
||||||
|
authChallengeRequest,
|
||||||
|
authChallengeSolution,
|
||||||
|
queryVaultState,
|
||||||
|
notSet
|
||||||
|
}
|
||||||
|
|
||||||
class ClientRequest extends $pb.GeneratedMessage {
|
class ClientRequest extends $pb.GeneratedMessage {
|
||||||
factory ClientRequest({
|
factory ClientRequest({
|
||||||
$0.Request? auth,
|
AuthChallengeRequest? authChallengeRequest,
|
||||||
$1.Request? vault,
|
AuthChallengeSolution? authChallengeSolution,
|
||||||
$2.Request? evm,
|
$0.Empty? queryVaultState,
|
||||||
$core.int? requestId,
|
$core.int? requestId,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (auth != null) result.auth = auth;
|
if (authChallengeRequest != null)
|
||||||
if (vault != null) result.vault = vault;
|
result.authChallengeRequest = authChallengeRequest;
|
||||||
if (evm != null) result.evm = evm;
|
if (authChallengeSolution != null)
|
||||||
|
result.authChallengeSolution = authChallengeSolution;
|
||||||
|
if (queryVaultState != null) result.queryVaultState = queryVaultState;
|
||||||
if (requestId != null) result.requestId = requestId;
|
if (requestId != null) result.requestId = requestId;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -48,9 +327,9 @@ class ClientRequest extends $pb.GeneratedMessage {
|
|||||||
|
|
||||||
static const $core.Map<$core.int, ClientRequest_Payload>
|
static const $core.Map<$core.int, ClientRequest_Payload>
|
||||||
_ClientRequest_PayloadByTag = {
|
_ClientRequest_PayloadByTag = {
|
||||||
1: ClientRequest_Payload.auth,
|
1: ClientRequest_Payload.authChallengeRequest,
|
||||||
2: ClientRequest_Payload.vault,
|
2: ClientRequest_Payload.authChallengeSolution,
|
||||||
3: ClientRequest_Payload.evm,
|
3: ClientRequest_Payload.queryVaultState,
|
||||||
0: ClientRequest_Payload.notSet
|
0: ClientRequest_Payload.notSet
|
||||||
};
|
};
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
@@ -58,12 +337,14 @@ class ClientRequest extends $pb.GeneratedMessage {
|
|||||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
|
||||||
createEmptyInstance: create)
|
createEmptyInstance: create)
|
||||||
..oo(0, [1, 2, 3])
|
..oo(0, [1, 2, 3])
|
||||||
..aOM<$0.Request>(1, _omitFieldNames ? '' : 'auth',
|
..aOM<AuthChallengeRequest>(
|
||||||
subBuilder: $0.Request.create)
|
1, _omitFieldNames ? '' : 'authChallengeRequest',
|
||||||
..aOM<$1.Request>(2, _omitFieldNames ? '' : 'vault',
|
subBuilder: AuthChallengeRequest.create)
|
||||||
subBuilder: $1.Request.create)
|
..aOM<AuthChallengeSolution>(
|
||||||
..aOM<$2.Request>(3, _omitFieldNames ? '' : 'evm',
|
2, _omitFieldNames ? '' : 'authChallengeSolution',
|
||||||
subBuilder: $2.Request.create)
|
subBuilder: AuthChallengeSolution.create)
|
||||||
|
..aOM<$0.Empty>(3, _omitFieldNames ? '' : 'queryVaultState',
|
||||||
|
subBuilder: $0.Empty.create)
|
||||||
..aI(4, _omitFieldNames ? '' : 'requestId')
|
..aI(4, _omitFieldNames ? '' : 'requestId')
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@@ -97,37 +378,38 @@ class ClientRequest extends $pb.GeneratedMessage {
|
|||||||
void clearPayload() => $_clearField($_whichOneof(0));
|
void clearPayload() => $_clearField($_whichOneof(0));
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
$0.Request get auth => $_getN(0);
|
AuthChallengeRequest get authChallengeRequest => $_getN(0);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
set auth($0.Request value) => $_setField(1, value);
|
set authChallengeRequest(AuthChallengeRequest value) => $_setField(1, value);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
$core.bool hasAuth() => $_has(0);
|
$core.bool hasAuthChallengeRequest() => $_has(0);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
void clearAuth() => $_clearField(1);
|
void clearAuthChallengeRequest() => $_clearField(1);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
$0.Request ensureAuth() => $_ensure(0);
|
AuthChallengeRequest ensureAuthChallengeRequest() => $_ensure(0);
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$1.Request get vault => $_getN(1);
|
AuthChallengeSolution get authChallengeSolution => $_getN(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
set vault($1.Request value) => $_setField(2, value);
|
set authChallengeSolution(AuthChallengeSolution value) =>
|
||||||
|
$_setField(2, value);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$core.bool hasVault() => $_has(1);
|
$core.bool hasAuthChallengeSolution() => $_has(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
void clearVault() => $_clearField(2);
|
void clearAuthChallengeSolution() => $_clearField(2);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$1.Request ensureVault() => $_ensure(1);
|
AuthChallengeSolution ensureAuthChallengeSolution() => $_ensure(1);
|
||||||
|
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
$2.Request get evm => $_getN(2);
|
$0.Empty get queryVaultState => $_getN(2);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
set evm($2.Request value) => $_setField(3, value);
|
set queryVaultState($0.Empty value) => $_setField(3, value);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
$core.bool hasEvm() => $_has(2);
|
$core.bool hasQueryVaultState() => $_has(2);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
void clearEvm() => $_clearField(3);
|
void clearQueryVaultState() => $_clearField(3);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
$2.Request ensureEvm() => $_ensure(2);
|
$0.Empty ensureQueryVaultState() => $_ensure(2);
|
||||||
|
|
||||||
@$pb.TagNumber(4)
|
@$pb.TagNumber(4)
|
||||||
$core.int get requestId => $_getIZ(3);
|
$core.int get requestId => $_getIZ(3);
|
||||||
@@ -139,19 +421,32 @@ class ClientRequest extends $pb.GeneratedMessage {
|
|||||||
void clearRequestId() => $_clearField(4);
|
void clearRequestId() => $_clearField(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ClientResponse_Payload { auth, vault, evm, notSet }
|
enum ClientResponse_Payload {
|
||||||
|
authChallenge,
|
||||||
|
authResult,
|
||||||
|
evmSignTransaction,
|
||||||
|
evmAnalyzeTransaction,
|
||||||
|
vaultState,
|
||||||
|
notSet
|
||||||
|
}
|
||||||
|
|
||||||
class ClientResponse extends $pb.GeneratedMessage {
|
class ClientResponse extends $pb.GeneratedMessage {
|
||||||
factory ClientResponse({
|
factory ClientResponse({
|
||||||
$0.Response? auth,
|
AuthChallenge? authChallenge,
|
||||||
$1.Response? vault,
|
AuthResult? authResult,
|
||||||
$2.Response? evm,
|
$1.EvmSignTransactionResponse? evmSignTransaction,
|
||||||
|
$1.EvmAnalyzeTransactionResponse? evmAnalyzeTransaction,
|
||||||
|
VaultState? vaultState,
|
||||||
$core.int? requestId,
|
$core.int? requestId,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (auth != null) result.auth = auth;
|
if (authChallenge != null) result.authChallenge = authChallenge;
|
||||||
if (vault != null) result.vault = vault;
|
if (authResult != null) result.authResult = authResult;
|
||||||
if (evm != null) result.evm = evm;
|
if (evmSignTransaction != null)
|
||||||
|
result.evmSignTransaction = evmSignTransaction;
|
||||||
|
if (evmAnalyzeTransaction != null)
|
||||||
|
result.evmAnalyzeTransaction = evmAnalyzeTransaction;
|
||||||
|
if (vaultState != null) result.vaultState = vaultState;
|
||||||
if (requestId != null) result.requestId = requestId;
|
if (requestId != null) result.requestId = requestId;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -167,22 +462,30 @@ class ClientResponse extends $pb.GeneratedMessage {
|
|||||||
|
|
||||||
static const $core.Map<$core.int, ClientResponse_Payload>
|
static const $core.Map<$core.int, ClientResponse_Payload>
|
||||||
_ClientResponse_PayloadByTag = {
|
_ClientResponse_PayloadByTag = {
|
||||||
1: ClientResponse_Payload.auth,
|
1: ClientResponse_Payload.authChallenge,
|
||||||
2: ClientResponse_Payload.vault,
|
2: ClientResponse_Payload.authResult,
|
||||||
3: ClientResponse_Payload.evm,
|
3: ClientResponse_Payload.evmSignTransaction,
|
||||||
|
4: ClientResponse_Payload.evmAnalyzeTransaction,
|
||||||
|
6: ClientResponse_Payload.vaultState,
|
||||||
0: ClientResponse_Payload.notSet
|
0: ClientResponse_Payload.notSet
|
||||||
};
|
};
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
_omitMessageNames ? '' : 'ClientResponse',
|
_omitMessageNames ? '' : 'ClientResponse',
|
||||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client'),
|
||||||
createEmptyInstance: create)
|
createEmptyInstance: create)
|
||||||
..oo(0, [1, 2, 3])
|
..oo(0, [1, 2, 3, 4, 6])
|
||||||
..aOM<$0.Response>(1, _omitFieldNames ? '' : 'auth',
|
..aOM<AuthChallenge>(1, _omitFieldNames ? '' : 'authChallenge',
|
||||||
subBuilder: $0.Response.create)
|
subBuilder: AuthChallenge.create)
|
||||||
..aOM<$1.Response>(2, _omitFieldNames ? '' : 'vault',
|
..aE<AuthResult>(2, _omitFieldNames ? '' : 'authResult',
|
||||||
subBuilder: $1.Response.create)
|
enumValues: AuthResult.values)
|
||||||
..aOM<$2.Response>(3, _omitFieldNames ? '' : 'evm',
|
..aOM<$1.EvmSignTransactionResponse>(
|
||||||
subBuilder: $2.Response.create)
|
3, _omitFieldNames ? '' : 'evmSignTransaction',
|
||||||
|
subBuilder: $1.EvmSignTransactionResponse.create)
|
||||||
|
..aOM<$1.EvmAnalyzeTransactionResponse>(
|
||||||
|
4, _omitFieldNames ? '' : 'evmAnalyzeTransaction',
|
||||||
|
subBuilder: $1.EvmAnalyzeTransactionResponse.create)
|
||||||
|
..aE<VaultState>(6, _omitFieldNames ? '' : 'vaultState',
|
||||||
|
enumValues: VaultState.values)
|
||||||
..aI(7, _omitFieldNames ? '' : 'requestId')
|
..aI(7, _omitFieldNames ? '' : 'requestId')
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@@ -208,52 +511,76 @@ class ClientResponse extends $pb.GeneratedMessage {
|
|||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
@$pb.TagNumber(6)
|
||||||
ClientResponse_Payload whichPayload() =>
|
ClientResponse_Payload whichPayload() =>
|
||||||
_ClientResponse_PayloadByTag[$_whichOneof(0)]!;
|
_ClientResponse_PayloadByTag[$_whichOneof(0)]!;
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
@$pb.TagNumber(6)
|
||||||
void clearPayload() => $_clearField($_whichOneof(0));
|
void clearPayload() => $_clearField($_whichOneof(0));
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
$0.Response get auth => $_getN(0);
|
AuthChallenge get authChallenge => $_getN(0);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
set auth($0.Response value) => $_setField(1, value);
|
set authChallenge(AuthChallenge value) => $_setField(1, value);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
$core.bool hasAuth() => $_has(0);
|
$core.bool hasAuthChallenge() => $_has(0);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
void clearAuth() => $_clearField(1);
|
void clearAuthChallenge() => $_clearField(1);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
$0.Response ensureAuth() => $_ensure(0);
|
AuthChallenge ensureAuthChallenge() => $_ensure(0);
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$1.Response get vault => $_getN(1);
|
AuthResult get authResult => $_getN(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
set vault($1.Response value) => $_setField(2, value);
|
set authResult(AuthResult value) => $_setField(2, value);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$core.bool hasVault() => $_has(1);
|
$core.bool hasAuthResult() => $_has(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
void clearVault() => $_clearField(2);
|
void clearAuthResult() => $_clearField(2);
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$1.Response ensureVault() => $_ensure(1);
|
|
||||||
|
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
$2.Response get evm => $_getN(2);
|
$1.EvmSignTransactionResponse get evmSignTransaction => $_getN(2);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
set evm($2.Response value) => $_setField(3, value);
|
set evmSignTransaction($1.EvmSignTransactionResponse value) =>
|
||||||
|
$_setField(3, value);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
$core.bool hasEvm() => $_has(2);
|
$core.bool hasEvmSignTransaction() => $_has(2);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
void clearEvm() => $_clearField(3);
|
void clearEvmSignTransaction() => $_clearField(3);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
$2.Response ensureEvm() => $_ensure(2);
|
$1.EvmSignTransactionResponse ensureEvmSignTransaction() => $_ensure(2);
|
||||||
|
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$1.EvmAnalyzeTransactionResponse get evmAnalyzeTransaction => $_getN(3);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
set evmAnalyzeTransaction($1.EvmAnalyzeTransactionResponse value) =>
|
||||||
|
$_setField(4, value);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$core.bool hasEvmAnalyzeTransaction() => $_has(3);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
void clearEvmAnalyzeTransaction() => $_clearField(4);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$1.EvmAnalyzeTransactionResponse ensureEvmAnalyzeTransaction() => $_ensure(3);
|
||||||
|
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
VaultState get vaultState => $_getN(4);
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
set vaultState(VaultState value) => $_setField(6, value);
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
$core.bool hasVaultState() => $_has(4);
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
void clearVaultState() => $_clearField(6);
|
||||||
|
|
||||||
@$pb.TagNumber(7)
|
@$pb.TagNumber(7)
|
||||||
$core.int get requestId => $_getIZ(3);
|
$core.int get requestId => $_getIZ(5);
|
||||||
@$pb.TagNumber(7)
|
@$pb.TagNumber(7)
|
||||||
set requestId($core.int value) => $_setSignedInt32(3, value);
|
set requestId($core.int value) => $_setSignedInt32(5, value);
|
||||||
@$pb.TagNumber(7)
|
@$pb.TagNumber(7)
|
||||||
$core.bool hasRequestId() => $_has(3);
|
$core.bool hasRequestId() => $_has(5);
|
||||||
@$pb.TagNumber(7)
|
@$pb.TagNumber(7)
|
||||||
void clearRequestId() => $_clearField(7);
|
void clearRequestId() => $_clearField(7);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,3 +9,72 @@
|
|||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
// ignore_for_file: curly_braces_in_flow_control_structures
|
||||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
||||||
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
|
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
|
||||||
|
|
||||||
|
import 'dart:core' as $core;
|
||||||
|
|
||||||
|
import 'package:protobuf/protobuf.dart' as $pb;
|
||||||
|
|
||||||
|
class AuthResult extends $pb.ProtobufEnum {
|
||||||
|
static const AuthResult AUTH_RESULT_UNSPECIFIED =
|
||||||
|
AuthResult._(0, _omitEnumNames ? '' : 'AUTH_RESULT_UNSPECIFIED');
|
||||||
|
static const AuthResult AUTH_RESULT_SUCCESS =
|
||||||
|
AuthResult._(1, _omitEnumNames ? '' : 'AUTH_RESULT_SUCCESS');
|
||||||
|
static const AuthResult AUTH_RESULT_INVALID_KEY =
|
||||||
|
AuthResult._(2, _omitEnumNames ? '' : 'AUTH_RESULT_INVALID_KEY');
|
||||||
|
static const AuthResult AUTH_RESULT_INVALID_SIGNATURE =
|
||||||
|
AuthResult._(3, _omitEnumNames ? '' : 'AUTH_RESULT_INVALID_SIGNATURE');
|
||||||
|
static const AuthResult AUTH_RESULT_APPROVAL_DENIED =
|
||||||
|
AuthResult._(4, _omitEnumNames ? '' : 'AUTH_RESULT_APPROVAL_DENIED');
|
||||||
|
static const AuthResult AUTH_RESULT_NO_USER_AGENTS_ONLINE = AuthResult._(
|
||||||
|
5, _omitEnumNames ? '' : 'AUTH_RESULT_NO_USER_AGENTS_ONLINE');
|
||||||
|
static const AuthResult AUTH_RESULT_INTERNAL =
|
||||||
|
AuthResult._(6, _omitEnumNames ? '' : 'AUTH_RESULT_INTERNAL');
|
||||||
|
|
||||||
|
static const $core.List<AuthResult> values = <AuthResult>[
|
||||||
|
AUTH_RESULT_UNSPECIFIED,
|
||||||
|
AUTH_RESULT_SUCCESS,
|
||||||
|
AUTH_RESULT_INVALID_KEY,
|
||||||
|
AUTH_RESULT_INVALID_SIGNATURE,
|
||||||
|
AUTH_RESULT_APPROVAL_DENIED,
|
||||||
|
AUTH_RESULT_NO_USER_AGENTS_ONLINE,
|
||||||
|
AUTH_RESULT_INTERNAL,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.List<AuthResult?> _byValue =
|
||||||
|
$pb.ProtobufEnum.$_initByValueList(values, 6);
|
||||||
|
static AuthResult? valueOf($core.int value) =>
|
||||||
|
value < 0 || value >= _byValue.length ? null : _byValue[value];
|
||||||
|
|
||||||
|
const AuthResult._(super.value, super.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
class VaultState extends $pb.ProtobufEnum {
|
||||||
|
static const VaultState VAULT_STATE_UNSPECIFIED =
|
||||||
|
VaultState._(0, _omitEnumNames ? '' : 'VAULT_STATE_UNSPECIFIED');
|
||||||
|
static const VaultState VAULT_STATE_UNBOOTSTRAPPED =
|
||||||
|
VaultState._(1, _omitEnumNames ? '' : 'VAULT_STATE_UNBOOTSTRAPPED');
|
||||||
|
static const VaultState VAULT_STATE_SEALED =
|
||||||
|
VaultState._(2, _omitEnumNames ? '' : 'VAULT_STATE_SEALED');
|
||||||
|
static const VaultState VAULT_STATE_UNSEALED =
|
||||||
|
VaultState._(3, _omitEnumNames ? '' : 'VAULT_STATE_UNSEALED');
|
||||||
|
static const VaultState VAULT_STATE_ERROR =
|
||||||
|
VaultState._(4, _omitEnumNames ? '' : 'VAULT_STATE_ERROR');
|
||||||
|
|
||||||
|
static const $core.List<VaultState> values = <VaultState>[
|
||||||
|
VAULT_STATE_UNSPECIFIED,
|
||||||
|
VAULT_STATE_UNBOOTSTRAPPED,
|
||||||
|
VAULT_STATE_SEALED,
|
||||||
|
VAULT_STATE_UNSEALED,
|
||||||
|
VAULT_STATE_ERROR,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.List<VaultState?> _byValue =
|
||||||
|
$pb.ProtobufEnum.$_initByValueList(values, 4);
|
||||||
|
static VaultState? valueOf($core.int value) =>
|
||||||
|
value < 0 || value >= _byValue.length ? null : _byValue[value];
|
||||||
|
|
||||||
|
const VaultState._(super.value, super.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const $core.bool _omitEnumNames =
|
||||||
|
$core.bool.fromEnvironment('protobuf.omit_enum_names');
|
||||||
|
|||||||
@@ -15,37 +15,160 @@ import 'dart:convert' as $convert;
|
|||||||
import 'dart:core' as $core;
|
import 'dart:core' as $core;
|
||||||
import 'dart:typed_data' as $typed_data;
|
import 'dart:typed_data' as $typed_data;
|
||||||
|
|
||||||
|
@$core.Deprecated('Use authResultDescriptor instead')
|
||||||
|
const AuthResult$json = {
|
||||||
|
'1': 'AuthResult',
|
||||||
|
'2': [
|
||||||
|
{'1': 'AUTH_RESULT_UNSPECIFIED', '2': 0},
|
||||||
|
{'1': 'AUTH_RESULT_SUCCESS', '2': 1},
|
||||||
|
{'1': 'AUTH_RESULT_INVALID_KEY', '2': 2},
|
||||||
|
{'1': 'AUTH_RESULT_INVALID_SIGNATURE', '2': 3},
|
||||||
|
{'1': 'AUTH_RESULT_APPROVAL_DENIED', '2': 4},
|
||||||
|
{'1': 'AUTH_RESULT_NO_USER_AGENTS_ONLINE', '2': 5},
|
||||||
|
{'1': 'AUTH_RESULT_INTERNAL', '2': 6},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `AuthResult`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||||
|
final $typed_data.Uint8List authResultDescriptor = $convert.base64Decode(
|
||||||
|
'CgpBdXRoUmVzdWx0EhsKF0FVVEhfUkVTVUxUX1VOU1BFQ0lGSUVEEAASFwoTQVVUSF9SRVNVTF'
|
||||||
|
'RfU1VDQ0VTUxABEhsKF0FVVEhfUkVTVUxUX0lOVkFMSURfS0VZEAISIQodQVVUSF9SRVNVTFRf'
|
||||||
|
'SU5WQUxJRF9TSUdOQVRVUkUQAxIfChtBVVRIX1JFU1VMVF9BUFBST1ZBTF9ERU5JRUQQBBIlCi'
|
||||||
|
'FBVVRIX1JFU1VMVF9OT19VU0VSX0FHRU5UU19PTkxJTkUQBRIYChRBVVRIX1JFU1VMVF9JTlRF'
|
||||||
|
'Uk5BTBAG');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use vaultStateDescriptor instead')
|
||||||
|
const VaultState$json = {
|
||||||
|
'1': 'VaultState',
|
||||||
|
'2': [
|
||||||
|
{'1': 'VAULT_STATE_UNSPECIFIED', '2': 0},
|
||||||
|
{'1': 'VAULT_STATE_UNBOOTSTRAPPED', '2': 1},
|
||||||
|
{'1': 'VAULT_STATE_SEALED', '2': 2},
|
||||||
|
{'1': 'VAULT_STATE_UNSEALED', '2': 3},
|
||||||
|
{'1': 'VAULT_STATE_ERROR', '2': 4},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `VaultState`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||||
|
final $typed_data.Uint8List vaultStateDescriptor = $convert.base64Decode(
|
||||||
|
'CgpWYXVsdFN0YXRlEhsKF1ZBVUxUX1NUQVRFX1VOU1BFQ0lGSUVEEAASHgoaVkFVTFRfU1RBVE'
|
||||||
|
'VfVU5CT09UU1RSQVBQRUQQARIWChJWQVVMVF9TVEFURV9TRUFMRUQQAhIYChRWQVVMVF9TVEFU'
|
||||||
|
'RV9VTlNFQUxFRBADEhUKEVZBVUxUX1NUQVRFX0VSUk9SEAQ=');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use clientInfoDescriptor instead')
|
||||||
|
const ClientInfo$json = {
|
||||||
|
'1': 'ClientInfo',
|
||||||
|
'2': [
|
||||||
|
{'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
|
||||||
|
{
|
||||||
|
'1': 'description',
|
||||||
|
'3': 2,
|
||||||
|
'4': 1,
|
||||||
|
'5': 9,
|
||||||
|
'9': 0,
|
||||||
|
'10': 'description',
|
||||||
|
'17': true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'version',
|
||||||
|
'3': 3,
|
||||||
|
'4': 1,
|
||||||
|
'5': 9,
|
||||||
|
'9': 1,
|
||||||
|
'10': 'version',
|
||||||
|
'17': true
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'8': [
|
||||||
|
{'1': '_description'},
|
||||||
|
{'1': '_version'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `ClientInfo`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List clientInfoDescriptor = $convert.base64Decode(
|
||||||
|
'CgpDbGllbnRJbmZvEhIKBG5hbWUYASABKAlSBG5hbWUSJQoLZGVzY3JpcHRpb24YAiABKAlIAF'
|
||||||
|
'ILZGVzY3JpcHRpb26IAQESHQoHdmVyc2lvbhgDIAEoCUgBUgd2ZXJzaW9uiAEBQg4KDF9kZXNj'
|
||||||
|
'cmlwdGlvbkIKCghfdmVyc2lvbg==');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use authChallengeRequestDescriptor instead')
|
||||||
|
const AuthChallengeRequest$json = {
|
||||||
|
'1': 'AuthChallengeRequest',
|
||||||
|
'2': [
|
||||||
|
{'1': 'pubkey', '3': 1, '4': 1, '5': 12, '10': 'pubkey'},
|
||||||
|
{
|
||||||
|
'1': 'client_info',
|
||||||
|
'3': 2,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.arbiter.client.ClientInfo',
|
||||||
|
'10': 'clientInfo'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `AuthChallengeRequest`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List authChallengeRequestDescriptor = $convert.base64Decode(
|
||||||
|
'ChRBdXRoQ2hhbGxlbmdlUmVxdWVzdBIWCgZwdWJrZXkYASABKAxSBnB1YmtleRI7CgtjbGllbn'
|
||||||
|
'RfaW5mbxgCIAEoCzIaLmFyYml0ZXIuY2xpZW50LkNsaWVudEluZm9SCmNsaWVudEluZm8=');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use authChallengeDescriptor instead')
|
||||||
|
const AuthChallenge$json = {
|
||||||
|
'1': 'AuthChallenge',
|
||||||
|
'2': [
|
||||||
|
{'1': 'pubkey', '3': 1, '4': 1, '5': 12, '10': 'pubkey'},
|
||||||
|
{'1': 'nonce', '3': 2, '4': 1, '5': 5, '10': 'nonce'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `AuthChallenge`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List authChallengeDescriptor = $convert.base64Decode(
|
||||||
|
'Cg1BdXRoQ2hhbGxlbmdlEhYKBnB1YmtleRgBIAEoDFIGcHVia2V5EhQKBW5vbmNlGAIgASgFUg'
|
||||||
|
'Vub25jZQ==');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use authChallengeSolutionDescriptor instead')
|
||||||
|
const AuthChallengeSolution$json = {
|
||||||
|
'1': 'AuthChallengeSolution',
|
||||||
|
'2': [
|
||||||
|
{'1': 'signature', '3': 1, '4': 1, '5': 12, '10': 'signature'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `AuthChallengeSolution`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List authChallengeSolutionDescriptor = $convert.base64Decode(
|
||||||
|
'ChVBdXRoQ2hhbGxlbmdlU29sdXRpb24SHAoJc2lnbmF0dXJlGAEgASgMUglzaWduYXR1cmU=');
|
||||||
|
|
||||||
@$core.Deprecated('Use clientRequestDescriptor instead')
|
@$core.Deprecated('Use clientRequestDescriptor instead')
|
||||||
const ClientRequest$json = {
|
const ClientRequest$json = {
|
||||||
'1': 'ClientRequest',
|
'1': 'ClientRequest',
|
||||||
'2': [
|
'2': [
|
||||||
{'1': 'request_id', '3': 4, '4': 1, '5': 5, '10': 'requestId'},
|
{'1': 'request_id', '3': 4, '4': 1, '5': 5, '10': 'requestId'},
|
||||||
{
|
{
|
||||||
'1': 'auth',
|
'1': 'auth_challenge_request',
|
||||||
'3': 1,
|
'3': 1,
|
||||||
'4': 1,
|
'4': 1,
|
||||||
'5': 11,
|
'5': 11,
|
||||||
'6': '.arbiter.client.auth.Request',
|
'6': '.arbiter.client.AuthChallengeRequest',
|
||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'auth'
|
'10': 'authChallengeRequest'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'1': 'vault',
|
'1': 'auth_challenge_solution',
|
||||||
'3': 2,
|
'3': 2,
|
||||||
'4': 1,
|
'4': 1,
|
||||||
'5': 11,
|
'5': 11,
|
||||||
'6': '.arbiter.client.vault.Request',
|
'6': '.arbiter.client.AuthChallengeSolution',
|
||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'vault'
|
'10': 'authChallengeSolution'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'1': 'evm',
|
'1': 'query_vault_state',
|
||||||
'3': 3,
|
'3': 3,
|
||||||
'4': 1,
|
'4': 1,
|
||||||
'5': 11,
|
'5': 11,
|
||||||
'6': '.arbiter.client.evm.Request',
|
'6': '.google.protobuf.Empty',
|
||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'evm'
|
'10': 'queryVaultState'
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'8': [
|
'8': [
|
||||||
@@ -55,10 +178,12 @@ const ClientRequest$json = {
|
|||||||
|
|
||||||
/// Descriptor for `ClientRequest`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `ClientRequest`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List clientRequestDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List clientRequestDescriptor = $convert.base64Decode(
|
||||||
'Cg1DbGllbnRSZXF1ZXN0Eh0KCnJlcXVlc3RfaWQYBCABKAVSCXJlcXVlc3RJZBIyCgRhdXRoGA'
|
'Cg1DbGllbnRSZXF1ZXN0Eh0KCnJlcXVlc3RfaWQYBCABKAVSCXJlcXVlc3RJZBJcChZhdXRoX2'
|
||||||
'EgASgLMhwuYXJiaXRlci5jbGllbnQuYXV0aC5SZXF1ZXN0SABSBGF1dGgSNQoFdmF1bHQYAiAB'
|
'NoYWxsZW5nZV9yZXF1ZXN0GAEgASgLMiQuYXJiaXRlci5jbGllbnQuQXV0aENoYWxsZW5nZVJl'
|
||||||
'KAsyHS5hcmJpdGVyLmNsaWVudC52YXVsdC5SZXF1ZXN0SABSBXZhdWx0Ei8KA2V2bRgDIAEoCz'
|
'cXVlc3RIAFIUYXV0aENoYWxsZW5nZVJlcXVlc3QSXwoXYXV0aF9jaGFsbGVuZ2Vfc29sdXRpb2'
|
||||||
'IbLmFyYml0ZXIuY2xpZW50LmV2bS5SZXF1ZXN0SABSA2V2bUIJCgdwYXlsb2Fk');
|
'4YAiABKAsyJS5hcmJpdGVyLmNsaWVudC5BdXRoQ2hhbGxlbmdlU29sdXRpb25IAFIVYXV0aENo'
|
||||||
|
'YWxsZW5nZVNvbHV0aW9uEkQKEXF1ZXJ5X3ZhdWx0X3N0YXRlGAMgASgLMhYuZ29vZ2xlLnByb3'
|
||||||
|
'RvYnVmLkVtcHR5SABSD3F1ZXJ5VmF1bHRTdGF0ZUIJCgdwYXlsb2Fk');
|
||||||
|
|
||||||
@$core.Deprecated('Use clientResponseDescriptor instead')
|
@$core.Deprecated('Use clientResponseDescriptor instead')
|
||||||
const ClientResponse$json = {
|
const ClientResponse$json = {
|
||||||
@@ -74,31 +199,49 @@ const ClientResponse$json = {
|
|||||||
'17': true
|
'17': true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'1': 'auth',
|
'1': 'auth_challenge',
|
||||||
'3': 1,
|
'3': 1,
|
||||||
'4': 1,
|
'4': 1,
|
||||||
'5': 11,
|
'5': 11,
|
||||||
'6': '.arbiter.client.auth.Response',
|
'6': '.arbiter.client.AuthChallenge',
|
||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'auth'
|
'10': 'authChallenge'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'1': 'vault',
|
'1': 'auth_result',
|
||||||
'3': 2,
|
'3': 2,
|
||||||
'4': 1,
|
'4': 1,
|
||||||
'5': 11,
|
'5': 14,
|
||||||
'6': '.arbiter.client.vault.Response',
|
'6': '.arbiter.client.AuthResult',
|
||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'vault'
|
'10': 'authResult'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'1': 'evm',
|
'1': 'evm_sign_transaction',
|
||||||
'3': 3,
|
'3': 3,
|
||||||
'4': 1,
|
'4': 1,
|
||||||
'5': 11,
|
'5': 11,
|
||||||
'6': '.arbiter.client.evm.Response',
|
'6': '.arbiter.evm.EvmSignTransactionResponse',
|
||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'evm'
|
'10': 'evmSignTransaction'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'evm_analyze_transaction',
|
||||||
|
'3': 4,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.arbiter.evm.EvmAnalyzeTransactionResponse',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'evmAnalyzeTransaction'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'vault_state',
|
||||||
|
'3': 6,
|
||||||
|
'4': 1,
|
||||||
|
'5': 14,
|
||||||
|
'6': '.arbiter.client.VaultState',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'vaultState'
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
'8': [
|
'8': [
|
||||||
@@ -109,8 +252,12 @@ const ClientResponse$json = {
|
|||||||
|
|
||||||
/// Descriptor for `ClientResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `ClientResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List clientResponseDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List clientResponseDescriptor = $convert.base64Decode(
|
||||||
'Cg5DbGllbnRSZXNwb25zZRIiCgpyZXF1ZXN0X2lkGAcgASgFSAFSCXJlcXVlc3RJZIgBARIzCg'
|
'Cg5DbGllbnRSZXNwb25zZRIiCgpyZXF1ZXN0X2lkGAcgASgFSAFSCXJlcXVlc3RJZIgBARJGCg'
|
||||||
'RhdXRoGAEgASgLMh0uYXJiaXRlci5jbGllbnQuYXV0aC5SZXNwb25zZUgAUgRhdXRoEjYKBXZh'
|
'5hdXRoX2NoYWxsZW5nZRgBIAEoCzIdLmFyYml0ZXIuY2xpZW50LkF1dGhDaGFsbGVuZ2VIAFIN'
|
||||||
'dWx0GAIgASgLMh4uYXJiaXRlci5jbGllbnQudmF1bHQuUmVzcG9uc2VIAFIFdmF1bHQSMAoDZX'
|
'YXV0aENoYWxsZW5nZRI9CgthdXRoX3Jlc3VsdBgCIAEoDjIaLmFyYml0ZXIuY2xpZW50LkF1dG'
|
||||||
'ZtGAMgASgLMhwuYXJiaXRlci5jbGllbnQuZXZtLlJlc3BvbnNlSABSA2V2bUIJCgdwYXlsb2Fk'
|
'hSZXN1bHRIAFIKYXV0aFJlc3VsdBJbChRldm1fc2lnbl90cmFuc2FjdGlvbhgDIAEoCzInLmFy'
|
||||||
'Qg0KC19yZXF1ZXN0X2lk');
|
'Yml0ZXIuZXZtLkV2bVNpZ25UcmFuc2FjdGlvblJlc3BvbnNlSABSEmV2bVNpZ25UcmFuc2FjdG'
|
||||||
|
'lvbhJkChdldm1fYW5hbHl6ZV90cmFuc2FjdGlvbhgEIAEoCzIqLmFyYml0ZXIuZXZtLkV2bUFu'
|
||||||
|
'YWx5emVUcmFuc2FjdGlvblJlc3BvbnNlSABSFWV2bUFuYWx5emVUcmFuc2FjdGlvbhI9Cgt2YX'
|
||||||
|
'VsdF9zdGF0ZRgGIAEoDjIaLmFyYml0ZXIuY2xpZW50LlZhdWx0U3RhdGVIAFIKdmF1bHRTdGF0'
|
||||||
|
'ZUIJCgdwYXlsb2FkQg0KC19yZXF1ZXN0X2lk');
|
||||||
|
|||||||
@@ -1,395 +0,0 @@
|
|||||||
// This is a generated file - do not edit.
|
|
||||||
//
|
|
||||||
// Generated from client/auth.proto.
|
|
||||||
|
|
||||||
// @dart = 3.3
|
|
||||||
|
|
||||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
|
||||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
|
||||||
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
|
|
||||||
|
|
||||||
import 'dart:core' as $core;
|
|
||||||
|
|
||||||
import 'package:protobuf/protobuf.dart' as $pb;
|
|
||||||
|
|
||||||
import '../shared/client.pb.dart' as $0;
|
|
||||||
import 'auth.pbenum.dart';
|
|
||||||
|
|
||||||
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
|
|
||||||
|
|
||||||
export 'auth.pbenum.dart';
|
|
||||||
|
|
||||||
class AuthChallengeRequest extends $pb.GeneratedMessage {
|
|
||||||
factory AuthChallengeRequest({
|
|
||||||
$core.List<$core.int>? pubkey,
|
|
||||||
$0.ClientInfo? clientInfo,
|
|
||||||
}) {
|
|
||||||
final result = create();
|
|
||||||
if (pubkey != null) result.pubkey = pubkey;
|
|
||||||
if (clientInfo != null) result.clientInfo = clientInfo;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthChallengeRequest._();
|
|
||||||
|
|
||||||
factory AuthChallengeRequest.fromBuffer($core.List<$core.int> data,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromBuffer(data, registry);
|
|
||||||
factory AuthChallengeRequest.fromJson($core.String json,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromJson(json, registry);
|
|
||||||
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
||||||
_omitMessageNames ? '' : 'AuthChallengeRequest',
|
|
||||||
package:
|
|
||||||
const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client.auth'),
|
|
||||||
createEmptyInstance: create)
|
|
||||||
..a<$core.List<$core.int>>(
|
|
||||||
1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY)
|
|
||||||
..aOM<$0.ClientInfo>(2, _omitFieldNames ? '' : 'clientInfo',
|
|
||||||
subBuilder: $0.ClientInfo.create)
|
|
||||||
..hasRequiredFields = false;
|
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
AuthChallengeRequest clone() => deepCopy();
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
AuthChallengeRequest copyWith(void Function(AuthChallengeRequest) updates) =>
|
|
||||||
super.copyWith((message) => updates(message as AuthChallengeRequest))
|
|
||||||
as AuthChallengeRequest;
|
|
||||||
|
|
||||||
@$core.override
|
|
||||||
$pb.BuilderInfo get info_ => _i;
|
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static AuthChallengeRequest create() => AuthChallengeRequest._();
|
|
||||||
@$core.override
|
|
||||||
AuthChallengeRequest createEmptyInstance() => create();
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static AuthChallengeRequest getDefault() => _defaultInstance ??=
|
|
||||||
$pb.GeneratedMessage.$_defaultFor<AuthChallengeRequest>(create);
|
|
||||||
static AuthChallengeRequest? _defaultInstance;
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.List<$core.int> get pubkey => $_getN(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
set pubkey($core.List<$core.int> value) => $_setBytes(0, value);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.bool hasPubkey() => $_has(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearPubkey() => $_clearField(1);
|
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$0.ClientInfo get clientInfo => $_getN(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
set clientInfo($0.ClientInfo value) => $_setField(2, value);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$core.bool hasClientInfo() => $_has(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
void clearClientInfo() => $_clearField(2);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$0.ClientInfo ensureClientInfo() => $_ensure(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
class AuthChallenge extends $pb.GeneratedMessage {
|
|
||||||
factory AuthChallenge({
|
|
||||||
$core.List<$core.int>? pubkey,
|
|
||||||
$core.int? nonce,
|
|
||||||
}) {
|
|
||||||
final result = create();
|
|
||||||
if (pubkey != null) result.pubkey = pubkey;
|
|
||||||
if (nonce != null) result.nonce = nonce;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthChallenge._();
|
|
||||||
|
|
||||||
factory AuthChallenge.fromBuffer($core.List<$core.int> data,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromBuffer(data, registry);
|
|
||||||
factory AuthChallenge.fromJson($core.String json,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromJson(json, registry);
|
|
||||||
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
||||||
_omitMessageNames ? '' : 'AuthChallenge',
|
|
||||||
package:
|
|
||||||
const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client.auth'),
|
|
||||||
createEmptyInstance: create)
|
|
||||||
..a<$core.List<$core.int>>(
|
|
||||||
1, _omitFieldNames ? '' : 'pubkey', $pb.PbFieldType.OY)
|
|
||||||
..aI(2, _omitFieldNames ? '' : 'nonce')
|
|
||||||
..hasRequiredFields = false;
|
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
AuthChallenge clone() => deepCopy();
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
AuthChallenge copyWith(void Function(AuthChallenge) updates) =>
|
|
||||||
super.copyWith((message) => updates(message as AuthChallenge))
|
|
||||||
as AuthChallenge;
|
|
||||||
|
|
||||||
@$core.override
|
|
||||||
$pb.BuilderInfo get info_ => _i;
|
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static AuthChallenge create() => AuthChallenge._();
|
|
||||||
@$core.override
|
|
||||||
AuthChallenge createEmptyInstance() => create();
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static AuthChallenge getDefault() => _defaultInstance ??=
|
|
||||||
$pb.GeneratedMessage.$_defaultFor<AuthChallenge>(create);
|
|
||||||
static AuthChallenge? _defaultInstance;
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.List<$core.int> get pubkey => $_getN(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
set pubkey($core.List<$core.int> value) => $_setBytes(0, value);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.bool hasPubkey() => $_has(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearPubkey() => $_clearField(1);
|
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$core.int get nonce => $_getIZ(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
set nonce($core.int value) => $_setSignedInt32(1, value);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$core.bool hasNonce() => $_has(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
void clearNonce() => $_clearField(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
class AuthChallengeSolution extends $pb.GeneratedMessage {
|
|
||||||
factory AuthChallengeSolution({
|
|
||||||
$core.List<$core.int>? signature,
|
|
||||||
}) {
|
|
||||||
final result = create();
|
|
||||||
if (signature != null) result.signature = signature;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthChallengeSolution._();
|
|
||||||
|
|
||||||
factory AuthChallengeSolution.fromBuffer($core.List<$core.int> data,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromBuffer(data, registry);
|
|
||||||
factory AuthChallengeSolution.fromJson($core.String json,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromJson(json, registry);
|
|
||||||
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
||||||
_omitMessageNames ? '' : 'AuthChallengeSolution',
|
|
||||||
package:
|
|
||||||
const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client.auth'),
|
|
||||||
createEmptyInstance: create)
|
|
||||||
..a<$core.List<$core.int>>(
|
|
||||||
1, _omitFieldNames ? '' : 'signature', $pb.PbFieldType.OY)
|
|
||||||
..hasRequiredFields = false;
|
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
AuthChallengeSolution clone() => deepCopy();
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
AuthChallengeSolution copyWith(
|
|
||||||
void Function(AuthChallengeSolution) updates) =>
|
|
||||||
super.copyWith((message) => updates(message as AuthChallengeSolution))
|
|
||||||
as AuthChallengeSolution;
|
|
||||||
|
|
||||||
@$core.override
|
|
||||||
$pb.BuilderInfo get info_ => _i;
|
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static AuthChallengeSolution create() => AuthChallengeSolution._();
|
|
||||||
@$core.override
|
|
||||||
AuthChallengeSolution createEmptyInstance() => create();
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static AuthChallengeSolution getDefault() => _defaultInstance ??=
|
|
||||||
$pb.GeneratedMessage.$_defaultFor<AuthChallengeSolution>(create);
|
|
||||||
static AuthChallengeSolution? _defaultInstance;
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.List<$core.int> get signature => $_getN(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
set signature($core.List<$core.int> value) => $_setBytes(0, value);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.bool hasSignature() => $_has(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearSignature() => $_clearField(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Request_Payload { challengeRequest, challengeSolution, notSet }
|
|
||||||
|
|
||||||
class Request extends $pb.GeneratedMessage {
|
|
||||||
factory Request({
|
|
||||||
AuthChallengeRequest? challengeRequest,
|
|
||||||
AuthChallengeSolution? challengeSolution,
|
|
||||||
}) {
|
|
||||||
final result = create();
|
|
||||||
if (challengeRequest != null) result.challengeRequest = challengeRequest;
|
|
||||||
if (challengeSolution != null) result.challengeSolution = challengeSolution;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Request._();
|
|
||||||
|
|
||||||
factory Request.fromBuffer($core.List<$core.int> data,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromBuffer(data, registry);
|
|
||||||
factory Request.fromJson($core.String json,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromJson(json, registry);
|
|
||||||
|
|
||||||
static const $core.Map<$core.int, Request_Payload> _Request_PayloadByTag = {
|
|
||||||
1: Request_Payload.challengeRequest,
|
|
||||||
2: Request_Payload.challengeSolution,
|
|
||||||
0: Request_Payload.notSet
|
|
||||||
};
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
||||||
_omitMessageNames ? '' : 'Request',
|
|
||||||
package:
|
|
||||||
const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client.auth'),
|
|
||||||
createEmptyInstance: create)
|
|
||||||
..oo(0, [1, 2])
|
|
||||||
..aOM<AuthChallengeRequest>(1, _omitFieldNames ? '' : 'challengeRequest',
|
|
||||||
subBuilder: AuthChallengeRequest.create)
|
|
||||||
..aOM<AuthChallengeSolution>(2, _omitFieldNames ? '' : 'challengeSolution',
|
|
||||||
subBuilder: AuthChallengeSolution.create)
|
|
||||||
..hasRequiredFields = false;
|
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
Request clone() => deepCopy();
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
Request copyWith(void Function(Request) updates) =>
|
|
||||||
super.copyWith((message) => updates(message as Request)) as Request;
|
|
||||||
|
|
||||||
@$core.override
|
|
||||||
$pb.BuilderInfo get info_ => _i;
|
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static Request create() => Request._();
|
|
||||||
@$core.override
|
|
||||||
Request createEmptyInstance() => create();
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static Request getDefault() =>
|
|
||||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Request>(create);
|
|
||||||
static Request? _defaultInstance;
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
Request_Payload whichPayload() => _Request_PayloadByTag[$_whichOneof(0)]!;
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
void clearPayload() => $_clearField($_whichOneof(0));
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
AuthChallengeRequest get challengeRequest => $_getN(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
set challengeRequest(AuthChallengeRequest value) => $_setField(1, value);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.bool hasChallengeRequest() => $_has(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearChallengeRequest() => $_clearField(1);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
AuthChallengeRequest ensureChallengeRequest() => $_ensure(0);
|
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
AuthChallengeSolution get challengeSolution => $_getN(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
set challengeSolution(AuthChallengeSolution value) => $_setField(2, value);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$core.bool hasChallengeSolution() => $_has(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
void clearChallengeSolution() => $_clearField(2);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
AuthChallengeSolution ensureChallengeSolution() => $_ensure(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Response_Payload { challenge, result, notSet }
|
|
||||||
|
|
||||||
class Response extends $pb.GeneratedMessage {
|
|
||||||
factory Response({
|
|
||||||
AuthChallenge? challenge,
|
|
||||||
AuthResult? result,
|
|
||||||
}) {
|
|
||||||
final result$ = create();
|
|
||||||
if (challenge != null) result$.challenge = challenge;
|
|
||||||
if (result != null) result$.result = result;
|
|
||||||
return result$;
|
|
||||||
}
|
|
||||||
|
|
||||||
Response._();
|
|
||||||
|
|
||||||
factory Response.fromBuffer($core.List<$core.int> data,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromBuffer(data, registry);
|
|
||||||
factory Response.fromJson($core.String json,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromJson(json, registry);
|
|
||||||
|
|
||||||
static const $core.Map<$core.int, Response_Payload> _Response_PayloadByTag = {
|
|
||||||
1: Response_Payload.challenge,
|
|
||||||
2: Response_Payload.result,
|
|
||||||
0: Response_Payload.notSet
|
|
||||||
};
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
||||||
_omitMessageNames ? '' : 'Response',
|
|
||||||
package:
|
|
||||||
const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client.auth'),
|
|
||||||
createEmptyInstance: create)
|
|
||||||
..oo(0, [1, 2])
|
|
||||||
..aOM<AuthChallenge>(1, _omitFieldNames ? '' : 'challenge',
|
|
||||||
subBuilder: AuthChallenge.create)
|
|
||||||
..aE<AuthResult>(2, _omitFieldNames ? '' : 'result',
|
|
||||||
enumValues: AuthResult.values)
|
|
||||||
..hasRequiredFields = false;
|
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
Response clone() => deepCopy();
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
Response copyWith(void Function(Response) updates) =>
|
|
||||||
super.copyWith((message) => updates(message as Response)) as Response;
|
|
||||||
|
|
||||||
@$core.override
|
|
||||||
$pb.BuilderInfo get info_ => _i;
|
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static Response create() => Response._();
|
|
||||||
@$core.override
|
|
||||||
Response createEmptyInstance() => create();
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static Response getDefault() =>
|
|
||||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Response>(create);
|
|
||||||
static Response? _defaultInstance;
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
Response_Payload whichPayload() => _Response_PayloadByTag[$_whichOneof(0)]!;
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
void clearPayload() => $_clearField($_whichOneof(0));
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
AuthChallenge get challenge => $_getN(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
set challenge(AuthChallenge value) => $_setField(1, value);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.bool hasChallenge() => $_has(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearChallenge() => $_clearField(1);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
AuthChallenge ensureChallenge() => $_ensure(0);
|
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
AuthResult get result => $_getN(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
set result(AuthResult value) => $_setField(2, value);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$core.bool hasResult() => $_has(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
void clearResult() => $_clearField(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
const $core.bool _omitFieldNames =
|
|
||||||
$core.bool.fromEnvironment('protobuf.omit_field_names');
|
|
||||||
const $core.bool _omitMessageNames =
|
|
||||||
$core.bool.fromEnvironment('protobuf.omit_message_names');
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
// This is a generated file - do not edit.
|
|
||||||
//
|
|
||||||
// Generated from client/auth.proto.
|
|
||||||
|
|
||||||
// @dart = 3.3
|
|
||||||
|
|
||||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
|
||||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
|
||||||
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
|
|
||||||
|
|
||||||
import 'dart:core' as $core;
|
|
||||||
|
|
||||||
import 'package:protobuf/protobuf.dart' as $pb;
|
|
||||||
|
|
||||||
class AuthResult extends $pb.ProtobufEnum {
|
|
||||||
static const AuthResult AUTH_RESULT_UNSPECIFIED =
|
|
||||||
AuthResult._(0, _omitEnumNames ? '' : 'AUTH_RESULT_UNSPECIFIED');
|
|
||||||
static const AuthResult AUTH_RESULT_SUCCESS =
|
|
||||||
AuthResult._(1, _omitEnumNames ? '' : 'AUTH_RESULT_SUCCESS');
|
|
||||||
static const AuthResult AUTH_RESULT_INVALID_KEY =
|
|
||||||
AuthResult._(2, _omitEnumNames ? '' : 'AUTH_RESULT_INVALID_KEY');
|
|
||||||
static const AuthResult AUTH_RESULT_INVALID_SIGNATURE =
|
|
||||||
AuthResult._(3, _omitEnumNames ? '' : 'AUTH_RESULT_INVALID_SIGNATURE');
|
|
||||||
static const AuthResult AUTH_RESULT_APPROVAL_DENIED =
|
|
||||||
AuthResult._(4, _omitEnumNames ? '' : 'AUTH_RESULT_APPROVAL_DENIED');
|
|
||||||
static const AuthResult AUTH_RESULT_NO_USER_AGENTS_ONLINE = AuthResult._(
|
|
||||||
5, _omitEnumNames ? '' : 'AUTH_RESULT_NO_USER_AGENTS_ONLINE');
|
|
||||||
static const AuthResult AUTH_RESULT_INTERNAL =
|
|
||||||
AuthResult._(6, _omitEnumNames ? '' : 'AUTH_RESULT_INTERNAL');
|
|
||||||
|
|
||||||
static const $core.List<AuthResult> values = <AuthResult>[
|
|
||||||
AUTH_RESULT_UNSPECIFIED,
|
|
||||||
AUTH_RESULT_SUCCESS,
|
|
||||||
AUTH_RESULT_INVALID_KEY,
|
|
||||||
AUTH_RESULT_INVALID_SIGNATURE,
|
|
||||||
AUTH_RESULT_APPROVAL_DENIED,
|
|
||||||
AUTH_RESULT_NO_USER_AGENTS_ONLINE,
|
|
||||||
AUTH_RESULT_INTERNAL,
|
|
||||||
];
|
|
||||||
|
|
||||||
static final $core.List<AuthResult?> _byValue =
|
|
||||||
$pb.ProtobufEnum.$_initByValueList(values, 6);
|
|
||||||
static AuthResult? valueOf($core.int value) =>
|
|
||||||
value < 0 || value >= _byValue.length ? null : _byValue[value];
|
|
||||||
|
|
||||||
const AuthResult._(super.value, super.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
const $core.bool _omitEnumNames =
|
|
||||||
$core.bool.fromEnvironment('protobuf.omit_enum_names');
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
// This is a generated file - do not edit.
|
|
||||||
//
|
|
||||||
// Generated from client/auth.proto.
|
|
||||||
|
|
||||||
// @dart = 3.3
|
|
||||||
|
|
||||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
|
||||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
|
||||||
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
|
|
||||||
// ignore_for_file: unused_import
|
|
||||||
|
|
||||||
import 'dart:convert' as $convert;
|
|
||||||
import 'dart:core' as $core;
|
|
||||||
import 'dart:typed_data' as $typed_data;
|
|
||||||
|
|
||||||
@$core.Deprecated('Use authResultDescriptor instead')
|
|
||||||
const AuthResult$json = {
|
|
||||||
'1': 'AuthResult',
|
|
||||||
'2': [
|
|
||||||
{'1': 'AUTH_RESULT_UNSPECIFIED', '2': 0},
|
|
||||||
{'1': 'AUTH_RESULT_SUCCESS', '2': 1},
|
|
||||||
{'1': 'AUTH_RESULT_INVALID_KEY', '2': 2},
|
|
||||||
{'1': 'AUTH_RESULT_INVALID_SIGNATURE', '2': 3},
|
|
||||||
{'1': 'AUTH_RESULT_APPROVAL_DENIED', '2': 4},
|
|
||||||
{'1': 'AUTH_RESULT_NO_USER_AGENTS_ONLINE', '2': 5},
|
|
||||||
{'1': 'AUTH_RESULT_INTERNAL', '2': 6},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `AuthResult`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
|
||||||
final $typed_data.Uint8List authResultDescriptor = $convert.base64Decode(
|
|
||||||
'CgpBdXRoUmVzdWx0EhsKF0FVVEhfUkVTVUxUX1VOU1BFQ0lGSUVEEAASFwoTQVVUSF9SRVNVTF'
|
|
||||||
'RfU1VDQ0VTUxABEhsKF0FVVEhfUkVTVUxUX0lOVkFMSURfS0VZEAISIQodQVVUSF9SRVNVTFRf'
|
|
||||||
'SU5WQUxJRF9TSUdOQVRVUkUQAxIfChtBVVRIX1JFU1VMVF9BUFBST1ZBTF9ERU5JRUQQBBIlCi'
|
|
||||||
'FBVVRIX1JFU1VMVF9OT19VU0VSX0FHRU5UU19PTkxJTkUQBRIYChRBVVRIX1JFU1VMVF9JTlRF'
|
|
||||||
'Uk5BTBAG');
|
|
||||||
|
|
||||||
@$core.Deprecated('Use authChallengeRequestDescriptor instead')
|
|
||||||
const AuthChallengeRequest$json = {
|
|
||||||
'1': 'AuthChallengeRequest',
|
|
||||||
'2': [
|
|
||||||
{'1': 'pubkey', '3': 1, '4': 1, '5': 12, '10': 'pubkey'},
|
|
||||||
{
|
|
||||||
'1': 'client_info',
|
|
||||||
'3': 2,
|
|
||||||
'4': 1,
|
|
||||||
'5': 11,
|
|
||||||
'6': '.arbiter.shared.ClientInfo',
|
|
||||||
'10': 'clientInfo'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `AuthChallengeRequest`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List authChallengeRequestDescriptor = $convert.base64Decode(
|
|
||||||
'ChRBdXRoQ2hhbGxlbmdlUmVxdWVzdBIWCgZwdWJrZXkYASABKAxSBnB1YmtleRI7CgtjbGllbn'
|
|
||||||
'RfaW5mbxgCIAEoCzIaLmFyYml0ZXIuc2hhcmVkLkNsaWVudEluZm9SCmNsaWVudEluZm8=');
|
|
||||||
|
|
||||||
@$core.Deprecated('Use authChallengeDescriptor instead')
|
|
||||||
const AuthChallenge$json = {
|
|
||||||
'1': 'AuthChallenge',
|
|
||||||
'2': [
|
|
||||||
{'1': 'pubkey', '3': 1, '4': 1, '5': 12, '10': 'pubkey'},
|
|
||||||
{'1': 'nonce', '3': 2, '4': 1, '5': 5, '10': 'nonce'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `AuthChallenge`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List authChallengeDescriptor = $convert.base64Decode(
|
|
||||||
'Cg1BdXRoQ2hhbGxlbmdlEhYKBnB1YmtleRgBIAEoDFIGcHVia2V5EhQKBW5vbmNlGAIgASgFUg'
|
|
||||||
'Vub25jZQ==');
|
|
||||||
|
|
||||||
@$core.Deprecated('Use authChallengeSolutionDescriptor instead')
|
|
||||||
const AuthChallengeSolution$json = {
|
|
||||||
'1': 'AuthChallengeSolution',
|
|
||||||
'2': [
|
|
||||||
{'1': 'signature', '3': 1, '4': 1, '5': 12, '10': 'signature'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `AuthChallengeSolution`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List authChallengeSolutionDescriptor = $convert.base64Decode(
|
|
||||||
'ChVBdXRoQ2hhbGxlbmdlU29sdXRpb24SHAoJc2lnbmF0dXJlGAEgASgMUglzaWduYXR1cmU=');
|
|
||||||
|
|
||||||
@$core.Deprecated('Use requestDescriptor instead')
|
|
||||||
const Request$json = {
|
|
||||||
'1': 'Request',
|
|
||||||
'2': [
|
|
||||||
{
|
|
||||||
'1': 'challenge_request',
|
|
||||||
'3': 1,
|
|
||||||
'4': 1,
|
|
||||||
'5': 11,
|
|
||||||
'6': '.arbiter.client.auth.AuthChallengeRequest',
|
|
||||||
'9': 0,
|
|
||||||
'10': 'challengeRequest'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'1': 'challenge_solution',
|
|
||||||
'3': 2,
|
|
||||||
'4': 1,
|
|
||||||
'5': 11,
|
|
||||||
'6': '.arbiter.client.auth.AuthChallengeSolution',
|
|
||||||
'9': 0,
|
|
||||||
'10': 'challengeSolution'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'8': [
|
|
||||||
{'1': 'payload'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `Request`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List requestDescriptor = $convert.base64Decode(
|
|
||||||
'CgdSZXF1ZXN0ElgKEWNoYWxsZW5nZV9yZXF1ZXN0GAEgASgLMikuYXJiaXRlci5jbGllbnQuYX'
|
|
||||||
'V0aC5BdXRoQ2hhbGxlbmdlUmVxdWVzdEgAUhBjaGFsbGVuZ2VSZXF1ZXN0ElsKEmNoYWxsZW5n'
|
|
||||||
'ZV9zb2x1dGlvbhgCIAEoCzIqLmFyYml0ZXIuY2xpZW50LmF1dGguQXV0aENoYWxsZW5nZVNvbH'
|
|
||||||
'V0aW9uSABSEWNoYWxsZW5nZVNvbHV0aW9uQgkKB3BheWxvYWQ=');
|
|
||||||
|
|
||||||
@$core.Deprecated('Use responseDescriptor instead')
|
|
||||||
const Response$json = {
|
|
||||||
'1': 'Response',
|
|
||||||
'2': [
|
|
||||||
{
|
|
||||||
'1': 'challenge',
|
|
||||||
'3': 1,
|
|
||||||
'4': 1,
|
|
||||||
'5': 11,
|
|
||||||
'6': '.arbiter.client.auth.AuthChallenge',
|
|
||||||
'9': 0,
|
|
||||||
'10': 'challenge'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'1': 'result',
|
|
||||||
'3': 2,
|
|
||||||
'4': 1,
|
|
||||||
'5': 14,
|
|
||||||
'6': '.arbiter.client.auth.AuthResult',
|
|
||||||
'9': 0,
|
|
||||||
'10': 'result'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'8': [
|
|
||||||
{'1': 'payload'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `Response`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
|
||||||
'CghSZXNwb25zZRJCCgljaGFsbGVuZ2UYASABKAsyIi5hcmJpdGVyLmNsaWVudC5hdXRoLkF1dG'
|
|
||||||
'hDaGFsbGVuZ2VIAFIJY2hhbGxlbmdlEjkKBnJlc3VsdBgCIAEoDjIfLmFyYml0ZXIuY2xpZW50'
|
|
||||||
'LmF1dGguQXV0aFJlc3VsdEgAUgZyZXN1bHRCCQoHcGF5bG9hZA==');
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
// This is a generated file - do not edit.
|
|
||||||
//
|
|
||||||
// Generated from client/evm.proto.
|
|
||||||
|
|
||||||
// @dart = 3.3
|
|
||||||
|
|
||||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
|
||||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
|
||||||
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
|
|
||||||
|
|
||||||
import 'dart:core' as $core;
|
|
||||||
|
|
||||||
import 'package:protobuf/protobuf.dart' as $pb;
|
|
||||||
|
|
||||||
import '../evm.pb.dart' as $0;
|
|
||||||
|
|
||||||
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
|
|
||||||
|
|
||||||
enum Request_Payload { signTransaction, analyzeTransaction, notSet }
|
|
||||||
|
|
||||||
class Request extends $pb.GeneratedMessage {
|
|
||||||
factory Request({
|
|
||||||
$0.EvmSignTransactionRequest? signTransaction,
|
|
||||||
$0.EvmAnalyzeTransactionRequest? analyzeTransaction,
|
|
||||||
}) {
|
|
||||||
final result = create();
|
|
||||||
if (signTransaction != null) result.signTransaction = signTransaction;
|
|
||||||
if (analyzeTransaction != null)
|
|
||||||
result.analyzeTransaction = analyzeTransaction;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Request._();
|
|
||||||
|
|
||||||
factory Request.fromBuffer($core.List<$core.int> data,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromBuffer(data, registry);
|
|
||||||
factory Request.fromJson($core.String json,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromJson(json, registry);
|
|
||||||
|
|
||||||
static const $core.Map<$core.int, Request_Payload> _Request_PayloadByTag = {
|
|
||||||
1: Request_Payload.signTransaction,
|
|
||||||
2: Request_Payload.analyzeTransaction,
|
|
||||||
0: Request_Payload.notSet
|
|
||||||
};
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
||||||
_omitMessageNames ? '' : 'Request',
|
|
||||||
package:
|
|
||||||
const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client.evm'),
|
|
||||||
createEmptyInstance: create)
|
|
||||||
..oo(0, [1, 2])
|
|
||||||
..aOM<$0.EvmSignTransactionRequest>(
|
|
||||||
1, _omitFieldNames ? '' : 'signTransaction',
|
|
||||||
subBuilder: $0.EvmSignTransactionRequest.create)
|
|
||||||
..aOM<$0.EvmAnalyzeTransactionRequest>(
|
|
||||||
2, _omitFieldNames ? '' : 'analyzeTransaction',
|
|
||||||
subBuilder: $0.EvmAnalyzeTransactionRequest.create)
|
|
||||||
..hasRequiredFields = false;
|
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
Request clone() => deepCopy();
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
Request copyWith(void Function(Request) updates) =>
|
|
||||||
super.copyWith((message) => updates(message as Request)) as Request;
|
|
||||||
|
|
||||||
@$core.override
|
|
||||||
$pb.BuilderInfo get info_ => _i;
|
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static Request create() => Request._();
|
|
||||||
@$core.override
|
|
||||||
Request createEmptyInstance() => create();
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static Request getDefault() =>
|
|
||||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Request>(create);
|
|
||||||
static Request? _defaultInstance;
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
Request_Payload whichPayload() => _Request_PayloadByTag[$_whichOneof(0)]!;
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
void clearPayload() => $_clearField($_whichOneof(0));
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$0.EvmSignTransactionRequest get signTransaction => $_getN(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
set signTransaction($0.EvmSignTransactionRequest value) =>
|
|
||||||
$_setField(1, value);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.bool hasSignTransaction() => $_has(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearSignTransaction() => $_clearField(1);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$0.EvmSignTransactionRequest ensureSignTransaction() => $_ensure(0);
|
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$0.EvmAnalyzeTransactionRequest get analyzeTransaction => $_getN(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
set analyzeTransaction($0.EvmAnalyzeTransactionRequest value) =>
|
|
||||||
$_setField(2, value);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$core.bool hasAnalyzeTransaction() => $_has(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
void clearAnalyzeTransaction() => $_clearField(2);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$0.EvmAnalyzeTransactionRequest ensureAnalyzeTransaction() => $_ensure(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Response_Payload { signTransaction, analyzeTransaction, notSet }
|
|
||||||
|
|
||||||
class Response extends $pb.GeneratedMessage {
|
|
||||||
factory Response({
|
|
||||||
$0.EvmSignTransactionResponse? signTransaction,
|
|
||||||
$0.EvmAnalyzeTransactionResponse? analyzeTransaction,
|
|
||||||
}) {
|
|
||||||
final result = create();
|
|
||||||
if (signTransaction != null) result.signTransaction = signTransaction;
|
|
||||||
if (analyzeTransaction != null)
|
|
||||||
result.analyzeTransaction = analyzeTransaction;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Response._();
|
|
||||||
|
|
||||||
factory Response.fromBuffer($core.List<$core.int> data,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromBuffer(data, registry);
|
|
||||||
factory Response.fromJson($core.String json,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromJson(json, registry);
|
|
||||||
|
|
||||||
static const $core.Map<$core.int, Response_Payload> _Response_PayloadByTag = {
|
|
||||||
1: Response_Payload.signTransaction,
|
|
||||||
2: Response_Payload.analyzeTransaction,
|
|
||||||
0: Response_Payload.notSet
|
|
||||||
};
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
||||||
_omitMessageNames ? '' : 'Response',
|
|
||||||
package:
|
|
||||||
const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.client.evm'),
|
|
||||||
createEmptyInstance: create)
|
|
||||||
..oo(0, [1, 2])
|
|
||||||
..aOM<$0.EvmSignTransactionResponse>(
|
|
||||||
1, _omitFieldNames ? '' : 'signTransaction',
|
|
||||||
subBuilder: $0.EvmSignTransactionResponse.create)
|
|
||||||
..aOM<$0.EvmAnalyzeTransactionResponse>(
|
|
||||||
2, _omitFieldNames ? '' : 'analyzeTransaction',
|
|
||||||
subBuilder: $0.EvmAnalyzeTransactionResponse.create)
|
|
||||||
..hasRequiredFields = false;
|
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
Response clone() => deepCopy();
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
Response copyWith(void Function(Response) updates) =>
|
|
||||||
super.copyWith((message) => updates(message as Response)) as Response;
|
|
||||||
|
|
||||||
@$core.override
|
|
||||||
$pb.BuilderInfo get info_ => _i;
|
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static Response create() => Response._();
|
|
||||||
@$core.override
|
|
||||||
Response createEmptyInstance() => create();
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static Response getDefault() =>
|
|
||||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Response>(create);
|
|
||||||
static Response? _defaultInstance;
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
Response_Payload whichPayload() => _Response_PayloadByTag[$_whichOneof(0)]!;
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
void clearPayload() => $_clearField($_whichOneof(0));
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$0.EvmSignTransactionResponse get signTransaction => $_getN(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
set signTransaction($0.EvmSignTransactionResponse value) =>
|
|
||||||
$_setField(1, value);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.bool hasSignTransaction() => $_has(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearSignTransaction() => $_clearField(1);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$0.EvmSignTransactionResponse ensureSignTransaction() => $_ensure(0);
|
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$0.EvmAnalyzeTransactionResponse get analyzeTransaction => $_getN(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
set analyzeTransaction($0.EvmAnalyzeTransactionResponse value) =>
|
|
||||||
$_setField(2, value);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$core.bool hasAnalyzeTransaction() => $_has(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
void clearAnalyzeTransaction() => $_clearField(2);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$0.EvmAnalyzeTransactionResponse ensureAnalyzeTransaction() => $_ensure(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const $core.bool _omitFieldNames =
|
|
||||||
$core.bool.fromEnvironment('protobuf.omit_field_names');
|
|
||||||
const $core.bool _omitMessageNames =
|
|
||||||
$core.bool.fromEnvironment('protobuf.omit_message_names');
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// This is a generated file - do not edit.
|
|
||||||
//
|
|
||||||
// Generated from client/evm.proto.
|
|
||||||
|
|
||||||
// @dart = 3.3
|
|
||||||
|
|
||||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
|
||||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
|
||||||
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
// This is a generated file - do not edit.
|
|
||||||
//
|
|
||||||
// Generated from client/evm.proto.
|
|
||||||
|
|
||||||
// @dart = 3.3
|
|
||||||
|
|
||||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
|
||||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
|
||||||
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
|
|
||||||
// ignore_for_file: unused_import
|
|
||||||
|
|
||||||
import 'dart:convert' as $convert;
|
|
||||||
import 'dart:core' as $core;
|
|
||||||
import 'dart:typed_data' as $typed_data;
|
|
||||||
|
|
||||||
@$core.Deprecated('Use requestDescriptor instead')
|
|
||||||
const Request$json = {
|
|
||||||
'1': 'Request',
|
|
||||||
'2': [
|
|
||||||
{
|
|
||||||
'1': 'sign_transaction',
|
|
||||||
'3': 1,
|
|
||||||
'4': 1,
|
|
||||||
'5': 11,
|
|
||||||
'6': '.arbiter.evm.EvmSignTransactionRequest',
|
|
||||||
'9': 0,
|
|
||||||
'10': 'signTransaction'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'1': 'analyze_transaction',
|
|
||||||
'3': 2,
|
|
||||||
'4': 1,
|
|
||||||
'5': 11,
|
|
||||||
'6': '.arbiter.evm.EvmAnalyzeTransactionRequest',
|
|
||||||
'9': 0,
|
|
||||||
'10': 'analyzeTransaction'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'8': [
|
|
||||||
{'1': 'payload'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `Request`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List requestDescriptor = $convert.base64Decode(
|
|
||||||
'CgdSZXF1ZXN0ElMKEHNpZ25fdHJhbnNhY3Rpb24YASABKAsyJi5hcmJpdGVyLmV2bS5Fdm1TaW'
|
|
||||||
'duVHJhbnNhY3Rpb25SZXF1ZXN0SABSD3NpZ25UcmFuc2FjdGlvbhJcChNhbmFseXplX3RyYW5z'
|
|
||||||
'YWN0aW9uGAIgASgLMikuYXJiaXRlci5ldm0uRXZtQW5hbHl6ZVRyYW5zYWN0aW9uUmVxdWVzdE'
|
|
||||||
'gAUhJhbmFseXplVHJhbnNhY3Rpb25CCQoHcGF5bG9hZA==');
|
|
||||||
|
|
||||||
@$core.Deprecated('Use responseDescriptor instead')
|
|
||||||
const Response$json = {
|
|
||||||
'1': 'Response',
|
|
||||||
'2': [
|
|
||||||
{
|
|
||||||
'1': 'sign_transaction',
|
|
||||||
'3': 1,
|
|
||||||
'4': 1,
|
|
||||||
'5': 11,
|
|
||||||
'6': '.arbiter.evm.EvmSignTransactionResponse',
|
|
||||||
'9': 0,
|
|
||||||
'10': 'signTransaction'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'1': 'analyze_transaction',
|
|
||||||
'3': 2,
|
|
||||||
'4': 1,
|
|
||||||
'5': 11,
|
|
||||||
'6': '.arbiter.evm.EvmAnalyzeTransactionResponse',
|
|
||||||
'9': 0,
|
|
||||||
'10': 'analyzeTransaction'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'8': [
|
|
||||||
{'1': 'payload'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `Response`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
|
||||||
'CghSZXNwb25zZRJUChBzaWduX3RyYW5zYWN0aW9uGAEgASgLMicuYXJiaXRlci5ldm0uRXZtU2'
|
|
||||||
'lnblRyYW5zYWN0aW9uUmVzcG9uc2VIAFIPc2lnblRyYW5zYWN0aW9uEl0KE2FuYWx5emVfdHJh'
|
|
||||||
'bnNhY3Rpb24YAiABKAsyKi5hcmJpdGVyLmV2bS5Fdm1BbmFseXplVHJhbnNhY3Rpb25SZXNwb2'
|
|
||||||
'5zZUgAUhJhbmFseXplVHJhbnNhY3Rpb25CCQoHcGF5bG9hZA==');
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
// This is a generated file - do not edit.
|
|
||||||
//
|
|
||||||
// Generated from client/vault.proto.
|
|
||||||
|
|
||||||
// @dart = 3.3
|
|
||||||
|
|
||||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
|
||||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
|
||||||
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
|
|
||||||
|
|
||||||
import 'dart:core' as $core;
|
|
||||||
|
|
||||||
import 'package:protobuf/protobuf.dart' as $pb;
|
|
||||||
import 'package:protobuf/well_known_types/google/protobuf/empty.pb.dart' as $0;
|
|
||||||
|
|
||||||
import '../shared/vault.pbenum.dart' as $1;
|
|
||||||
|
|
||||||
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
|
|
||||||
|
|
||||||
enum Request_Payload { queryState, notSet }
|
|
||||||
|
|
||||||
class Request extends $pb.GeneratedMessage {
|
|
||||||
factory Request({
|
|
||||||
$0.Empty? queryState,
|
|
||||||
}) {
|
|
||||||
final result = create();
|
|
||||||
if (queryState != null) result.queryState = queryState;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Request._();
|
|
||||||
|
|
||||||
factory Request.fromBuffer($core.List<$core.int> data,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromBuffer(data, registry);
|
|
||||||
factory Request.fromJson($core.String json,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromJson(json, registry);
|
|
||||||
|
|
||||||
static const $core.Map<$core.int, Request_Payload> _Request_PayloadByTag = {
|
|
||||||
1: Request_Payload.queryState,
|
|
||||||
0: Request_Payload.notSet
|
|
||||||
};
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
||||||
_omitMessageNames ? '' : 'Request',
|
|
||||||
package: const $pb.PackageName(
|
|
||||||
_omitMessageNames ? '' : 'arbiter.client.vault'),
|
|
||||||
createEmptyInstance: create)
|
|
||||||
..oo(0, [1])
|
|
||||||
..aOM<$0.Empty>(1, _omitFieldNames ? '' : 'queryState',
|
|
||||||
subBuilder: $0.Empty.create)
|
|
||||||
..hasRequiredFields = false;
|
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
Request clone() => deepCopy();
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
Request copyWith(void Function(Request) updates) =>
|
|
||||||
super.copyWith((message) => updates(message as Request)) as Request;
|
|
||||||
|
|
||||||
@$core.override
|
|
||||||
$pb.BuilderInfo get info_ => _i;
|
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static Request create() => Request._();
|
|
||||||
@$core.override
|
|
||||||
Request createEmptyInstance() => create();
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static Request getDefault() =>
|
|
||||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Request>(create);
|
|
||||||
static Request? _defaultInstance;
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
Request_Payload whichPayload() => _Request_PayloadByTag[$_whichOneof(0)]!;
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearPayload() => $_clearField($_whichOneof(0));
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$0.Empty get queryState => $_getN(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
set queryState($0.Empty value) => $_setField(1, value);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.bool hasQueryState() => $_has(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearQueryState() => $_clearField(1);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$0.Empty ensureQueryState() => $_ensure(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Response_Payload { state, notSet }
|
|
||||||
|
|
||||||
class Response extends $pb.GeneratedMessage {
|
|
||||||
factory Response({
|
|
||||||
$1.VaultState? state,
|
|
||||||
}) {
|
|
||||||
final result = create();
|
|
||||||
if (state != null) result.state = state;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
Response._();
|
|
||||||
|
|
||||||
factory Response.fromBuffer($core.List<$core.int> data,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromBuffer(data, registry);
|
|
||||||
factory Response.fromJson($core.String json,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromJson(json, registry);
|
|
||||||
|
|
||||||
static const $core.Map<$core.int, Response_Payload> _Response_PayloadByTag = {
|
|
||||||
1: Response_Payload.state,
|
|
||||||
0: Response_Payload.notSet
|
|
||||||
};
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
||||||
_omitMessageNames ? '' : 'Response',
|
|
||||||
package: const $pb.PackageName(
|
|
||||||
_omitMessageNames ? '' : 'arbiter.client.vault'),
|
|
||||||
createEmptyInstance: create)
|
|
||||||
..oo(0, [1])
|
|
||||||
..aE<$1.VaultState>(1, _omitFieldNames ? '' : 'state',
|
|
||||||
enumValues: $1.VaultState.values)
|
|
||||||
..hasRequiredFields = false;
|
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
Response clone() => deepCopy();
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
Response copyWith(void Function(Response) updates) =>
|
|
||||||
super.copyWith((message) => updates(message as Response)) as Response;
|
|
||||||
|
|
||||||
@$core.override
|
|
||||||
$pb.BuilderInfo get info_ => _i;
|
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static Response create() => Response._();
|
|
||||||
@$core.override
|
|
||||||
Response createEmptyInstance() => create();
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static Response getDefault() =>
|
|
||||||
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Response>(create);
|
|
||||||
static Response? _defaultInstance;
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
Response_Payload whichPayload() => _Response_PayloadByTag[$_whichOneof(0)]!;
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearPayload() => $_clearField($_whichOneof(0));
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$1.VaultState get state => $_getN(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
set state($1.VaultState value) => $_setField(1, value);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.bool hasState() => $_has(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearState() => $_clearField(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
const $core.bool _omitFieldNames =
|
|
||||||
$core.bool.fromEnvironment('protobuf.omit_field_names');
|
|
||||||
const $core.bool _omitMessageNames =
|
|
||||||
$core.bool.fromEnvironment('protobuf.omit_message_names');
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// This is a generated file - do not edit.
|
|
||||||
//
|
|
||||||
// Generated from client/vault.proto.
|
|
||||||
|
|
||||||
// @dart = 3.3
|
|
||||||
|
|
||||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
|
||||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
|
||||||
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
// This is a generated file - do not edit.
|
|
||||||
//
|
|
||||||
// Generated from client/vault.proto.
|
|
||||||
|
|
||||||
// @dart = 3.3
|
|
||||||
|
|
||||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
|
||||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
|
||||||
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
|
|
||||||
// ignore_for_file: unused_import
|
|
||||||
|
|
||||||
import 'dart:convert' as $convert;
|
|
||||||
import 'dart:core' as $core;
|
|
||||||
import 'dart:typed_data' as $typed_data;
|
|
||||||
|
|
||||||
@$core.Deprecated('Use requestDescriptor instead')
|
|
||||||
const Request$json = {
|
|
||||||
'1': 'Request',
|
|
||||||
'2': [
|
|
||||||
{
|
|
||||||
'1': 'query_state',
|
|
||||||
'3': 1,
|
|
||||||
'4': 1,
|
|
||||||
'5': 11,
|
|
||||||
'6': '.google.protobuf.Empty',
|
|
||||||
'9': 0,
|
|
||||||
'10': 'queryState'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'8': [
|
|
||||||
{'1': 'payload'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `Request`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List requestDescriptor = $convert.base64Decode(
|
|
||||||
'CgdSZXF1ZXN0EjkKC3F1ZXJ5X3N0YXRlGAEgASgLMhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5SA'
|
|
||||||
'BSCnF1ZXJ5U3RhdGVCCQoHcGF5bG9hZA==');
|
|
||||||
|
|
||||||
@$core.Deprecated('Use responseDescriptor instead')
|
|
||||||
const Response$json = {
|
|
||||||
'1': 'Response',
|
|
||||||
'2': [
|
|
||||||
{
|
|
||||||
'1': 'state',
|
|
||||||
'3': 1,
|
|
||||||
'4': 1,
|
|
||||||
'5': 14,
|
|
||||||
'6': '.arbiter.shared.VaultState',
|
|
||||||
'9': 0,
|
|
||||||
'10': 'state'
|
|
||||||
},
|
|
||||||
],
|
|
||||||
'8': [
|
|
||||||
{'1': 'payload'},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `Response`. Decode as a `google.protobuf.DescriptorProto`.
|
|
||||||
final $typed_data.Uint8List responseDescriptor = $convert.base64Decode(
|
|
||||||
'CghSZXNwb25zZRIyCgVzdGF0ZRgBIAEoDjIaLmFyYml0ZXIuc2hhcmVkLlZhdWx0U3RhdGVIAF'
|
|
||||||
'IFc3RhdGVCCQoHcGF5bG9hZA==');
|
|
||||||
@@ -19,7 +19,6 @@ import 'package:protobuf/well_known_types/google/protobuf/timestamp.pb.dart'
|
|||||||
as $0;
|
as $0;
|
||||||
|
|
||||||
import 'evm.pbenum.dart';
|
import 'evm.pbenum.dart';
|
||||||
import 'shared/evm.pb.dart' as $2;
|
|
||||||
|
|
||||||
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
|
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
|
||||||
|
|
||||||
@@ -816,6 +815,825 @@ class SpecificGrant extends $pb.GeneratedMessage {
|
|||||||
TokenTransferSettings ensureTokenTransfer() => $_ensure(1);
|
TokenTransferSettings ensureTokenTransfer() => $_ensure(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class EtherTransferMeaning extends $pb.GeneratedMessage {
|
||||||
|
factory EtherTransferMeaning({
|
||||||
|
$core.List<$core.int>? to,
|
||||||
|
$core.List<$core.int>? value,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (to != null) result.to = to;
|
||||||
|
if (value != null) result.value = value;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
EtherTransferMeaning._();
|
||||||
|
|
||||||
|
factory EtherTransferMeaning.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory EtherTransferMeaning.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'EtherTransferMeaning',
|
||||||
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
1, _omitFieldNames ? '' : 'to', $pb.PbFieldType.OY)
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
2, _omitFieldNames ? '' : 'value', $pb.PbFieldType.OY)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
EtherTransferMeaning clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
EtherTransferMeaning copyWith(void Function(EtherTransferMeaning) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as EtherTransferMeaning))
|
||||||
|
as EtherTransferMeaning;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static EtherTransferMeaning create() => EtherTransferMeaning._();
|
||||||
|
@$core.override
|
||||||
|
EtherTransferMeaning createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static EtherTransferMeaning getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<EtherTransferMeaning>(create);
|
||||||
|
static EtherTransferMeaning? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.List<$core.int> get to => $_getN(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set to($core.List<$core.int> value) => $_setBytes(0, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasTo() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearTo() => $_clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.List<$core.int> get value => $_getN(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set value($core.List<$core.int> value) => $_setBytes(1, value);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasValue() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearValue() => $_clearField(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
class TokenInfo extends $pb.GeneratedMessage {
|
||||||
|
factory TokenInfo({
|
||||||
|
$core.String? symbol,
|
||||||
|
$core.List<$core.int>? address,
|
||||||
|
$fixnum.Int64? chainId,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (symbol != null) result.symbol = symbol;
|
||||||
|
if (address != null) result.address = address;
|
||||||
|
if (chainId != null) result.chainId = chainId;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenInfo._();
|
||||||
|
|
||||||
|
factory TokenInfo.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory TokenInfo.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'TokenInfo',
|
||||||
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..aOS(1, _omitFieldNames ? '' : 'symbol')
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
2, _omitFieldNames ? '' : 'address', $pb.PbFieldType.OY)
|
||||||
|
..a<$fixnum.Int64>(3, _omitFieldNames ? '' : 'chainId', $pb.PbFieldType.OU6,
|
||||||
|
defaultOrMaker: $fixnum.Int64.ZERO)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
TokenInfo clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
TokenInfo copyWith(void Function(TokenInfo) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as TokenInfo)) as TokenInfo;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static TokenInfo create() => TokenInfo._();
|
||||||
|
@$core.override
|
||||||
|
TokenInfo createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static TokenInfo getDefault() =>
|
||||||
|
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<TokenInfo>(create);
|
||||||
|
static TokenInfo? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.String get symbol => $_getSZ(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set symbol($core.String value) => $_setString(0, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasSymbol() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearSymbol() => $_clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.List<$core.int> get address => $_getN(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set address($core.List<$core.int> value) => $_setBytes(1, value);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasAddress() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearAddress() => $_clearField(2);
|
||||||
|
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$fixnum.Int64 get chainId => $_getI64(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
set chainId($fixnum.Int64 value) => $_setInt64(2, value);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.bool hasChainId() => $_has(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
void clearChainId() => $_clearField(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mirror of token_transfers::Meaning
|
||||||
|
class TokenTransferMeaning extends $pb.GeneratedMessage {
|
||||||
|
factory TokenTransferMeaning({
|
||||||
|
TokenInfo? token,
|
||||||
|
$core.List<$core.int>? to,
|
||||||
|
$core.List<$core.int>? value,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (token != null) result.token = token;
|
||||||
|
if (to != null) result.to = to;
|
||||||
|
if (value != null) result.value = value;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TokenTransferMeaning._();
|
||||||
|
|
||||||
|
factory TokenTransferMeaning.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory TokenTransferMeaning.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'TokenTransferMeaning',
|
||||||
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..aOM<TokenInfo>(1, _omitFieldNames ? '' : 'token',
|
||||||
|
subBuilder: TokenInfo.create)
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
2, _omitFieldNames ? '' : 'to', $pb.PbFieldType.OY)
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
3, _omitFieldNames ? '' : 'value', $pb.PbFieldType.OY)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
TokenTransferMeaning clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
TokenTransferMeaning copyWith(void Function(TokenTransferMeaning) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as TokenTransferMeaning))
|
||||||
|
as TokenTransferMeaning;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static TokenTransferMeaning create() => TokenTransferMeaning._();
|
||||||
|
@$core.override
|
||||||
|
TokenTransferMeaning createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static TokenTransferMeaning getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<TokenTransferMeaning>(create);
|
||||||
|
static TokenTransferMeaning? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
TokenInfo get token => $_getN(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set token(TokenInfo value) => $_setField(1, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasToken() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearToken() => $_clearField(1);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
TokenInfo ensureToken() => $_ensure(0);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.List<$core.int> get to => $_getN(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set to($core.List<$core.int> value) => $_setBytes(1, value);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasTo() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearTo() => $_clearField(2);
|
||||||
|
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.List<$core.int> get value => $_getN(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
set value($core.List<$core.int> value) => $_setBytes(2, value);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.bool hasValue() => $_has(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
void clearValue() => $_clearField(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum SpecificMeaning_Meaning { etherTransfer, tokenTransfer, notSet }
|
||||||
|
|
||||||
|
/// Mirror of policies::SpecificMeaning
|
||||||
|
class SpecificMeaning extends $pb.GeneratedMessage {
|
||||||
|
factory SpecificMeaning({
|
||||||
|
EtherTransferMeaning? etherTransfer,
|
||||||
|
TokenTransferMeaning? tokenTransfer,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (etherTransfer != null) result.etherTransfer = etherTransfer;
|
||||||
|
if (tokenTransfer != null) result.tokenTransfer = tokenTransfer;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpecificMeaning._();
|
||||||
|
|
||||||
|
factory SpecificMeaning.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory SpecificMeaning.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static const $core.Map<$core.int, SpecificMeaning_Meaning>
|
||||||
|
_SpecificMeaning_MeaningByTag = {
|
||||||
|
1: SpecificMeaning_Meaning.etherTransfer,
|
||||||
|
2: SpecificMeaning_Meaning.tokenTransfer,
|
||||||
|
0: SpecificMeaning_Meaning.notSet
|
||||||
|
};
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'SpecificMeaning',
|
||||||
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..oo(0, [1, 2])
|
||||||
|
..aOM<EtherTransferMeaning>(1, _omitFieldNames ? '' : 'etherTransfer',
|
||||||
|
subBuilder: EtherTransferMeaning.create)
|
||||||
|
..aOM<TokenTransferMeaning>(2, _omitFieldNames ? '' : 'tokenTransfer',
|
||||||
|
subBuilder: TokenTransferMeaning.create)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
SpecificMeaning clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
SpecificMeaning copyWith(void Function(SpecificMeaning) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as SpecificMeaning))
|
||||||
|
as SpecificMeaning;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static SpecificMeaning create() => SpecificMeaning._();
|
||||||
|
@$core.override
|
||||||
|
SpecificMeaning createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static SpecificMeaning getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<SpecificMeaning>(create);
|
||||||
|
static SpecificMeaning? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
SpecificMeaning_Meaning whichMeaning() =>
|
||||||
|
_SpecificMeaning_MeaningByTag[$_whichOneof(0)]!;
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearMeaning() => $_clearField($_whichOneof(0));
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
EtherTransferMeaning get etherTransfer => $_getN(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set etherTransfer(EtherTransferMeaning value) => $_setField(1, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasEtherTransfer() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearEtherTransfer() => $_clearField(1);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
EtherTransferMeaning ensureEtherTransfer() => $_ensure(0);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
TokenTransferMeaning get tokenTransfer => $_getN(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set tokenTransfer(TokenTransferMeaning value) => $_setField(2, value);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasTokenTransfer() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearTokenTransfer() => $_clearField(2);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
TokenTransferMeaning ensureTokenTransfer() => $_ensure(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// --- Eval error types ---
|
||||||
|
class GasLimitExceededViolation extends $pb.GeneratedMessage {
|
||||||
|
factory GasLimitExceededViolation({
|
||||||
|
$core.List<$core.int>? maxGasFeePerGas,
|
||||||
|
$core.List<$core.int>? maxPriorityFeePerGas,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (maxGasFeePerGas != null) result.maxGasFeePerGas = maxGasFeePerGas;
|
||||||
|
if (maxPriorityFeePerGas != null)
|
||||||
|
result.maxPriorityFeePerGas = maxPriorityFeePerGas;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
GasLimitExceededViolation._();
|
||||||
|
|
||||||
|
factory GasLimitExceededViolation.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory GasLimitExceededViolation.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'GasLimitExceededViolation',
|
||||||
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
1, _omitFieldNames ? '' : 'maxGasFeePerGas', $pb.PbFieldType.OY)
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
2, _omitFieldNames ? '' : 'maxPriorityFeePerGas', $pb.PbFieldType.OY)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
GasLimitExceededViolation clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
GasLimitExceededViolation copyWith(
|
||||||
|
void Function(GasLimitExceededViolation) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as GasLimitExceededViolation))
|
||||||
|
as GasLimitExceededViolation;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static GasLimitExceededViolation create() => GasLimitExceededViolation._();
|
||||||
|
@$core.override
|
||||||
|
GasLimitExceededViolation createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static GasLimitExceededViolation getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<GasLimitExceededViolation>(create);
|
||||||
|
static GasLimitExceededViolation? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.List<$core.int> get maxGasFeePerGas => $_getN(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set maxGasFeePerGas($core.List<$core.int> value) => $_setBytes(0, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasMaxGasFeePerGas() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearMaxGasFeePerGas() => $_clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.List<$core.int> get maxPriorityFeePerGas => $_getN(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set maxPriorityFeePerGas($core.List<$core.int> value) => $_setBytes(1, value);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasMaxPriorityFeePerGas() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearMaxPriorityFeePerGas() => $_clearField(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum EvalViolation_Kind {
|
||||||
|
invalidTarget,
|
||||||
|
gasLimitExceeded,
|
||||||
|
rateLimitExceeded,
|
||||||
|
volumetricLimitExceeded,
|
||||||
|
invalidTime,
|
||||||
|
invalidTransactionType,
|
||||||
|
notSet
|
||||||
|
}
|
||||||
|
|
||||||
|
class EvalViolation extends $pb.GeneratedMessage {
|
||||||
|
factory EvalViolation({
|
||||||
|
$core.List<$core.int>? invalidTarget,
|
||||||
|
GasLimitExceededViolation? gasLimitExceeded,
|
||||||
|
$1.Empty? rateLimitExceeded,
|
||||||
|
$1.Empty? volumetricLimitExceeded,
|
||||||
|
$1.Empty? invalidTime,
|
||||||
|
$1.Empty? invalidTransactionType,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (invalidTarget != null) result.invalidTarget = invalidTarget;
|
||||||
|
if (gasLimitExceeded != null) result.gasLimitExceeded = gasLimitExceeded;
|
||||||
|
if (rateLimitExceeded != null) result.rateLimitExceeded = rateLimitExceeded;
|
||||||
|
if (volumetricLimitExceeded != null)
|
||||||
|
result.volumetricLimitExceeded = volumetricLimitExceeded;
|
||||||
|
if (invalidTime != null) result.invalidTime = invalidTime;
|
||||||
|
if (invalidTransactionType != null)
|
||||||
|
result.invalidTransactionType = invalidTransactionType;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
EvalViolation._();
|
||||||
|
|
||||||
|
factory EvalViolation.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory EvalViolation.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static const $core.Map<$core.int, EvalViolation_Kind>
|
||||||
|
_EvalViolation_KindByTag = {
|
||||||
|
1: EvalViolation_Kind.invalidTarget,
|
||||||
|
2: EvalViolation_Kind.gasLimitExceeded,
|
||||||
|
3: EvalViolation_Kind.rateLimitExceeded,
|
||||||
|
4: EvalViolation_Kind.volumetricLimitExceeded,
|
||||||
|
5: EvalViolation_Kind.invalidTime,
|
||||||
|
6: EvalViolation_Kind.invalidTransactionType,
|
||||||
|
0: EvalViolation_Kind.notSet
|
||||||
|
};
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'EvalViolation',
|
||||||
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..oo(0, [1, 2, 3, 4, 5, 6])
|
||||||
|
..a<$core.List<$core.int>>(
|
||||||
|
1, _omitFieldNames ? '' : 'invalidTarget', $pb.PbFieldType.OY)
|
||||||
|
..aOM<GasLimitExceededViolation>(
|
||||||
|
2, _omitFieldNames ? '' : 'gasLimitExceeded',
|
||||||
|
subBuilder: GasLimitExceededViolation.create)
|
||||||
|
..aOM<$1.Empty>(3, _omitFieldNames ? '' : 'rateLimitExceeded',
|
||||||
|
subBuilder: $1.Empty.create)
|
||||||
|
..aOM<$1.Empty>(4, _omitFieldNames ? '' : 'volumetricLimitExceeded',
|
||||||
|
subBuilder: $1.Empty.create)
|
||||||
|
..aOM<$1.Empty>(5, _omitFieldNames ? '' : 'invalidTime',
|
||||||
|
subBuilder: $1.Empty.create)
|
||||||
|
..aOM<$1.Empty>(6, _omitFieldNames ? '' : 'invalidTransactionType',
|
||||||
|
subBuilder: $1.Empty.create)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
EvalViolation clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
EvalViolation copyWith(void Function(EvalViolation) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as EvalViolation))
|
||||||
|
as EvalViolation;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static EvalViolation create() => EvalViolation._();
|
||||||
|
@$core.override
|
||||||
|
EvalViolation createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static EvalViolation getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<EvalViolation>(create);
|
||||||
|
static EvalViolation? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
@$pb.TagNumber(5)
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
EvalViolation_Kind whichKind() => _EvalViolation_KindByTag[$_whichOneof(0)]!;
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
@$pb.TagNumber(5)
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
void clearKind() => $_clearField($_whichOneof(0));
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.List<$core.int> get invalidTarget => $_getN(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set invalidTarget($core.List<$core.int> value) => $_setBytes(0, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasInvalidTarget() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearInvalidTarget() => $_clearField(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
GasLimitExceededViolation get gasLimitExceeded => $_getN(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set gasLimitExceeded(GasLimitExceededViolation value) => $_setField(2, value);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasGasLimitExceeded() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearGasLimitExceeded() => $_clearField(2);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
GasLimitExceededViolation ensureGasLimitExceeded() => $_ensure(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$1.Empty get rateLimitExceeded => $_getN(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
set rateLimitExceeded($1.Empty value) => $_setField(3, value);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.bool hasRateLimitExceeded() => $_has(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
void clearRateLimitExceeded() => $_clearField(3);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$1.Empty ensureRateLimitExceeded() => $_ensure(2);
|
||||||
|
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$1.Empty get volumetricLimitExceeded => $_getN(3);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
set volumetricLimitExceeded($1.Empty value) => $_setField(4, value);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$core.bool hasVolumetricLimitExceeded() => $_has(3);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
void clearVolumetricLimitExceeded() => $_clearField(4);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$1.Empty ensureVolumetricLimitExceeded() => $_ensure(3);
|
||||||
|
|
||||||
|
@$pb.TagNumber(5)
|
||||||
|
$1.Empty get invalidTime => $_getN(4);
|
||||||
|
@$pb.TagNumber(5)
|
||||||
|
set invalidTime($1.Empty value) => $_setField(5, value);
|
||||||
|
@$pb.TagNumber(5)
|
||||||
|
$core.bool hasInvalidTime() => $_has(4);
|
||||||
|
@$pb.TagNumber(5)
|
||||||
|
void clearInvalidTime() => $_clearField(5);
|
||||||
|
@$pb.TagNumber(5)
|
||||||
|
$1.Empty ensureInvalidTime() => $_ensure(4);
|
||||||
|
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
$1.Empty get invalidTransactionType => $_getN(5);
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
set invalidTransactionType($1.Empty value) => $_setField(6, value);
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
$core.bool hasInvalidTransactionType() => $_has(5);
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
void clearInvalidTransactionType() => $_clearField(6);
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
$1.Empty ensureInvalidTransactionType() => $_ensure(5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transaction was classified but no grant covers it
|
||||||
|
class NoMatchingGrantError extends $pb.GeneratedMessage {
|
||||||
|
factory NoMatchingGrantError({
|
||||||
|
SpecificMeaning? meaning,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (meaning != null) result.meaning = meaning;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
NoMatchingGrantError._();
|
||||||
|
|
||||||
|
factory NoMatchingGrantError.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory NoMatchingGrantError.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'NoMatchingGrantError',
|
||||||
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..aOM<SpecificMeaning>(1, _omitFieldNames ? '' : 'meaning',
|
||||||
|
subBuilder: SpecificMeaning.create)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
NoMatchingGrantError clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
NoMatchingGrantError copyWith(void Function(NoMatchingGrantError) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as NoMatchingGrantError))
|
||||||
|
as NoMatchingGrantError;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static NoMatchingGrantError create() => NoMatchingGrantError._();
|
||||||
|
@$core.override
|
||||||
|
NoMatchingGrantError createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static NoMatchingGrantError getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<NoMatchingGrantError>(create);
|
||||||
|
static NoMatchingGrantError? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
SpecificMeaning get meaning => $_getN(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set meaning(SpecificMeaning value) => $_setField(1, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasMeaning() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearMeaning() => $_clearField(1);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
SpecificMeaning ensureMeaning() => $_ensure(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transaction was classified and a grant was found, but constraints were violated
|
||||||
|
class PolicyViolationsError extends $pb.GeneratedMessage {
|
||||||
|
factory PolicyViolationsError({
|
||||||
|
SpecificMeaning? meaning,
|
||||||
|
$core.Iterable<EvalViolation>? violations,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (meaning != null) result.meaning = meaning;
|
||||||
|
if (violations != null) result.violations.addAll(violations);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
PolicyViolationsError._();
|
||||||
|
|
||||||
|
factory PolicyViolationsError.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory PolicyViolationsError.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'PolicyViolationsError',
|
||||||
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..aOM<SpecificMeaning>(1, _omitFieldNames ? '' : 'meaning',
|
||||||
|
subBuilder: SpecificMeaning.create)
|
||||||
|
..pPM<EvalViolation>(2, _omitFieldNames ? '' : 'violations',
|
||||||
|
subBuilder: EvalViolation.create)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
PolicyViolationsError clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
PolicyViolationsError copyWith(
|
||||||
|
void Function(PolicyViolationsError) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as PolicyViolationsError))
|
||||||
|
as PolicyViolationsError;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static PolicyViolationsError create() => PolicyViolationsError._();
|
||||||
|
@$core.override
|
||||||
|
PolicyViolationsError createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static PolicyViolationsError getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<PolicyViolationsError>(create);
|
||||||
|
static PolicyViolationsError? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
SpecificMeaning get meaning => $_getN(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set meaning(SpecificMeaning value) => $_setField(1, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasMeaning() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearMeaning() => $_clearField(1);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
SpecificMeaning ensureMeaning() => $_ensure(0);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$pb.PbList<EvalViolation> get violations => $_getList(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TransactionEvalError_Kind {
|
||||||
|
contractCreationNotSupported,
|
||||||
|
unsupportedTransactionType,
|
||||||
|
noMatchingGrant,
|
||||||
|
policyViolations,
|
||||||
|
notSet
|
||||||
|
}
|
||||||
|
|
||||||
|
/// top-level error returned when transaction evaluation fails
|
||||||
|
class TransactionEvalError extends $pb.GeneratedMessage {
|
||||||
|
factory TransactionEvalError({
|
||||||
|
$1.Empty? contractCreationNotSupported,
|
||||||
|
$1.Empty? unsupportedTransactionType,
|
||||||
|
NoMatchingGrantError? noMatchingGrant,
|
||||||
|
PolicyViolationsError? policyViolations,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (contractCreationNotSupported != null)
|
||||||
|
result.contractCreationNotSupported = contractCreationNotSupported;
|
||||||
|
if (unsupportedTransactionType != null)
|
||||||
|
result.unsupportedTransactionType = unsupportedTransactionType;
|
||||||
|
if (noMatchingGrant != null) result.noMatchingGrant = noMatchingGrant;
|
||||||
|
if (policyViolations != null) result.policyViolations = policyViolations;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionEvalError._();
|
||||||
|
|
||||||
|
factory TransactionEvalError.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory TransactionEvalError.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static const $core.Map<$core.int, TransactionEvalError_Kind>
|
||||||
|
_TransactionEvalError_KindByTag = {
|
||||||
|
1: TransactionEvalError_Kind.contractCreationNotSupported,
|
||||||
|
2: TransactionEvalError_Kind.unsupportedTransactionType,
|
||||||
|
3: TransactionEvalError_Kind.noMatchingGrant,
|
||||||
|
4: TransactionEvalError_Kind.policyViolations,
|
||||||
|
0: TransactionEvalError_Kind.notSet
|
||||||
|
};
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'TransactionEvalError',
|
||||||
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..oo(0, [1, 2, 3, 4])
|
||||||
|
..aOM<$1.Empty>(1, _omitFieldNames ? '' : 'contractCreationNotSupported',
|
||||||
|
subBuilder: $1.Empty.create)
|
||||||
|
..aOM<$1.Empty>(2, _omitFieldNames ? '' : 'unsupportedTransactionType',
|
||||||
|
subBuilder: $1.Empty.create)
|
||||||
|
..aOM<NoMatchingGrantError>(3, _omitFieldNames ? '' : 'noMatchingGrant',
|
||||||
|
subBuilder: NoMatchingGrantError.create)
|
||||||
|
..aOM<PolicyViolationsError>(4, _omitFieldNames ? '' : 'policyViolations',
|
||||||
|
subBuilder: PolicyViolationsError.create)
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
TransactionEvalError clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
TransactionEvalError copyWith(void Function(TransactionEvalError) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as TransactionEvalError))
|
||||||
|
as TransactionEvalError;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static TransactionEvalError create() => TransactionEvalError._();
|
||||||
|
@$core.override
|
||||||
|
TransactionEvalError createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static TransactionEvalError getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<TransactionEvalError>(create);
|
||||||
|
static TransactionEvalError? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
TransactionEvalError_Kind whichKind() =>
|
||||||
|
_TransactionEvalError_KindByTag[$_whichOneof(0)]!;
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
void clearKind() => $_clearField($_whichOneof(0));
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$1.Empty get contractCreationNotSupported => $_getN(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
set contractCreationNotSupported($1.Empty value) => $_setField(1, value);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$core.bool hasContractCreationNotSupported() => $_has(0);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
void clearContractCreationNotSupported() => $_clearField(1);
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$1.Empty ensureContractCreationNotSupported() => $_ensure(0);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$1.Empty get unsupportedTransactionType => $_getN(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
set unsupportedTransactionType($1.Empty value) => $_setField(2, value);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$core.bool hasUnsupportedTransactionType() => $_has(1);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
void clearUnsupportedTransactionType() => $_clearField(2);
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$1.Empty ensureUnsupportedTransactionType() => $_ensure(1);
|
||||||
|
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
NoMatchingGrantError get noMatchingGrant => $_getN(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
set noMatchingGrant(NoMatchingGrantError value) => $_setField(3, value);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
$core.bool hasNoMatchingGrant() => $_has(2);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
void clearNoMatchingGrant() => $_clearField(3);
|
||||||
|
@$pb.TagNumber(3)
|
||||||
|
NoMatchingGrantError ensureNoMatchingGrant() => $_ensure(2);
|
||||||
|
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
PolicyViolationsError get policyViolations => $_getN(3);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
set policyViolations(PolicyViolationsError value) => $_setField(4, value);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$core.bool hasPolicyViolations() => $_has(3);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
void clearPolicyViolations() => $_clearField(4);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
PolicyViolationsError ensurePolicyViolations() => $_ensure(3);
|
||||||
|
}
|
||||||
|
|
||||||
/// --- UserAgent grant management ---
|
/// --- UserAgent grant management ---
|
||||||
class EvmGrantCreateRequest extends $pb.GeneratedMessage {
|
class EvmGrantCreateRequest extends $pb.GeneratedMessage {
|
||||||
factory EvmGrantCreateRequest({
|
factory EvmGrantCreateRequest({
|
||||||
@@ -1479,7 +2297,7 @@ enum EvmSignTransactionResponse_Result { signature, evalError, error, notSet }
|
|||||||
class EvmSignTransactionResponse extends $pb.GeneratedMessage {
|
class EvmSignTransactionResponse extends $pb.GeneratedMessage {
|
||||||
factory EvmSignTransactionResponse({
|
factory EvmSignTransactionResponse({
|
||||||
$core.List<$core.int>? signature,
|
$core.List<$core.int>? signature,
|
||||||
$2.TransactionEvalError? evalError,
|
TransactionEvalError? evalError,
|
||||||
EvmError? error,
|
EvmError? error,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
@@ -1512,8 +2330,8 @@ class EvmSignTransactionResponse extends $pb.GeneratedMessage {
|
|||||||
..oo(0, [1, 2, 3])
|
..oo(0, [1, 2, 3])
|
||||||
..a<$core.List<$core.int>>(
|
..a<$core.List<$core.int>>(
|
||||||
1, _omitFieldNames ? '' : 'signature', $pb.PbFieldType.OY)
|
1, _omitFieldNames ? '' : 'signature', $pb.PbFieldType.OY)
|
||||||
..aOM<$2.TransactionEvalError>(2, _omitFieldNames ? '' : 'evalError',
|
..aOM<TransactionEvalError>(2, _omitFieldNames ? '' : 'evalError',
|
||||||
subBuilder: $2.TransactionEvalError.create)
|
subBuilder: TransactionEvalError.create)
|
||||||
..aE<EvmError>(3, _omitFieldNames ? '' : 'error',
|
..aE<EvmError>(3, _omitFieldNames ? '' : 'error',
|
||||||
enumValues: EvmError.values)
|
enumValues: EvmError.values)
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
@@ -1559,15 +2377,15 @@ class EvmSignTransactionResponse extends $pb.GeneratedMessage {
|
|||||||
void clearSignature() => $_clearField(1);
|
void clearSignature() => $_clearField(1);
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$2.TransactionEvalError get evalError => $_getN(1);
|
TransactionEvalError get evalError => $_getN(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
set evalError($2.TransactionEvalError value) => $_setField(2, value);
|
set evalError(TransactionEvalError value) => $_setField(2, value);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$core.bool hasEvalError() => $_has(1);
|
$core.bool hasEvalError() => $_has(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
void clearEvalError() => $_clearField(2);
|
void clearEvalError() => $_clearField(2);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$2.TransactionEvalError ensureEvalError() => $_ensure(1);
|
TransactionEvalError ensureEvalError() => $_ensure(1);
|
||||||
|
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
EvmError get error => $_getN(2);
|
EvmError get error => $_getN(2);
|
||||||
@@ -1654,8 +2472,8 @@ enum EvmAnalyzeTransactionResponse_Result { meaning, evalError, error, notSet }
|
|||||||
|
|
||||||
class EvmAnalyzeTransactionResponse extends $pb.GeneratedMessage {
|
class EvmAnalyzeTransactionResponse extends $pb.GeneratedMessage {
|
||||||
factory EvmAnalyzeTransactionResponse({
|
factory EvmAnalyzeTransactionResponse({
|
||||||
$2.SpecificMeaning? meaning,
|
SpecificMeaning? meaning,
|
||||||
$2.TransactionEvalError? evalError,
|
TransactionEvalError? evalError,
|
||||||
EvmError? error,
|
EvmError? error,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
@@ -1686,10 +2504,10 @@ class EvmAnalyzeTransactionResponse extends $pb.GeneratedMessage {
|
|||||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.evm'),
|
||||||
createEmptyInstance: create)
|
createEmptyInstance: create)
|
||||||
..oo(0, [1, 2, 3])
|
..oo(0, [1, 2, 3])
|
||||||
..aOM<$2.SpecificMeaning>(1, _omitFieldNames ? '' : 'meaning',
|
..aOM<SpecificMeaning>(1, _omitFieldNames ? '' : 'meaning',
|
||||||
subBuilder: $2.SpecificMeaning.create)
|
subBuilder: SpecificMeaning.create)
|
||||||
..aOM<$2.TransactionEvalError>(2, _omitFieldNames ? '' : 'evalError',
|
..aOM<TransactionEvalError>(2, _omitFieldNames ? '' : 'evalError',
|
||||||
subBuilder: $2.TransactionEvalError.create)
|
subBuilder: TransactionEvalError.create)
|
||||||
..aE<EvmError>(3, _omitFieldNames ? '' : 'error',
|
..aE<EvmError>(3, _omitFieldNames ? '' : 'error',
|
||||||
enumValues: EvmError.values)
|
enumValues: EvmError.values)
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
@@ -1727,26 +2545,26 @@ class EvmAnalyzeTransactionResponse extends $pb.GeneratedMessage {
|
|||||||
void clearResult() => $_clearField($_whichOneof(0));
|
void clearResult() => $_clearField($_whichOneof(0));
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
$2.SpecificMeaning get meaning => $_getN(0);
|
SpecificMeaning get meaning => $_getN(0);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
set meaning($2.SpecificMeaning value) => $_setField(1, value);
|
set meaning(SpecificMeaning value) => $_setField(1, value);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
$core.bool hasMeaning() => $_has(0);
|
$core.bool hasMeaning() => $_has(0);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
void clearMeaning() => $_clearField(1);
|
void clearMeaning() => $_clearField(1);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
$2.SpecificMeaning ensureMeaning() => $_ensure(0);
|
SpecificMeaning ensureMeaning() => $_ensure(0);
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$2.TransactionEvalError get evalError => $_getN(1);
|
TransactionEvalError get evalError => $_getN(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
set evalError($2.TransactionEvalError value) => $_setField(2, value);
|
set evalError(TransactionEvalError value) => $_setField(2, value);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$core.bool hasEvalError() => $_has(1);
|
$core.bool hasEvalError() => $_has(1);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
void clearEvalError() => $_clearField(2);
|
void clearEvalError() => $_clearField(2);
|
||||||
@$pb.TagNumber(2)
|
@$pb.TagNumber(2)
|
||||||
$2.TransactionEvalError ensureEvalError() => $_ensure(1);
|
TransactionEvalError ensureEvalError() => $_ensure(1);
|
||||||
|
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
EvmError get error => $_getN(2);
|
EvmError get error => $_getN(2);
|
||||||
|
|||||||
@@ -327,6 +327,308 @@ final $typed_data.Uint8List specificGrantDescriptor = $convert.base64Decode(
|
|||||||
'AiABKAsyIi5hcmJpdGVyLmV2bS5Ub2tlblRyYW5zZmVyU2V0dGluZ3NIAFINdG9rZW5UcmFuc2'
|
'AiABKAsyIi5hcmJpdGVyLmV2bS5Ub2tlblRyYW5zZmVyU2V0dGluZ3NIAFINdG9rZW5UcmFuc2'
|
||||||
'ZlckIHCgVncmFudA==');
|
'ZlckIHCgVncmFudA==');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use etherTransferMeaningDescriptor instead')
|
||||||
|
const EtherTransferMeaning$json = {
|
||||||
|
'1': 'EtherTransferMeaning',
|
||||||
|
'2': [
|
||||||
|
{'1': 'to', '3': 1, '4': 1, '5': 12, '10': 'to'},
|
||||||
|
{'1': 'value', '3': 2, '4': 1, '5': 12, '10': 'value'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `EtherTransferMeaning`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List etherTransferMeaningDescriptor = $convert.base64Decode(
|
||||||
|
'ChRFdGhlclRyYW5zZmVyTWVhbmluZxIOCgJ0bxgBIAEoDFICdG8SFAoFdmFsdWUYAiABKAxSBX'
|
||||||
|
'ZhbHVl');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use tokenInfoDescriptor instead')
|
||||||
|
const TokenInfo$json = {
|
||||||
|
'1': 'TokenInfo',
|
||||||
|
'2': [
|
||||||
|
{'1': 'symbol', '3': 1, '4': 1, '5': 9, '10': 'symbol'},
|
||||||
|
{'1': 'address', '3': 2, '4': 1, '5': 12, '10': 'address'},
|
||||||
|
{'1': 'chain_id', '3': 3, '4': 1, '5': 4, '10': 'chainId'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `TokenInfo`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List tokenInfoDescriptor = $convert.base64Decode(
|
||||||
|
'CglUb2tlbkluZm8SFgoGc3ltYm9sGAEgASgJUgZzeW1ib2wSGAoHYWRkcmVzcxgCIAEoDFIHYW'
|
||||||
|
'RkcmVzcxIZCghjaGFpbl9pZBgDIAEoBFIHY2hhaW5JZA==');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use tokenTransferMeaningDescriptor instead')
|
||||||
|
const TokenTransferMeaning$json = {
|
||||||
|
'1': 'TokenTransferMeaning',
|
||||||
|
'2': [
|
||||||
|
{
|
||||||
|
'1': 'token',
|
||||||
|
'3': 1,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.arbiter.evm.TokenInfo',
|
||||||
|
'10': 'token'
|
||||||
|
},
|
||||||
|
{'1': 'to', '3': 2, '4': 1, '5': 12, '10': 'to'},
|
||||||
|
{'1': 'value', '3': 3, '4': 1, '5': 12, '10': 'value'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `TokenTransferMeaning`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List tokenTransferMeaningDescriptor = $convert.base64Decode(
|
||||||
|
'ChRUb2tlblRyYW5zZmVyTWVhbmluZxIsCgV0b2tlbhgBIAEoCzIWLmFyYml0ZXIuZXZtLlRva2'
|
||||||
|
'VuSW5mb1IFdG9rZW4SDgoCdG8YAiABKAxSAnRvEhQKBXZhbHVlGAMgASgMUgV2YWx1ZQ==');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use specificMeaningDescriptor instead')
|
||||||
|
const SpecificMeaning$json = {
|
||||||
|
'1': 'SpecificMeaning',
|
||||||
|
'2': [
|
||||||
|
{
|
||||||
|
'1': 'ether_transfer',
|
||||||
|
'3': 1,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.arbiter.evm.EtherTransferMeaning',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'etherTransfer'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'token_transfer',
|
||||||
|
'3': 2,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.arbiter.evm.TokenTransferMeaning',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'tokenTransfer'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'8': [
|
||||||
|
{'1': 'meaning'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `SpecificMeaning`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List specificMeaningDescriptor = $convert.base64Decode(
|
||||||
|
'Cg9TcGVjaWZpY01lYW5pbmcSSgoOZXRoZXJfdHJhbnNmZXIYASABKAsyIS5hcmJpdGVyLmV2bS'
|
||||||
|
'5FdGhlclRyYW5zZmVyTWVhbmluZ0gAUg1ldGhlclRyYW5zZmVyEkoKDnRva2VuX3RyYW5zZmVy'
|
||||||
|
'GAIgASgLMiEuYXJiaXRlci5ldm0uVG9rZW5UcmFuc2Zlck1lYW5pbmdIAFINdG9rZW5UcmFuc2'
|
||||||
|
'ZlckIJCgdtZWFuaW5n');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use gasLimitExceededViolationDescriptor instead')
|
||||||
|
const GasLimitExceededViolation$json = {
|
||||||
|
'1': 'GasLimitExceededViolation',
|
||||||
|
'2': [
|
||||||
|
{
|
||||||
|
'1': 'max_gas_fee_per_gas',
|
||||||
|
'3': 1,
|
||||||
|
'4': 1,
|
||||||
|
'5': 12,
|
||||||
|
'9': 0,
|
||||||
|
'10': 'maxGasFeePerGas',
|
||||||
|
'17': true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'max_priority_fee_per_gas',
|
||||||
|
'3': 2,
|
||||||
|
'4': 1,
|
||||||
|
'5': 12,
|
||||||
|
'9': 1,
|
||||||
|
'10': 'maxPriorityFeePerGas',
|
||||||
|
'17': true
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'8': [
|
||||||
|
{'1': '_max_gas_fee_per_gas'},
|
||||||
|
{'1': '_max_priority_fee_per_gas'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `GasLimitExceededViolation`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List gasLimitExceededViolationDescriptor = $convert.base64Decode(
|
||||||
|
'ChlHYXNMaW1pdEV4Y2VlZGVkVmlvbGF0aW9uEjEKE21heF9nYXNfZmVlX3Blcl9nYXMYASABKA'
|
||||||
|
'xIAFIPbWF4R2FzRmVlUGVyR2FziAEBEjsKGG1heF9wcmlvcml0eV9mZWVfcGVyX2dhcxgCIAEo'
|
||||||
|
'DEgBUhRtYXhQcmlvcml0eUZlZVBlckdhc4gBAUIWChRfbWF4X2dhc19mZWVfcGVyX2dhc0IbCh'
|
||||||
|
'lfbWF4X3ByaW9yaXR5X2ZlZV9wZXJfZ2Fz');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use evalViolationDescriptor instead')
|
||||||
|
const EvalViolation$json = {
|
||||||
|
'1': 'EvalViolation',
|
||||||
|
'2': [
|
||||||
|
{
|
||||||
|
'1': 'invalid_target',
|
||||||
|
'3': 1,
|
||||||
|
'4': 1,
|
||||||
|
'5': 12,
|
||||||
|
'9': 0,
|
||||||
|
'10': 'invalidTarget'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'gas_limit_exceeded',
|
||||||
|
'3': 2,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.arbiter.evm.GasLimitExceededViolation',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'gasLimitExceeded'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'rate_limit_exceeded',
|
||||||
|
'3': 3,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.google.protobuf.Empty',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'rateLimitExceeded'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'volumetric_limit_exceeded',
|
||||||
|
'3': 4,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.google.protobuf.Empty',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'volumetricLimitExceeded'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'invalid_time',
|
||||||
|
'3': 5,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.google.protobuf.Empty',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'invalidTime'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'invalid_transaction_type',
|
||||||
|
'3': 6,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.google.protobuf.Empty',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'invalidTransactionType'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'8': [
|
||||||
|
{'1': 'kind'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `EvalViolation`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List evalViolationDescriptor = $convert.base64Decode(
|
||||||
|
'Cg1FdmFsVmlvbGF0aW9uEicKDmludmFsaWRfdGFyZ2V0GAEgASgMSABSDWludmFsaWRUYXJnZX'
|
||||||
|
'QSVgoSZ2FzX2xpbWl0X2V4Y2VlZGVkGAIgASgLMiYuYXJiaXRlci5ldm0uR2FzTGltaXRFeGNl'
|
||||||
|
'ZWRlZFZpb2xhdGlvbkgAUhBnYXNMaW1pdEV4Y2VlZGVkEkgKE3JhdGVfbGltaXRfZXhjZWVkZW'
|
||||||
|
'QYAyABKAsyFi5nb29nbGUucHJvdG9idWYuRW1wdHlIAFIRcmF0ZUxpbWl0RXhjZWVkZWQSVAoZ'
|
||||||
|
'dm9sdW1ldHJpY19saW1pdF9leGNlZWRlZBgEIAEoCzIWLmdvb2dsZS5wcm90b2J1Zi5FbXB0eU'
|
||||||
|
'gAUhd2b2x1bWV0cmljTGltaXRFeGNlZWRlZBI7CgxpbnZhbGlkX3RpbWUYBSABKAsyFi5nb29n'
|
||||||
|
'bGUucHJvdG9idWYuRW1wdHlIAFILaW52YWxpZFRpbWUSUgoYaW52YWxpZF90cmFuc2FjdGlvbl'
|
||||||
|
'90eXBlGAYgASgLMhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5SABSFmludmFsaWRUcmFuc2FjdGlv'
|
||||||
|
'blR5cGVCBgoEa2luZA==');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use noMatchingGrantErrorDescriptor instead')
|
||||||
|
const NoMatchingGrantError$json = {
|
||||||
|
'1': 'NoMatchingGrantError',
|
||||||
|
'2': [
|
||||||
|
{
|
||||||
|
'1': 'meaning',
|
||||||
|
'3': 1,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.arbiter.evm.SpecificMeaning',
|
||||||
|
'10': 'meaning'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `NoMatchingGrantError`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List noMatchingGrantErrorDescriptor = $convert.base64Decode(
|
||||||
|
'ChROb01hdGNoaW5nR3JhbnRFcnJvchI2CgdtZWFuaW5nGAEgASgLMhwuYXJiaXRlci5ldm0uU3'
|
||||||
|
'BlY2lmaWNNZWFuaW5nUgdtZWFuaW5n');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use policyViolationsErrorDescriptor instead')
|
||||||
|
const PolicyViolationsError$json = {
|
||||||
|
'1': 'PolicyViolationsError',
|
||||||
|
'2': [
|
||||||
|
{
|
||||||
|
'1': 'meaning',
|
||||||
|
'3': 1,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.arbiter.evm.SpecificMeaning',
|
||||||
|
'10': 'meaning'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'violations',
|
||||||
|
'3': 2,
|
||||||
|
'4': 3,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.arbiter.evm.EvalViolation',
|
||||||
|
'10': 'violations'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `PolicyViolationsError`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List policyViolationsErrorDescriptor = $convert.base64Decode(
|
||||||
|
'ChVQb2xpY3lWaW9sYXRpb25zRXJyb3ISNgoHbWVhbmluZxgBIAEoCzIcLmFyYml0ZXIuZXZtLl'
|
||||||
|
'NwZWNpZmljTWVhbmluZ1IHbWVhbmluZxI6Cgp2aW9sYXRpb25zGAIgAygLMhouYXJiaXRlci5l'
|
||||||
|
'dm0uRXZhbFZpb2xhdGlvblIKdmlvbGF0aW9ucw==');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use transactionEvalErrorDescriptor instead')
|
||||||
|
const TransactionEvalError$json = {
|
||||||
|
'1': 'TransactionEvalError',
|
||||||
|
'2': [
|
||||||
|
{
|
||||||
|
'1': 'contract_creation_not_supported',
|
||||||
|
'3': 1,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.google.protobuf.Empty',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'contractCreationNotSupported'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'unsupported_transaction_type',
|
||||||
|
'3': 2,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.google.protobuf.Empty',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'unsupportedTransactionType'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'no_matching_grant',
|
||||||
|
'3': 3,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.arbiter.evm.NoMatchingGrantError',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'noMatchingGrant'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'1': 'policy_violations',
|
||||||
|
'3': 4,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.arbiter.evm.PolicyViolationsError',
|
||||||
|
'9': 0,
|
||||||
|
'10': 'policyViolations'
|
||||||
|
},
|
||||||
|
],
|
||||||
|
'8': [
|
||||||
|
{'1': 'kind'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `TransactionEvalError`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List transactionEvalErrorDescriptor = $convert.base64Decode(
|
||||||
|
'ChRUcmFuc2FjdGlvbkV2YWxFcnJvchJfCh9jb250cmFjdF9jcmVhdGlvbl9ub3Rfc3VwcG9ydG'
|
||||||
|
'VkGAEgASgLMhYuZ29vZ2xlLnByb3RvYnVmLkVtcHR5SABSHGNvbnRyYWN0Q3JlYXRpb25Ob3RT'
|
||||||
|
'dXBwb3J0ZWQSWgocdW5zdXBwb3J0ZWRfdHJhbnNhY3Rpb25fdHlwZRgCIAEoCzIWLmdvb2dsZS'
|
||||||
|
'5wcm90b2J1Zi5FbXB0eUgAUhp1bnN1cHBvcnRlZFRyYW5zYWN0aW9uVHlwZRJPChFub19tYXRj'
|
||||||
|
'aGluZ19ncmFudBgDIAEoCzIhLmFyYml0ZXIuZXZtLk5vTWF0Y2hpbmdHcmFudEVycm9ySABSD2'
|
||||||
|
'5vTWF0Y2hpbmdHcmFudBJRChFwb2xpY3lfdmlvbGF0aW9ucxgEIAEoCzIiLmFyYml0ZXIuZXZt'
|
||||||
|
'LlBvbGljeVZpb2xhdGlvbnNFcnJvckgAUhBwb2xpY3lWaW9sYXRpb25zQgYKBGtpbmQ=');
|
||||||
|
|
||||||
@$core.Deprecated('Use evmGrantCreateRequestDescriptor instead')
|
@$core.Deprecated('Use evmGrantCreateRequestDescriptor instead')
|
||||||
const EvmGrantCreateRequest$json = {
|
const EvmGrantCreateRequest$json = {
|
||||||
'1': 'EvmGrantCreateRequest',
|
'1': 'EvmGrantCreateRequest',
|
||||||
@@ -563,7 +865,7 @@ const EvmSignTransactionResponse$json = {
|
|||||||
'3': 2,
|
'3': 2,
|
||||||
'4': 1,
|
'4': 1,
|
||||||
'5': 11,
|
'5': 11,
|
||||||
'6': '.arbiter.shared.evm.TransactionEvalError',
|
'6': '.arbiter.evm.TransactionEvalError',
|
||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'evalError'
|
'10': 'evalError'
|
||||||
},
|
},
|
||||||
@@ -585,9 +887,9 @@ const EvmSignTransactionResponse$json = {
|
|||||||
/// Descriptor for `EvmSignTransactionResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `EvmSignTransactionResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List evmSignTransactionResponseDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List evmSignTransactionResponseDescriptor = $convert.base64Decode(
|
||||||
'ChpFdm1TaWduVHJhbnNhY3Rpb25SZXNwb25zZRIeCglzaWduYXR1cmUYASABKAxIAFIJc2lnbm'
|
'ChpFdm1TaWduVHJhbnNhY3Rpb25SZXNwb25zZRIeCglzaWduYXR1cmUYASABKAxIAFIJc2lnbm'
|
||||||
'F0dXJlEkkKCmV2YWxfZXJyb3IYAiABKAsyKC5hcmJpdGVyLnNoYXJlZC5ldm0uVHJhbnNhY3Rp'
|
'F0dXJlEkIKCmV2YWxfZXJyb3IYAiABKAsyIS5hcmJpdGVyLmV2bS5UcmFuc2FjdGlvbkV2YWxF'
|
||||||
'b25FdmFsRXJyb3JIAFIJZXZhbEVycm9yEi0KBWVycm9yGAMgASgOMhUuYXJiaXRlci5ldm0uRX'
|
'cnJvckgAUglldmFsRXJyb3ISLQoFZXJyb3IYAyABKA4yFS5hcmJpdGVyLmV2bS5Fdm1FcnJvck'
|
||||||
'ZtRXJyb3JIAFIFZXJyb3JCCAoGcmVzdWx0');
|
'gAUgVlcnJvckIICgZyZXN1bHQ=');
|
||||||
|
|
||||||
@$core.Deprecated('Use evmAnalyzeTransactionRequestDescriptor instead')
|
@$core.Deprecated('Use evmAnalyzeTransactionRequestDescriptor instead')
|
||||||
const EvmAnalyzeTransactionRequest$json = {
|
const EvmAnalyzeTransactionRequest$json = {
|
||||||
@@ -613,7 +915,7 @@ const EvmAnalyzeTransactionResponse$json = {
|
|||||||
'3': 1,
|
'3': 1,
|
||||||
'4': 1,
|
'4': 1,
|
||||||
'5': 11,
|
'5': 11,
|
||||||
'6': '.arbiter.shared.evm.SpecificMeaning',
|
'6': '.arbiter.evm.SpecificMeaning',
|
||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'meaning'
|
'10': 'meaning'
|
||||||
},
|
},
|
||||||
@@ -622,7 +924,7 @@ const EvmAnalyzeTransactionResponse$json = {
|
|||||||
'3': 2,
|
'3': 2,
|
||||||
'4': 1,
|
'4': 1,
|
||||||
'5': 11,
|
'5': 11,
|
||||||
'6': '.arbiter.shared.evm.TransactionEvalError',
|
'6': '.arbiter.evm.TransactionEvalError',
|
||||||
'9': 0,
|
'9': 0,
|
||||||
'10': 'evalError'
|
'10': 'evalError'
|
||||||
},
|
},
|
||||||
@@ -643,8 +945,7 @@ const EvmAnalyzeTransactionResponse$json = {
|
|||||||
|
|
||||||
/// Descriptor for `EvmAnalyzeTransactionResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `EvmAnalyzeTransactionResponse`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List evmAnalyzeTransactionResponseDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List evmAnalyzeTransactionResponseDescriptor = $convert.base64Decode(
|
||||||
'Ch1Fdm1BbmFseXplVHJhbnNhY3Rpb25SZXNwb25zZRI/CgdtZWFuaW5nGAEgASgLMiMuYXJiaX'
|
'Ch1Fdm1BbmFseXplVHJhbnNhY3Rpb25SZXNwb25zZRI4CgdtZWFuaW5nGAEgASgLMhwuYXJiaX'
|
||||||
'Rlci5zaGFyZWQuZXZtLlNwZWNpZmljTWVhbmluZ0gAUgdtZWFuaW5nEkkKCmV2YWxfZXJyb3IY'
|
'Rlci5ldm0uU3BlY2lmaWNNZWFuaW5nSABSB21lYW5pbmcSQgoKZXZhbF9lcnJvchgCIAEoCzIh'
|
||||||
'AiABKAsyKC5hcmJpdGVyLnNoYXJlZC5ldm0uVHJhbnNhY3Rpb25FdmFsRXJyb3JIAFIJZXZhbE'
|
'LmFyYml0ZXIuZXZtLlRyYW5zYWN0aW9uRXZhbEVycm9ySABSCWV2YWxFcnJvchItCgVlcnJvch'
|
||||||
'Vycm9yEi0KBWVycm9yGAMgASgOMhUuYXJiaXRlci5ldm0uRXZtRXJyb3JIAFIFZXJyb3JCCAoG'
|
'gDIAEoDjIVLmFyYml0ZXIuZXZtLkV2bUVycm9ySABSBWVycm9yQggKBnJlc3VsdA==');
|
||||||
'cmVzdWx0');
|
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
// This is a generated file - do not edit.
|
|
||||||
//
|
|
||||||
// Generated from shared/client.proto.
|
|
||||||
|
|
||||||
// @dart = 3.3
|
|
||||||
|
|
||||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
|
||||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
|
||||||
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
|
|
||||||
|
|
||||||
import 'dart:core' as $core;
|
|
||||||
|
|
||||||
import 'package:protobuf/protobuf.dart' as $pb;
|
|
||||||
|
|
||||||
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
|
|
||||||
|
|
||||||
class ClientInfo extends $pb.GeneratedMessage {
|
|
||||||
factory ClientInfo({
|
|
||||||
$core.String? name,
|
|
||||||
$core.String? description,
|
|
||||||
$core.String? version,
|
|
||||||
}) {
|
|
||||||
final result = create();
|
|
||||||
if (name != null) result.name = name;
|
|
||||||
if (description != null) result.description = description;
|
|
||||||
if (version != null) result.version = version;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
ClientInfo._();
|
|
||||||
|
|
||||||
factory ClientInfo.fromBuffer($core.List<$core.int> data,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromBuffer(data, registry);
|
|
||||||
factory ClientInfo.fromJson($core.String json,
|
|
||||||
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
|
||||||
create()..mergeFromJson(json, registry);
|
|
||||||
|
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
|
||||||
_omitMessageNames ? '' : 'ClientInfo',
|
|
||||||
package: const $pb.PackageName(_omitMessageNames ? '' : 'arbiter.shared'),
|
|
||||||
createEmptyInstance: create)
|
|
||||||
..aOS(1, _omitFieldNames ? '' : 'name')
|
|
||||||
..aOS(2, _omitFieldNames ? '' : 'description')
|
|
||||||
..aOS(3, _omitFieldNames ? '' : 'version')
|
|
||||||
..hasRequiredFields = false;
|
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
ClientInfo clone() => deepCopy();
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
|
||||||
ClientInfo copyWith(void Function(ClientInfo) updates) =>
|
|
||||||
super.copyWith((message) => updates(message as ClientInfo)) as ClientInfo;
|
|
||||||
|
|
||||||
@$core.override
|
|
||||||
$pb.BuilderInfo get info_ => _i;
|
|
||||||
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static ClientInfo create() => ClientInfo._();
|
|
||||||
@$core.override
|
|
||||||
ClientInfo createEmptyInstance() => create();
|
|
||||||
@$core.pragma('dart2js:noInline')
|
|
||||||
static ClientInfo getDefault() => _defaultInstance ??=
|
|
||||||
$pb.GeneratedMessage.$_defaultFor<ClientInfo>(create);
|
|
||||||
static ClientInfo? _defaultInstance;
|
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.String get name => $_getSZ(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
set name($core.String value) => $_setString(0, value);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
$core.bool hasName() => $_has(0);
|
|
||||||
@$pb.TagNumber(1)
|
|
||||||
void clearName() => $_clearField(1);
|
|
||||||
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$core.String get description => $_getSZ(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
set description($core.String value) => $_setString(1, value);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
$core.bool hasDescription() => $_has(1);
|
|
||||||
@$pb.TagNumber(2)
|
|
||||||
void clearDescription() => $_clearField(2);
|
|
||||||
|
|
||||||
@$pb.TagNumber(3)
|
|
||||||
$core.String get version => $_getSZ(2);
|
|
||||||
@$pb.TagNumber(3)
|
|
||||||
set version($core.String value) => $_setString(2, value);
|
|
||||||
@$pb.TagNumber(3)
|
|
||||||
$core.bool hasVersion() => $_has(2);
|
|
||||||
@$pb.TagNumber(3)
|
|
||||||
void clearVersion() => $_clearField(3);
|
|
||||||
}
|
|
||||||
|
|
||||||
const $core.bool _omitFieldNames =
|
|
||||||
$core.bool.fromEnvironment('protobuf.omit_field_names');
|
|
||||||
const $core.bool _omitMessageNames =
|
|
||||||
$core.bool.fromEnvironment('protobuf.omit_message_names');
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
// This is a generated file - do not edit.
|
|
||||||
//
|
|
||||||
// Generated from shared/client.proto.
|
|
||||||
|
|
||||||
// @dart = 3.3
|
|
||||||
|
|
||||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: curly_braces_in_flow_control_structures
|
|
||||||
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
|
|
||||||
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user