Files
mpc/cmd/mpcd
zeekay bc08c7363d TON and XRP deposit addresses, derived the way the spender derives them
TON's wallet version is not a preference, it is FORCED. The account_id is a hash
of the wallet contract's StateInit, so the same Ed25519 key yields a different
address under v3R2, v4R2 and W5 — and luxfi/bridge's TON assembler is the only
code in the estate that can SPEND TON. It builds v4R2 with subwallet 698983191.
Emitting any other version would produce addresses that fund normally and that
our own signer can never deploy a contract for: money in, unspendable. So this
makes the identical call into the identical library rather than hand-rolling
cell serialization.

Non-bounceable (UQ…) because a bounceable message to a not-yet-deployed contract
is returned to sender, and a deposit address is undeployed by definition until
its first spend. Bridge makes the same choice.

⚠ SECOND LIVE WRONG-ADDRESS BUG, same shape as the TON/Solana one removed last
week: handlers_wallets served addresses["xrp"] = *wallet.EVMAddress — a 20-byte
hex EVM address labelled XRP. Both xrp and ton now derive from the stored public
keys through pkg/address. Derived on READ rather than adding columns, so every
wallet minted before today gets a correct address with no migration.

Vectors are external on purpose. XRP matches three published XRPL fixtures
(xrpl.js secp256k1 + ed25519, and the dev-portal address-encoding example). TON
uses MAINNET as the oracle — library fixtures would be self-referential — by
harvesting accounts whose code_hash is the v4R2 code hash and asking each
contract for its own public key; one row is corroborated independently by
@ton/ton's published spec address. The code cell is pinned to mainnet's hash and
the data layout to seqno‖subwallet‖pubkey.

Curve confusion fails closed both ways: TON refuses a secp256k1 key, XRP refuses
an Ed25519 one, and TON refuses every torsion-order point Solana refuses.

Known trap left alone deliberately: pubKeyToEVMAddress and pubKeyToBtcAddress
still assume even-y for a 32-byte input, which is wrong for half of keys. Not
reachable today because keygen serializes the 33-byte compressed point, but it
is one refactor from being live. address.XRP refuses that input instead. Fixing
the EVM/BTC paths is a higher-risk edit that deserves its own review.
2026-08-06 17:50:11 -07:00
..

mpcd — MPC Daemon

mpcd is a threshold-signature daemon. It speaks ZAP wire protocol over TLS 1.3 directly between peers — no NATS, no Consul, no PostgreSQL in the critical path. The daemon is the same binary in every deployment shape; the operating mode is selected entirely by flags.

Operating modes

There is exactly one start command (mpcd start) and the topology is determined by the --peer and --threshold flags:

Topology Flags Use
Standalone (1-of-1 self-loop) --threshold 1 (no --peer) Smoke tests, dev sandbox
Networked t-of-n cluster --threshold t --peer ... (n−1 peers) Production

In all cases mpcd runs the consensus-embedded transport (pkg/transport). The "consensus" here is local PoA membership over the node's own Ed25519 identities — it is intentionally not the Lux mainnet chain, because key shares must never leave the cluster.

Standalone (1-of-1)

mpcd start \
    --node-id solo \
    --listen :9999 \
    --api :9800 \
    --data /var/lib/mpcd \
    --threshold 1

Starts mpcd with itself as the only quorum member. /healthz will report signing_quorum: true immediately. Keygen and signing of fresh wallets require ≥2 parties — CGGMP21 and FROST cannot produce shares with a single party — so this mode exists only for boot-up smoke tests, e2e harness fixtures, and verifying TLS / membership / API plumbing. For real signing, run a multi-node cluster.

Networked (t-of-n)

# Node 0
mpcd start --node-id node0 --listen :9999 --api :9800 \
    --data /data/node0 --threshold 2 \
    --peer [email protected]:9999 --peer [email protected]:9999

# Node 1
mpcd start --node-id node1 --listen :9999 --api :9800 \
    --data /data/node1 --threshold 2 \
    --peer [email protected]:9999 --peer [email protected]:9999

# Node 2 — same pattern

The daemon expects the cluster size n = len(peers) + 1 and rejects start when threshold > n. See pkg/transport/registry.go::HasSigningQuorum for the quorum arithmetic.

Smoke test

go test -tags=integration ./cmd/mpcd/... -count=1 -timeout 90s

standalone_test.go builds mpcd, runs it with --threshold 1 on free ports and a fresh data directory, polls /healthz until quorum holds, and asserts the response shape. It does not exercise keygen — see e2e/ for that.