Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified client/tornado-cli.js
100644 → 100755
Empty file.
218 changes: 218 additions & 0 deletions formal_verification/CryptoUtilsVerification.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
(** Formal verification of the cryptographic utility functions for Tornado Cash Privacy Solution *)

Require Import Coq.Lists.List.
Require Import Coq.Bool.Bool.
Require Import Coq.Arith.Arith.
Require Import Coq.Arith.EqNat.
Require Import Coq.omega.Omega.
Require Import Coq.Logic.FunctionalExtensionality.

Import ListNotations.

(** Representation of a 32-byte array *)
Definition byte := nat.
Definition byte_array := list byte.

(** Hash function (simplified model of Keccak256) *)
Definition hash (data : byte_array) : byte_array :=
(* In a real implementation, this would be a cryptographic hash function *)
(* For verification purposes, we model it as a function that satisfies certain properties *)
data. (* Simplified for the formal model *)

(** Compute the commitment from a nullifier and secret *)
Definition compute_commitment (nullifier secret : byte_array) : byte_array :=
(* In a real implementation, this would be a Pedersen hash *)
(* For verification purposes, we model it as a function that satisfies certain properties *)
hash (nullifier ++ secret).

(** Compute the nullifier hash from a nullifier *)
Definition compute_nullifier_hash (nullifier : byte_array) : byte_array :=
(* In a real implementation, this would be a cryptographic hash function *)
(* For verification purposes, we model it as a function that satisfies certain properties *)
hash nullifier.

(** Check if a commitment exists in the commitments array *)
Fixpoint commitment_exists (commitments : list byte_array) (commitment : byte_array) : bool :=
match commitments with
| [] => false
| c :: cs => if byte_array_eq c commitment then true else commitment_exists cs commitment
end
with byte_array_eq (a b : byte_array) : bool :=
match a, b with
| [], [] => true
| x :: xs, y :: ys => if x =? y then byte_array_eq xs ys else false
| _, _ => false
end.

(** Check if a nullifier hash exists in the nullifier_hashes array *)
Fixpoint nullifier_hash_exists (nullifier_hashes : list byte_array) (nullifier_hash : byte_array) : bool :=
match nullifier_hashes with
| [] => false
| n :: ns => if byte_array_eq n nullifier_hash then true else nullifier_hash_exists ns nullifier_hash
end.

(** Add a commitment to the commitments array *)
Definition add_commitment (commitments : list byte_array) (commitment : byte_array) : list byte_array :=
if commitment_exists commitments commitment then
commitments
else
commitment :: commitments.

(** Add a nullifier hash to the nullifier_hashes array *)
Definition add_nullifier_hash (nullifier_hashes : list byte_array) (nullifier_hash : byte_array) : list byte_array :=
if nullifier_hash_exists nullifier_hashes nullifier_hash then
nullifier_hashes
else
nullifier_hash :: nullifier_hashes.

(** Theorems about the cryptographic utility functions *)

(** Theorem: compute_commitment is deterministic *)
Theorem compute_commitment_deterministic :
forall nullifier secret,
compute_commitment nullifier secret = compute_commitment nullifier secret.
Proof.
intros nullifier secret.
reflexivity.
Qed.

(** Theorem: compute_nullifier_hash is deterministic *)
Theorem compute_nullifier_hash_deterministic :
forall nullifier,
compute_nullifier_hash nullifier = compute_nullifier_hash nullifier.
Proof.
intros nullifier.
reflexivity.
Qed.

