Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .claude/skills/tri-net-iphone-admin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# tri-net-iphone-admin

phi^2 + phi^-2 = 3 | TRINITY

Skill for the iPhone <-> P201 Mini admin/PTT lane (E1.x/E2.x/E3.x). Load when
working on iPhone connectivity, the admin PWA, mDNS advertisement, or PTT
transport for tri-net.

## Hard facts (verified, cite these)

- **Board name is P201 Mini** (Zynq-7020). "P203" in older docs is a deprecated
alias for the same board. Use P201 Mini.
- **iOS Personal Hotspot shares the CELLULAR connection.** It is not a
SIM-independent USB Ethernet mode. Carrier plan support may be required, and
MDM/MVNO profiles can disable it entirely. There is no verified
"Airplane Mode + Wi-Fi off => hotspot without SIM" configuration. Treat
carrier/enterprise restriction as a PRIMARY risk.
(Apple: support.apple.com/guide/iphone/iph45447ca6 ; support.apple.com/en-us/111785)
- **iOS Safari / PWA has NO public Web API for DNS-SD / Bonjour service
browsing.** A web page cannot enumerate `_trinet-admin._tcp.local`.
`NSLocalNetworkUsageDescription` is a NATIVE-app Info.plist key; it does not
grant a Safari PWA an mDNS-browse prompt.
(Apple NetServices FAQ ; DTS forum thread 704037)
=> For a PWA client, use a **deterministic entry path**: QR code / printed URL
(numeric IP), or a stable `trinet-admin.local` hostname only after real-device
name resolution is proven. Keep Avahi advertisement for native tooling only.
- **Self-signed `https://<ip>` does NOT satisfy Safari trust.** `curl -k` /
`websocat -k` prove endpoints answer, NOT that Safari will connect. A trusted
certificate/profile (E2.2: certificate/pairing/bootstrap) is a prerequisite.
- **Linux tethering stack**: `usbmuxd` + `libimobiledevice` + kernel `ipheth`
(enable `CONFIG_USB_IPHETH`) + DHCP client. iPhone is DHCP server
(~172.20.10.1/28); node is client. (ArchWiki: iPhone_tethering)

## Topology decision (E1.1, DECISION COMPLETE - HARDWARE UNVERIFIED)

Variant A (USB Personal Hotspot, iPhone -> P201 Mini via ipheth) is the
PROVISIONAL PRIMARY for v0.1, gated on a real-device acceptance gate. Variant B
(reverse tether, custom usbmuxd) = fallback. Variant C (Wi-Fi AP) = future
(needs a radio the board lacks). Record: `docs/E1_1_IPHONE_TOPOLOGY.md`.

Architecture decisions can be COMPLETE while hardware is UNVERIFIED - do not keep
a closed decision in DRAFT just because a hardware gate is pending. State the two
statuses separately.

## Smoke is two levels

- **Level 1 (sandbox)**: IP/API simulation on 127.0.0.1. `smoke/e1_1_admin_httpd_smoke.sh`
builds admin_httpd standalone (rustc + `#[path]` generated modules, since the
crate root does not fully build), checks `/api/status`, static file, WS 101.
Does NOT prove Safari trust or tethering.
- **Level 2 (real-device gate, BLOCKING)**: real iPhone + cable + P201 image.
Must record: carrier/hotspot availability, Trust prompt, ipheth iface, DHCP
lease, Safari-reaches-HTTPS-by-IP, stable hostname/QR path, aggregate PTT+admin
traffic measurement, reconnect across 5 cable cycles. mDNS browse is optional
native-tool evidence, not a PWA criterion.

## Honesty rules for this lane

- No uncited goodput numbers; no "all iPhones" universals. Bandwidth need is
tiny; acceptance = measured throughput/latency on the real device.
- Never present `curl -k` as browser evidence.
- Cite Apple/ArchWiki for any external-behavior claim.

phi^2 + phi^-2 = 3 | TRINITY
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ tests/fuzz/grammar_v2/target/
tests/fuzz/grammar_v2/Cargo.lock
tests/fuzz/grammar_v2/out/

