Documentation

Complexity.Crypto.Computational.MerkleTree.Binding

Merkle trees as authenticated data structures: verification and position binding #

Cslib.Crypto.merkleRoot hashes a list of leaves and Cslib.Crypto.merklePath opens one position. This module adds the verifier and the security notion of an authenticated data structure scheme ([BS23], Section 8.9.1): a verifier merkleVerify with completeness, binding and a polynomial-time certificate, the game of Attack Game 8.2, and the security of Theorem 8.8, reduced to collision resistance with no loss.

Differences from [BS23]. Attack Game 8.2 is stated for a scheme over (X^n, Y) with a fixed number n of items; here:

Kilian's construction. Kilian's succinct arguments commit to a PCP proof with a hash tree and open the queried positions (Barak and Goldreich, Construction 3.4, steps P1.3 and V3). That tree differs from this one, which is [BS23]'s Figure 8.12: its leaf labels are the proof bits themselves, not their hashes, and an internal label hashes the plain concatenation ℓ_γ0 ℓ_γ1 of its children's labels, not their encPair pairing. Its prover sends both child labels at every level of a path rather than only the siblings, and the verifier checks the same hash equations along the path. Barak and Goldreich's hashing family resists non-uniform circuits.

Main definitions #

Main results #

References #

Verifying openings #

def Cslib.Crypto.merkleVerify (H : List Bool → List Bool → List Bool) (k : List Bool) (d : ℕ) (root : List Bool) (i : ℕ) (leaf : List Bool) (path : List (List Bool)) :

Accept path as an opening of position i of a depth-d tree with root root to leaf: the path has d labels, i < 2 ^ d, and the recomputed root is root. This is the verifier V of the Merkle tree as an authenticated data structure ([BS23], Definition 8.3), with the depth as an explicit argument.

