zk-SNARK: about private key, ring signature, ZKKSP

zk-SNARK: about private key, ring signature, ZKKSP

Untitled - 3

img

private key

In theory, a zero-knowledge proof (ZKP) can be constructed for any mathematical problem without disclosing its solution. In practice, developing a ZKP for a problem often requires inventing an entirely new cryptographic algorithm. It has no standard "recipe" and requires extensive and in-depth knowledge of cryptography. For example, ZK's puzzle involves ∑-protocol and ZK key-statement proof Pedersen commitment and Fiat-Shamir heuristic.

zk-SNARKs normalize the generation of ZKPs for arbitrary problems. Simply express the original problem to be proved in ZK format, for example using specific languages ​​Circom or Zokrates. The rest is handled by the generic zk-SNARK framework, which hides all the complexities of the underlying cryptography.

Before zk-SNARKs, building a ZKP for a new problem was akin to building a new ASIC when a new ZKP application needed to be designed. zk-SNARKs allow the construction of new ZKPs by simply programming a general-purpose "ZK CPU". The former requires a cryptographer, while the latter only requires a programmer, greatly lowering the entry barrier for ZKP.

To demonstrate the power of this paradigm shift, we use it to build one of the most popular ZKPs: knowledge of the private key given the public key (aka discrete logarithm).

ZKP of private key knowledge

To lock bitcoins to a public key/address, the owner must show that he knows the corresponding private key. But he can't make it public, or the bitcoins could be stolen. This is done through digital signatures, which are a form of ZKP². We will show another way to achieve the same goal using zk-SNARKs.

In Bitcoin, the public key Q is the private key d times the generator G.

img

The Circom code below implements scalar multiplication on the Bitcoin elliptic curve secp256k1. We can easily use this to prove that Q is the public key of d:

  • Set the input scalar on line 3 to d: note that it is private, so it still needs to be kept secret.
  • Set the input point on line 4 to G.
  • Set the output of line 6 to Q.
// encoded with k registers of n bits each
template Secp256k1ScalarMult(n, k) {
    signal private input scalar[k];
    signal public input point[2][k];

    signal output out[2][k];

    component n2b[k];
    for (var i = 0; i < k; i++) {
        n2b[i] = Num2Bits(n);
        n2b[i].in <== scalar[i];
    }

    // has_prev_non_zero[n * i + j] == 1 if there is a nonzero bit in location [i][j] or higher order bit
    component has_prev_non_zero[k * n];
    for (var i = k - 1; i >= 0; i--) {
        for (var j = n - 1; j >= 0; j--) {
            has_prev_non_zero[n * i + j] = OR();
            if (i == k - 1 && j == n - 1) {
                has_prev_non_zero[n * i + j].a <== 0;
                has_prev_non_zero[n * i + j].b <== n2b[i].out[j];
            } else {
                has_prev_non_zero[n * i + j].a <== has_prev_non_zero[n * i + j + 1].out;
                has_prev_non_zero[n * i + j].b <== n2b[i].out[j];
            }
        }
    }

    signal partial[n * k][2][k];
    signal intermed[n * k - 1][2][k];
    component adders[n * k - 1];
    component doublers[n * k - 1];
    for (var i = k - 1; i >= 0; i--) {
        for (var j = n - 1; j >= 0; j--) {
            if (i == k - 1 && j == n - 1) {
                for (var idx = 0; idx < k; idx++) {
                    partial[n * i + j][0][idx] <== point[0][idx];
                    partial[n * i + j][1][idx] <== point[1][idx];
                }
            }
            if (i < k - 1 || j < n - 1) {
                adders[n * i + j] = Secp256k1AddUnequal(n, k);
                doublers[n * i + j] = Secp256k1Double(n, k);
                for (var idx = 0; idx < k; idx++) {
                    doublers[n * i + j].in[0][idx] <== partial[n * i + j + 1][0][idx];
                    doublers[n * i + j].in[1][idx] <== partial[n * i + j + 1][1][idx];
                }
                for (var idx = 0; idx < k; idx++) {
                    adders[n * i + j].a[0][idx] <== doublers[n * i + j].out[0][idx];
                    adders[n * i + j].a[1][idx] <== doublers[n * i + j].out[1][idx];
                    adders[n * i + j].b[0][idx] <== point[0][idx];
                    adders[n * i + j].b[1][idx] <== point[1][idx];
                }
                // partial[n * i + j]
                // = has_prev_non_zero[n * i + j + 1] * ((1 - n2b[i].out[j]) * doublers[n * i + j] + n2b[i].out[j] * adders[n * i + j])
                //   + (1 - has_prev_non_zero[n * i + j + 1]) * point
                for (var idx = 0; idx < k; idx++) {
                    intermed[n * i + j][0][idx] <== n2b[i].out[j] * (adders[n * i + j].out[0][idx] - doublers[n * i + j].out[0][idx]) + doublers[n * i + j].out[0][idx];
                    intermed[n * i + j][1][idx] <== n2b[i].out[j] * (adders[n * i + j].out[1][idx] - doublers[n * i + j].out[1][idx]) + doublers[n * i + j].out[1][idx];
                    partial[n * i + j][0][idx] <== has_prev_non_zero[n * i + j + 1].out * (intermed[n * i + j][0][idx] - point[0][idx]) + point[0][idx];
                    partial[n * i + j][1][idx] <== has_prev_non_zero[n * i + j + 1].out * (intermed[n * i + j][1][idx] - point[1][idx]) + point[1][idx];
                }
            }
        }
    }

    for (var idx = 0; idx < k; idx++) {
        out[0][idx] <== partial[0][0][idx];
        out[1][idx] <== partial[0][1][idx];
    }
}

