device ~rac@1.0: Ratcheting Authenticated Channels. - #1033
Open
samcamwilliams wants to merge 1 commit into
Open
Conversation
~rac@1.0 provides ordered, exactly-once, replay-resistant messaging between AO processes. A sender stamps each outbound message with a monotonic per-channel slot (send); a recipient admits a message only when its slot satisfies the channel's ingest rule -- strict, ratchet, or conditional-ratchet -- advancing a per-sender ratchet (compute) and rejecting replays and mis-ordered delivery. It composes ahead of an application device in a process execution stack, or a custom execution device can call it directly. Preloaded util device alongside ~dedup@1.0. Tests cover the ingest rules, a ~stack@1.0 skip-protocol integration, and a ~process@1.0 + ~lua@5.3a process driving send/compute via ao.resolve.
samcamwilliams
marked this pull request as ready for review
July 22, 2026 22:40
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Device: ~rac@1.0
~rac@1.0(Ratcheting Authenticated Channels) provides ordered, exactly-once, replay-resistant messaging between AO processes.tl;dr:
~rac@1.0provides ordered, exactly-once, replay-resistant messaging between AO processes. A sender stamps each outbound message with a monotonic per-channel slot (send); a recipient admits a message only when its slot satisfies the channel's ingest rule -- strict, ratchet, or conditional-ratchet -- advancing a per-sender ratchet (compute) and rejecting replays and mis-ordered delivery. It composes ahead of an application device in a process execution stack, or an execution device can call it directly.Tests cover the ingest rules,
~stack@1.0skip statuses, and a~process@1.0+~lua@5.3aexecution device process drivingsend/computeviaao.resolve.Full details, from the spec...
Overview
It is often necessary for an AO process to consume a stream of authenticated
messages — events, or state updates — from another process, applying each at
most once and in a well-defined order.
~rac@1.0addresses this by numbering messages rather than identifying them. Asender assigns each message a monotonic slot on a named channel; a recipient
maintains a per-sender ratchet and admits a message only when its slot
satisfies the channel's ingest rule, rejecting replays and mis-ordered delivery.
Core concept: channels, slots, and the ratchet
A channel is a named, unidirectional stream from a sending process to a
receiving process. Channels are independent and their slots begin at
0.stamps the next slot onto each outbound message.
each channel — the ratchet — and advances it as messages are admitted.
A message carries its slot, its channel, and the ingest rule under which the
recipient may admit it.
State
base/rac-outbound/<address>/<channel><address>on<channel>. Absent ⇒-1; the next slot emitted is0.base/rac-inbound/<address>/<channel><address>on<channel>(the ratchet). Absent ⇒-1.rac-slotrac-channeldefault.rac-ratchetcompute). Absent ⇒false.<address>is the counterparty's process identifier: on the sending side, therecipient's address; on the receiving side, the sender's address, taken from the
inbound message's
from-processfield (established by~push@1.0, and notsettable by the sending process's own logic).
rac-outboundandrac-inboundare the only state this device introduces.Keys
sendAction: Emit
bodytorecipienton a channel, stamping it with thechannel's next slot and appending it to the process outbox for delivery.
Inputs (Request):
recipient— (required) address of the destination process.body— (required) the message to send.channel— (optional, defaultdefault) channel name.ratchet— (optional, defaultfalse) ingest rule to stamp on the message:false,true, or a non-negative integer (seecompute).Behaviour:
Slot = base/rac-outbound/<recipient>/<channel>+ 1 (a missing counter is-1, so the first slot is0).rac-slot = Slotandtarget = recipient,plus
rac-channel/rac-ratchetwhen they differ from their defaults.results/outbox).base/rac-outbound/<recipient>/<channel> = Slot.Response:
{ok, <base>}— the base with the outbox entry appended and theoutbound counter advanced.
Delivery, provenance (
from-process), and any re-signing to the recipient'spolicy are performed by
~push@1.0when it drains the outbox;~rac@1.0onlystamps and enqueues.
computeAction: Admit or reject one inbound message according to its channel's
ingest rule, advancing the ratchet on admission.
computeis intended to run once per assigned message, ahead of applicationlogic. It is
~multipass@1.0-aware, mirroring~dedup@1.0: it acts only on thefirst pass and returns the base unchanged on later passes.
Inputs: the base is the process state; the inbound message is the request's
body, from whichcomputereadsrac-slot,rac-channel(defaultdefault),rac-ratchet(defaultfalse), and the sender (from-process, orthe message's first committer if absent).
Behaviour:
On any pass other than the first →
{ok, <base>}.If the inbound message has no
rac-slot→{ok, <base>}(untagged trafficpasses through unmodified).
Let
Ratchet = base/rac-inbound/<sender>/<channel>(absent ⇒-1) andSlot = rac-slot. Admit the message iff the ingest rule holds:rac-ratchetfalse(default)Slot == Ratchet + 1trueSlot > RatchetNSlot > RatchetandRatchet >= NNAdmit: set
base/rac-inbound/<sender>/<channel> = Slot, return{ok, <base>}(application logic proceeds). Reject: return{skip, <base>}— state is unchanged; in a stack, no subsequent device runsfor this message.
Example (integer rule). With
rac-inbound/<sender>/default = 5, a messagerac-slot = 8, rac-ratchet = 6is rejected (ratchet has not reached6); oncethe ratchet reaches
6the same message is admitted and jumps it to8. Withrac-ratchet = trueit would be admitted immediately. In either case, once therac-slot = 8message is admitted the ratchet stands at8, so a laterrac-slot = 7is never admissible.Usage
~rac@1.0can be composed two ways:~rac@1.0ahead of the application device(and ahead of
~dedup@1.0, if also used) in a process's execution stack. A{skip, ...}fromcomputehalts the stack for that message, so theapplication device never observes a rejected message.
computeagainst{as, ~rac@1.0, <base>}before running its own logic, treating askipresult as "reject this message." A sender likewise resolves
sendagainst{as, ~rac@1.0, <base>}from within its own compute.Both use-cases are expressed through
rac-ratchet:falsegives ordered,exactly-once event ingest;
trueor an integer gives last-writer-wins statesynchronization that tolerates gaps.
Properties
application state advances at most once per admitted slot.
false, messages are admitted strictly in emission order,per channel.
(
from-process); its integrity is enforced by the~security@1.0device oranother mechanism, not by
~rac@1.0itself.sendandcomputeare pure functions of the base and themessage; no wall-clock, randomness, or node configuration affects their
output.
Conformance
The following behaviours are required. The reference test suite exercises them
through
~lua@5.3ascripts that resolvesendandcomputeagainst{as, ~rac@1.0, <base>}directly, and through a~process@1.0whose executionstack folds
~rac@1.0ahead of an application device.sends on a channel carryrac-slot0, 1, 2, …, andrac-outbound/<recipient>/<channel>tracks the last.channels, or to differentrecipients, keep independent counters.
false). Slots0, 1, 2are admitted in order; theratchet ends at
2.copy with a fresh message id — yields
skip; state is unchanged.false). Slot2while the ratchet is at0yieldsskip; then1and2are admitted in order.true). From a fresh channel, atruemessage with slot5is admitted (ratchet jumps to5); any later slot≤ 5is rejected.rac-slot = 8, rac-ratchet = 6is rejected while the ratchet is below
6, and admitted once it reaches6(jumping to
8); a subsequent slot7is rejected.independently.
~multipass@1.0, an admitted message is appliedonce, not once per pass.
rac-slotis admitted (ok)and unmodified.