Instances For
    theorem Cslib.Crypto.merkleVerify_merklePath {H : List Bool → List Bool → List Bool} {k : List Bool} (leaves : List (List Bool)) {i : ℕ} (hi : i < 2 ^ Nat.clog 2 leaves.length) :
    merkleVerify H k (Nat.clog 2 leaves.length) (merkleRoot H k leaves) i (leaves.getD i []) (merklePath H k leaves i) = true

    Honest openings are accepted: every position i < 2 ^ Nat.clog 2 leaves.length of the tree over leaves, opened by its authentication path, is accepted against the root (the correctness requirement of [BS23], Definition 8.3).

    theorem Cslib.Crypto.merkleVerify_merklePath_of_clog_eq {H : List Bool → List Bool → List Bool} {k : List Bool} {leaves : List (List Bool)} {d i : ℕ} (hd : Nat.clog 2 leaves.length = d) (hi : i < 2 ^ d) :
    merkleVerify H k d (merkleRoot H k leaves) i (leaves.getD i []) (merklePath H k leaves i) = true

    Honest openings are accepted at any depth d equal to the tree's depth, for instance d = Nat.clog 2 N for N leaves.

    theorem Cslib.Crypto.length_of_mem_merkleLevel {H : List Bool → List Bool → List Bool} {k : List Bool} (hlen : ∀ (k x : List Bool), (H k x).length = k.length) {leaves : List (List Bool)} {j : ℕ} {y : List Bool} (hy : y ∈ merkleLevel H k leaves j) :

    Every node of the tree has k.length bits when H k returns k.length bits.

    theorem Cslib.Crypto.length_le_of_mem_merklePath {H : List Bool → List Bool → List Bool} {k : List Bool} (hlen : ∀ (k x : List Bool), (H k x).length = k.length) {leaves : List (List Bool)} {i : ℕ} {s : List Bool} (hs : s ∈ merklePath H k leaves i) :

    Openings are short: the labels of an authentication path have at most k.length bits (a sibling outside the tree reads as []), so an opening of the tree over leaves has at most Nat.clog 2 leaves.length * k.length bits of labels.

    theorem Cslib.Crypto.isCollision_of_merkleVerify {H : List Bool → List Bool → List Bool} {k : List Bool} {d : ℕ} {root : List Bool} {i : ℕ} {leaf₁ leaf₂ : List Bool} {path₁ path₂ : List (List Bool)} (h₁ : merkleVerify H k d root i leaf₁ path₁ = true) (h₂ : merkleVerify H k d root i leaf₂ path₂ = true) (hne : leaf₁ ≠ leaf₂) (n : ℕ) :
    isCollision H (fun (x : ℕ) (x_1 : List Bool) => true) n k (extractMerkleCollision H k leaf₁ leaf₂ i path₁ path₂).1 (extractMerkleCollision H k leaf₁ leaf₂ i path₁ path₂).2 = true

    Binding of the verifier: two openings of position i to different leaves, both accepted against root at depth d, contain a collision of H k, which extractMerkleCollision finds. The length check makes the two paths equally long, as isCollision_extractMerkleCollision requires.

    Position binding #

    @[reducible, inline]

    The output of a position-binding adversary: a root and a position, then two openings of that position, each a leaf and its path. The depth is the length of the first path.

    Instances For
      @[reducible, inline]

      The encoding of MerkleBindingOutput: bit strings as themselves, the position in unary, as in computableInPolyTime_extractMerkleCollision. An efficient adversary therefore outputs only polynomially bounded positions.

      Instances For
        @[reducible, inline]

        A position-binding adversary receives the unary security parameter n and the hash key, and outputs a root, a position and two openings.

        Instances For

          out breaks position binding under the key k: both openings are accepted at the same position of the same root, at the depth of the first path, and their leaves differ.

          Instances For
            noncomputable def Cslib.Crypto.merkleBindingGame (H : List Bool → List Bool → List Bool) (adversary : MerkleBindingAdversary) (n : ℕ) :

            The position-binding experiment ([BS23], Attack Game 8.2, for a keyed hash): sample a uniform n-bit key k, run the adversary on (n, k), and accept when its output breaks binding under k.

            Instances For

              Position binding of Merkle trees over H ([BS23], Definition 8.4): every uniform strict PPT adversary breaks binding with negligible probability. Adversaries write positions in unary, so this covers polynomially bounded positions.

              Instances For

                The collision finder built from a position-binding adversary: run it, and extract a collision from its two openings with extractMerkleCollision.

                Instances For
                  theorem Cslib.Crypto.isCollision_of_merkleBreaksBinding {H : List Bool → List Bool → List Bool} {k : List Bool} {out : MerkleBindingOutput} (h : merkleBreaksBinding H k out = true) (n : ℕ) :
                  isCollision H (fun (x : ℕ) (x_1 : List Bool) => true) n k (extractMerkleCollision H k out.2.1.1 out.2.2.1 out.1.2 out.2.1.2 out.2.2.2).1 (extractMerkleCollision H k out.2.1.1 out.2.2.1 out.1.2 out.2.1.2 out.2.2.2).2 = true

                  A binding break yields a collision: if out breaks position binding under k, then extractMerkleCollision finds two different inputs with the same hash under k.

                  The reduction loses nothing: at every security parameter, a position-binding adversary succeeds at most as often as its collision finder.

                  The reduction is efficient: the collision finder of a strict PPT binding adversary is strict PPT, for a polynomial-time H that returns k.length bits.

                  Position binding gives negligible success probability to every strict PPT adversary.

                  Establish position binding by bounding every strict PPT adversary's success probability.

                  Collision-resistant hash families give position-binding Merkle trees ([BS23], Theorem 8.8, for keyed families and uniform adversaries): no uniform strict PPT adversary can open a position of a Merkle tree over a HashFamily to two different leaves, except with negligible probability. The reduction is merkleCollisionFinder, with no loss (succProbability_merkleBindingGame_le).