mirror of
https://github.com/luxfi/proofs.git
synced 2026-10-30 15:21:05 +00:00
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]>
303 lines
12 KiB
Lean4
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
|