Files
signer/plans/otp_multiuser_update_plan.md

99 lines
6.1 KiB
Markdown

# Multi-User OTP Partitioning Parity & Enhancement Plan
## Overview
The multi-user / multi-device OTP dynamic partitioning scheme (`OTP-GRANT-V1`, `end_offset`, `next_extent`, and passive `peer_extent` tracking in a single `<chksum>.state` file) is partially implemented in `signer`, but has several protocol parity gaps compared to `~/lt/otp` (`src/pads.c` and `src/crypto.c`), as well as missing RPC and TUI integration.
```mermaid
sequenceDiagram
participant Donor as Donor Device - otp or signer
participant StateA as Donor .state
participant Recip as Recipient - signer
participant StateB as Recipient .state
Donor->>Donor: mid = align_256((offset + end) / 2)
Donor->>StateA: Write end_offset=mid & peer_extent=mid+256:old_end + fsync
Donor->>Donor: Encrypt OTP-GRANT-V1 at offset mid (256B)
Donor->>Recip: Send -----BEGIN OTP MESSAGE-----
Recip->>Recip: Decrypt chunk at mid & verify Pad-ChkSum
Recip->>Recip: Check no overlap with [offset, end_offset)
Recip->>StateB: Adopt immediately (if new/exhausted) or push next_extent + fsync
```
---
## 1. Core Protocol & Safety Fixes in `src/otp_pad.rs`
1. **Validate `Pad-ChkSum` in `parse_grant_message`**:
- Update `parse_grant_message(plaintext: &[u8]) -> Option<(String, u64, u64)>` to require and return the `Pad-ChkSum: <64hex>` header (matching `parse_grant_message` in `~/lt/otp/src/pads.c`).
- In `OtpPadState::decrypt_encoded`, verify `grant_chksum == self.chksum` before applying the grant; return `Err(SignerError::InvalidInput)` on mismatch.
2. **Harden Grant Import (`apply_grant` logic in `decrypt_encoded`)**:
- **Overlap check**: If the state file already exists, reject any grant where `start < self.partition_end() && self.offset < end` (`Err(SignerError::InvalidInput)`), matching `apply_grant_to_state` in `~/lt/otp/src/pads.c`.
- **Immediate adoption on exhausted partition**: If `self.offset >= self.partition_end() && self.next_extents.is_empty()`, immediately set `self.offset = start` and `self.end_offset = Some(end)` instead of pushing to `next_extents`.
- **Bounds check**: When queueing into `self.next_extents`, enforce `self.next_extents.len() < MAX_NEXT_EXTENTS` (return `Err(SignerError::InvalidInput)` if full).
3. **Crash-Safe Persistence (`fsync`)**:
- In `OtpPadState::persist_state`, open/create the `.tmp` file, write the state content, call `file.sync_all()`, close it, and then `std::fs::rename` over `<chksum>.state`. This guarantees the shrunken `end_offset` is on disk before any grant message is emitted.
4. **Extended `.state` Support in `scan_pads` and `PadInfo`**:
- Expand `PadInfo` to include `end_offset: Option<u64>`, `next_count: usize`, `peer_count: usize`, and a helper `partition_status(&self) -> String` that formats `"whole"`, `"1/2"`, `"2/3+q"`, etc. (matching `format_partition_status` in `~/lt/otp/src/pads.c`).
- Fix `scan_pads` to call `parse_state_file` instead of `s.trim().parse::<u64>()` so `.state` files with `offset=<N>` and multi-line extents report accurate offsets and partition status.
5. **Global Status & Global Split Entry Point**:
- Update `OtpPadState` and `global_status()` to display the active partition upper bound (`offset/end_offset (part 1/2)`) rather than only `offset/pad_size`.
- Add `pub struct OtpSplitOutput { pub pad_chksum: String, pub grant_start: u64, pub grant_end: u64, pub new_end_offset: u64, pub grant_armor: String }` and `pub fn global_split() -> Result<OtpSplitOutput, SignerError>` which calls `split_partition()`, `format_grant_message()`, and `encrypt_encoded_at(grant.start - 256, ...)`.
---
## 2. JSON-RPC & Server Support (`src/enforcement.rs`, `src/dispatcher.rs`, `src/server.rs`)
1. **`VERB_SPLIT` Constant & Routing**:
- Add `pub const VERB_SPLIT: &str = "split";` in `src/enforcement.rs`.
- In `src/dispatcher.rs`:
- Route `VERB_SPLIT` (when `options.algorithm == "otp"` or no other algorithm is specified) to `handle_otp_verb`.
- Include `"split"` in `get_info` verbs list.
- Implement `VERB_SPLIT` in `handle_otp_verb` calling `crate::otp_pad::global_split()` and returning:
```json
{
"grant": "<ascii-armored OTP-GRANT-V1>",
"pad_chksum": "<64hex>",
"grant_start": 2304,
"grant_end": 4096,
"end_offset": 2048
}
```
- In `src/server.rs`, allow `VERB_SPLIT` alongside `VERB_ENCRYPT` and `VERB_DECRYPT` in `process_request`.
---
## 3. TUI Updates (`src/tui.rs`)
1. **OTP Pad Browser (`RoleAddStage::OtpBrowse`)**:
- Display both partition usage and partition status (`whole`, `1/2`, `1/2+q`) from `PadInfo` when browsing pads.
2. **Main Screen Connections Panel**:
- Automatically reflects the richer `global_status()` showing partition bounds and peer/queued counts.
---
## 4. `signer-client` Updates (`src/client/`)
1. **Fix `end_offset` Output Bug in `src/client/main.rs`**:
- In `run_split`, fix line 627 from `println!("This device's new end_offset: {}", grant.start);` to `grant.start - 256` (matching `otp.partition_end()` and `~/lt/otp/src/pads.c`).
2. **Support Daemon RPC Split**:
- Add `otp_split(&mut self, alg: &AlgOptions)` to `SignerSigner` in `src/client/signer.rs`.
- In `src/client/cli.rs` and `src/client/main.rs`, allow `signer-client -a otp split` (or `signer-client split --remote`) to invoke the `split` RPC verb against the running `signer` daemon's bound pad, while keeping `signer-client split <pad-prefix>` for direct local pad splitting.
---
## 5. Tests (`src/otp_pad.rs`, `src/dispatcher.rs`)
1. Update existing unit tests in `src/otp_pad.rs` for the 3-tuple return of `parse_grant_message`.
2. Add unit tests for:
- Rejecting a grant with a mismatched `Pad-ChkSum`.
- Rejecting a grant that overlaps the active `[offset, end_offset)` partition.
- Immediate adoption of a grant when the current partition is exhausted (`offset == end_offset`).
- Rejecting grants beyond `MAX_NEXT_EXTENTS`.
- `scan_pads` parsing `offset=512`, `end_offset=...`, `next_extent=...`, and `peer_extent=...`.
3. Add dispatcher unit test `test_otp_split_and_grant_import_roundtrip` in `src/dispatcher.rs`.