An experimental, air‑gapped Ethereum transaction signer
Explore the docs »
Report Bug
·
Request Feature
Table of Contents
coldsign is an experimental, air-gapped Ethereum transaction signer.
It parses explicit transaction intents, enforces strict policy and identity checks, builds and signs EIP-1559 ETH transfers offline, and exports the signed transaction via terminal output as text or QR for broadcasting on an online machine.
The design is intentionally minimal, auditable, and refusal-first.
- Parses explicit
ETH_SENDtransaction intents - Enforces local, refusal-first policy (chain, fees, bounds)
- Derives an Ethereum account from a BIP-39 mnemonic (BIP-44 index)
- Verifies the derived address matches the intent (
fromAddress) - Builds an unsigned EIP-1559 ETH transfer
- Requires explicit user confirmation before signing
- Signs the transaction offline
- Outputs:
- human-readable transaction review
- signed transaction hash
- raw signed transaction hex
- optional terminal QR for air-gap transfer
- No networking
- No RPC calls
- No broadcasting
- No ERC-20, NFT, or contract calls (ETH transfers only)
- No key storage or persistence
- No GUI
- No intent construction (signing only)
This section describes how to build coldsign on an online build machine and run it on an offline signing machine.
- Go 1.25.5 or later
- One online machine (build / broadcast)
- One offline machine (air-gapped signer)
- A BIP-39 mnemonic phrase
coldsign is intended to run on an air-gapped (offline) machine.
Binaries are built on a separate online machine and transferred to the offline signing machine via removable media.
git clone https://github.com/am-hernandez/coldsign.git
cd coldsign
git checkout v1.0.0 # or desired tag
go build -ldflags "-X main.Version=$(git describe --tags --abbrev=0)" -o dist/coldsign ./cmd/coldsignVerify the build:
./coldsign versionTransfer the resulting coldsign binary to the offline machine using removable media.
chmod +x ./coldsign
mkdir -p ~/.local/bin
mv coldsign ~/.local/bin/
coldsign versionThe offline machine does not require Go or any additional packages.
coldsign uses a subcommand-based interface. Running coldsign help shows:
░█▀▀░█▀█░█░░░█▀▄░█▀▀░▀█▀░█▀▀░█▀█
░█░░░█░█░█░░░█░█░▀▀█░░█░░█░█░█░█
░▀▀▀░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀▀▀░▀▀▀░▀░▀
air-gapped Ethereum transaction signer
Usage:
coldsign sign [flags] <intent.json>
coldsign addr --index N [--qr]
coldsign help
coldsign version
Commands:
sign Review and sign transaction intents
addr Derive and display Ethereum addresses
help Show this help message
version Show version information
coldsign sign- Review and sign transaction intentscoldsign addr- Derive and display Ethereum addressescoldsign help- Show help messagecoldsign version- Show version information
./coldsign sign sample_intent.jsonOr use backward-compatible syntax:
./coldsign sample_intent.jsonThis prints a full transaction review and exits without signing.
./coldsign sign --sign sample_intent.jsonYou will be shown a detailed review and asked to confirm the destination address before signing.
echo "coldintent:v1:..." | ./coldsign sign --intent-stdin --signThis mode is designed for camera / QR pipelines and reads a single-line intent from stdin.
./coldsign sign --sign --qr sample_intent.jsonThis prints the signed raw transaction as a terminal QR (to stderr).
./coldsign addr --index 0Derives an Ethereum address from a BIP-39 mnemonic at the specified BIP-44 index. Optionally output as QR:
./coldsign addr --index 0 --qrAfter signing on the offline machine, transfer the signed transaction to an online machine for broadcasting.
If you used --qr to generate a QR code:
-
Scan the QR code using a camera tool:
Point your camera at the QR code displayed on the offline machine's terminal. This outputs the raw signed transaction hex (
0x...). -
Broadcast the transaction using any Ethereum RPC provider:
# Using cast (Foundry) cast publish 0xYOUR_SIGNED_TX_HEX --rpc-url https://YOUR-RPC-URL # Or using curl curl -X POST https://YOUR-RPC-URL \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0xYOUR_SIGNED_TX_HEX"],"id":1}'
If you copied the raw transaction hex manually from the offline machine:
- Copy the
Signed raw tx hex:value from the coldsign output - Broadcast it using any Ethereum RPC provider (see commands above)
After broadcasting, verify the transaction was included:
cast tx <TX_HASH> --rpc-url https://YOUR-RPC-URL- Keys never leave the offline machine
- Signing is review-only by default
- Intent explicitly binds identity and transaction
- Address mismatch causes refusal
- User must explicitly confirm destination before signing
- All signed bytes are inspectable before broadcast
- QR acts as a one-way data diode
Experimental / educational.
Do not use with funds you cannot afford to lose.
Notable changes are documented in CHANGELOG.md.
Distributed under the MIT License. See LICENSE for more information.