Stash
Stash is the reference implementation of a nara runtime service. It provides encrypted, memory-only persistence by delegating storage to trusted “confidants”. See the Stash Developer Guide for a deep-dive into the implementation.
1. Purpose
Section titled “1. Purpose”- Enable naras to survive restarts without local disk persistence by “stashing” their state on peers. See the Memory Model for the “Hazy Memory” context.
- Provide a blueprint for runtime services using typed messages, Call() for request/response, and versioned handlers. See Behaviors & Patterns.
- Ensure that only the owner of a stash can ever decrypt it.
2. Conceptual Model
Section titled “2. Conceptual Model”- Owner: The nara whose state is being stored.
- Confidant: A peer that holds an
EncryptedStashfor an owner. - EncryptedStash: A record containing
OwnerID,Nonce,Ciphertext, and aStoredAttimestamp. - Call: Runtime primitive for request/response pairs (e.g.,
store->ack). Services callrt.Call(msg, timeout)and receive responses viaInReplyTomatching. - Encryptor: Derived from the owner’s seed; uses XChaCha20-Poly1305.
Invariants
Section titled “Invariants”- Confidants MUST NOT be able to read the plaintext of the stashes they hold.
- Every stash operation (store/request) MUST be signed and verified via the runtime identity.
- Storage is volatile (RAM-only). If all confidants of an owner restart, the stash is lost.
- Symmetric keys MUST be derived deterministically from the owner’s private seed.
3. External Behavior
Section titled “3. External Behavior”- Storage:
StoreWith(confidantID, data)encrypts data and waits for astash:ack. - Retrieval:
RequestFrom(confidantID)fetches and decrypts the owner’s stash. - Recovery:
RecoverFromAny()attempts retrieval from all configured confidants until success. See Boot Sequence. - Confidant Duty: When receiving a
stash:store, a nara saves the blob in its localstoredmap. - Refresh: Broadcasting
stash-refreshvia MQTT triggers confidants to send back their held stashes over mesh. See Plaza (MQTT).
4. Interfaces
Section titled “4. Interfaces”Service API
Section titled “Service API”StoreWith(targetID, data) error: Encrypt and send to specific peer.RequestFrom(targetID) ([]byte, error): Request and decrypt from specific peer.RecoverFromAny() ([]byte, error): Sequential recovery attempt.SetConfidants([]ID): Configure the set of peers to use for stashing.MarshalState(): Returns the currentstoredmap for debugging.
Message Kinds
Section titled “Message Kinds”stash:store: (MeshOnly) Carries encrypted blob to a confidant. Response:stash:ack.stash:ack: (MeshOnly) Confirmation of storage.stash:request: (MeshOnly) Retrieval request. Response:stash:response.stash:response: (MeshOnly) Carries the encrypted blob back to the owner.stash-refresh: (MQTT) Broadcast tonara/plaza/stash_refresh.
5. Event Types & Schemas
Section titled “5. Event Types & Schemas”stash:store (v1)
Section titled “stash:store (v1)”OwnerID: Nara ID of the owner.Nonce: 24-byte random nonce.Ciphertext: XChaCha20-Poly1305 encrypted blob.Timestamp: When the stash was created.
stash:ack (v1)
Section titled “stash:ack (v1)”OwnerID: Echoed owner ID.Success: Boolean indicating if stored.StoredAt: Confidant’s local timestamp.
stash-refresh (v1)
Section titled “stash-refresh (v1)”OwnerID: ID of the nara looking for its stash.
6. Algorithms
Section titled “6. Algorithms”Encryption (HKDF + XChaCha20-Poly1305)
Section titled “Encryption (HKDF + XChaCha20-Poly1305)”- Key Derivation:
HKDF-SHA256- Salt:
nara:stash:v1 - Info:
symmetric - Seed: 32-byte private seed (derived from Ed25519 key).
- Salt:
- Encryption:
Seal(plaintext)generates a random 24-byte nonce and appends the Poly1305 tag to the ciphertext.
Request/Response (Call)
Section titled “Request/Response (Call)”- Services use
rt.Call(msg, timeout)for request/response patterns. - The runtime’s
CallRegistrytracks pending calls byMessage.ID. - The response MUST set
InReplyToto the request’sID. - When a response arrives, the runtime matches
InReplyToand resolves the pending call. - Default timeout: 30 seconds.
Recovery Workflow
Section titled “Recovery Workflow”- Emit
stash-refreshon MQTT. - Confidants seeing their own ID in the refresh payload (or having a record for that
OwnerID) emitstash:responsevia mesh. - Owner receives
stash:response, matches viaInReplyTo(or direct handling), and decrypts.
7. Failure Modes
Section titled “7. Failure Modes”- Transport Error: If the mesh target is unreachable, the Call times out.
- Decryption Error: If the owner’s seed changes or ciphertext is corrupted,
Openfails. - Missing Stash: If a confidant doesn’t have the requested record, it replies with
Found: false. - Invalid Payload: malformed store/request payloads result in failure acks or ignored messages.
8. Security / Trust Model
Section titled “8. Security / Trust Model”- Confidentiality: Guaranteed by AEAD (XChaCha20-Poly1305). Only the owner possesses the symmetric key.
- Authenticity: Every message is signed by the sender’s nara identity. Confidants verify the owner’s signature before accepting a
stash:store. - Integrity: Poly1305 protects against tampering of the encrypted blobs.
9. Test Oracle
Section titled “9. Test Oracle”TestStashStoreAndAck: Verifies the full store -> ack flow with a mock runtime.TestStashRequestAndResponse: Verifies request -> response -> decryption.TestStashEncryptionDecryption: Validates that HKDF derivation and XChaCha20 round-trip correctly.TestStashStateMarshaling: Ensuresstoredstashes survive service state serialization.
10. Open Questions / TODO
Section titled “10. Open Questions / TODO”- Seed Derivation: Currently uses a placeholder seed; must be linked to the Nara’s soul (Ed25519 seed).
- Auto-Selection: Runtime should eventually provide “Best Confidants” based on uptime/memory heuristics.