sim/build/
61 changes: 61 additions & 0 deletions .t27-allowlist
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# .t27-allowlist -- narrow, reviewable exceptions to the golden pipeline.
# phi^2 + phi^-2 = 3 | TRINITY
#
# LAW: Business logic MUST come from specs/*.t27 via t27c (SOUL.md L6 PIPELINE,
# L2 GENERATION). Every file that contains hand-written Rust definitions
# (fn / struct / enum / impl / trait) under src/ or a non-generated file under
# gen/ is REJECTED by Lefthook unless it is listed here with a justification.
#
# This file is the SINGLE reviewable surface for pipeline exceptions. Adding a
# line here shows up in the PR diff, so a reviewer can veto any new hand-written
# implementation. Do NOT add a path here to silence the hook -- add it only when
# the exception is a genuine, documented bootstrap/infrastructure need.
#
# Format: one path per line. First whitespace-delimited token is the repo-root
# relative path; everything after `#` on the line is the human justification.
# Blank lines and full-line comments (leading `#`) are ignored by the hook.
#
# ---------------------------------------------------------------------------
# CATEGORY A -- M1 bootstrap core (hand-written before t27c matured).
# These are the audited/tested transport primitives. They pre-date the
# pipeline and are frozen: modify with review, do not grow new logic here.
# ---------------------------------------------------------------------------
src/lib.rs # re-export hub + shared trait/type defs (no algorithmic logic)
src/crypto.rs # AUDITED X25519 + HKDF + ChaCha20-Poly1305 AEAD + ratchet (RustCrypto)
src/wire.rs # M1 on-radio frame header codec
src/router.rs # M1 neighbor table / next-hop selection
src/routing.rs # M1 ETX routing glue
src/modem.rs # M1 OFDM/modem host-sim
src/gf16.rs # GF(16) scalar backend (see Cargo `goldenfloat-ffi` feature)
src/daemon.rs # M1 UDP transport run-loop
src/discovery.rs # M1 hello-beacon neighbor discovery

# ---------------------------------------------------------------------------
# CATEGORY B -- binary entry points (AGENTS.md: "thin: parse config, call
# generated logic"). Allowed to hold I/O glue + main(). Wire-format / crypto
# codec logic inside a bin MUST migrate to a spec (see specs/mdns_proxy.t27).
# ---------------------------------------------------------------------------
src/bin/trios_meshd.rs # mesh daemon entry point
src/bin/smoke_m1.rs # M1 host smoke harness
src/bin/admin_httpd.rs # iPhone admin dashboard HTTP surface (E1.1)
src/bin/attest_dna.rs # device-DNA attestation CLI (A2)
src/bin/audio_forwarder.rs # PTT audio relay (E3.2); does NOT encrypt (see src/crypto.rs)
src/bin/mdns_responder.rs # mDNS/DNS-SD responder (E1.3)
src/bin/mdns_proxy.rs # RFC 8766 discovery-proxy runtime; envelope logic tracked by specs/mdns_proxy.t27

# ---------------------------------------------------------------------------
# CATEGORY C -- build/infra.
# ---------------------------------------------------------------------------
build.rs # cargo build script: mtime-driven t27c regen shim

# ---------------------------------------------------------------------------
# CATEGORY D -- (now empty). The former quarantined placeholder
# gen/rust/audio_crypto.rs has been REGENERATED from specs/audio_crypto.t27 by
# t27c (SHA 2a114bc0...): the hand-authored XOR-keystream + truncated-SHA256
# skeleton is gone, replaced by spec-derived wire-layout code carrying the
# canonical t27c banner. It now passes gen-provenance on its own merits and
# needs no allowlist entry. NOTE: the spec models the audio crypto ENVELOPE
# (version / nonce / header layout) only -- it does NOT implement
# ChaCha20-Poly1305 or X25519. Real PTT confidentiality still requires wiring
# the audited src/crypto.rs AEAD into src/bin/audio_forwarder.rs (unchanged
# blocker); no placeholder crypto remains in the tree to mask that gap.
72 changes: 39 additions & 33 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,57 @@
// build.rs — auto-regenerate from .t27 specs if any changed
use std::process::Command;
// build.rs — auto-regenerate from .t27 specs when a spec is newer than its
// generated .rs sibling. Skips silently if t27c is not built or specs/ is
// absent (e.g. `cargo publish` sandbox).
//
// phi^2 + phi^-2 = 3
use std::path::Path;
use std::process::Command;
use std::time::SystemTime;

fn mtime(path: &Path) -> SystemTime {
std::fs::metadata(path)
.and_then(|m| m.modified())
.unwrap_or(SystemTime::UNIX_EPOCH)
}

