zkat’s diary

技術ブログ

MATTRのBBS+署名ライブラリを試してみる

概要

MATTRというニュージーランドの企業が開発している、ライブラリbbs-signaturesを使ってみたメモです。

背景

今だ大規模な応用は不明ですが、プライバシーに関する利点(下記)から近年BBS+はVerifiable Credential(以下、VC)の署名アルゴリズムとして使用され出しています。

なお、VCの署名アルゴリズムにBBS+を用いることについてはMATTRが初めに提唱したようです。

JSON-LD ZKP with BBS+は、ニュージーランドのMATTR社が2020年4月のIIWで発表した比較的新しい方式です。

www.iij.ad.jp

プライバシーに関する利点

選択的開示に対応している

個人の属性情報(例えば:氏名、年齢、住所、性別、所属先、年収、etc)を電子データで表現し、第三者に提示するシナリオに於いては目的の属性情報だけに絞り、選択的に自身の属性を開示できることがプライバシーへ配慮する上で重要です。酒類購入の年齢確認に際して、氏名や所属先、年収を開示する必要はないわけです。

BBS+で署名したVCでは特定の属性のみを開示し、且つその値がVC発行者によって認証された値であることを暗号学的な方法で担保します。

リンクが不可能

VCを元に自身の属性を提示する度に、なにか毎回不変な値も併せて含まれているとします。(例えば、VC発行者による署名値) この場合、その値がIDとして機能し提示された属性値が突合されてしまうこと(リンク)が可能となります。

例えば、Aさんには自身の住所を提示し、別のBさんには年収を提示したとします。それぞれの提示の際に不変な値が含まれている場合、AさんとBさんが結託することで住所当たりの年収が判明してしまうこととなります。

BBS+で署名されたVCではこのようなことが生じないようにすることができます。それぞれの提示の際に、不変となる値が含まれないようにすることができる為です。 一方でSD-JWTでは生じます。

使ってみる

リポジトリのREADMEに記載のコードを動かしてみます。

github.com

なお、このリポジトリはBBS+のプリミティブな機能実装となっており、実際にVCへの署名の為に用いる際には、これを応用した別のライブラリ jsonld-signatures-bbsの使用が必要です。

鍵の作成

楕円曲線BLS12-381を用いた鍵が作成されます。

const keyPair = await generateBls12381G2KeyPair();

console.log(keyPair.publicKey)
console.log(keyPair.secretKey)
/*
公開鍵↓
Uint8Array(96) [
  178, 241, 165, 193,  46,  53, 138, 187,  17, 220, 172,  27,
   67,  49, 171, 251, 130, 242,  76, 152, 184,  44, 179,  16,
   23, 103,  95,  99,  45, 183,  48, 131, 229, 247, 212,  56,
    7, 184, 235, 200, 129,  65,   0, 184, 221,  23, 125,  52,
   17, 200, 118, 178, 228, 118, 249, 151, 160, 228,  10, 144,
   43,   7, 134, 135, 143, 198, 200, 126, 201,  14, 169,   6,
  156,  49, 116, 138, 218, 169, 250,  90, 234, 119, 177, 233,
  171, 106, 153,  56,  83,  75, 111,  51, 137,  97, 170, 103
]

秘密鍵↓
Uint8Array(32) [
  103,  20, 117, 167,  97, 243,  75, 128,
  249, 142, 207,  26,  13,  14, 131, 247,
  182,   6, 147,  21,  22,  69, 201, 255,
  201, 229, 184, 149, 178, 213, 104,  75
]
*/

署名の作成

2つのメッセージに対して、署名を行います。ここで定義するメッセージのそれぞれが選択的開示の対象となる属性情報となります。 BBS+による署名は、以下の通り複数のメッセージを入力として受け取ります。

const messages = [
    Uint8Array.from(Buffer.from("message1", "utf-8")),
    Uint8Array.from(Buffer.from("message2", "utf-8")),
];

console.log(messages)

//Create the signature
const signature = await blsSign({
    keyPair,
    messages: messages,
});

console.log(signature)

/*
メッセージ↓
[
  Uint8Array(8) [
    109, 101, 115, 115,
     97, 103, 101,  49
  ],
  Uint8Array(8) [
    109, 101, 115, 115,
     97, 103, 101,  50
  ]
]

署名↓
Uint8Array(112) [
  153,  61,  54, 215, 116, 172,  10,  40,  66, 216,  98, 208,
  114,  85,  66,  71,  85,  50,  99, 233, 128,  83, 234,  24,
   94, 168, 249,  29, 129,  37, 140, 230, 166,  61, 159,  55,
  244, 181, 115, 165, 105,  54, 127, 219, 233,  26, 133, 181,
   97,  65, 229,  41, 252, 220, 112,  78,  38, 239,   6, 153,
  202, 130, 196, 144,  18, 197, 136, 173, 160, 231, 132,  54,
  139, 224, 157, 181, 128,  96,  13, 217,  20,  14, 202,  20,
   45,  32,  76, 112, 125,  76, 192,  97, 240, 118,  55, 215,
  166,  87,  69,  52,
  ... 12 more items
]
*/

