stream_agent_node, package peaq_ros2_stream) that subscribes to the topics you allow, applies field rules, then signs, encrypts, and each message, stores the ciphertext, posts a signed to the Stream backend, anchors the payload hash on peaq, and serves purchased chunks to buyers. Keys never leave the machine in the clear, and the agent publishes no ROS topics.
It runs alongside the peaqos_node runtime: the runtime provides the machine’s identity and the events/submit service the agent calls to anchor data on-chain. For the trust model behind the chunks, see Data streams.
How it works
Per inbound message: capture → field transform → sign → encrypt → chunk → store → post manifest → anchor on-chain. When an order is paid, the agent re-wraps that buyer’s chunk keys and serves the encrypted chunks from a local delivery server.Install and run
The agent lives inpeaq_ros2_stream and depends on peaq_ros2_interfaces (for the PeaqosSubmitEvent service). ROS 2 Humble (Docker image) or Jazzy (native host) are both supported.
peaq_ros2_stream currently ships on the feature/peaqos-ros2-runtime branch and merges to the default branch shortly, so the clone below checks that branch out. Once it lands on the default branch, the git checkout step is no longer needed.config_yaml argument):
rosdep or pip): PyNaCl, PyYAML, boto3, google-api-python-client, google-auth, requests. The agent is inert until stream_agent.enabled is true.
Configuration resolves in this order, last wins: built-in defaults → the
config_yaml file → PEAQOS_STREAM_* environment variables → ROS parameters → an optional standalone policy_path file.Configure: the Data Event Map
The agent reads one YAML block,stream_agent (the alias stream also works; keys accept snake_case or camelCase). This is the Data Event Map: what to read, how to protect each field, where ciphertext goes, and who can decrypt.
stream_config.yaml
enabled, machine_id / agent_id / agent_token / identity_ref and at least one topic and one key recipient are required; recipient_type is machine / owner / operator; each public_key_hex is a 32-byte (64-hex) x25519 key; storage.backend outside the known set falls back to local; delivery.token is required if delivery is on.
Local state defaults under ~/.peaq_robot/ (signing key, sequence counters, chunk .bin files, manifests, the SQLite catalog and key store) and is configurable per path. An offline buffer (SQLite) holds envelopes when the backend is unreachable and drains on a retry timer.
Field rules
Rules run before signing, so protected fields never leave the machine in the clear while the package stays verifiable. Rules are ordered; if anyinclude rule is present the agent starts from an allow-list, otherwise from the full message.
How a message becomes a sellable chunk
- Capture. The subscription fires; the ROS message is converted to a canonical ordered dict.
- Transform. Field rules apply (above).
- Sequence. A monotonic per-(machine, topic, policy version) number is assigned.
- Sign. The agent builds a
peaqos-stream-envelope@v1carrying thepayloadHashand signs it with the machine’s Ed25519 key (signature.keyId,algorithm: "ed25519"). - Encrypt. The transformed payload is encrypted with a fresh per-chunk XChaCha20-Poly1305 key (32-byte key, 24-byte nonce), yielding
plaintextHash,encryptedDataHash, and akeyCommitment. - Chunk ID. A deterministic
sha256:id is computed over{ schemaVersion, previousChunkId, index, plaintextHash, encryptedDataHash }, forming a hash . - Store. The chosen adapter writes the ciphertext and returns a
storageRef. - Manifest. A
peaq.stream.chunks.v1manifest wraps the chunk key to everykey_recipient(x25519 sealed box) and is Ed25519-signed overencryptedDataHash, thenPOSTed to/api/v1/stream/chunks. The envelope isPOSTed to/api/v1/stream/events, returning a receipt. - Anchor. If
peaqos_event.enabled, the agent calls/peaqos_node/events/submit(PeaqosSubmitEvent) with the payload hash asraw_data_hexand{ streamReceiptId, policyId, policyVersion }asmetadata_hex, then patches the receipt with the returnedtxHash.
keyId = {agent_id}-stream-ed25519).
Storage adapters
Every adapter writes the local.bin first (so the delivery server always has a copy), then pushes to the remote.
On-chain anchoring
Anchoring is optional and runs through thepeaqos_node runtime, so that node must be up with events/submit available. The agent submits only the payload hash plus non-secret metadata — never raw data or keys — producing a tamper-evident on-chain record that links each Stream receipt to a peaq .
Selling and delivery
The agent polls the backend for orders. When an order ispaid, it looks up each purchased chunk’s symmetric key from the local key store, re-wraps it to the buyer’s public key (x25519 sealed box, peaq.stream.buyer-access.v1), and submits the . The chunk data is never re-encrypted.
If delivery.enabled, a token-gated local HTTP server serves the encrypted chunks:
Environment variables
Every config field has aPEAQOS_STREAM_* override (applied above the YAML file, below ROS params) — use them to keep secrets out of committed YAML. The essentials:
WALRUS_PUBLISHER_URL, …), S3 (AWS_ACCESS_KEY_ID, AWS_REGION, AWS_ENDPOINT_URL_S3, …), and Google Drive (GOOGLE_APPLICATION_CREDENTIALS, GOOGLE_DRIVE_FOLDER_ID).
Security
- The Ed25519 signing key is generated on first run, written to
signing_key_path(0600), and never sent or logged — only the public key is registered and embedded in manifests. - Per-chunk symmetric keys live only in the local SQLite key store and travel only as sealed-box-wrapped blobs (to recipients, then to the buyer). Plaintext keys never appear in ROS messages, manifests, the backend payload, or logs.
- No secrets cross ROS. The agent publishes no topics; its only ROS egress is the
events/submitservice call, which carries a hash and non-secret metadata. - The delivery server silences request logging and gates every route except
/healthbehinddelivery.token. Keepagent_token,api_key,delivery.token, and storage credentials in environment variables, not committed YAML.

