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.
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
|