Overview
This document specifies the Daemon gRPC service used to administer a local Seed Daemon instance: key generation/registration, node info, maintenance operations (sync/reindex), raw blob ingestion, device linking, and generic signing.
The API is intentionally low-level and local‑first: it operates the node, key store, and background services. It does not manage application‑level content beyond accepting raw blobs for storage.
Goals
Provide a minimal control plane to initialize a node (mnemonic → key → account binding).
Offer maintenance controls (force sync/reindex) and diagnostics (GetInfo).
Enable device linking (out‑of‑band session exchange) and generic signing.
Support multi‑key setups with CRUD operations over named signing keys.
Non‑Goals
Defining authorization for app resources (covered by AccessControl/Resources).
Managing peer networking directly (see Networking service).
Long‑running streaming diagnostics; this API is request/response.
Protocol Definition
syntax = "proto3";
package com.seed.daemon.v1alpha;
import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";
option go_package = "seed/backend/genproto/daemon/v1alpha;daemon";
// Daemon API allows to control and administer the Seed Daemon.
service Daemon {
// Generates a set of BIP-39-compatible mnemonic words encoding a cryptographic seed.
// This is a stateless call, and the generated mnemonic is not stored anywhere.
// Subsequent call to RegisterKey can be used to register a new signing key derived from the mnemonic.
rpc GenMnemonic(GenMnemonicRequest) returns (GenMnemonicResponse);
// After generating the seed, this call is used to commit the seed and
// create an account binding between the device and account.
rpc RegisterKey(RegisterKeyRequest) returns (NamedKey);
// Get generic information about the running node.
rpc GetInfo(GetInfoRequest) returns (Info);
// Force-trigger periodic background sync of Seed objects.
rpc ForceSync(ForceSyncRequest) returns (google.protobuf.Empty);
// Forces the daemon to reindex the entire database.
rpc ForceReindex(ForceReindexRequest) returns (ForceReindexResponse);
// Lists all the signing keys registered on this Daemon.
rpc ListKeys(ListKeysRequest) returns (ListKeysResponse);
// Updates the existing key.
rpc UpdateKey(UpdateKeyRequest) returns (NamedKey);
// Deletes a key from the underlying key store.
rpc DeleteKey(DeleteKeyRequest) returns (google.protobuf.Empty);
// Deletes all Seed keys from the underlying key store.
rpc DeleteAllKeys(DeleteAllKeysRequest) returns (google.protobuf.Empty);
// Receives raw blobs to be stored.
// The request may fail if blobs can't be recognized by the daemon.
rpc StoreBlobs(StoreBlobsRequest) returns (StoreBlobsResponse);
// Creates a new device link session.
// The session information has to be transferred to the other device,
// to establish a direct P2P connection between the devices, and complete the linking process.
//
// There can only be one active session at a time, and creating a new one will invalidate the previous one.
//
// After the session is redeemed, it becomes invalid.
rpc CreateDeviceLinkSession(CreateDeviceLinkSessionRequest) returns (DeviceLinkSession);
// Get the current device link session (if it exists).
rpc GetDeviceLinkSession(GetDeviceLinkSessionRequest) returns (DeviceLinkSession);
// Sign arbitrary data with an existing signing key.
rpc SignData(SignDataRequest) returns (SignDataResponse);
}
// Request to generate mnemonic words.
message GenMnemonicRequest {
// Optional. Number of mnemonic words to encode the seed.
// Usually 12 or 24 words.
// By default 12 words are generated.
int32 word_count = 1;
}
// Response with the generated mnemonic.
message GenMnemonicResponse {
// The list of human-friendly words that can be used to backup the seed. These
// words must be stored in a secret place by the user.
repeated string mnemonic = 1;
}
// Request to register a new account key derived from the mnemonic.
message RegisterKeyRequest {
// Required. The list of BIP-39 mnemonic words.
repeated string mnemonic = 1;
// Optional. Passphrase for the seed.
string passphrase = 2;
// Required. Private name/label for the signing key, to easily identify keys when they are more than one.
// Name must be unique across all the registered keys.
string name = 3;
}
// Request to get basic information about the running daemon.
message GetInfoRequest {}
// Request to force the syncing process.
message ForceSyncRequest {}
// Request to force reindexing of the entire database.
message ForceReindexRequest {}
// Response after forcing reindexing.
message ForceReindexResponse {}
// Request to delete all keys.
message DeleteAllKeysRequest {}
// Request to list signing keys.
message ListKeysRequest {}
// Response with the list of registered signing keys.
message ListKeysResponse {
// List of registered keys.
repeated NamedKey keys = 1;
}
// Request to change the key name.
message UpdateKeyRequest {
// Current name of the key.
string current_name = 1;
// New name for the key.
string new_name = 2;
}
// Request to delete an existing key.
message DeleteKeyRequest {
// Name of the key to delete.
string name = 1;
}
// Request to store blobs.
message StoreBlobsRequest {
// Required. List of blobs to be stored.
// The request is atomic: either all blobs are stored or none of them.
repeated Blob blobs = 1;
}
// Response after storing blobs.
message StoreBlobsResponse {
// List of CIDs for the stored blobs.
// The order is the same as in the request.
repeated string cids = 1;
}
// Request to create a new device link session.
message CreateDeviceLinkSessionRequest {
// Required. Name of the signing key for which to create the delegation.
string signing_key_name = 1;
// Optional. Label that will be used for the newly created capability.
// The label is publicly visible.
string label = 2;
}
// Request to get the device link session.
message GetDeviceLinkSessionRequest {}
// Request to sign data.
message SignDataRequest {
// Required. Name of the signing key to use for signing.
string signing_key_name = 1;
// Required. Data to be signed.
bytes data = 2;
}
// Response for signing data.
message SignDataResponse {
// Signature over the data.
bytes signature = 1;
}
// Information about the device link session.
message DeviceLinkSession {
// Dial information for the node.
AddrInfo addr_info = 1;
// Secret token for the linking session.
string secret_token = 2;
// Account ID that wants to link the new device.
string account_id = 3;
// Label for the future capability as defined by the user.
string label = 4;
// Expiration time of the session.
google.protobuf.Timestamp expire_time = 5;
// Optional. Time when the session was redeemed,
// i.e. when the device link exchange was completed successfully.
google.protobuf.Timestamp redeem_time = 6;
}
// Address information about a single peer.
message AddrInfo {
string peer_id = 1;
repeated string addrs = 2;
}
// Raw blob to be stored.
message Blob {
// Optional. The client can provide a CID for the blob (the server will verify it).
// If not provided, the data is assumed to be DAG-CBOR encoded, and the server will generate a CID
// using its default hash function.
string cid = 1;
// Required. Raw data of the blob.
bytes data = 2;
}
// Info is a generic information about the running node.
message Info {
// Current state of the daemon.
State state = 1;
// Libp2p Peer ID of this node.
string peer_id = 2;
// Start time of the node.
google.protobuf.Timestamp start_time = 3;
// The libp2p protocol ID that the daemon is using.
string protocol_id = 4;
}
// State describes various states of the daemon.
enum State {
// Daemon in starting up and it not ready to use yet.
STARTING = 0;
// Daemon is running a data migration, which may take a while.
// Callers should poll and wait until the daemon becomes ACTIVE.
MIGRATING = 1;
// Daemon is active and ready to use.
ACTIVE = 3;
}
// Signing key with an internal name.
message NamedKey {
// Public key in Multikey format.
// https://www.w3.org/TR/vc-data-integrity/#multikey.
string public_key = 1;
// Private name for the key. Useful to identify the keys when there're more than one.
string name = 2;
// Account ID representation of this key.
string account_id = 3;
}
Service
Daemon exposes administrative RPCs:
GenMnemonic(GenMnemonicRequest) → GenMnemonicResponse— Generate BIP‑39 mnemonic words for seed backup (stateless, not stored by the daemon).RegisterKey(RegisterKeyRequest) → NamedKey— Import/derive a key from mnemonic + optional passphrase and bind an Account ID, storing under a unique local name.GetInfo(GetInfoRequest) → Info— Snapshot of daemon state, peer ID, start time, and protocol id.ForceSync(ForceSyncRequest) → Empty— Kick the background sync loop.ForceReindex(ForceReindexRequest) → ForceReindexResponse— Trigger a full database reindex.ListKeys(ListKeysRequest) → ListKeysResponse— List all registered signing keys.UpdateKey(UpdateKeyRequest) → NamedKey— Rename an existing key (local alias only).DeleteKey(DeleteKeyRequest) → Empty— Delete a specific key from the keystore.DeleteAllKeys(DeleteAllKeysRequest) → Empty— Purge all Seed keys from the keystore.StoreBlobs(StoreBlobsRequest) → StoreBlobsResponse— Atomically ingest raw blobs, returning CIDs in request order.CreateDeviceLinkSession(CreateDeviceLinkSessionRequest) → DeviceLinkSession— Create a short‑lived session for secure device linking.GetDeviceLinkSession(GetDeviceLinkSessionRequest) → DeviceLinkSession— Fetch the current active session, if any.SignData(SignDataRequest) → SignDataResponse— Sign arbitrary bytes with a named key.
Messages
Mnemonic & Keys
GenMnemonicRequest
word_count(int32, optional) — Typically12or24; server may cap to supported sizes.
GenMnemonicResponse
mnemonic(string[]) — BIP‑39 word list; must be stored securely by the user.
RegisterKeyRequest
mnemonic(string[], required) — The BIP‑39 words.passphrase(string, optional) — Optional BIP‑39 passphrase.name(string, required) — Unique local alias for this key.
NamedKey
public_key(string) — Multikey format (W3C Data Integrity spec).name(string) — Local alias.account_id(string) — Account representation derived from the key.
Node Info & Maintenance
GetInfoRequest — Empty.
Info
state(State) — STARTING | MIGRATING | ACTIVE.peer_id(string) — libp2p peer ID of this node.start_time(Timestamp) — Node start timestamp.protocol_id(string) — libp2p protocol id.
ForceSyncRequest, ForceReindexRequest, ForceReindexResponse — Maintenance controls.
Key Management
ListKeysRequest, ListKeysResponse.keys: NamedKey[] — Enumerate keys.
UpdateKeyRequest
current_name(string) — Existing alias.new_name(string) — New unique alias.
DeleteKeyRequest
name(string) — Alias to remove.
DeleteAllKeysRequest — Purge keystore.
Blobs
StoreBlobsRequest.blobs: Blob[] — Atomic batch of blobs.
Blob
cid(string, optional) — Client‑provided CID (server validates). When absent, server assumes DAG‑CBOR and computes CID with default hash.data(bytes, required) — Raw payload.
StoreBlobsResponse.cids: string[] — CIDs in same order as request.
Device Linking
CreateDeviceLinkSessionRequest
signing_key_name(string, required) — The key to delegate from.label(string, optional) — Public label for the capability created as part of linking.
DeviceLinkSession
addr_info(AddrInfo) — Peer id + multiaddrs to dial.secret_token(string) — One‑time linking secret to authenticate the session.account_id(string) — Account initiating the link.label(string) — Future capability label.expire_time(Timestamp) — Session expiration.redeem_time(Timestamp, optional) — When the session was successfully redeemed.
GetDeviceLinkSessionRequest — Empty.
AddrInfo
peer_id(string) — libp2p peer ID.addrs(string[]) — Dialable multiaddrs.
Signing
SignDataRequest
signing_key_name(string, required) — Alias of key to use.data(bytes, required) — Bytes to sign.
SignDataResponse.signature (bytes) — Signature over data.
State Enum
State
STARTING— Node is booting.MIGRATING— Node is migrating data; wait until ACTIVE.ACTIVE— Node is ready.
Field Semantics & Validation
Mnemonic sizes — Server SHOULD accept only supported BIP‑39 sizes (e.g., 12/24); reject others with
INVALID_ARGUMENT.Key names — Must be unique, non‑empty, and normalized (case policy documented by server). Renames must be atomic.
DeleteAllKeys — Irreversible purge; consider confirmation at higher layers.
Blobs — When
cidis provided, server MUST verify integrity; otherwise, assume DAG‑CBOR and compute canonical CID. Batch is atomic.Device link session — Only one active session at a time; creating a new one invalidates the previous. Sessions expire at
expire_timeor upon successful redemption.SignData — Server signs exactly the provided bytes; callers must apply their own domain separation and hashing policy.
Errors
Representative gRPC status codes:
INVALID_ARGUMENT— Unsupportedword_count, malformed mnemonic, duplicate/new key name invalid, bad CID, empty blob list.ALREADY_EXISTS—RegisterKeyorUpdateKeyname collision.NOT_FOUND— Missing key alias onUpdateKey/DeleteKey/SignData.FAILED_PRECONDITION— Another active device link session exists and cannot be replaced per policy.RESOURCE_EXHAUSTED— Keystore/quota limits; blob size exceeds limits.UNAVAILABLE— Maintenance subsystems down (sync/reindex).DEADLINE_EXCEEDED— Long‑runningForceReindex(if bounded by deadline).PERMISSION_DENIED— Caller lacks admin privileges for the daemon.INTERNAL— Unexpected server error.
Security & Permissions
Mnemonic handling —
GenMnemonicis stateless; never log or persist mnemonics. Encourage users to store mnemonics offline.Keystore — Keys must be stored encrypted at rest; renames and deletes should scrub metadata.
Device linking —
secret_tokenis sensitive; transmit via secure OOB channel. Sessions must be short‑lived and single‑use.Signing — Apply domain separation; record key usage for audit (who signed what/when) without leaking payloads.
Blobs — Validate and cap sizes; verify CIDs to prevent hash‑confusion.
Versioning Strategy
Additive message fields are backwards compatible; breaking changes require new package (e.g.,
v1beta).Introduce new maintenance RPCs feature‑flagged when needed.
Examples
grpcurl — Generate Mnemonic (24 words)
grpcurl api.seed.hyper.media:443 \
com.seed.daemon.v1alpha.Daemon.GenMnemonic \
'{"word_count":24}'
grpcurl — Register Key
grpcurl -H "Authorization: Bearer $TOKEN" \
api.seed.hyper.media:443 com.seed.daemon.v1alpha.Daemon.RegisterKey \
'{"mnemonic":["mystery","apple",...],"passphrase":"","name":"primary"}'
grpcurl — Get Info
grpcurl api.seed.hyper.media:443 com.seed.daemon.v1alpha.Daemon.GetInfo '{}'
grpcurl — Force Sync
grpcurl -H "Authorization: Bearer $TOKEN" \
api.seed.hyper.media:443 com.seed.daemon.v1alpha.Daemon.ForceSync '{}'
grpcurl — Store Blobs (two items)
grpcurl -d '{"blobs":[{"data":"base64..."},{"cid":"bafy...","data":"base64..."}]}' \
api.seed.hyper.media:443 com.seed.daemon.v1alpha.Daemon.StoreBlobs
grpcurl — Device Link Session
grpcurl -H "Authorization: Bearer $TOKEN" \
api.seed.hyper.media:443 com.seed.daemon.v1alpha.Daemon.CreateDeviceLinkSession \
'{"signing_key_name":"primary","label":"MacBook Pro"}'
grpcurl — Sign Data
echo -n "hello" | base64 | \
xargs -I{} grpcurl -d '{"signing_key_name":"primary","data":"{}"}' \
api.seed.hyper.media:443 com.seed.daemon.v1alpha.Daemon.SignData
Client Guidelines
Treat key names as local aliases; store the returned
account_idfor cross‑device identity.Show strong warnings for mnemonic handling; recommend 24 words for higher entropy when appropriate.
For
StoreBlobs, preserve response order to map CIDs back to inputs.For device linking, display countdown to
expire_timeand clear session state after redemption.
Server Guidelines
Enforce rate limits on
GenMnemonic,RegisterKey, andSignDatato mitigate abuse.Implement atomic rename for keys and ensure durable keystore writes.
Ensure
DeleteAllKeysscrubs all secrets and metadata; require elevated authorization.Limit blob sizes, validate CID formats, and prefer streaming ingestion in future versions.
Allow only one active device link session and invalidate previous sessions upon creation.
Future Work
Streaming logs/metrics for maintenance operations.
Export/import keystore (encrypted backup/restore).
Hardware key support (WebAuthn/FIDO, Ledger) for
SignData.Add
RevokeDeviceLinkSessionand session introspection.Streaming blob ingest and CAR import.
Do you like what you are reading? Subscribe to receive updates.
Unsubscribe anytime