For ease of explanation, we use the standard double-add algorithm. The main loop occurs on lines 33 to 65. We use Secp256k1AddUnequal for point addition on line 42 and point doubling using Secp256k1Double on line 43 . In each iteration, we double at lines 355-358. If the current bit is set, we also add.

Once we have the Circom code, we can use the general purpose zk-SNARK library to prove knowledge of the private key while keeping it confidential. We have demonstrated the method without a digital signature.

Prove Organization Membership

Below, we show how to implement another complex cryptographic primitive: the ring signature, by simply "programming" it in the zero-knowledge language Circom.

Ring signatures using zk-SNARKs

In ring signatures, any member of a group/ring can sign to prove their membership without revealing their specific identity. Based on the signature, the verifier can determine that a member of the group signed it, but he does not know which member signed it.

img

Due to the programmability and composability of zk-SNARKs, we can simply "encode" the ring signature on top of the previous point multiplication library, as shown below.

// `n`: chunk length in bits for a private key
// `k`: chunk count for a private key
// `m`: group member count
template Main(n, k, m) {
  signal private input privkey[k];
  
  signal public input pubKeyGroup[m][2][k];
  signal output existInGroup;

  // get pubkey from privkey
  component privToPub = ECDSAPrivToPub(n, k);
  for (var i = 0; i < k; i++) {
    privToPub.privkey[i] <== privkey[i];
  }

  signal pubkey[2][k];

  // assign pubkey to intermediate var
  for (var i = 0; i < k; i++) {
    pubkey[0][i] <== privToPub.pubkey[0][i];
    pubkey[1][i] <== privToPub.pubkey[1][i];
  }

  // check whether pubkey exists in group
  var exist = 0;
  component eq[2*m];
  var compareResult[m];

  for (var i = 0; i < m; i++) {
    // pubkey `x` comparer
    eq[i] = BigIsEqual(k);

    // pubkey `y` comparer
    eq[i+m] = BigIsEqual(k);

    for(var j = 0; j < k; j++) {
      // compare `x`
      eq[i].in[0][j] <== pubkey[0][j];
      eq[i].in[1][j] <== pubKeyGroup[i][0][j];

      // compare `y`
      eq[i+m].in[0][j] <== pubkey[1][j];
      eq[i+m].in[1][j] <== pubKeyGroup[i][1][j];
    }
    
    compareResult[i] = eq[i].out * eq[i+m].out;
  }

  component checker = InGroupChecker(m);
  for(var i = 0; i < m; i++) {
    checker.in[i] <== compareResult[i];
  }

  existInGroup <== checker.out;
}

From line 11 to line 22, we derive the public key in line 16 from the private key in line 5 using ECDSAPrivToPub introduced in the private key section. We then compare the resulting public key with each public key in the group defined in line 7. We return true if and only if it matches any of the 54th rows in the group.

Since the private key input is private and kept hidden, it cannot be used by the verifier to identify which member created the attestation. We have created a ZKP with ring signatures of members in groups/rings without needing to know any underlying cryptography.