署名の検証

Issuerの公開鍵と署名値、オリジナルのメッセージを元に署名を検証します。この署名の検証処理は、VCに記載されている本人(ホルダ-)が、Issuerから受け取ったVCが有効であるかどうかを確認するために行います。VCを提示された者(Verifier)が行う処理ではないことに注意が必要です。

const isVerified = await blsVerify({
    publicKey: keyPair.publicKey,
    messages: messages,
    signature,
});

console.log(isVerified)

/*
{ verified: true, error: undefined }
*/

プルーフの作成と検証

ZKPの技術を用いることで、選択的開示された値が認証されていることを確認できます。 ここでは、message1を開示しています。なお、2回に開示を行いそれぞれのproofの値が異なることを確認し、連結可能な値がないことをも確認してみます。

/* 1回目の開示・検証 */
const proof1 = await blsCreateProof({
    signature,
    publicKey: keyPair.publicKey,
    messages,
    nonce: Uint8Array.from(Buffer.from("nonce1", "utf8")),
    revealed: [0],
});

console.log("--- proof1 ---")
console.log(proof1)

//Verify the created proof
const isProofVerified1 = await blsVerifyProof({
    proof: proof1,
    publicKey: keyPair.publicKey,
    messages: messages.slice(0, 1),
    nonce: Uint8Array.from(Buffer.from("nonce1", "utf8")),
});

console.log("--- isProofVerified1 ---")
console.log(isProofVerified1)

/*
--- proof1 ---
Uint8Array(415) [
    0,   2,   1, 169, 122, 185,  38, 200, 101, 243, 184, 181,
   86, 142,  88, 175, 148, 221,  43,  53, 196, 211,  89,  87,
   34, 206, 133, 186, 252, 111, 247,  24, 181, 195,  50, 143,
  227, 140,  68, 154,  10, 154, 184,  86, 169,  49, 104, 128,
  185,  94,  62, 151,   4, 104,  54, 125,  67, 193, 131,  62,
  155, 225,  62, 215,  20,  86,  17, 204, 115,  59,  37,  76,
  101, 196,  38, 210, 234, 147, 103,  75,  59, 177,  89, 184,
   63, 120, 220, 139,  67, 180,  40, 180, 171,  10, 137, 176,
   62,  60,  19, 151,
  ... 315 more items
]
--- isProofVerified1 ---
{ verified: true, error: undefined }
*/
/* 2回目の開示・検証 */
const proof2 = await blsCreateProof({
    signature,
    publicKey: keyPair.publicKey,
    messages,
    nonce: Uint8Array.from(Buffer.from("nonce2", "utf8")),
    revealed: [0],
});

console.log("--- proof2 ---")
console.log(proof2)

//Verify the created proof
const isProofVerified2 = await blsVerifyProof({
    proof: proof2,
    publicKey: keyPair.publicKey,
    messages: messages.slice(0, 1),
    nonce: Uint8Array.from(Buffer.from("nonce2", "utf8")),
});

console.log("--- isProofVerified2 ---")
console.log(isProofVerified2)
/*
--- proof2 ---
Uint8Array(415) [
    0,   2,   1, 167, 148,  55, 142, 251, 176, 179, 130, 173,
  183, 138, 173,  67, 102, 100,  11,  57,  31,  71, 189,  32,
   22,  16, 200, 167, 163, 191, 137,   4, 142, 191, 181, 194,
  254,  84, 155,  99, 114, 175, 194, 108, 106,  79,  60, 133,
  105,  81, 191, 183, 143,  57,  11, 154,  74, 156, 148,  91,
   73, 207,  60, 141,  52,  79,  83, 221, 114,  25, 220, 104,
  155,  63, 244, 208, 192,  61, 180, 250,  31, 153,  83,  51,
  214, 165,  21, 136,  26, 144, 219, 106,  62, 255, 123, 194,
  200,  51, 190, 175,
  ... 315 more items
]
--- isProofVerified2 ---
{ verified: true, error: undefined }
*/

初回開示時のプルーフと2回目の開示でのproofのそれぞれで値が異なっていることも確認できました。