Files

128 lines
5.2 KiB
Markdown
Raw Permalink Normal View History

# Lux Governance — Multisig Signer Policy
**Status**: Active 2026-06-05 → next rotation TBD
**Owners of this doc**: Lux Industries protocol governance
## Scope
This document is the canonical record of the launch-time multisig signer
configuration for production Safe deployments across the Lux network of
blockchains (Lux primary, Hanzo L2, Zoo L2, Pars L2, SPC L2, Osage L2 and
all corresponding testnet/devnet pairs).
It is the **only** place the signer derivation, threshold, and rotation
plan live. Any code that constructs a launch-time Safe (notably
`lux/standard/contracts/script/DeploySafe.s.sol`) reads these constants
back from this doc — when they change, the doc changes first.
## Launch-time signer set
| Slot | Derivation path (BIP44, ETH cointype 60) | Role |
|------|------------------------------------------|------|
| 0 | `m/44'/60'/0'/0/0` (LUX_MNEMONIC) | Founder operator (deployer) |
| 1 | `m/44'/60'/0'/0/1` (LUX_MNEMONIC) | Co-founder operator |
| 2 | `m/44'/60'/0'/0/2` (LUX_MNEMONIC) | Security lead |
| 3 | `m/44'/60'/0'/0/3` (LUX_MNEMONIC) | Treasury / operations lead |
| 4 | `m/44'/60'/0'/0/4` (LUX_MNEMONIC) | Engineering lead |
All five slots derive from the **same** `LUX_MNEMONIC` seed for launch.
This is intentional and time-bounded: launch-time signers exist to
deploy the rest of the stack and immediately hand off control. Each
hardware-key holder will replace their derived slot via the standard
Safe owner-management flow (`addOwnerWithThreshold``removeOwner` or
`swapOwner`) once the deploy completes.
## Threshold
- **Production multisigs (mainnet)**: **3 of 5** (default)
- **Testnet / devnet multisigs**: **2 of 5** is acceptable, set via
`SAFE_THRESHOLD=2` at deploy time.
- Hard ceiling: threshold ≤ owner count (`SIGNER_COUNT = 5`).
- Threshold ≥ 1.
`DeploySafe.s.sol` reads `SAFE_THRESHOLD` from env; default is 3.
## Deterministic predictability
The Safe proxy address per (brand, env, chainId) is deterministic by
construction:
```
saltNonce = uint256(keccak256(abi.encode(BRAND, SAFE_ENV, block.chainid)))
proxy = SafeProxyFactory.createProxyWithNonce(singleton, setupCalldata, saltNonce)
```
so re-running the deploy script against the same `(brand, env,
chainId)` produces the **same proxy address** (the second call will
revert with the upstream Safe "creation failed" error rather than
overwriting). This is the intended behaviour — it gives reviewers a
way to pre-compute proxy addresses for spec docs without trusting any
live broadcast.
## Rotation plan (post-launch, before mainnet bridge activation)
1. Each of the five hardware-key holders signs a registration message
committing their hardware-derived address to a public proof file at
`~/work/lux/dao/signers/<slot>.proof`.
2. The Safe is reconfigured **per-brand**: each brand's Safe receives a
per-brand owner set drawn from the same five hardware keys, possibly
with brand-specific advisor seats added.
3. After every brand's Safe is rotated, the launch-time derived
addresses are removed via `removeOwner`. The launch-time mnemonic
key chain is then no longer authoritative for any Safe.
Rotation must happen **before** the BridgeV4 activation timestamp on
mainnet (≥ 2026-08-08 per `~/work/lux/genesis/configs/mainnet/upgrade.json`).
If the rotation is not complete, BridgeV4 activation must be deferred —
a five-launch-key Safe is fine for protocol bringup but not for
holding bridge fee receivership in production steady-state.
## Per-chain Safe deployment manifest
Each (brand, env) Safe address is recorded in:
```
~/work/lux/standard/deployments/brand-l1-<env>/<brand>.json
.contracts.Safe
```
The same Safe is used as the `protocolFeeController` in
`~/work/lux/genesis/configs/mainnet/upgrade.json` once activation lands.
## Why not Snowdrop-style governance now
A Snowdrop-style on-chain proposal flow with `Timelock + vLUX + GaugeController`
already exists in the Standard contract set (`DeployMultiNetwork.s.sol`
Phase 5) and is intentionally deployed **alongside** the Safe. The Safe
is the operational signer for protocol upgrades during the rotation
window; the Timelock + vLUX path becomes the authoritative governance
route once token distribution lands and quorum is reachable. Until
quorum is reachable, a 3-of-5 Safe is the right amount of friction.
## Anti-patterns explicitly avoided
- **Single-key admin** — never write `admin = deployer` in production
contracts. Always rotate to the Safe immediately after deploy.
- **Threshold < ⌈n/2⌉** — minority threshold is collusion-friendly. We
default 3-of-5 and refuse to encode 2-of-5 in production code.
- **Per-app mnemonics** — one `LUX_MNEMONIC` for launch derivation. No
per-app seeds (those just multiply the number of single-key compromise
surfaces).
- **Hot-wallet signers on mainnet** — launch-time derived slots get
removed before bridge mainnet activation.
## Verification
Anyone with `LUX_MNEMONIC` and `forge` can independently reproduce all
five launch-time signers:
```bash
for i in 0 1 2 3 4; do
cast wallet address \
--mnemonic "$LUX_MNEMONIC" \
--mnemonic-index "$i"
done
```
These addresses must match what is logged by `DeploySafe.s.sol`.