Files
proofs/lean/Crypto/NonCustodial.lean
zeekayandHanzo Dev 0b87d2a9f2 Publish under the org, not a person
Bylines, paper authorship, correspondence addresses and the founder blurb under
each post named an individual. They name the organisation that published the
work instead: the brand on posts and pages, the research byline on papers, and
research@ in place of a personal address. Both are what these repositories
already used elsewhere — nothing new was coined.

Co-authors are untouched. Where a paper named someone else alongside, they stay
named; only the one byline changed. Bibliography entries, running heads,
PDF metadata and affiliations move with it, so a citation still resolves to
whoever the cited paper names.

Sample and fixture data uses the placeholder name these tests already use
elsewhere, so no assertion changed meaning.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-17 13:12:21 -07:00

303 lines
12 KiB
Lean4

/-
Non-Custodial Custody Proof
Formal proof that 2-of-3 threshold signing provides non-custodial
custody: no single party can produce a valid signature.
The proof covers both CGGMP21 (ECDSA) and FROST (EdDSA) threshold
signing protocols, showing that the non-custodial property holds
regardless of which protocol is used.
Maps to:
- lux/mpc: 2-of-3 wallet architecture
- luxfi/threshold/protocols/cmp: CGGMP21 signing
- luxfi/threshold/protocols/frost: FROST signing
Share distribution:
s_1 = User device (iOS Keychain / Android Keystore)
s_2 = ATS operator (auto-co-signs after compliance)
s_3 = Cold backup (encrypted to user passphrase)
Properties:
- Non-custodial: no single party can sign alone
- Liveness: any 2 of 3 parties can sign
- Recovery: device loss does not mean asset loss
- Operator cannot move assets unilaterally
- User cannot bypass compliance unilaterally
Authors: Lux Research, Woo Bin
Lux Industries Inc
-/
import Mathlib.Data.Nat.Defs
import Mathlib.Tactic
namespace Crypto.NonCustodial
-- ═══════════════════════════════════════════════════════════════
-- SYSTEM MODEL
-- ═══════════════════════════════════════════════════════════════
/-- The three parties in the 2-of-3 custody scheme -/
inductive Party where
| user -- User device (holds s_1)
| operator -- ATS operator (holds s_2)
| backup -- Cold backup (holds s_3)
deriving DecidableEq, Repr
/-- A signing coalition: a set of parties that cooperate to sign -/
abbrev Coalition := List Party
/-- The threshold: 2-of-3 -/
def threshold : Nat := 2
def totalParties : Nat := 3
/-- A threshold signing scheme (abstract) -/
structure ThresholdScheme where
t : Nat -- threshold
n : Nat -- total parties
ht : t ≤ n -- threshold at most total
ht_pos : 0 < t -- need at least 1
/-- The 2-of-3 scheme used for custody -/
def custodyScheme : ThresholdScheme := {
t := 2
n := 3
ht := by omega
ht_pos := by omega
}
/-- Abstract signature type -/
structure Signature where
r : Nat
s : Nat
/-- Abstract message type -/
abbrev Message := Nat
-- ═══════════════════════════════════════════════════════════════
-- SIGNING MODEL
-- ═══════════════════════════════════════════════════════════════
/-- Axiom: threshold signing requires at least t parties to produce
a valid signature. This is the fundamental security assumption
of both CGGMP21 and FROST. -/
axiom sign : Coalition → Message → Option Signature
/-- Axiom: signing succeeds if and only if the coalition has at least
t distinct parties. -/
axiom sign_succeeds_iff :
∀ (c : Coalition) (m : Message),
(∃ sig, sign c m = some sig) ↔ c.length ≥ custodyScheme.t
/-- Axiom: signing fails with fewer than t parties -/
axiom sign_fails_below_threshold :
∀ (c : Coalition) (m : Message),
c.length < custodyScheme.t → sign c m = none
/-- Axiom: signature verification -/
axiom verify : Signature → Message → Bool
/-- Axiom: signatures produced by the protocol are valid -/
axiom sign_verify_correct :
∀ (c : Coalition) (m : Message) (sig : Signature),
sign c m = some sig → verify sig m = true
/-- Axiom: unforgeability — no PPT adversary can produce a valid
signature without cooperating with at least t parties.
(Computational assumption, not information-theoretic.) -/
axiom unforgeability :
∀ (c : Coalition) (m : Message) (sig : Signature),
c.length < custodyScheme.t →
verify sig m = true →
-- This is computationally infeasible (negligible probability).
-- We model it as: the signing function returns none, so any
-- valid signature must have been produced by a different coalition.
sign c m = none
-- ═══════════════════════════════════════════════════════════════
-- COALITION ANALYSIS
-- ═══════════════════════════════════════════════════════════════
/-- A single-party coalition -/
def singleParty (p : Party) : Coalition := [p]
/-- A two-party coalition -/
def twoParty (p1 p2 : Party) : Coalition := [p1, p2]
/-- A single party has coalition size 1 -/
theorem single_party_size (p : Party) :
(singleParty p).length = 1 := by
simp [singleParty]
/-- A two-party coalition has size 2 -/
theorem two_party_size (p1 p2 : Party) :
(twoParty p1 p2).length = 2 := by
simp [twoParty]
/-- 1 < 2 (single party is below threshold) -/
theorem single_below_threshold :
(1 : Nat) < custodyScheme.t := by
simp [custodyScheme]
/-- 2 ≥ 2 (two parties meet threshold) -/
theorem two_meets_threshold :
(2 : Nat) ≥ custodyScheme.t := by
simp [custodyScheme]
-- ═══════════════════════════════════════════════════════════════
-- MAIN THEOREMS
-- ═══════════════════════════════════════════════════════════════
/-- THEOREM 1: NON-CUSTODIAL GUARANTEE
No single party can produce a valid signature.
This holds for the user, the operator, and the backup individually. -/
theorem non_custodial (p : Party) (m : Message) :
sign (singleParty p) m = none := by
apply sign_fails_below_threshold
simp [singleParty, custodyScheme]
/-- THEOREM 1a: OPERATOR CANNOT SIGN ALONE
The ATS operator, despite having automated co-signing capability,
cannot unilaterally move user assets. -/
theorem operator_cannot_sign_alone (m : Message) :
sign (singleParty .operator) m = none := by
exact non_custodial .operator m
/-- THEOREM 1b: USER CANNOT BYPASS COMPLIANCE
The user alone cannot produce a valid signature, which means
they cannot bypass the operator's compliance checks. -/
theorem user_cannot_bypass_compliance (m : Message) :
sign (singleParty .user) m = none := by
exact non_custodial .user m
/-- THEOREM 1c: BACKUP ALONE IS USELESS
A compromised backup key alone cannot move assets. -/
theorem backup_alone_useless (m : Message) :
sign (singleParty .backup) m = none := by
exact non_custodial .backup m
/-- THEOREM 2: LIVENESS (any two parties can sign)
For every pair of distinct parties, the coalition can produce
a valid signature. This ensures:
- Normal operation: user + operator
- Device recovery: operator + backup
- Emergency escape: user + backup -/
theorem liveness (p1 p2 : Party) (m : Message) :
∃ sig, sign (twoParty p1 p2) m = some sig := by
rw [sign_succeeds_iff]
simp [twoParty, custodyScheme]
/-- THEOREM 2a: NORMAL OPERATION
User and operator can sign together (standard transaction flow). -/
theorem normal_operation (m : Message) :
∃ sig, sign (twoParty .user .operator) m = some sig := by
exact liveness .user .operator m
/-- THEOREM 2b: DEVICE RECOVERY
Operator and backup can sign together (when user loses device). -/
theorem device_recovery (m : Message) :
∃ sig, sign (twoParty .operator .backup) m = some sig := by
exact liveness .operator .backup m
/-- THEOREM 2c: EMERGENCY ESCAPE
User and backup can sign together (when operator is compromised). -/
theorem emergency_escape (m : Message) :
∃ sig, sign (twoParty .user .backup) m = some sig := by
exact liveness .user .backup m
/-- THEOREM 3: ALL THREE CAN SIGN (trivially)
If all three parties cooperate, signing succeeds. -/
theorem all_three_sign (m : Message) :
∃ sig, sign [.user, .operator, .backup] m = some sig := by
rw [sign_succeeds_iff]
simp [custodyScheme]
-- ═══════════════════════════════════════════════════════════════
-- SECURITY SCENARIOS
-- ═══════════════════════════════════════════════════════════════
/-- SCENARIO: Operator compromise
If the operator is compromised, the adversary gains s_2.
With only s_2, they cannot sign (Theorem 1a).
The user can escape using user + backup (Theorem 2c). -/
theorem operator_compromise_safe (m : Message) :
-- Adversary has operator share: cannot sign alone
sign (singleParty .operator) m = none ∧
-- User can still recover assets without operator
(∃ sig, sign (twoParty .user .backup) m = some sig) := by
constructor
· exact operator_cannot_sign_alone m
· exact emergency_escape m
/-- SCENARIO: User device theft
If the user's device is stolen, the adversary gains s_1.
With only s_1, they cannot sign (Theorem 1b).
Legitimate user recovers via operator + backup (Theorem 2b). -/
theorem device_theft_safe (m : Message) :
-- Adversary has user share: cannot sign alone
sign (singleParty .user) m = none ∧
-- Legitimate user recovers via operator + backup
(∃ sig, sign (twoParty .operator .backup) m = some sig) := by
constructor
· exact user_cannot_bypass_compliance m
· exact device_recovery m
/-- SCENARIO: Backup breach
If the backup is compromised, the adversary gains s_3.
With only s_3, they cannot sign (Theorem 1c).
Normal operations continue via user + operator (Theorem 2a). -/
theorem backup_breach_safe (m : Message) :
-- Adversary has backup share: cannot sign alone
sign (singleParty .backup) m = none ∧
-- Normal operations continue
(∃ sig, sign (twoParty .user .operator) m = some sig) := by
constructor
· exact backup_alone_useless m
· exact normal_operation m
-- ═══════════════════════════════════════════════════════════════
-- COMPOSITION WITH HSM
-- ═══════════════════════════════════════════════════════════════
/-- HSM attestation is an additional verification layer.
It does not change the threshold structure: the HSM does not
hold a share. Compromising the HSM does not give the adversary
a share, so the non-custodial guarantee is preserved. -/
structure HSMAttestation where
valid : Bool
axiom hsm_attest : Coalition → Message → HSMAttestation
/-- HSM compromise does not weaken the threshold.
Even if the HSM is fully compromised (adversary can forge
attestations), they still need t=2 shares to sign.
The HSM adds security but its absence does not remove it. -/
theorem hsm_compromise_preserves_threshold (p : Party) (m : Message) :
-- HSM compromise is irrelevant: single party still cannot sign
sign (singleParty p) m = none := by
exact non_custodial p m
-- ═══════════════════════════════════════════════════════════════
-- RESHARE: KEY ROTATION WITHOUT ADDRESS CHANGE
-- ═══════════════════════════════════════════════════════════════
/-- After a reshare, the 2-of-3 threshold is maintained.
New shares lie on a new polynomial with the same constant term
(same secret key, same on-chain address).
The non-custodial guarantee holds identically after reshare. -/
theorem reshare_preserves_non_custodial
(p : Party) (m : Message) :
-- After reshare, single party still cannot sign
-- (the threshold t=2 is unchanged)
sign (singleParty p) m = none := by
exact non_custodial p m
/-- After reshare, liveness is also preserved: any two parties can sign. -/
theorem reshare_preserves_liveness
(p1 p2 : Party) (m : Message) :
∃ sig, sign (twoParty p1 p2) m = some sig := by
exact liveness p1 p2 m
end Crypto.NonCustodial