Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Cryptographic capability learning path: from agent to cryptographic actor

From cognokratos/arktos-wallet · docs/CRYPTOGRAPHIC-CAPABILITY-LEARNING-PATH.md · pinned revision 92650a034799

Arktos is not a course in blockchain basics.

It is a course in safely exposing cryptographic capabilities to software agents.

The principle the whole path is built on:

Give agents capabilities, never secrets.

The question it answers:

How do you let probabilistic software request cryptographic operations without making the model the custodian of cryptographic authority?

It is written for experienced software engineers. It assumes you can read Rust, and that you know HTTP, APIs, databases, authentication, Docker, the basic vocabulary of cryptography (hashes, MACs, symmetric encryption, KDFs, elliptic-curve keys) and what an MCP tool call is. It does not explain what an LLM, Bitcoin or AES is. It explains a primitive only where its engineering consequences shape the design.

Where this fits

The CognoKratos projects are four distinct tracks. Each one answers a different question about the same kind of system:

simple-agent-template      Production Agent Engineering
    teaches how to engineer the agent
        ↓
sophos-agent               Durable Agent Runtime Engineering
    teaches how to engineer the runtime
        ↓
etf-research-agent         Governed Decision Engineering
    teaches how to govern decisions
        ↓
arktos-wallet              Cryptographic Capability Engineering
    teaches how to expose cryptographic authority safely

The order is conceptual, not mandatory. You can start here if you already know what an agent loop and an MCP tool are. Arktos does not re-teach those topics. When a lesson depends on one, it links to the track that covers it:

TopicTaught in
Agent loops, ReAct, native tool callingsimple-agent-template, stages 1–2
MCP fundamentals, tools as capability boundariessimple-agent-template, stage 3
Guardrails, untrusted data, prompt injectionsimple-agent-template, stage 5
Authentication, identity and trust boundaries in generalsimple-agent-template, stage 8
Human-in-the-loop mechanics, approval tokenssimple-agent-template, stage 9
Durable execution, checkpoints, crash/resume, idempotent side effectssophos-agent runtime path
Deterministic policy, evidence contracts, consent for consequential decisionsetf-research-agent applied path

What Arktos teaches, and the others do not: cryptographic authority, secret containment and capability design for systems that agents can call.

What Arktos is, precisely

Arktos is one Rust process (Axum + Tokio) that serves:

  • a stateless MCP 2026-07-28 endpoint at /mcp, authenticated with a per-client API key in the X-API-KEY header;
  • an admin REST API at /admin/api-keys, authenticated with ADMIN_API_KEY;
  • liveness and readiness probes.

State lives in one local SQLCipher database. The MCP tool surface is exactly four tools:

ToolWhat it does
pingReturns "pong"
create_walletGenerates a 12-word BIP39 recovery phrase, encrypts it, stores it under the caller's API key
get_bitcoin_addressReturns (deriving and recording on first use) a BIP86 Taproot address
get_ethereum_addressReturns (deriving and recording on first use) a BIP44 Ethereum address, EIP-55 checksummed

Arktos does not sign transactions, broadcast transactions, sign arbitrary messages, read balances or talk to any blockchain node, and its MCP and HTTP APIs do not export private keys, seeds or recovery phrases. No current tool gives an agent the ability to move value. Wherever this path discusses signing, it is labelled future design.

Several properties of the current system are easy to overstate, so here they are exactly:

  • Custody. Four different things are easy to conflate:
    • Self-hosted says who runs the software. The operator holds MASTER_KEY and DATABASE_KEY.
    • Third-party custody exists when someone other than the wallet owner operates the instance. When the owner runs Arktos and controls the keys, there is no third-party custodian. When one party operates Arktos on behalf of another, that operator has effective custody of the stored wallet secrets. Self-hosted does not mean non-custodial from every participant's perspective.
    • Service custody: the running service decrypts recovery phrases, briefly, to derive accounts. It does have access to wallet secrets.
    • Model custody: none. No MCP tool returns secret material.
  • Secret access by principal. The agent cannot obtain a recovery phrase through any tool. The server uses phrases internally on its request path. The operator has separate, deliberate tooling (make decrypt, src/bin/secret.rs) that can decrypt and print a stored phrase. Capabilities are assigned by principal.
  • Private keys. Account private keys exist in process memory for the duration of one derivation. They are not persisted and not returned. They do exist.
  • Statelessness. The MCP protocol layer is stateless: there are no sessions. Wallet and application state is persistent, and the SQLCipher deployment is single-instance per database.

The authority flow

Every lesson refers back to this boundary:

