Ledgeroot
An MCP payment plugin and evidence engine for agent x402 payments
Every agent payment, on the record.
English · 中文
Contents
- What it is
- Why Ledgeroot
- Get started
- How a payment happens
- Architecture
- Where it sits
- Reference
- Known limits
- Contributing
- Design and research documents
What it is
Ledgeroot authorizes what an agent may spend, checks every payment against that authorization before signing it — fail-closed — and records a signed six-segment receipt whether the payment went through or was blocked. A third party verifies the whole chain offline, with nothing installed and nobody to call.
See it block a prompt-injected payment
$ LEDGEROOT_DRY_RUN=true npm run demo
1 sign a mandate → demo-mandate, issuer 0x19E7…ff2A
2 payment settles → paid, 0.1 USDC to agent402.tools/search
3 retry, same requestId → paid, deduplicated (no second charge)
4 prompt injection → denied: payTo "0x…dead" is not bound by the mandate
5 one-click kill switch → revoked 1 mandate
6 payment after revocation → denied: unknown mandate "demo-mandate"
7 offline verification → verified, 3 receipts, 0 issues
Abridged from a real run; steps 4 and 6 are the receipts'
reasonstrings verbatim. A denial is a receipt too — steps 4 and 6 carry their own signed entries, which is why the ledger ends at three receipts and still verifies clean.
No wallet, no USDC, no network, no account. That is the whole loop: authorize → constrain → pay → deny → revoke → verify offline.
Why Ledgeroot
- Fail-closed by construction. Every policy runs; the first denial stops the payment and is itself recorded. There is no path where a check is skipped and the payment proceeds.
- Verifiable, not just logged. Receipts are Ed25519-signed, hash-chained and committed to an epoch Merkle root on-chain. A third party verifies them offline — no call home, no vendor in the loop.
- Denials are evidence. A blocked attempt produces the same signed receipt a successful one does. That is the only place a blocked agent task is visible.
- It buys, not just settles.
ledgeroot_buyfetches the resource itself, so the quote it judges is the one the seller actually sent, and segment 6 is the body it actually received. - One key moves money, another attests. The signing key and the payment key deliberately do not fall back to each other.
- Money never touches a float. Six-decimal bigint arithmetic throughout.
- Honest about what it does not know. Missing evidence reports
incomplete; it is never reported as tampering, and never waved through. - Audit trail proves execution, not just intent. The consistency view re-checks paid receipts against the mandate that authorized them.
- MIT, local SQLite, no SaaS, no telemetry. Nothing leaves the machine.
Get started
1. Run the whole loop offline (60 seconds)
npm install
npm run build
LEDGEROOT_DRY_RUN=true npm run demo
No wallet, no USDC, no network, no account.
2. Add it to Claude Code
claude mcp add ledgeroot \
--env LEDGEROOT_PRIVATE_KEY=0xyour-private-key \
--env LEDGEROOT_SIGNING_KEY=0xyour-receipt-signing-key \
--env LEDGEROOT_DB=/absolute/path/ledgeroot.sqlite \
-- npx ledgeroot serve
Check the server came up before expecting a tool to run — Environment should read as empty because the values arrive from the shell that launched it:
claude mcp list # ledgeroot: node dist/cli.js serve - ✔ Connected
3. What using it looks like
Once registered, it is all conversation — natural-language parsing is the host's job:
- Issue an authorization — "authorize it to spend 5 USDC on agent402.tools data" → Claude calls
ledgeroot_mandate_sign→ you confirm. - Let the agent buy — "research X for me, buy the paid data" →
ledgeroot_buyfetches the resource itself, judges the seller's own 402 quote against every policy, and signs only if they all pass → six-segment receipt. - Revoke — "revoke its authorization" →
ledgeroot_mandate_revoke. The next payment is denied and recorded. - Audit — "verify the evidence" →
ledgeroot_verify; "export it" →ledgeroot_export.
The private key is passed via
--envand stays on the machine, and the tools are structured and deterministic — nothing is guessed at the payment boundary.
4. The same loop, headless
A scripted demo or CI drives it with one prompt. Mandates expire, so sign a fresh one first — npm run mandate:demo writes the demo-mandate-agent authorization the prompt below spends under:
npm run mandate:demo
claude -p 'You have the ledgeroot MCP tools available. Buy one call to the agent402.tools hash service through ledgeroot_buy, under mandate demo-mandate-agent. The resource is https://agent402.tools/api/hash, use method POST with body {"text":"mandatekey","algo":"sha256"} and intent "agent research: fingerprint a string". Then call ledgeroot_verify. Report exactly three things: the receipt id, the settlement transaction hash, and the verification status.'
A real run of exactly that prompt, on Monad mainnet, came back with:
The agent settled 0.001 USDC to agent402.tools, the receipt records chainId: 143, and the transaction is a transferWithAuthorization on the USDC contract. Check that rather than trusting the agent's summary — the verifier recomputes offline, and --check-chain goes to the chain for the settlement itself:
node dist/cli.js verify --db ./ledgeroot.sqlite --check-chain
⚠️ Anchoring is not automatic. The payment path writes the receipt; putting its root on chain is a separate, explicit step —
ledgeroot anchor, theledgeroot_anchortool, oranchor --watch. It is kept off the pay path deliberately, so the two never share a nonce. See Anchor boundaries for what an epoch does and does not cover.
CLI
node dist/cli.js verify [--db <path>] [--check-chain] # offline verification (+ optional chain check)
node dist/cli.js export [--db <path>] # evidence bundle as JSON
node dist/cli.js anchor [--db <path>] [--watch] [--every <s>] [--min-receipts <n>] [--force]
# submit the epoch root on-chain.
# --watch keeps anchoring as receipts
# arrive; a ledger with nothing new is
# skipped, so no root is re-paid for.
# a new contract re-anchors by itself;
# --force is for a record whose
# contract is unknown, or a re-anchor
node dist/cli.js buy <url> --mandate <id> \
[--method POST] [--body '<json>'] [--chain 143] # fetch a URL, pay its 402, record the receipt
node dist/cli.js jwks # the JWKS a third party needs
node dist/cli.js serve # MCP server over stdio
As a library
import { PolicyEngine, defaultPolicies, merkleProof, verifyMerkleProof } from "ledgeroot";
import { LedgerootStore } from "ledgeroot/store";
import { verifyReceiptChain } from "ledgeroot/verify";
Subpath exports: ledgeroot · /store · /verify · /anchor · /receipt · /types. The library does not load .env — the environment belongs to the caller (only the CLI and server entry points call loadEnv()).
Real payment, and anchoring
# Monad testnet. Needs LEDGEROOT_PRIVATE_KEY and testnet USDC (Circle faucet).
npm run demo:pay -- <payTo address> 0.001
npm run verify -- --db ./ledgeroot.sqlite
# Anchor the epoch root (needs Foundry for the contract; forge-std is a git submodule)
forge install foundry-rs/forge-std && forge build && forge test
LEDGEROOT_DEPLOYER_PRIVATE_KEY=... npm run deploy:monad # owner derives from LEDGEROOT_PRIVATE_KEY
npm run anchor -- --db ./ledgeroot.sqlite
How a payment happens
Buying a resource (the 402 flow)
ledgeroot_pay settles a payment whose quote the caller already holds — because the host did the 402 handshake somewhere else, where the audit trail cannot see it. ledgeroot_buy goes and gets the quote itself:
1 fetch the resource → 402 carrying the seller's requirements
2 pick a rail we can pay → the one network this build registers
3 run every policy → against *those* requirements, before signing
4 sign the authorization → only if step 3 allowed it
5 retry with the payment → the seller settles through its own facilitator
6 record the receipt → quote in segment 3, settlement in 5,
the hash of the response body in segment 6
The gate sits at the signing boundary, the last point at which refusing is still free. A denial is recorded as a denied receipt carrying the seller's own quote, next to the verdict of every policy that ran — so a purchase blocked at a real seller is on the record, with its reason.
A failure after the gate — a transport error, or a seller that asks for payment again after one was offered — is thrown rather than classified, because whether it settled is unknown. The idempotency claim is kept, so a retry refuses instead of buying twice.
📌 The protocol half of this — read the 402, create the payload, retry, settle — is
@x402/fetch. We register one rail and one gate on top of it. Re-implementing the handshake would only produce a second, worse copy, and the interesting question was never who can do the HTTP dance.
⚠️ The sellers we buy from are mainnet-only. agent402's live 402 offers
eip155:143(Monad mainnet) among twelve chains, and nothing on testnet.ledgeroot_buytherefore defaults tochainId: 143, and a real purchase is a real — if tiny — payment.
The six-segment receipt
intent → mandate → plan → call → transaction → delivery
A few things are deliberate:
- Segments are interlocked by RFC 8785 canonical hashing. Each receipt carries a
prevHashpointing back at the previous receipt'sreceiptHash. The log is append-only and cannot be updated in place — a receipt'sidis the canonical hash of its content, so changing one byte makes it a different receipt. - Every receipt carries a detached Ed25519 signature with a JWS-style
protectedheader. The signature covers{payload, protected}, soalgandkidsit inside the signed bytes and cannot be swapped after the fact.kidis the public key's RFC 7638 JWK thumbprint — the identifier is derived from the key itself, so it needs no registry and cannot drift away from the key. - The hash chain proves the content was not changed; the signature proves who made the statement. They are independent: tamper with the content and re-hash it, and the chain still verifies — only the signature catches it.
- Public keys are published as JWKS (
npx ledgeroot jwks) and shipped inside the evidence bundle, so a third party can verify signatures without calling home. - Segment 6 can only be reported by the caller (
responseBodyonledgeroot_pay, or the bought response inledgeroot_buy). Ledgeroot settles the payment but never fetches the resource on the pay path — only the agent sees the response body, so only the caller can supply its hash. - Segment 3 is checkable, not just descriptive. The quote is stored whole and committed to by hash, so a verifier recomputes
canonicalHash(segments.plan.quote)and compares. A quote swapped after signing fails that check even if the receipt is re-hashed and re-signed — which is exactly the edit an issuer could otherwise make quietly.payToand the quoted amount come from the same object, so a caller cannot pass a benignpayToalongside a quote pointing somewhere else.
Tri-state verification (offline first)
Verification depends on no server — ledgeroot verify reads the local database and recomputes.
This boundary is the single most important discipline in the product: tampered outranks incomplete, and unobtainable evidence is never reported as tampering. Reporting a network failure as tampering would destroy the credibility of the whole alarm; conversely, treating missing evidence as a pass would claim a check that never ran.
On-chain settlement checking is opt-in (--check-chain / checkChain: true): the offline path stays synchronous and network-free; only the chain check reaches for RPC.
Anchor boundaries
An epoch root covers "the receipts that existed at submission time" (receiptCount). Verification slices back to that boundary before recomputing, so payments made after an anchor do not read as tampering; conversely, fewer receipts present than the recorded count → tampered (that is a deletion).
Anchors record their contract. Every local anchor names the contract its root was submitted to, so repointing LEDGEROOT_ANCHOR_ADDRESS is detected instead of silently skipped: a record from the old contract cannot make the new one look already-anchored, and the next anchor re-submits on its own. Records written before the column existed carry no contract — their target is unknown, which is the case --force is for.
The five default policies (fail-closed)
Each attack shape has the policy that catches it:
- Counterparty whitelist — only pay x402 gateways listed in the mandate
- payTo binding — the settlement address must match the mandate (this is the one that stops the prompt-injected transfer in the demo above)
- Quote drift — the amount charged may not drift from the 402 quote beyond a threshold (10% by default)
- Endpoint rate limit — cap call frequency per endpoint
- Amount limits — a per-payment ceiling plus a cumulative ceiling
What the constraints mean: an empty whitelist or an empty payTo list means unconstrained, not deny-everything; rate limiting only applies when the mandate sets endpointRateLimit; the two amount limits always apply. ledgeroot_mandate_import intersects the mandate's constraints with the policies registered locally and records that intersection in segment 2.
The engine runs every policy and records every verdict, returning the first denial. Amounts always go through decimal.ts — bigint arithmetic over six-decimal units; money never touches a float.
Of these, the ones a chain cannot express are quote drift, endpoint rate limits, cumulative ceilings (structuring) and the denial record. A chain knows addresses, not hosts or quotes, and does not record "attempts that were blocked". That is the reason this layer exists.
Idempotency, the crash window, and task grouping
The x402 rail and the local database are not one transaction. Ledgeroot closes the gap with two optional correlation keys:
requestId — idempotency, claimed before the money moves
The naive version ("write the receipt after settling, dedupe by looking up receipts on retry") has a fatal window: if the process dies after settlement but before the receipt is on disk, the money moved, nothing was recorded, and requestId finds nothing — so the retry pays a second time. That loses both the evidence and the money, which is exactly what the product claims to prevent.
So requestId is consumed at attempt time, not at success time:
Why a failed claim must deny rather than retry: that row means "we do not know whether that payment settled". Refusing to spend is recoverable; paying twice is not.
⚠️ The pre-write record can only live outside the chain: a receipt's
idis its content hash, so flippingstatusfrompendingtopaidchanges the hash, and the next receipt'sprevHashwould point at a hash that no longer exists. The log is append-only — in-place updates are structurally impossible.⚠️ Pass no
requestIdand you get none of this protection — there is nothing to key the attempt on. Idempotency has to be asked for.
taskId — the intent chain
Groups several payments under one user task; the dashboard aggregates them as "N payments / total / N blocked".
Revocation
ledgeroot_mandate_revoke revokes a single mandate; the store also exposes a one-click kill switch (revokeAllMandates) for control planes to call. The next payment after revocation is denied and recorded.
Architecture
It is one Node process, not a service you deploy. The CLI, the MCP server and the library all assemble the same service graph over a single local SQLite file, and the MCP server is a stdio child of whatever host launches it. No backend, no account, no telemetry — which is why the whole loop runs with the network unplugged.
The shape of it
MCP host — Claude Code, opencode, …
│ stdio · eleven ledgeroot_* tools
▼
┌──────────────────────────────────────────────────────┐
│ tools/ pay · buy · mandate_* · receipt_* · │
│ verify · anchor · export │
├──────────────────────────────────────────────────────┤
│ THE GATE — pure, local, fail-closed │
│ mandate.ts EIP-712 authorization │
│ policy/engine.ts every policy runs, every │
│ verdict kept │
│ policy/defaults.ts the five policies │
├─────────────────────────┬────────────────────────────┤
│ THE RAIL — network │ THE RECORD — local │
│ x402/buyer.ts │ receipt/builder.ts │
│ @x402/fetch + our │ receipt/hashchain.ts │
│ gate at the signing │ RFC 8785 + SHA-256 │
│ boundary │ receipt/signing.ts │
│ x402/facilitator.ts │ Ed25519 + JWKS kid │
│ /verify → /settle │ store/db.ts │
│ EIP-3009, 2 serial │ SQLite, append-only, │
│ round trips │ WAL │
├─────────────────────────┴────────────────────────────┤
│ anchor/ RFC 6962 Merkle root → LedgerootAnchor.sol │
│ verify/ offline tri-state verifier + chain checks │
└──────────────────────────────────────────────────────┘
The gate sits on the local side on purpose. Judging a payment needs no network, no RPC and no model — it is pure functions over the mandate and the seller's quote, so it is deterministic and cheap. The network appears in exactly one place per payment (the facilitator), and once more per anchor.
The write path — one payment
ledgeroot_pay is the whole flow in one function (tools/pay.ts); ledgeroot_buy wraps the same gate around a 402 handshake it performs itself (tools/buy.ts → x402/buyer.ts).
Steps 1–8 and 10–11 are local and fast; step 9 is the only protocol-inherent wait. Both exits — paid and denied — write the same signed six-segment receipt, which is why a blocked attempt is on the record instead of merely absent. Idempotency, the crash window, and task grouping explains why step 8 has to precede step 9.
The read path — verification
verify recomputes everything from the local file and depends on no server: each receipt's id is its content hash, prevHash links the chain, segments.plan.quoteHash is recomputed against the stored quote, the Ed25519 signature is checked against the receipt's own kid, and the epoch root is recomputed and compared to the anchored one, sliced back to its receiptCount. It answers with one of three states — and the state machine, not the cryptography, is the hard part. See Tri-state verification.
export packages that same material — receipts, the anchor record, a Merkle inclusion proof per receipt, the JWKS, and the verdict — so a third party can re-run it elsewhere with nothing installed.
Module map
For the on-disk tree, see Repository layout.
Storage
Four tables in one file (store/db.ts), with journal_mode = WAL:
The service graph
bootstrap.ts is the one place the collaborators are wired together:
Three entry points build it, and nothing else does: dist/cli.js (verify / export / anchor / buy / jwks / serve), dist/server.js (MCP over stdio), and the library via the subpath exports ledgeroot, /store, /verify, /anchor, /receipt, /types. Only the CLI and the server load .env — a library caller's environment belongs to the caller.
Trust boundaries
Three properties hold across every path:
- Nothing skips a check. Every policy runs on every attempt, and the first denial stops the payment and is recorded — there is no code path where a failing check lets the payment through.
- The keys are separate and never fall back.
LEDGEROOT_PRIVATE_KEYmoves money,LEDGEROOT_SIGNING_KEYonly makes statements,LEDGEROOT_ANCHOR_KEYonly submits roots. Evidence that cannot be attributed readsincomplete, notverified. - Verification never calls home. Receipts carry their own
kidand the public keys travel in the bundle; the only network read is the opt-in chain check.
The three keys, and what happens when one is missing:
They deliberately do not fall back to each other (except LEDGEROOT_ANCHOR_KEY → LEDGEROOT_PRIVATE_KEY): one key moves money, another attests to what happened, a third submits roots, and none can do the others' job. Evidence that cannot be attributed should not read as verified.
Extension seams
- Chains are instances, not branches.
chains.tsholds testnet (10143) and mainnet (143), andFacilitatorNetworkConfigalready abstractschainId/network/scheme/usdcAddress.ledgeroot_buypicks a rail bychainId;ledgeroot_payis still pinned to testnet — see Known limits. - Settlement protocol is an explicit dimension of
segments.tx: a non-x402 protocol verifies asincompleterather than being waved through or wrongly accused. The seam is cut; no second protocol is implemented.
Deliberately not here
No custody (the key never leaves the machine), no router or discovery layer, no LLM in the decision, and no tenant boundary — one database, one signing key, one payment key. The reasoning is in Where it sits; the cost is stated plainly in Known limits.
Where it sits
Niche: on-chain stablecoins × agent micro-402 payments (unit price $0.001–$0.05, on the order of a million payments a month).
The niche is guaranteed by fee structure, not by technical superiority: a $0.30 + 2.9% card fee spread across a $0.005 payment is 6000% — physically impossible. That is why x402 exists, and why we don't do large-value payments. Large payments come with invoices, contracts and refund flows, and that ground belongs to Shopify, Stripe, Visa TAP and Mastercard.
⚠️ Fee structure excludes anyone taking a percentage. It does not exclude a cloud vendor that bills by load and treats payment as a platform feature.
📌 Two axes, not one. Amount is the first — card fees make a $0.005 payment impossible, so we don't do large-value. Acceptance is the second, and it is the sharper one: consumer-facing agent payments already run on Shop Pay / Stripe Link with one-time tokenized credentials — stablecoins never enter that flow, and what blocks them there is merchant acceptance, not fees. Stablecoins' ground is the end the card networks won't go: machine-to-machine, the cross-border long tail, API-level micropayments. Two scenarios, two stories — don't mix them, and don't reach for the fee argument to explain the consumer one.
Within that niche Ledgeroot does exactly three things:
📌 At this granularity a receipt is not the product (a ten-thousandth of $0.005) — it is raw material. Only the layer that turns receipts into a ledger can be charged for. That positioning does not lower the bar on the receipt engineering: the layer above can only be trusted if every receipt beneath it verifies independently.
Aligned with MAS SAFR
Ledgeroot's three layers map one-to-one onto the three runtime safeguards in MAS's Safeguards for Agentic Finance at Runtime — we implemented the same architecture independently, before seeing that document:
SAFR is a voluntary framework (published 2026-07-03); what will actually bind is MAS's forthcoming AI risk-management guidance covering agentic AI. Both are worth aligning with — so we implement SAFR's three layers now.
Works with
- Any MCP host — Claude Code, opencode, and anything else that speaks MCP (this is the only integration surface we maintain).
- x402 gateways — including those discoverable through Coinbase's Bazaar.
- Monad today — testnet (chainId 10143) by default, with mainnet (143) wired for buying, settlement checks and anchoring; the x402 facilitator is
x402-facilitator.molandak.org. Other chains are a config instance away, not a rewrite; see Known limits. - AP2-style mandates — import an externally signed authorization and intersect it with local policy.
Reference
The eleven ledgeroot_* tools
Environment variables
The anchor contract
contracts/src/LedgerootAnchor.sol — the only contract in the project, storing a 32-byte root, a back-pointer, an epoch counter and the owner, and nothing else.
- Owner-gated:
anchor()isonlyOwner. An openanchor()reduces "this root is on chain" to "somebody anchored something here" — an attacker could publish a forged root, or displace the honest one so valid receipts verify astampered. The owner is the anchoring wallet (derived fromLEDGEROOT_ANCHOR_KEY, orLEDGEROOT_PRIVATE_KEYwhen unset), not the deployer, so the two keys can be separated. - Ownership is transferable:
transferOwnershiplets the anchoring key be rotated without redeploying. While the owner was fixed at construction, replacing the anchoring wallet meant deploying again and orphaning every root on the old contract. It is one step rather than an accept-then-transfer handshake because a mistaken transfer is recoverable by the current owner, whereas a two-step one leaves the contract unable to anchor in between. - The contract owns the epoch:
lastEpochincrements on every anchor, and clients read it from the contract instead of counting locally — otherwise a fresh database would label its first anchor "epoch 1" no matter how far the contract has already run. - RFC 6962 MTH for Merkle: leaves are
SHA-256(0x00 ‖ d), internal nodesSHA-256(0x01 ‖ L ‖ R), split at the largest power of two below n (no duplicating an odd trailing node). Domain separation is what buys second-preimage resistance. Tests cross-check against the stack-based algorithm in RFC 9162 §2.1.2 as an independent oracle. - Deployed to Monad mainnet (chainId 143):
0x958f887f203a08f43fe45ba00a6a4199b9e01b61, owned by the anchoring wallet. It replaced0xca08c795357ae8bcee8af592c827d419750fdf84, whose owner was the payment key; the roots anchored there stay on that contract. An earlier testnet deployment (0xc0234ea7e3af77e5ae686caff62ff88eaccd8c30) produces no evidence. Addresses change — trust your own.env.
Repository layout
src/
policy/ policy engine + the five default policies + Zod schemas
receipt/ six-segment receipt builder + RFC 8785 hash chain + Ed25519 signing (JWKS / thumbprint kid)
anchor/ RFC 6962 Merkle (root / inclusion proofs) + anchorer (viem)
verify/ offline tri-state verifier + on-chain settlement content checks (ERC-3009 decoding)
store/ SQLite append-only store (receipts / mandates / anchors / payment_intents)
x402/ facilitator integration (EIP-3009 authorization + /verify + /settle) + the buyer (@x402/fetch with our policy gate at the signing boundary)
tools/ the eleven ledgeroot_* MCP tools
mandate.ts EIP-712 authorization (sign / verify / policy intersection)
consistency.ts post-hoc authorization-vs-execution analysis (did any paid receipt exceed its mandate)
decimal.ts USDC six-decimal bigint arithmetic, no floating point
env.ts environment loading + the dry-run switch
cli.ts verify / export / anchor / jwks / serve
scripts/ demo (dry-run, full loop) / pay-demo (real payment) / buy-demo (real x402 purchase) / deploy
contracts/ LedgerootAnchor (Solidity 0.8.24 + Foundry)
deploy/ anchor deployment (chain chosen by LEDGEROOT_CHAIN_ID)
docs/ architecture review / roadmap / competitor and standards research / commercialization
assets/ logo lockups (light + dark)
test/ 136 tests across 15 files
Known limits
The honest section. These are limits of the current implementation, not a repudiation of the design intent; the ordering and trade-offs are recorded in roadmap.md.
Contributing
Small, well-scoped changes are the easiest to land, and the roadmap is the best place to find one:
- Roadmap — the ordered plan, with acceptance criteria and affected files per item.
- Known limits — every item there is a real, bounded piece of work.
- Run the suite before opening a PR:
npm run typecheck && npm test && npm run build(this is exactly what CI runs).
New to the codebase? scripts/demo.ts walks the whole loop in one file, and test/pay.test.ts covers the crash-window behaviour end to end.
Design and research documents
📌 These documents are written in Chinese; there are no English versions yet.
License
MIT © 2026 Ledgeroot
The industry built the locks, but nobody built the keyring. It built the brakes, but nobody built the black box.
Verify it yourself — don't take our word for it.
⭐ Star it · 📖 中文 README · 🗺️ Roadmap
MIT · no SaaS · no telemetry · the private key never leaves the machine