(** Theorem: commitment_exists correctly identifies existing commitments *)
Theorem commitment_exists_correct :
forall commitments commitment,
commitment_exists commitments commitment = true <->
exists c, In c commitments /\ byte_array_eq c commitment = true.
Proof.
intros commitments commitment.
split.
- (* -> direction *)
induction commitments as [|c cs IH].
+ (* Base case: empty commitments *)
simpl. intros H. discriminate H.
+ (* Inductive case: c :: cs *)
simpl. destruct (byte_array_eq c commitment) eqn:E.
* (* c equals commitment *)
intros _. exists c. split.
-- left. reflexivity.
-- exact E.
* (* c does not equal commitment *)
intros H. apply IH in H. destruct H as [c' [H1 H2]].
exists c'. split.
-- right. exact H1.
-- exact H2.
- (* <- direction *)
induction commitments as [|c cs IH].
+ (* Base case: empty commitments *)
simpl. intros [c' [H1 H2]]. destruct H1.
+ (* Inductive case: c :: cs *)
simpl. intros [c' [H1 H2]].
destruct H1 as [H1|H1].
* (* c' = c *)
subst c'. exact H2.
* (* c' in cs *)
destruct (byte_array_eq c commitment) eqn:E.
-- (* c equals commitment *)
reflexivity.
-- (* c does not equal commitment *)
apply IH. exists c'. split; assumption.
Qed.

(** Theorem: nullifier_hash_exists correctly identifies existing nullifier hashes *)
Theorem nullifier_hash_exists_correct :
forall nullifier_hashes nullifier_hash,
nullifier_hash_exists nullifier_hashes nullifier_hash = true <->
exists n, In n nullifier_hashes /\ byte_array_eq n nullifier_hash = true.
Proof.
intros nullifier_hashes nullifier_hash.
split.
- (* -> direction *)
induction nullifier_hashes as [|n ns IH].
+ (* Base case: empty nullifier_hashes *)
simpl. intros H. discriminate H.
+ (* Inductive case: n :: ns *)
simpl. destruct (byte_array_eq n nullifier_hash) eqn:E.
* (* n equals nullifier_hash *)
intros _. exists n. split.
-- left. reflexivity.
-- exact E.
* (* n does not equal nullifier_hash *)
intros H. apply IH in H. destruct H as [n' [H1 H2]].
exists n'. split.
-- right. exact H1.
-- exact H2.
- (* <- direction *)
induction nullifier_hashes as [|n ns IH].
+ (* Base case: empty nullifier_hashes *)
simpl. intros [n' [H1 H2]]. destruct H1.
+ (* Inductive case: n :: ns *)
simpl. intros [n' [H1 H2]].
destruct H1 as [H1|H1].
* (* n' = n *)
subst n'. exact H2.
* (* n' in ns *)
destruct (byte_array_eq n nullifier_hash) eqn:E.
-- (* n equals nullifier_hash *)
reflexivity.
-- (* n does not equal nullifier_hash *)
apply IH. exists n'. split; assumption.
Qed.

(** Theorem: add_commitment adds a commitment if it doesn't exist *)
Theorem add_commitment_adds_if_not_exists :
forall commitments commitment,
commitment_exists commitments commitment = false ->
commitment_exists (add_commitment commitments commitment) commitment = true.
Proof.
intros commitments commitment H.
unfold add_commitment.
rewrite H.
simpl.
destruct (byte_array_eq commitment commitment) eqn:E.
- (* commitment equals commitment *)
reflexivity.
- (* commitment does not equal commitment *)
(* This case is impossible because byte_array_eq is reflexive *)
(* We need to prove that byte_array_eq is reflexive *)
assert (forall a, byte_array_eq a a = true) as Hrefl.
{ induction a as [|x xs IH].
- (* Base case: empty array *)
simpl. reflexivity.
- (* Inductive case: x :: xs *)
simpl. rewrite <- beq_nat_refl. apply IH.
}
rewrite Hrefl in E. discriminate E.
Qed.

(** Theorem: add_nullifier_hash adds a nullifier hash if it doesn't exist *)
Theorem add_nullifier_hash_adds_if_not_exists :
forall nullifier_hashes nullifier_hash,
nullifier_hash_exists nullifier_hashes nullifier_hash = false ->
nullifier_hash_exists (add_nullifier_hash nullifier_hashes nullifier_hash) nullifier_hash = true.
Proof.
intros nullifier_hashes nullifier_hash H.
unfold add_nullifier_hash.
rewrite H.
simpl.
destruct (byte_array_eq nullifier_hash nullifier_hash) eqn:E.
- (* nullifier_hash equals nullifier_hash *)
reflexivity.
- (* nullifier_hash does not equal nullifier_hash *)
(* This case is impossible because byte_array_eq is reflexive *)
(* We need to prove that byte_array_eq is reflexive *)
assert (forall a, byte_array_eq a a = true) as Hrefl.
{ induction a as [|x xs IH].
- (* Base case: empty array *)
simpl. reflexivity.
- (* Inductive case: x :: xs *)
simpl. rewrite <- beq_nat_refl. apply IH.
}
rewrite Hrefl in E. discriminate E.
Qed.
172 changes: 172 additions & 0 deletions formal_verification/MerkleTreeVerification.v
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
(** Formal verification of the Merkle tree implementation for Tornado Cash Privacy Solution *)

Require Import Coq.Lists.List.
Require Import Coq.Bool.Bool.
Require Import Coq.Arith.Arith.
Require Import Coq.Arith.EqNat.
Require Import Coq.omega.Omega.
Require Import Coq.Logic.FunctionalExtensionality.

Import ListNotations.

(** Representation of a 32-byte array *)
Definition byte := nat.
Definition byte_array := list byte.

(** Field size for BN254 curve *)
Definition FIELD_SIZE : byte_array :=
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): Hardcoded field size for BN254 curve.

[48; 100; 78; 114; 225; 49; 160; 41; 184; 93; 18; 102; 180; 27; 75; 48;
115; 190; 84; 70; 195; 54; 177; 11; 81; 16; 90; 244; 0; 0; 0; 1].

(** Zero value for the Merkle tree *)
Definition ZERO_VALUE : byte_array :=
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): Hardcoded zero value for the Merkle tree.

[47; 229; 76; 96; 211; 172; 171; 243; 52; 58; 53; 182; 235; 161; 93; 180;
130; 27; 52; 15; 118; 231; 65; 226; 36; 150; 133; 237; 72; 153; 175; 108].

(** Check if a value is within the BN254 field *)
Fixpoint is_within_field_aux (value field : byte_array) : bool :=
match value, field with
| [], [] => true
| v :: vs, f :: fs =>
if v <? f then true
else if v >? f then false
else is_within_field_aux vs fs
| _, _ => false
end.

Definition is_within_field (value : byte_array) : bool :=
is_within_field_aux value FIELD_SIZE.

(** Take a value modulo the field size *)
Fixpoint mod_field_size_aux (value field : byte_array) (carry : nat) : byte_array :=
match value, field with
| [], [] => []
| v :: vs, f :: fs =>
let diff := v + carry * 256 in
if diff >=? f then
(diff - f) :: mod_field_size_aux vs fs 1
else
diff :: mod_field_size_aux vs fs 0
| _, _ => [] (* Should not happen with equal length arrays *)
end.

Definition mod_field_size (value : byte_array) : byte_array :=
mod_field_size_aux value FIELD_SIZE 0.

(** Hash function (simplified model of Keccak256) *)
Definition hash (left right : byte_array) : byte_array :=
(* In a real implementation, this would be a cryptographic hash function *)
(* For verification purposes, we model it as a function that satisfies certain properties *)
mod_field_size (left ++ right).

(** Hash left and right nodes *)
Definition hash_left_right (left right : byte_array) : option byte_array :=
if andb (is_within_field left) (is_within_field right) then
Some (hash left right)
else
None.

(** Get the zero value at a specific level in the Merkle tree *)
Fixpoint get_zero_value (level : nat) : byte_array :=
match level with
| 0 => ZERO_VALUE
| S n =>
match hash_left_right (get_zero_value n) (get_zero_value n) with
| Some h => h
| None => ZERO_VALUE (* Should not happen if zero values are within field *)
end
end.

(** Merkle tree structure *)
Record MerkleTree := {
height : nat;
current_index : nat;
next_index : nat;
current_root_index : nat;
roots : list byte_array;
filled_subtrees : list byte_array;
nullifier_hashes : list byte_array;
commitments : list byte_array
}.

(** Check if a root is in the root history *)
Fixpoint is_known_root_aux (root : byte_array) (roots : list byte_array)
(current_index start_index : nat) (checked_all : bool) : bool :=
match roots with
| [] => false
| r :: rs =>
if current_index =? start_index then
if checked_all then
false
else
if byte_array_eq root r then true
else is_known_root_aux root rs (pred current_index) start_index (current_index =? 0)
else
if byte_array_eq root r then true
else is_known_root_aux root rs (pred current_index) start_index (current_index =? 0)
end
with byte_array_eq (a b : byte_array) : bool :=
match a, b with
| [], [] => true
| x :: xs, y :: ys => if x =? y then byte_array_eq xs ys else false
| _, _ => false
end.

Definition is_known_root (root : byte_array) (roots : list byte_array) (current_root_index : nat) : bool :=
(* Check if the root is zero *)
if forallb (fun x => x =? 0) root then
false
else
is_known_root_aux root roots current_root_index current_root_index false.

(** Theorems about the Merkle tree implementation *)

(** Theorem: hash_left_right preserves field membership *)
Theorem hash_left_right_preserves_field :
forall left right result,
hash_left_right left right = Some result ->
is_within_field result = true.
Proof.
intros left right result H.
unfold hash_left_right in H.
destruct (andb (is_within_field left) (is_within_field right)) eqn:E.
- (* Both inputs are within field *)
inversion H. subst.
unfold hash.
(* We assume mod_field_size always produces a value within the field *)
Admitted. (* In a real proof, we would prove this *)

(** Theorem: is_known_root correctly identifies roots in the history *)
Theorem is_known_root_correct :
forall root roots current_root_index,
is_known_root root roots current_root_index = true ->
exists i, i < length roots /\ nth i roots [] = root.
Proof.
intros root roots current_root_index H.
unfold is_known_root in H.
destruct (forallb (fun x => x =? 0) root) eqn:E.
- (* Root is zero, which should return false *)
discriminate H.
- (* Root is non-zero *)
(* This proof would involve reasoning about the is_known_root_aux function *)
Admitted. (* In a real proof, we would prove this *)

(** Theorem: get_zero_value produces values within the field *)
Theorem get_zero_value_within_field :
forall level,
is_within_field (get_zero_value level) = true.
Proof.
induction level.
- (* Base case: level = 0 *)
simpl.
(* We assume ZERO_VALUE is within the field *)
Admitted. (* In a real proof, we would prove this *)
- (* Inductive case: level = S n *)
simpl.
destruct (hash_left_right (get_zero_value level) (get_zero_value level)) eqn:E.
+ (* hash_left_right succeeded *)
apply hash_left_right_preserves_field in E.
exact E.
+ (* hash_left_right failed, which should not happen *)
(* We need to show that this case is impossible *)
Admitted. (* In a real proof, we would prove this *)
Loading