flowchart TB
    subgraph model["Model-visible"]
        agent["Agent / LLM<br/>chooses a tool + arguments"]
        result["Public result<br/>address, public key, path, ids"]
    end
    subgraph trusted["Trusted service boundary (Arktos process)"]
        http["HTTP layer<br/>X-API-KEY → HMAC lookup → ApiKey"]
        mcp["MCP tool router<br/>exactly four typed tools"]
        svc["WalletServices<br/>owner-scoped, validated operation"]
        crypto["Cryptography<br/>AES-256-GCM open · BIP39 · BIP32 · encode"]
    end
    subgraph secrets["Secret material (never crosses into the model)"]
        mk["MASTER_KEY → WalletSeedKey, ApiKeyHmacKey"]
        dk["DATABASE_KEY → SQLCipher"]
        phrase["Recovery phrase, seed, extended private keys"]
    end
    agent -- "capability request" --> http
    http -- "authenticated caller (out of band)" --> mcp
    mcp --> svc
    svc --> crypto
    crypto --- phrase
    crypto --- mk
    svc --- dk
    crypto -- "public data only" --> result

The model selects an operation. Through the current MCP surface it holds no key, does not supply its identity, and receives no secret material.

The path at a glance

StageQuestionCore lessonLesson
C1What authority does the agent actually have?Capabilities define the threat model01 — Model cryptographic authority
C2How are secrets separated?Key hierarchy and domain separation02 — Design key hierarchies
C3How should encrypted data survive change?Versioned authenticated encryption03 — Encryption is a data format
C4Where may plaintext secrets exist?Secret lifetime minimization04 — Minimize secret lifetimes
C5What should be deterministic?Standards-based key and address derivation05 — Derive, don't invent
C6Who owns which resources?Authenticated caller scope06 — Bind identity to capability
C7What should the agent be allowed to invoke?Least-capability tool design07 — Design least-capability tools
C8Can the owner recover safely?Backup, recovery, rotation, migration08 — Recovery is part of security
★How would transaction signing change the entire authority model?Future designChallenges

Then follow one wallet secret from OS entropy to a public address in the secret lifecycle walkthrough, read why the architecture looks the way it does in the case studies, and test yourself with the challenges.

How long things take

InYou canRead
5 minutesState what an agent can and cannot do through ArktosThis page, lesson 01's authority table
30 minutesExplain where every wallet secret lives and for how longSecret lifecycle walkthrough
An afternoonRun every test-based experiment in C1–C5Lessons 01–05; only cargo needed
A dayRun the live labs in C6–C8 against a local server, then attempt a challengeLessons 06–08 with the lab setup, challenges

Stage summaries

Each stage lists the question it asks, the files to read, an experiment, the failure mode it prevents, the takeaway, the related reference documentation and, where it applies, the upstream prerequisite.

C1 — What authority does the agent actually have?

C2 — How are secrets separated?

  • Implementation: src/keys.rs, src/config.rs, src/database.rs
  • Experiment: cargo test --lib keys::tests. These tests show that derivation is deterministic, that purposes are independent, and that a relabelled key cannot open existing ciphertext.
  • Failure mode: one key used for two purposes, or an innocent-looking label rename that makes every stored wallet unreadable.
  • Takeaway: cryptographic naming is part of persistent protocol design.
  • Reference: Architecture — Key Hierarchy & Secret Storage

C3 — How should encrypted data survive change?

  • Implementation: src/crypto.rs
  • Experiment: cargo test --lib crypto::tests. The tests tamper with the nonce, the ciphertext, alg and v, and try a key with a different purpose; check which error class each change produces.
  • Failure mode: unversioned ciphertext that cannot be migrated, or error messages that tell an attacker why decryption failed.
  • Takeaway: ciphertext is long-lived structured data. Design it like a versioned protocol.
  • Reference: Architecture — Key Hierarchy & Secret Storage, Data Models — Encryption

C4 — Where may plaintext secrets exist?

  • Implementation: src/wallet_manager.rs, src/wallet_services.rs
  • Experiment: trace one first-use get_ethereum_address call and mark every point where plaintext secret material exists. Then trace a repeat call and notice that no plaintext secret material is produced at all.
  • Failure mode: confusing using a secret with disclosing it, or believing that zeroization gives perfect memory secrecy.
  • Takeaway: secret use and secret disclosure are different operations.
  • Reference: Architecture — Key Hierarchy & Secret Storage

C5 — What should be deterministic?

C6 — Who owns which resources?

