@chainvara/cosigner
v1.2.3
Published
Chainvara API co-signer: your own machine approves every transfer and holds your half of every split and MPC key. One file, no dependencies, Node.js 20+.
Maintainers
Readme
Chainvara co-signer
Your own machine gets the last word on every transfer from your Chainvara organization, and holds half of every split key (2-of-2).
- Approves or refuses each transfer by your rules: per-transfer and daily USD limits, allowed networks, allowed destinations only, blocked destinations, instant pause.
- Your own approval server: set
callbackUrlin policy.json and the co-signer asks your server before approving each transfer, with every detail; both directions are HMAC-signed with the secret fromnode chainvara-cosigner.mjs callback-secretand bound to the request id. A down, slow or forged answer approves nothing — the transfer waits. - In-person approval above an amount you choose: the transfer waits until someone approves it on this machine (local page on
127.0.0.1:8788, never exposed). - MPC wallets (2-of-2): holds your share of every MPC wallet — FROST for Solana, the XRP Ledger, Stellar, Aptos, Sui and TON (Ed25519) and Bitcoin Taproot, threshold ECDSA (DKLs23) for Ethereum, every EVM chain, Tron (TRX, USDT), the Cosmos Hub, Osmosis, Celestia, Noble (USDC), Akash and dYdX (it decodes the Cosmos SignDoc: one bank send, recipient, amount, memo, fee, chain id and key), Litecoin and Dogecoin (on Dogecoin it reads the spent amounts from the previous transactions, whose ids it recomputes). Before adding its share it decodes the transaction itself — chain, destination, amount, token, change, fee and every signature hash — and refuses anything that differs from what it approved.
- Stuck transactions: a speed-up is the same EVM transaction with a higher fee (same nonce, fee cap still applies); a cancellation (0-value transaction to the wallet itself, 21,000 gas) is accepted only for a nonce this approval already signed. Bitcoin and Litecoin replace-by-fee: a speed-up pays the same recipient the same amount; a cancellation sends everything back to the wallet and is accepted only when it spends exactly the coins this approval already signed.
- Batched Bitcoin, Litecoin and Dogecoin payouts: one transaction paying several transfers is co-signed only if this co-signer approved every one of them and the transaction pays each exactly once, at its amount, with change only back to the wallet.
- Coin consolidation (Bitcoin, Litecoin, Dogecoin): co-signed only as one output back to the wallet itself, from at most the approved number of coins, within the fee cap.
- Solana resubmission: the same approval can be signed again only 5 minutes after the previous signature — once that transaction can no longer land (durable nonces are refused) — and four times at most.
- Tron energy (Stake 2.0): staking TRX for energy or bandwidth, unstaking, withdrawing and lending energy to another wallet are decoded from the protobuf bytes — contract type, this wallet as owner, the approved amount, resource and receiver, never a locked delegation — before the co-signer adds its share.
- Cosmos staking: delegate, undelegate and claim-rewards messages are decoded too — this wallet as delegator, exactly the approved validator(s) and amount, no memo.
- Cross-chain USDC (Circle CCTP): co-signs only the exact-amount USDC approval of Circle's TokenMessenger and a depositForBurn of exactly the approved amount, to the approved recipient and chain, with no destination caller, the approved fee cap and finality; on the destination chain, only a receiveMessage of exactly the approved Circle message, which must mint on that chain to the approved recipient.
- NFT transfers: for an ERC-721 or ERC-1155 transfer it checks the collection, the token id, the quantity and that the recipient is the approved destination, byte for byte, before adding its share.
- MPC 2-of-3 with a backup share: set
recoveryPublicKeyin policy.json (the line printed bynode chainvara-recovery.mjs keygen). For a wallet created with a backup share, this machine computes a third share and seals it at once to that key — never to a key sent by Chainvara — and forgets it. Signing is unchanged; any two of Chainvara's share, this co-signer's share and the backup rebuild the key.node chainvara-recovery.mjs recover-cosigner <cosigner-backup.json> <recovery-key.json> <out.json>restores those wallets from your co-signer backup alone. Their shares are not refreshed online (the backup share would no longer match). - Split keys 2-of-2: stores your half of each key, encrypted at rest (Windows DPAPI, or 0600 files on macOS/Linux), and releases it only for a transfer it approved, with the same intent hash, encrypted to a single-use key.
- Proactive key-share refresh: renews its share of an MPC key together with Chainvara (FROST and threshold ECDSA). The address never changes and a share copied before the refresh becomes useless; the new share is only used once both sides confirmed the same key. Make a new backup after each refresh.
- No public address needed: it connects to Chainvara by itself (outbound HTTPS, like Fireblocks' API Co-Signer) — no tunnel, no open port, works behind any router or firewall.
- Fail-closed: every request must carry Chainvara's HMAC signature (5-minute window, no replays); anything unexpected is refused, and a transfer never signs without an answer.
- Encrypted backups of every key share — split keys and MPC wallets (scrypt + AES-256-GCM), with a
verifycommand.
Exit plan: chainvara-recovery.mjs
If Chainvara were ever unavailable, rebuild every private key yourself, offline, with Node.js 20+ and this single dependency-free file:
node chainvara-recovery.mjs keygen recovery-key.json— once; paste the printed public key in Chainvara (Settings → Recovery kit).- Download a recovery kit from the console (owners, with 2FA) after creating wallets; keep it with your co-signer backup.
node chainvara-recovery.mjs recover kit.json recovery-key.json cosigner-backup.json keys.json— each key is rebuilt from Chainvara's share (sealed to your recovery key) and your co-signer's, then checked against the wallet's public key. MPC ECDSA keys come out as ordinary private keys (WIF for Bitcoin-family chains), Taproot astr(WIF), FROST Ed25519 keys as the secret scalar.- Wallets with a backup share (2-of-3) need any two shares: the kit alone rebuilds them (it carries the sealed backup share), and so does
node chainvara-recovery.mjs recover-cosigner cosigner-backup.json recovery-key.json keys.jsonwithout any kit.
Verify the file
The SHA-256 of each release is published here and in your Chainvara console (Developers → API co-signer). Both must match.
# Windows (PowerShell)
Get-FileHash chainvara-cosigner.mjs
# macOS / Linux
shasum -a 256 chainvara-cosigner.mjsInstall with npm
npm install -g @chainvara/[email protected]
chainvara-cosigner setup
chainvara-cosigner serveOr without installing: npx @chainvara/[email protected] setup, then npx @chainvara/[email protected] serve. The recovery tool
is included as chainvara-recovery.
- Pin the version (
@1.2.3, never@lateston a production machine) and update on purpose after reading the release notes. - Check where it comes from: every version is published only by this repository's workflow (npm trusted publishing: no npm token exists), with a provenance
attestation linking it to the exact commit.
npm audit signaturesverifies it; the files matchSHA256SUMSabove and in your Chainvara console. - No dependencies: nothing else is downloaded at install.
- Key shares live in your data folder (
%LOCALAPPDATA%\Chainvara\cosigner, or~/.local/share/Chainvara/cosigner), not in the package: updating or reinstalling never touches them. Back them up all the same.
Quick start (Node.js 20+)
- Chainvara → Developers → API co-signer: leave the address empty (outbound connection), activate, copy the secret (shown once).
- Only if you prefer inbound calls: publish
127.0.0.1:8787over HTTPS and paste that address instead. node chainvara-cosigner.mjs setupand paste the secret.node chainvara-cosigner.mjs serveunder a service manager (Windows Startup / Task Scheduler, systemdRestart=always, launchd).- Edit
policy.json(path shown bynode chainvara-cosigner.mjs status): setallowedDestinationsandhumanApprovalAboveUsd. - After each new split-key or MPC wallet:
node chainvara-cosigner.mjs backup <file>thenverify <file>. Keep it offline. A lost share means the funds of that wallet are lost.
Commands: setup | serve | status | pause | resume | approvals | approve <id> | reject <id> | backup <file> | verify <file> | restore <file>
Full guide: in your Chainvara console, Guides → Run your co-signer.
