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.
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.
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.
All of it runs on every commit. The repository is public and so is the test output.
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.
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.
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.
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.
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.
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.
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.
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.
Last reviewed 2026-09-07. The parity record in the repository lists what still differs, including two changes not yet merged upstream.