Files
signer/plans/otp_multiuser_update_plan.md

6.1 KiB

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.

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:
        {
          "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.