Verified parity · Checked in CI

Your agent signs under the same key file as your cold wallet

Most tools that let an AI agent transact invent their own key storage, and you end up with a second secret to protect. Coldstar's agent signer reads the file the cold wallet already wrote. Not a compatible format. The same file, proven by tests that run each implementation against the other's output.

What "the same" actually means

A Coldstar key file is one artifact, and both tools read and write it byte for byte:

{ "version": 1, "salt": "…", "nonce": "…", "ciphertext": "…", "public_key": "9QZ…" }

Argon2id turns your passphrase and a fresh 32-byte salt into a key. Version 1.3, at 64 MiB of memory, three passes, four lanes. AES-256-GCM under a fresh 12-byte nonce encrypts the 32-byte Ed25519 seed, with the authentication tag appended. The public key travels in the clear so a tool can tell you which key a file holds before spending 64 MiB finding out.

Every one of those numbers is a place the two could quietly disagree. A different lane count alone produces a different key from the same passphrase, and the failure looks exactly like a typo.

Why you should not take our word for it

An implementation tested only against itself keeps passing after it has drifted away from the format it must match. So we cross-wired the tests.

  1. The Python signer opens containers the TypeScript signer wrote, and produces a grant the TypeScript verifier accepts. Neither is checked against a copy of itself.
  2. The legacy-format tests use fixtures generated by Coldstar's own encryption code, not by a reimplementation of it. If our reader drifts, it stops opening real wallets, and the test says so.
  3. Key derivation is checked against the published RFC 9106 test vector. A subtly wrong Argon2 would still round-trip inside one package. It cannot match a number published by the people who designed the algorithm.
  4. The tests compare the air-gap wire format byte for byte against Coldstar's own encoder. A grant and a transaction cross the gap by the same route, including as a QR code.

All of it runs on every commit. The repository is public and so is the test output.

The one difference, and it is the point

The cold wallet's root key signs transactions, one human approval at a time. The agent signer's root key signs one policy, once, naming a session key and the limits it may work under.

That is the whole reason the agent tooling exists. An agent cannot wait at an air gap for a human to approve each transfer, and it must never hold the key that could move everything. So the root signs a grant instead: this session key, these programs, this much per transaction, this much per day, until this date. The agent holds only the session key. Let the grant expire, or revoke it on chain, and the agent's authority ends without the root ever coming online.

Everything else about the two tools is deliberately identical, including the parts that are inconvenient. Both refuse to read a root key on a machine with a live network interface. Both apply the same passphrase rules. Both treat an unreadable instruction as a reason to escalate rather than a reason to assume it is harmless.

Where the agent tooling is weaker

Memory handling

Coldstar's Rust signer holds the decrypted key in a buffer locked against swapping and zeroized when it is dropped, including if the process crashes. TypeScript and Python cannot do that. When the agent tools open a key themselves it sits in an ordinary heap.

What we did about it

When the Rust signer is installed, the agent tooling hands it the signing and the key never enters the other process at all. The container and passphrase go to it over standard input, never as command arguments, because arguments appear in process listings and shell history.

The pure TypeScript and Python path stays as the fallback, because it is the reason an air-gapped machine needs neither Node nor a package installer. If that is the path you are on, the protection is that the machine is unreachable, not that the buffer was wiped.

If you already have a Coldstar wallet

Point the signer at it. Current key files work directly. The signer also reads older files from Coldstar's libsodium era. One command converts them, which is the migration the wallet itself performs on an old file.

coldstar-encrypt-key --in old-wallet.json --out root.coldstar.json
coldstar-sign-policy --root root.coldstar.json --policy coldstar.policy.json \
  --session <session public key> --expires 7d > grant.json

Run both on the offline machine. The grant that comes out is plain JSON, and it crosses the gap as a file or a QR code.

Questions

Can an AI agent use my existing Coldstar wallet key?

Yes, and it never receives that key. The key signs a policy grant once, on the offline machine. The agent works under a separate session key bounded by that grant.

What if I lose the passphrase?

The key is gone. There is no recovery path and there is not meant to be one. Anything that could recover it for you could recover it for someone else.

Is this audited?

No. Beta, pre-audit, and it decrypts private keys. The threat model is the honest version, including what a compromised offline machine does to all of this.

Why does it matter that it is the same file?

Because the alternative is two secrets. A second key store means a second thing to back up, a second thing to lose, and a second implementation nobody has checked against the first. One file, read by two tools that are tested against each other, is a smaller thing to get right.

Set it up The full parity record What this does not protect

Last reviewed 2026-09-07. The parity record in the repository lists what still differs, including two changes not yet merged upstream.