Ethereum custody protocol

Ethereum Threshold ECDSA V1

Three cooperating Node.js processes control one Ethereum EOA and produce a standard recoverable secp256k1 ECDSA signature only when any two of the three nodes participate. The complete private key is never created during DKG or signing.

2-of-3 ECDSA secp256k1 EIP-1559 ERC-20 Node.js 20+ MIT
Key model

Shamir 2-of-3 shares, Feldman VSS commitments, and no assembled private key.

Signing result

Standard low-S Ethereum signature in r || s || yParity format.

Transaction scope

Native ETH, EIP-1559, arbitrary digests, messages, and ERC-20 transfers.

Implementation layout

The interface follows the BankNode Bitcoin project, preserving the same operational model across chains.

  • three-headed-bot.js - entry point, peer HTTP(S), DKG, and signing orchestration.
  • crypto_backend.js - cryptographic backend, Ethereum transaction building, and validation.
  • tg_backend.js - Telegram polling, commands, and approval flow.
  • node-{1,2,3}.config.example.json - three node configurations in the Bitcoin-compatible format.
  • data/ - each node's local secret share and persisted signing parameters.

Cryptographic model

  • Three participants independently create a secp256k1 contribution, a Shamir 2-of-3 share, a Feldman VSS commitment, and a 3072-bit Paillier key pair.
  • Threshold signing uses the corrected BitGo GG18 scheme with MtA, Paillier modulus challenge, range proofs, proof of share possession, and verifiable V/A and U/T commitment stages.
  • The result is a standard Ethereum low-S signature in r || s || yParity format, compatible with ethers.recoverAddress, geth, and EVM tooling.
  • Final 32-byte transaction digests are signed without an additional SHA-256 operation.
  • /sign and /multisign apply EIP-191 hashMessage before signing.
  • EIP-1559 and ERC-20 transactions are locally reconstructed and the recovered address is verified before broadcast.

Determinism and recovery

Each node's public contribution is derived deterministically from crypto.masterSeed_fix, the Telegram wallet key, and fixed parameters using HMAC-SHA512. Repeating DKG with the same fixed fields reproduces the same Ethereum address.

Paillier keys, Shamir polynomials, and signing nonces are generated by the system CSPRNG and differ on every run. Losing one share file is tolerated; losing any two shares requires a new DKG.

Important: In-flight signing nonce and protocol state are never persisted. After a crash, the signing ceremony must start again.

Installation

Node.js 20 or newer is required.

npm ci
npm run check
npm test

The full test creates real 3072-bit Paillier keys for all three participants and completes a 2-of-3 Ethereum signature. The auxiliary RSA modulus is reduced to 512 bits only in tests for speed; runtime always calls generateNtilde(3072).

Copy each .config.example.json file to .config.json and replace every PASTE_* and REPLACE_* value. Set telegram.botToken, telegram.allowedGroupId, and a unique crypto.masterSeed_fix containing at least 32 bytes.

npm start -- node-1.config.example.json
npm start -- node-2.config.example.json
npm start -- node-3.config.example.json

The default Ethereum peer ports are 6661, 6662, and 6663.

Production deployment

  1. Deploy the three nodes on separate machines and in separate security domains.
  2. Configure http.tls.certPem and http.tls.keyPem.
  3. Use HTTPS peer URLs and certificates signed by a system-trusted CA.
  4. Restrict inter-node ports to participant addresses with network ACLs.
  5. Store data/*.db on encrypted storage with separate backups and access controls.
  6. Set runtime.environment to production and retain the pinned package-lock.json.

During /create, all nodes generate reusable 3072-bit Ntilde signing parameters in parallel and persist them locally. Setup stages allow up to 60 minutes for slower processors; ordinary signing requests retain shorter timeouts.

Set runtime.logLevel to debug to inspect RPC calls, approvals, peer requests, challenge handling, and MPC signing rounds.

Telegram commands

Commands are accepted only from allowedGroupId and must be addressed to the bot.

/create@bot [user_id2 user_id3]
/address@bot
/balance@bot [address]
/tokenbalance@bot <token_contract> [owner]
/usdtbalance@bot [owner]
/utxo@bot
/status@bot
/sign@bot <message>
/signhash@bot <32-byte-hex>
/send@bot <to> <amount_wei> [max_fee_gwei]
/tokensend@bot <token_contract> <to> <amount_tokens> [max_fee_gwei]
/usdtsend@bot <to> <amount_usdt> [max_fee_gwei]
/multisign@bot <id2> <id3> <message>
/multisignhash@bot <id2> <id3> <32-byte-hex>
/multisend@bot <id2> <id3> <to> <amount_wei> [max_fee_gwei]
/multitokensend@bot <id2> <id3> <token_contract> <to> <amount_tokens> [max_fee_gwei]
/usdtmultisend@bot <id2> <id3> <to> <amount_usdt> [max_fee_gwei]
/approve@bot
/reject@bot

Transaction behavior

ETH transfers

/send and /multisend build EIP-1559 transactions, estimate gas, threshold-sign unsignedHash, verify the recovered address locally, and only then submit the raw transaction to an RPC node.

An amount_wei value of zero enables send-all mode. The system reserves the maximum EIP-1559 fee and signs the actual transferable balance.

ERC-20 and USDT

Token commands verify contract code, decimals, token balance, simulation results, and available ETH for gas. Amounts are converted to base units without floating-point arithmetic.

The USDT aliases use the fixed Ethereum Mainnet contract 0xdAC17F958D2ee523a2206206994597C13D831ec7 and operate only when ethereum.chainId = 1.

Approval flow

Before every signature, the second signer publishes the digest and operation description. Personal mode requires approval through the second node's bot; group mode requires approval from one of the two additional Telegram identities.

Network and storage

  • Peer configuration uses index_fix, id, ip, and port.
  • Production peer authentication relies on restricted network access and CA-validated HTTPS.
  • Secret DKG shares are transmitted directly to the recipient, never through the coordinator.
  • Database writes use a temporary file followed by atomic rename; POSIX file mode is 0600.
  • RPC providers are used in order with automatic failover.

License and audit

The project is MIT-licensed. The pinned cryptographic dependency stack uses @bitgo/[email protected], @bitgo/[email protected], and [email protected]. The package override must be retained to prevent an unintended dependency and license change.

AI-assisted security review

The Ethereum threshold ECDSA implementation and its integration controls were reviewed with Claude Mythos and GPT-5.6 as AI-assisted analysis tools. This review does not replace an independent professional security audit.