ZKKSP

Above, we have shown how to build a Zero-Knowledge Proof (ZKP) for the following statement using a technique called ZK Key-Statement proof (ZKKSP).

img

While ZKKSP works, it has a big limitation: it only works with one specific form of statement, namely that a secret is the private key of a given public key, and it is also the original image of a given hash. It's not clear how this could be extended to a slightly modified statement that, for example, secret is an even number in addition to being the private key and the original image. Furthermore, proposing it requires patent-level knowledge of cryptography such as Σ-protocol and commitment schemes.

ZKKSP uses zk-SNARKs

We reimplement ZKKSP by exploiting the programmability of zk-SNARKs. We simply combine the elliptic curve point multiplication used in the Proving Organization Membership section with the hashing library. The generated Circom code looks like this:

// library circuits from https://github.com/0xPARC/circom-ecdsa
include "lib-circom-ecdsa/ecdsa.circom";
include "../node_modules/circomlib/circuits/sha256/sha256.circom";
include "../node_modules/circomlib/circuits/bitify.circom";

// `n`: chunk length in bits for a private key
// `k`: chunk count for a private key
template Main(n, k) {
  // n * k == 256
  assert(n * k >= 256);
  assert(n * (k-1) < 256);

  // little-endian
  signal private input privkey[k];
  signal public input pubkey[2][k];

  signal public output privkeyHash[k];

  // get pubkey from privkey
  component privToPub = ECDSAPrivToPub(n, k);
  for (var i = 0; i < k; i++) {
    privToPub.privkey[i] <== privkey[i];
  }

  // verify input pubkey
  signal pub_x_diff[k];
  signal pub_y_diff[k];
  for (var i = 0; i < k; i++) {
    pub_x_diff[i] <-- privToPub.pubkey[0][i] - pubkey[0][i];
    pub_x_diff[i] === 0;
    pub_y_diff[i] <-- privToPub.pubkey[1][i] - pubkey[1][i];
    pub_y_diff[i] === 0;
  }

  // calculate sha256 of privkey
  component sha256 = Sha256(256);
  for (var i = 0; i < k; i++) {
    for (var j =0; j < n; j++) {
      // change privkey to big-endian as sha256 input
      sha256.in[i * n + j] <-- (privkey[k-1-i] >> (n-1-j)) & 1;
    }
  }

  // set output
  component b2n[k];
  for (var i = 0; i < k; i++) {
    b2n[i] = Bits2Num(n);
    for(var j = 0; j < n; j++) {
      // `b2n` input is little-endian in bits, `sha256` out is big-endian in bits
      b2n[i].in[n-1-j] <== sha256.out[i * n + j];
    }
    privkeyHash[i] <== b2n[i].out;
  }

}

component main {public [pubkey]} = Main(64, 4);

As before, we use ECDSAPrivToPub on line 15 to derive a public key from the private key on line 14. We then hash the same private key using Sha256 from the sha256 library imported on line 3 to ensure the result matches the given hash on line 17. We just "programmed" ZKKSP, no prior knowledge of advanced cryptography is required. Furthermore, due to the composability of zk-SNARKs, we can easily extend them to add constraints on secrets.

Source:

https://xiaohuiliu.medium.com/programmable-zero-knowledge-proofs-using-zk-snarks-part-1-9599deb1db47

https://xiaohuiliu.medium.com/programmable-zero-knowledge-proofs-using-zk-snarks-part-2-890869249291

https://xiaohuiliu.medium.com/programmable-zero-knowledge-proofs-using-zk-snarks-part-3-d707dbfa2b28

about

ChinaDeFi - ChinaDeFi.com is a research-driven DeFi innovation organization, and we are also a blockchain development team. Every day, from nearly 900 pieces of content from more than 500 high-quality information sources around the world, we look for content that is more in-depth and systematic, and synchronizes to the Chinese market at the fastest speed to provide decision-making auxiliary materials.

Layer 2 Daoist Friends - Welcome blockchain technology enthusiasts and research analysts who are interested in Layer 2 to contact Gavin (WeChat: chinadefi) to discuss the landing opportunities brought by Layer 2. Please pay attention to our WeChat public account "Decentralized Financial Community" .

img

Guess you like

Origin blog.csdn.net/chinadefi/article/details/125972500
Recommended