背景
全ての取引を公開管理台帳(Blockchain)に公開したかった。しかし、個人情報保護のため、個人を特定できるような情報を秘匿する必要があった。
要件の整理
方法案
以下の3案を候補にあげたが、最終的に3番を採用した。
1. 公開鍵暗号方式
最初に思いつく方法は、公開鍵暗号方式。秘密鍵でトランザクションを暗号化して、公開鍵をユーザ認証に使う。残念ながらこの手法は、使えない。なぜなら公開鍵から個人を特定できてしまうからだ。前述のように、全トランザクションを公開しているので、公開鍵に紐づく全てのトランザクションを抽出できる。そこから個人を推測することができてしまう。
2. 階層的決定性ウォレット
次に思いつくのは、階層的決定性ウォレットの仕組みを使う方法。ユーザ認証鍵をトランザクション毎に分けることで、個人を推測できない。個別のユーザ認証鍵を紛失したとしても、Seedさえ分かれば、全てのユーザ認証鍵を階層決定的に復元できる点が魅力的。 問題は、ユーザ認証鍵の管理コスト。トランザクション量に比例してかかる。削減したい。
3. トランザクションの暗号化ハッシュを認証鍵にする
トランザクションをユーザ鍵で暗号化し、その結果を認証鍵として使う。認証鍵でトランザクションを再暗号化して署名を作成する。 認証鍵を作れるのは、ユーザ鍵の所持者だけ。認証鍵を開示することで、トランザクションの所有権を示すことができる。トランザクションに対応する認証鍵は毎回違うので、所有者を推測できない。ユーザ鍵だけ管理すればよいので低コスト。
※ただし「なりすまし」は検知できない
技術詳細
まずは概要図。
Step1: ユーザ鍵生成
ユーザ鍵は、共通鍵生成アルゴリズムのAESを使って作成する。AESにした深い理由があるわけではない。他のアルゴリズムでも構わない。BitcoinやEthereumの秘密鍵でも構わない。
参考のためRubyでコードを示す。
require 'openssl' OpenSSL::Cipher::AES256.new(:CBC).random_key => "d\xDC\xE9\e\r\x127@\xAA\x8A\xB4\x02\xAD\xA9\x95\xF9\xCA\x18@cW\xD6G\b\xF4\xB3J\xE33B2\x9A"
Step2: 認証鍵生成
トランザクションのうち、個人情報に当たらない部分は公開する。この部分をPayloadと呼称する。Payloadをユーザ鍵でHMACしたものをユーザ認証鍵とする。
HMACのハッシュ関数でSHA256
を使ってるのは、広く使われてるから。本心ではKeccak256
を使いたかった。残念ながらRubyのopenssl
ライブラリで未対応だった。
user_key = "d\xDC\xE9\e\r\x127@\xAA\x8A\xB4\x02\xAD\xA9\x95\xF9\xCA\x18@cW\xD6G\b\xF4\xB3J\xE33B2\x9A" payload = "{ type: xxx, amount: yyy }" authentication_key = OpenSSL::HMAC.digest('SHA256', user_key, payload) => "\xB0\xDA{\xFC\xD3p\x8CR_\xA9\xB2\x1E\xB8/\x1E&\xF6\xAAZ\xE4\x89\x0E\x01G\xA7\xAEm]\x18\xB6\x9C\x8C"
Step3: 署名作成
認証鍵でPayloadをHMACしたものを署名として使う。
authentication_key = "\xB0\xDA{\xFC\xD3p\x8CR_\xA9\xB2\x1E\xB8/\x1E&\xF6\xAAZ\xE4\x89\x0E\x01G\xA7\xAEm]\x18\xB6\x9C\x8C" payload = "{ type: xxx, amount: yyy }" signature = OpenSSL::HMAC.hexdigest('SHA256', authentication_key, payload) => '2e49b12c72ab5c23c515faa0ceee8f9d1910af4c20540bcb544cd49027a8e5f6'
個人情報にあたるuser
を署名に置き換えて公開する。
original_tx = { user: 000, type: xxx, amount: yyy } public_tx = { signature: "2e49b12c72ab5c23c515faa0ceee8f9d1910af4c20540bcb544cd49027a8e5f6" payload: "{ type: xxx, amount: yyy }" }
Step4: 認証鍵送付
ユーザは証明したいトランザクションのIDと認証鍵を検証者に送る。
Step5: 認証
検証者はトランザクションIDを元に公開トランザクションから該当するトランザクションを取得する。Payloadを認証鍵でハッシュ化し、署名と等しいことを確認する。
public_tx = { signature: "2e49b12c72ab5c23c515faa0ceee8f9d1910af4c20540bcb544cd49027a8e5f6" payload: "{ type: xxx, amount: yyy }" } authentication_key = "\xB0\xDA{\xFC\xD3p\x8CR_\xA9\xB2\x1E\xB8/\x1E&\xF6\xAAZ\xE4\x89\x0E\x01G\xA7\xAEm]\x18\xB6\x9C\x8C" authentication_hash = OpenSSL::HMAC.hexdigest('SHA256', authentication_key, payload) => '2e49b12c72ab5c23c515faa0ceee8f9d1910af4c20540bcb544cd49027a8e5f6' if authentication_hash == public_tx.signature { // User verified! }