C7 — What should the agent be allowed to invoke?

  • Implementation: src/wallet_services.rs (request/response types), src/mcp.rs
  • Experiment: inspect the generated input and output schemas (cargo test --test mcp_protocol_tests tools_publish_input_and_output_schemas). Then design prove_ownership both as export_private_key and as sign_challenge, and compare the two.
  • Failure mode: a generic wallet_execute(operation, payload) tool whose real authority is far broader than its name suggests.
  • Takeaway: capability design is more important than prompt design when agents can act.
  • Reference: API Contracts — Conventions
  • Upstream: template stage 5 — guardrails and untrusted data

C8 — Can the owner recover safely?

★ Future design — How would transaction signing change the entire authority model?

Signing would add very little API surface: one tool. It would change the threat model far more than that. Signing turns a prompt-injected tool call from "the agent received a wrong address" into "value left the wallet". It introduces replay, chain identity, policy over destination and value, and human consent as hard requirements. Work through it in Challenge 1, after the capability escalation ladder in lesson 01. For consent and governed decisions, see the etf-research-agent applied path, which treats that problem in depth.

Recurring principles

These come up in every lesson:

Give agents capabilities, never secrets.

Every tool is an authority grant.

Credentials belong outside model-visible tool arguments.

Deterministic cryptography should remain deterministic.

The safest private key is often the one you never persist.

Key names, purpose labels and envelope versions are persistent protocol design.

Encryption without recoverability can become data loss.

Protocol statelessness does not imply application statelessness.

A future signing tool changes the threat model much more than it changes the API surface.

Lab setup

Lessons 01–05 need only cargo, because their experiments are tests. Lessons 06–08 use a throwaway local server. Run the labs with lab keys in a scratch directory and never against a database that holds real wallets.

# A scratch location, separate from data/ and from any .env you use.
export LAB=$(mktemp -d)
export DATABASE_PATH="$LAB/arktos.db"
export ADMIN_API_KEY=lab-admin
export MASTER_KEY=$(make secret)       # two independent values; never reuse
export DATABASE_KEY=$(make secret)

cargo run --quiet --bin arktos-wallet  # leave running; use a second shell below

In a second shell with the same exported variables, create API keys and define a tiny raw MCP helper. Real agents use an MCP client. The helper shows exactly what goes over the wire: the API key travels in a header, and the tool arguments carry no identity.

new_key() {  # usage: new_key <name>  → prints a new client API key (lab only)
  curl -s -X POST http://localhost:8080/admin/api-keys \
    -H "X-API-KEY: $ADMIN_API_KEY" -H 'Content-Type: application/json' \
    -d "{\"name\":\"$1\"}" | python3 -c 'import sys, json; print(json.load(sys.stdin)["api_key"])'
}

mcp() {  # usage: mcp <api-key> <tool> '<json arguments>'
  curl -s http://localhost:8080/mcp \
    -H "X-API-KEY: $1" \
    -H 'Content-Type: application/json' \
    -H 'Accept: application/json, text/event-stream' \
    -H 'MCP-Protocol-Version: 2026-07-28' \
    -H 'Mcp-Method: tools/call' -H "Mcp-Name: $2" \
    -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"'"$2"'","arguments":'"$3"',"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"lab","version":"1"},"io.modelcontextprotocol/clientCapabilities":{}}}}'
  echo
}

A=$(new_key agent-a)
B=$(new_key agent-b)
mcp "$A" ping '{}'

The helper puts a lab API key on a curl command line, where other local users can see it in the process list. That is acceptable for a throwaway key. It is not acceptable for a real one.

The labs inspect the database through the sqlcipher shell. If you use make sql, be aware that the Makefile reads .env if one exists, and .env values take precedence over your exported lab variables. Either run the labs from a checkout without .env, or open the shell directly:

labsql() {  # usage: labsql "<SQL>"   (key passed via an owner-only temp file, not argv)
  init=$(mktemp); chmod 600 "$init"
  printf "PRAGMA key = '%s';\n" "$(printf '%s' "$DATABASE_KEY" | sed "s/'/''/g")" > "$init"
  sqlcipher -init "$init" "$DATABASE_PATH" "$1"; rm -f "$init"
}

None of the labs prints a recovery phrase, a seed or a private key. Some operator tooling can print a recovery phrase (make decrypt), and the labs deliberately never use it. Lesson 07 explains why that tool exists outside the agent surface.

Verifying the learning material

make docs-check checks every relative link, heading anchor, referenced repository path and make target in the Markdown documentation. It runs offline and is part of make ci.

This chapter is maintained in cognokratos/arktos-wallet beside the code it teaches. The book shows docs/CRYPTOGRAPHIC-CAPABILITY-LEARNING-PATH.md at revision 92650a0347993622cbb3e5eeac2c908069c67fd6 (branch main). View source at this revision · Report a correction.

Corrections are made upstream against the current main branch and appear here when the book's pin for this source is updated.