Blog
Bytes crossed two radio hops between four boards and arrived byte-exact, with one coverage seal recomputed independently at three points and agreeing at all three. What that proves, what it does not, what the thing is built on, and where the commercial radios are plainly ahead.
Four single-board radios on a desk, one subnet, 2.4 GHz. Bytes left the first board, crossed two radio hops, and arrived byte-exact at the fourth. At three separate points along that path a coverage seal was recomputed independently, and all three agreed: 0x9DBE2510. That is the whole result, and the rest of this page is about what it is worth.
Ternary here is one specific circuit, and it deserves naming before anything else: a two-bit weight code where 01 selects +x, 10 selects -x and everything else selects 0. Every multiply becomes a sign flip, the multiplier disappears, and a plain adder tree does the rest. That single primitive carries the 8-tap matched filter, the 63-tap PN despreader, a systolic tile, and a small classifier whose weights are all -1, 0 or +1.
A second, unrelated thing in the same repository also wears the word. GF-T, the number format, has an exponent that is balanced-ternary only in the sense that its code space holds 3^Et values centred on a bias. In hardware the exponent update is a_off + b_off + carry - BIAS: an ordinary binary add. No trit is ever materialised. Both are called ternary and only the first is a ternary circuit.
| Block | What is ternary about it | Highest status actually reached | What proves it |
|---|---|---|---|
| Ternary MAC probe | The primitive on its own: the processor hands the fabric a sample and a two-bit weight, the fabric answers +x, -x or 0 | Ran in the PL of a board on 19 Aug 2026: anchor 0x47C0, heartbeat moving, and +42, -42, 0, 0, +127, -127 for weight codes 01, 10, 00, 11 -- bit-exact | results/ps7_probe_silicon_2026-08-19.log, produced on the day |
| 8-tap matched filter | Sign-select taps; the sync code uses all three symbols, two of eight taps are 0 | Ran in the fabric of a Zynq XC7Z020 on real captured air: 256 of 256 bit-exact | results/cstream2.log, on branch feat/own-rtl-in-radio-path |
| 63-tap PN despreader | 63 sign-select taps summed in a balanced adder tree, zero DSP48E1 by construction | Ran in the fabric: 256 of 256 bit-exact, with negative controls and two faults injected into the bitstream itself failing as predicted | results/pn63_success.log, negative_controls.log, bitstream_mutants.log, same branch |
| Systolic tile and classifier | Same primitive; the classifier's weight file uses exactly three codes and nothing else | Synthesised: 527 LUT, 45 CARRY4, 165 flip-flops and no DSP48E1 cell at all | yosys synth_xilinx on tern_corr8_stream, re-run rather than quoted |
| GF-T arithmetic in RTL | Exponent code space of 3^Et values; the add itself is binary | Simulated and synthesised: a 16-bit multiply is 1 DSP48E1, 47 LUT, 18 CARRY4 | fpga/gft/SYNTH_RESULTS.md, reproduced |
| GF-T on silicon | Would be the same format, executing on an Artix-7 | Not proven. Claimed in detail, but no place-and-route log, no bitstream, no UART capture exists in any branch | docs/VERIFIABLE_COMPUTE.md asserts it; fpga/gft/SYNTH_RESULTS.md says place-and-route and a loadable bitstream are not proven |
That last row was worse than a gap when this page first went up: one document claimed the engines had been flashed to a board, another in the same directory said a loadable bitstream was not proven. Both could not be true. The correction is written and public as tri-net pull request 382 -- it makes the claim read as simulation and names the four artefacts that would close it: a pin-complete constraint file, a routing log naming the top, the bitstream that log produced, and a programming transcript with the part identifier read back. The quoted vectors could never have settled it, because they are the same golden vectors the simulation benches carry.
That row is not a quotation from an old log. It was produced while this page was being written, on a board plugged into the desk: the JTAG chain reads back part identifier 0x23727093, the XC7Z020 this page claims, and after the load the fabric answered with the anchor 0x47C0 that separates our bitstream from the vendor one. The first attempt rebooted the board -- writing a bitstream while the radio DMA is mid-transaction hangs the processor and only the watchdog ends it -- so the load now tears the bus down first: kill the sample streams, unbind the radio cores, unbind both DMA engines, drop the fabric Ethernet, then write. A plain reboot restored the vendor image and the radio afterwards, which is why this is a safe thing to have done.
It is not a product you can buy. It is an open repository -- one Rust crate, Apache-2.0 -- plus a log of measurements taken on hardware anyone can order, written so a stranger can re-run them. What the code does: it moves bytes between radio nodes and, for every hop, emits a signed receipt saying which node carried how many bytes in which epoch.
The hardware is four Puzhi P201Mini boards, each a Zynq-7020 with an AD9361 radio, plus one ALINX AX7203 (XC7A200T) on the bench for the arithmetic work. You buy the boards yourself; nothing here is sold.
The claim under test is narrow and it is an accounting claim, not a radio claim: when a byte crosses a network of independent radios, can the network produce a record of who carried it that a third party could check without trusting any of them?
| Layer | What it actually is | Where it runs | Artefact |
|---|---|---|---|
| Node board | Puzhi P201Mini: Zynq-7020, part xc7z020clg400-1, dual Cortex-A9 plus Artix-class fabric, with an AD9361 transceiver | Four of them, on one subnet | tools/jtag-bootstrap/README.md |
| Bench board | ALINX AX7203: Artix-7, part xc7a200tfbg484-2, 740 DSP48E1 and 134,600 6-LUTs | Desk, separate from the radio nodes | fpga/gft/gft_alu_ax7203.xdc |
| Radio front end | Analog Devices AD9361, driven from ARM Linux over the IIO interface -- LO, bandwidth, sampling rate, RSSI | On the node's Linux, not in the fabric | tools/ad9361_config.rs |
| Bitstream flow -- open | yosys synth_xilinx, then nextpnr-xilinx, then prjxray fasm2frames and xc7frames2bit, in the openXC7 container. No Vivado anywhere in this path | Host, under Docker | fpga/ternary/ps7/build/run_openxc7.sh |
| Boot chain -- still vendor | BOOT.BIN and the FSBL come from Xilinx bootgen via PetaLinux, or from the vendor board image. The fabric flow is open; the boot chain under it is not | Host, preparing the SD card | docs/LOCAL_FLASH.md |
| Software | One Rust crate, trios-mesh, edition 2021, Apache-2.0: 10,805 lines under src/ and 432 test blocks. Above it, 107 t27 specification files that generate the Rust, C and Zig | Cross-compiled armv7 musl, run on the Cortex-A9 | Cargo.toml, tri-facts.json |
| The link that carried the bytes | A DBPSK modem on a 768 kHz subcarrier with a 63-bit PN preamble and a differential detector, fed to the AD9361 through iio_writedev and iio_readdev | In software on the node's ARM, with no bitstream of ours loaded into the fabric | smoke/DEPIN_OTA_CLOSED_2026-07-18.md |
| Session encryption | ChaCha20-Poly1305 AEAD, key from an X25519 handshake through HKDF-SHA256, directional nonce, 64-frame replay window, re-key every 2^20 frames | Node ARM and host | src/crypto.rs |
| Receipts | Ed25519 signatures verified against the executor's public key; SHA-256 digests and Merkle batching | Node ARM and host | src/bin/trinet_a2a_node.rs |
| Coverage seal | epoch_seal: three rounds of a 32-bit xor-shift-add mixer combined with a rotate. It binds identity, epoch and byte count into one 32-bit word | On the nodes during relay runs | specs/tri_depin.t27 |
| Routing | An ETX metric with a WMEWMA delivery estimator, mesh subnet 10.42.0.0/24, default TTL 8. Marked -sim: host-testable. The shipped daemon uses UDP-over-Ethernet as a stand-in for the radio link | Host tests; daemon on the nodes | src/routing.rs, src/bin/trios_meshd.rs |
| Arithmetic in the fabric | GF-T, a float ladder with a balanced-ternary exponent. A 16-bit multiplier synthesises to 1 DSP48E1, 47 LUTs and 18 CARRY4 | Real silicon on the AX7203, over UART | fpga/gft/SYNTH_RESULTS.md |
Two rows in that table are the ones to read twice. The bitstream flow is genuinely open -- no Vivado produces the fabric image -- but the boot chain beneath it still comes from the vendor, and saying otherwise would be the easiest lie on this page. And the modem that actually carried the bytes ran in software on the ARM core: no bitstream of ours has ever been configured into a node's fabric.
epoch_seal is three rounds of a 32-bit mixer. It is keyed, and it binds identity, epoch and byte count, so two witnesses agreeing on it is evidence they metered the same traffic. It is not a cryptographic hash, it is 32 bits wide, and the specification's own comment says a production settlement would additionally sign it. Session traffic is separately protected by ChaCha20-Poly1305; the seal is the accounting digest on top, and it is the weaker of the two.
| Run | Result | Under what conditions | Recorded in |
|---|---|---|---|
| Two radio hops, .13 to .12 to .10 | Coverage seal 0x9DBE2510 identical at all three points; 4 distinct payloads at hop 1 | One shared 2.4 GHz channel, store-and-forward, hops time-separated rather than concurrent | DEPIN_2HOP_RELAY_2026-07-18.md |
| Two hops carrying an ordered 31-byte message | 6 of 6 chunks at both hops; seal 0x37A9A9F6 identical at origin, relay and receiver | Fixed test string on a cyclic buffer | DEPIN_MSG_RELAY_2026-07-18.md |
| Two hops with the hops running at the same time | 5 of 5 chunks at the relay while its own transmitter ran; seal 0xE0AA4F5D identical at three points | 50 MHz transmit/receive separation on one AD9361 in FDD | DEPIN_FDD_PIPELINE_2026-07-18.md |
| Three hops, .13 to .12 to .11 to .10 | Correlation peak 1.000, 0.998, 0.998; bit error rate 0 of 128 on every hop, first attempt on each | One transmitter at a time on the shared medium; capture retried until clean | DEPIN_RELAY3_RTI_2026-07-19.md |
| Four nodes, one transmitter, three witnesses | All three witnesses independently reported 8 distinct frames and the same seal 0xCDB1F3B1 | Each witness captured the air on its own and metered what it heard | DEPIN_STREAMING_4NODE_2026-07-18.md |
| Eight bytes over the air, closed loop | Correlation peak 1.000, bit error rate 0 of 64, payload recovered exactly -- twice | 8-byte frame, transmit LO 2400 MHz at -10 dB gain | DEPIN_OTA_CLOSED_2026-07-18.md |
| Transmit power sweep | -10 dB: 0 of 64 errors. -20 dB: 0 of 64. -30 dB: 0 of 64. -40 dB: 34 of 64 -- the link breaks | Gain is the AD9361 setting in dB, not radiated power | DEPIN_RADIO_RECEIPT_2026-07-18.md |
| How far apart two channels must be on one chip | 50 MHz and 20 MHz separation: clean. 15 MHz and 13 MHz: wrong seal on some chunks | Analog limit; narrowing the receive filter did not help | DEPIN_FDD_SWEEP_LIVE_2026-07-18.md |
| Sixteen-megabyte capture, demodulated offline | 815 frames, 748 clean (92%), 6,520 bytes, one receipt | About 136 ms of air; roughly 3 s of processing on the ARM. One run, not repeated | DEPIN_STREAMING_4NODE_2026-07-18.md |
| Why frame tracking was needed | Fixed grid: 0 of 819 frames clean. Two boards' 30.72 MHz oscillators differ by about 10 ppm | The control condition that locked-position tracking replaced | DEPIN_STREAMING_4NODE_2026-07-18.md |
| Arithmetic on real silicon | 5 of 5 GF-T16 multiplies and 3 of 3 dot products bit-exact against a host oracle | AX7203, openXC7 flow, results returned over UART | docs/VERIFIABLE_COMPUTE.md |
| Adversarial cases on the sealed wire | 5 negatives rejected: tampered assignment, tampered result, replayed counter, operand swap, and a blind relay | Real AEAD with the routing header as associated data | docs/VERIFIABLE_COMPUTE.md |
The first attempt at hop one did not produce 0x9DBE2510. It produced 0x7ACD8A11, from five distinct payloads instead of four: one frame carried bit errors, passed the correlation gate at 0.9, and decoded into a payload that was never transmitted. The seal did exactly what a seal is for -- it disagreed. A majority filter over repeated copies was added, and the run was repeated until the three points agreed.
This is the part worth keeping. A metering scheme that never disagrees with itself has not been tested; this one disagreed, the disagreement was traced to a specific decoder weakness, and both the failure and the fix are in the log.
Strip the radio away and what remains is bookkeeping. Each node keeps a running count of the bytes it carried, binds that count to its own identity and to an epoch, and signs the result. A second node that heard the same traffic produces the same digest independently. Settlement is then arithmetic over records that several parties can check, rather than a claim by whoever owns the base station.
That is why the interesting artefact of a two-hop relay is not the bytes. It is three independent recomputations of one number by three machines that did not consult each other.
| Situation | What the receipt proves | Who has a reason to pay |
|---|---|---|
| Someone is paid to provide coverage in a place nobody audits -- a rural relay, a campus, a building | That traffic actually crossed that node in that epoch, attested by neighbours who also heard it | Whoever funds the coverage and currently has to take the operator's word |
| A sensor network bills by data delivered | How many bytes reached the far end, not how many were sent hopefully | The party being invoiced |
| A contractor claims a link was up during an outage window | Per-epoch counts from more than one witness, which a single operator cannot forge alone | Insurers and whoever wrote the service-level clause |
| A compute job runs on a device you do not own | That the returned answer matches a signed commitment to the inputs and the code path | Whoever pays for the compute |
None of these is a deployment. No settlement contract is deployed, and no buyer for any of it exists today. They are the situations the measurements were designed against.
| Who | What they sell | What they do not give you | Where this differs |
|---|---|---|---|
| Persistent Systems, MPU5 | A fielded MIMO mesh radio; datasheet ceiling up to 150 Mbps, FIPS 140-2 Level 2 crypto module | No published waveform or routing spec, no conformance vectors | The wire contract here is a file in the repository under Apache-2.0 |
| Silvus, StreamCaster | Proprietary MIMO waveform, 100+ Mbps, 7 ms average latency at 20 MHz | No published spec, no conformance vectors, no unit price | The largest network demonstrated here is four nodes; three of them independently metered the same air |
| Rajant, Kinetic Mesh | Patented Layer-2 mesh routing, no root node, hundreds of Mbps per radio | Base implementation is capable but not certified | Crypto here is ChaCha20-Poly1305 with X25519, specified in-repo and certified by nobody -- a weaker assurance, stated plainly |
| Doodle Labs, Mesh Rider | Low-power multiband mesh for drones; NASA measured 37.9, 5.6, 1.2 and 0.3 Mbit/s at one to four hops | Encryption and routing internals not published | Bytes here survived two hops bit-exact with a matching seal, but no throughput at any hop count was ever measured |
| Meshtastic | Open LoRa mesh, 0.3 to 300 kbps, AES-256 in software, tens of kilometres | No wideband channel, no metering or reward layer | Wideband 2.4 GHz and a receipt layer, against a far smaller and far less mature ecosystem |
| openwifi / open-sdr | Fully open Verilog 802.11 with the OFDM physical layer running in FPGA fabric | Not mesh-native; targets a consumer standard | The closest open competitor, and ahead where it counts: its physical layer runs in fabric. Ours ran in software on the ARM |
| AREDN | Free firmware turning commercial routers into a 2.4/5 GHz mesh, 5 to 50 km | No custom waveform, no metering or attestation layer | Ahead on maturity and deployed node count, which the repository lists as its own disadvantage |
| Helium and similar token networks | Token rewards to hotspot operators, plus carrier data offload | Participation is not bound to a specific verified device | Three witnesses here independently produced the same coverage seal from air they each heard |
Read that table for where it loses. On throughput, latency, range, ecosystem and certification, the commercial radios are ahead by margins that are not close, and openwifi is ahead on the one open-hardware axis that matters most. What is different here is narrow: the accounting layer, the fact that the wire contract is a file rather than a datasheet bullet, and a log that records the failures alongside the passes.
A partner joining now is joining a measurement discipline, not a network. What exists is the instrument: hardware anyone can buy, a toolchain with no vendor licence in the fabric path, and a log in which the failures are written down with the same care as the results.
Every figure above is measured, and the limits are named with it.