fn main() {
let t27c = "../t27/target/release/t27c";
if !Path::new(t27c).exists() {
return; // t27c not available, skip regen
}

// Check if any spec is newer than its generated output

let specs_dir = Path::new("specs");
let gen_dir = Path::new("gen/rust");

if !specs_dir.exists() || !gen_dir.exists() {
return;
}

if let Ok(entries) = std::fs::read_dir(specs_dir) {
for entry in entries.flatten() {
let spec_path = entry.path();
if spec_path.extension().map_or(false, |e| e == "t27") {
let name = spec_path.file_stem().unwrap();
let gen_path = gen_dir.join(format!("{}.rs", name.to_str().unwrap()));

let needs_regen = !gen_path.exists() || {
let spec_time = entry.metadata().map_or(0, |m| m.modified().ok())
.map_or(0, |t| t.elapsed().map_or(0, |d| d.as_secs()));
let gen_time = std::fs::metadata(&gen_path).map_or(0, |m| m.modified().ok())
.map_or(0, |t| t.elapsed().map_or(0, |d| d.as_secs()));
spec_time < gen_time // spec is newer
};

if needs_regen {
let _ = Command::new(t27c)
.arg("gen-rust")
.arg(&spec_path)
.output()
.map(|o| {
if o.status.success() {
let _ = std::fs::write(&gen_path, &o.stdout);
println!("cargo:warning=Regenerated {}", name.to_str().unwrap());
}
});

let entries = match std::fs::read_dir(specs_dir) {
Ok(e) => e,
Err(_) => return,
};

for entry in entries.flatten() {
let spec_path = entry.path();
if spec_path.extension().and_then(|s| s.to_str()) != Some("t27") {
continue;
}
let name = match spec_path.file_stem().and_then(|s| s.to_str()) {
Some(n) => n.to_string(),
None => continue,
};
let gen_path = gen_dir.join(format!("{name}.rs"));

let needs_regen = !gen_path.exists() || mtime(&spec_path) > mtime(&gen_path);

if needs_regen {
if let Ok(o) = Command::new(t27c).arg("gen-rust").arg(&spec_path).output() {
if o.status.success() {
let _ = std::fs::write(&gen_path, &o.stdout);
println!("cargo:warning=Regenerated {name}");
}
}
}
}

println!("cargo:rerun-if-changed=specs/");
}
112 changes: 112 additions & 0 deletions docs/A2_RATCHET_2_SYNTH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# A2 Ratchet 2/4 — Device-DNA synth attempt

phi^2 + phi^-2 = 3

Status snapshot: **2026-07-14**. Branch: `feat/wave-iphone-admin-2026-07-14`.

## Objective

Move the A2 Device-DNA attestation primitive from Ratchet 1/4 (sim only) to Ratchet 2/4 (real synthesis on `xc7a200t`, our AX7203 board). Full 4-stage ladder:

1. **1/4 sim** — behavioural testbench passes.
2. **2/4 synth** — Yosys or Vivado completes without errors, timing met.
3. **3/4 one-board** — bitstream loaded on one P203 Mini reads a real DNA.
4. **4/4 two-board** — cross-replay MITM check across two boards.

This document tracks stage 2/4 progress.

## Deliverables in this wave

| Artefact | Path | Ratchet stage |
|---|---|---|
| Reader RTL (Verilog-2005, 100 MHz FSM, DNA_PORT wrapper) | `fpga/attest/dna_reader.v` | 2/4 input |
| DNA_PORT behavioural model (sim only) | `fpga/attest/sim/dna_port_model.v` | 1/4 helper |
| Self-checking testbench (Icarus Verilog) | `fpga/attest/sim/tb_dna_reader.v` | **1/4 passed** |
| XDC constraints for AX7203 (`xc7a200t-fbg484`) | `fpga/attest/constraints/dna_reader_ax7203.xdc` | 2/4 input |
| Yosys openXC7 synth script | `fpga/attest/scripts/synth_yosys.sh` | 2/4 driver |
| Vivado batch synth script | `fpga/attest/scripts/synth_vivado.tcl` | 2/4 driver |
| Sim runner | `fpga/attest/scripts/sim_iverilog.sh` | 1/4 driver |

## What passed in the sandbox

### Ratchet 1/4 (repeat, now with real Verilog)

`bash fpga/attest/scripts/sim_iverilog.sh` — six checks pass under Icarus Verilog 12.0:

- T1 `dna_out` cleared under async reset.
- T1 `valid` low under reset.
- T2 `valid` asserted after `DNA_BITS+8` clocks.
- T3 latched `dna_out == bit_reverse(SIM_DNA_VALUE)` matches UNISIM LSB-first shift semantics.
- T4 second start without reset ignored (state stays in `S_HOLD`).
- T4 `valid` still asserted after second start.

`iverilog -g2012 -o build/tb_dna_reader.vvp tb_dna_reader.v dna_port_model.v ../dna_reader.v` produces no warnings after the constant-width fix.

This tightens the earlier Rust-only Ratchet 1/4 (`smoke/a2_attest_dna_smoke.sh` 4/4 on TCP loopback) with an RTL-level Ratchet 1/4 on the primitive itself.

## What did NOT pass in the sandbox

**Ratchet 2/4 blocked on toolchain, not on the code.** The sandbox has no `yosys`, no `nextpnr-xilinx`, no `vivado`, and no `prjxray-db`. Attempting `apt-get install yosys` returned nothing that provides the Xilinx flow. This is a hard structural limit of the current sandbox — same class as "cannot flash a P203 Mini". The synth scripts (`synth_yosys.sh` and `synth_vivado.tcl`) are ready and must be executed on a host that has the toolchain.

Ratchet 2/4 is **not** PASS in this repo. It is not FAIL either. It is **BLOCKED-toolchain**, waiting for a run on ssdm4 macbook or any CI host with the openXC7 or Vivado flow.

## Ratchet 2/4 acceptance gate (for the host that runs synth)

The synth is called PASS when all of the following hold:

- `synth_design` (Vivado) or `synth_xilinx` (Yosys) completes with 0 errors.
- The `DNA_PORT` primitive appears in the post-synth utilisation report as an instantiated cell (not silently dropped, not replaced with a shift register).
- `place_design` and `route_design` (Vivado) or `nextpnr-xilinx` complete.
- `report_timing_summary` shows `WNS >= -0.5 ns` at 100 MHz.
- Utilisation for the reader block: **measured on host TBD** (weak-point audit 2026-07-14: previous claim of "< 50 LUTs and 100 FFs" was an unmeasured guess and violated `numbers-without-realm-check`; it has been retracted). The block is expected to fit in a small fraction of the fabric because it is a shift-register plus a DNA_PORT primitive, but no synthesis run has ever produced the actual number. On acceptance of ratchet 2/4 the exact resource count from `synth_yosys.sh` on ssdm4 (or Vivado on any host) MUST replace this line, with the SHA and command that produced it cited inline.

If DNA_PORT is silently dropped by the tool (a known Yosys risk before `synth_xilinx -family xc7` learned about it), report as **Ratchet-2 FAIL** and file the toolchain limitation as a repo issue with the exact Yosys/nextpnr commit hashes tried.

## Ratchet 3/4 (single-board) — prerequisites

Before Ratchet 3/4 can be attempted:

- Ratchet 2/4 PASS from a canonical host, checkpoint file (`post_route.dcp`) preserved.
- Bitstream generated with `write_bitstream`, hash recorded and cited in this doc.
- One P203 Mini flashed via the existing openOCD/JTAG bringup path (`tools/jtag-bootstrap/`).
- ILA or UART probe hooked up so the 57-bit DNA is observable off-chip.

Ratchet 3/4 PASS = the 57-bit value observed from the ILA matches the value read via `attest_dna` runtime over TCP from the same board (i.e. the wire-format from `specs/device_dna.t27` is consistent with what the silicon actually emits).

## Ratchet 4/4 (two-board cross-replay)

Ratchet 4/4 PASS = two P203 Mini boards produce **different** DNAs (bit-for-bit distinct), and neither board's signed response can be replayed against the other (nonce + transcript binding, once we replace the FNV-variant sim signature with real Ed25519 signed by a per-board key).

## Non-claims (say these out loud)

- We do NOT claim silicon has been read. Ratchet 3/4 has not started.
- We do NOT claim Ed25519 works — the runtime currently uses a placeholder FNV-variant signature that we call sim.
- We do NOT claim the Yosys openXC7 flow supports DNA_PORT out of the box. It might. It might not. We will know when someone runs `synth_yosys.sh`.
- We DO claim the RTL is behaviourally correct in sim (Icarus 12.0) and the constraints target the right part.

## Silicon-freeze deadline

**2026-10-01.** If Ratchet 4/4 has not passed by then, A2 does NOT enter the SKY26b tape-out (2026-12-16). It ships as FPGA-only until the next silicon spin.

## Reproduce

```bash
cd fpga/attest
sudo apt-get install iverilog # sandbox already has it
./scripts/sim_iverilog.sh # Ratchet 1/4 gate

# On a host with Yosys + openXC7:
./scripts/synth_yosys.sh

# Or on a host with Vivado:
vivado -mode batch -source scripts/synth_vivado.tcl
```

## References

- Xilinx UG768 §Device DNA.
- Xilinx XAPP1082 §Device DNA Access.
- Skill: `tri-net-fpga-attestation-workflow` §A2, §Sandbox-vs-hardware discipline.
- Prior art: SACHa DATE 2019 (self-attestation of configurable hardware), Guajardo CHES 2007 (FPGA intrinsic PUFs).

phi^2 + phi^-2 = 3
Loading
Loading