zk-SNARK: 秘密鍵、リング署名、ZKKSP について

zk-SNARK: 秘密鍵、リング署名、ZKKSP について

無題 - 3

画像

秘密鍵

理論的には、ゼロ知識証明 (ZKP) は、その解決策を公開することなく、あらゆる数学的問題に対して構築できます。実際には、問題に対する ZKP を開発するには、まったく新しい暗号アルゴリズムの発明が必要になることがよくあります。これには標準的な「レシピ」がなく、暗号化に関する広範で深い知識が必要です。たとえば、ZK のパズルには、Σ プロトコルと ZK キー ステートメントの証明 Pedersen コミットメントと Fiat-Shamir ヒューリスティックが含まれます。

zk-SNARK は、任意の問題に対する ZKP の生成を正規化します。たとえば特定の言語 Circom や Zokrates を使用して、証明する元の問題を ZK 形式で単純に表現します。残りは汎用の zk-SNARK フレームワークによって処理され、基礎となる暗号化の複雑さはすべて隠蔽されます。

zk-SNARKs が登場する前は、新しい問題に対応する ZKP を構築することは、新しい ZKP アプリケーションを設計する必要があるときに新しい ASIC を構築することに似ていました。zk-SNARKでは、汎用の「ZK CPU」をプログラミングするだけで新しいZKPを構築できます。前者には暗号化技術者が必要ですが、後者にはプログラマーのみが必要なので、ZKP への参入障壁は大幅に低くなります。

このパラダイム シフトの力を実証するために、これを使用して最も人気のある ZKP の 1 つである、公開キーから与えられた秘密キーの知識 (別名離散対数) を構築します。

秘密鍵知識の ZKP

ビットコインを公開鍵/アドレスにロックするには、所有者は対応する秘密鍵を知っていることを示す必要があります。しかし、それを公表することはできず、さもなければビットコインが盗まれる可能性がある。これは、ZKP² の形式であるデジタル署名を通じて行われます。zk-SNARK を使用して同じ目標を達成する別の方法を示します。

ビットコインでは、公開鍵 Q は秘密鍵の d 倍のジェネレーター G です。

画像

以下の Circom コードは、ビットコイン楕円曲線 secp256k1 にスカラー乗算を実装します。これを使用して、Q が d の公開鍵であることを簡単に証明できます。

  • 3 行目の入力スカラーを d に設定します。これはプライベートであるため、依然として秘密にしておく必要があることに注意してください。
  • 4行目の入力ポイントをGに設定します。
  • 6行目の出力を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];
    }
}

説明を簡単にするために、標準の二重加算アルゴリズムを使用します。メインループは 33 行目から 65 行目で発生します。42 行目で点の追加にSecp256k1AddUnequal を使用し、 43 行目でSecp256k1Doubleを使用して点を 2 倍にします。各反復では、355 ~ 358 行目を 2 倍にします。現在のビットが設定されている場合も追加します。

Circom コードを取得したら、汎用の zk-SNARK ライブラリを使用して、秘密キーの機密性を保ちながら秘密キーの知識を証明できます。デジタル署名を使用しない方法を実証しました。

組織のメンバーシップを証明する

以下では、別の複雑な暗号化プリミティブであるリング署名を、ゼロ知識言語 Circom で単に「プログラミング」することによって実装する方法を示します。

zk-SNARK を使用したリング署名

リング署名では、グループ/リングのメンバーは誰でも、特定の身元を明らかにすることなく、メンバーシップを証明するために署名できます。署名に基づいて、検証者はグループのメンバーが署名したことを判断できますが、どのメンバーが署名したかはわかりません。

画像

zk-SNARK のプログラム可能性と構成可能性により、以下に示すように、以前の点乗算ライブラリの上にリング署名を単に「エンコード」することができます。

// `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;
}

11 行目から 22 行目では、秘密鍵のセクションで紹介した ECDSAPrivToPub を使用して、5 行目の秘密鍵から 16 行目の公開鍵を導出しています。次に、結果の公開キーを 7 行目で定義されたグループ内の各公開キーと比較します。グループ内の 54 行目のいずれかに一致する場合にのみ、true を返します。

秘密キーの入力は秘密であり、隠蔽されているため、検証者がそれを使用して、どのメンバーが証明書を作成したかを特定することはできません。基盤となる暗号化を知る必要がなく、グループ/リング内のメンバーのリング署名を使用して ZKP を作成しました。

ZKKSP

上記では、ZK Key-Statement Proof (ZKKSP) と呼ばれる手法を使用して、次のステートメントのゼロ知識証明 (ZKP) を構築する方法を示しました。

画像

ZKKSP は機能しますが、大きな制限が 1 つあります。つまり、シークレットは特定の公開キーの秘密キーであり、特定のハッシュの元のイメージでもあるという、特定の形式のステートメントでのみ機能します。これを、たとえば、秘密キーと元のイメージに加えて偶数であるなど、わずかに修正されたステートメントにどのように拡張できるかは不明です。さらに、それを提案するには、Σプロトコルやコミットメントスキームなどの暗号化に関する特許レベルの知識が必要です。

ZKKSP は zk-SNARK を使用します

zk-SNARK のプログラマビリティを利用して、ZKKSP を再実装します。「組織メンバーシップの証明」セクションで使用される楕円曲線の点乗算をハッシュ ライブラリと単純に組み合わせます生成された Circom コードは次のようになります。

// 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);

前と同様に、15 行目で ECDSAPrivToPub を使用して、14 行目の秘密鍵から公開鍵を導出します。次に、3 行目でインポートされた sha256 ライブラリの Sha256 を使用して同じ秘密キーをハッシュし、結果が 17 行目の指定されたハッシュと一致することを確認します。ZKKSP を「プログラム」しただけなので、高度な暗号化に関する事前知識は必要ありません。さらに、zk-SNARK は構成可能であるため、簡単に拡張してシークレットに制約を追加できます。

ソース:

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

だいたい

ChinaDeFi - ChinaDeFi.com は研究主導型の DeFi イノベーション組織であり、ブロックチェーン開発チームでもあります。毎日、世界中の500以上の質の高い情報源からの約900のコンテンツから、より深く体系的で、最速のスピードで中国市場に同期して意思決定を提供するコンテンツを探します。副資材。

Layer 2 Daoist Friends - Layer 2 に興味のあるブロックチェーン技術愛好家や研究アナリストを歓迎します。Gavin (WeChat: chinadefi) に連絡して、Layer 2 によってもたらされる着陸の機会について話し合ってください。WeChatパブリックアカウント「分散型金融コミュニティ」にご注目ください。

画像

おすすめ

転載: blog.csdn.net/chinadefi/article/details/125972500