【完全準同型暗号】node-seal導入手順

はじめに

完全準同型暗号とは?node-sealって? みたいな説明はこちらの記事に任せます。

qiita.com

私からは、こちらのライブラリの導入と、簡単な加法ができるまで、その結果をHTMLで表示するまでを書いていきます。 また、ライブラリのドキュメントは以下の記事に書いてあります。

docs.morfix.io

全文英語ですがそんなに難しくもないと思うので、時間がある方はこちらを読んだ方がいいと思います。

node-sealとは

完全準同型暗号ライブラリであるMicrosoft社のSEALを、WebAssemblyでラップしたライブラリになります。

JavaScriptで利用できることから、一般的なWebサービスにも組み込むことが容易になりました。

インストール

# npm install node-sael
もしくは
# yarn add node-seal

これだけです。

必要になる環境

node-sealはSEALをWeb Assemblyでラップしているライブラリになるので、Web Assemblyの環境構築を行います。

M1 Macの場合、以下の記事を参考に環境構築を行えば良いです(2022/11/18時点)。

私はM1 Macでしか動かしてないので、他の環境はわかりません。ごめんなさい。

qiita.com

また、今回はブラウザ上で動かしたいので、webpackを入れます。

webpackの説明と環境構築手段については以下のサイトに全て載っていますのでお任せします。

ics.media

加算をしてみる

まずは動かしてみましょう。

とりあえずソースコードを貼ります。ある程度はコメントに説明を書いておきました。

ソースコード(BFV形式)

hoge.js

// ライブラリのインポート
import SEAL from 'node-seal'

//非同期関数を定義する
export async function Add() {
    const seal = await SEAL()
    ////////////////////////
    // Encryption Parameters
    ////////////////////////
    
    // 準同型暗号化方式を定義
    const schemeType = seal.SchemeType.bfv    

    // セキュリティレベル(暗号強度)を定義
    const securityLevel = seal.SecurityLevel.tc128


    // 多項式環の次数を定義
    const polyModulusDegree = 4096
    // 多項式環の法を定義
    const bitSizes = [36,36,37]
    // Batching処理に使われる法を定義
    const bitSize = 20
    
    // 値渡し
    const encParms = seal.EncryptionParameters(schemeType)
    encParms.setPolyModulusDegree(polyModulusDegree)
    encParms.setCoeffModulus(
      seal.CoeffModulus.Create(
        polyModulusDegree,
        Int32Array.from(bitSizes)
      )
    )
    encParms.setPlainModulus(
      seal.PlainModulus.Batching(
        polyModulusDegree,
        bitSize
      )
    )

    ////////////////////////
    // Context
    ////////////////////////
    
    // 暗号化パラメータを使用してContextを作成する
    const context = seal.Context(
      encParms,
      true,
      securityLevel
    )

    // Contextが正常に作られたことを確認する
    if (!context.parametersSet()) {
      throw new Error('Could not set the parameters in the given context. Please try different encryption parameters.')
    }

    ////////////////////////
    // Keys
    ////////////////////////
    
    // Contextを元に新しいKeyGeneratorを作成する
    const keyGenerator = seal.KeyGenerator(
      context
    )

    // 秘密鍵を生成
    const Secret_key_Keypair_A_ = keyGenerator.secretKey()

    // 公開鍵を生成
    const Public_key_Keypair_A_ = keyGenerator.createPublicKey()

    /* 公開鍵の出力
       const publicBase64Key = Public_key_Keypair_A_.save()
       console.log(publicBase64Key)
    */

    ////////////////////////
    // Variables
    ////////////////////////

    // 平文を格納する変数
    const PlainText = seal.PlainText();
 
    // 暗号文を格納する変数
    const CipherText = seal.CipherText();

    ////////////////////////
    // Instances
    ////////////////////////

    // 各種演算操作を行う際に利用する
    const evaluator = seal.Evaluator(context)

    // BFV形式で使用される、バッチエンコーダの定義
    const batchEncoder = seal.BatchEncoder(context)

    // 平文を暗号化するために使用される
    const encryptor = seal.Encryptor(
      context,
      Public_key_Keypair_A_
    )

    // 暗号文を復号するために使用される
    const decryptor = seal.Decryptor(
      context,
      Secret_key_Keypair_A_
    )
  
    ////////////////////////
    // Homomorphic Functions
    ////////////////////////
    
    encryptor.encrypt(
      PlainText,
      CipherText
    )
    
    // 平文の定義(多項式である必要があるので、数列を代入する)
    const plainText = batchEncoder.encode(
        Int32Array.from([1, 2, 3 ,4, 5]) // This could also be a Uint32Array
    )

    // 暗号化
    const cipherText = encryptor.encrypt(plainText)
    const cipherTextD = seal.CipherText()

    // 加算
    evaluator.add(cipherText, cipherText, cipherTextD)

    // 復号
    const plainTextD = decryptor.decrypt(cipherTextD)

    // 出力できるようにする
    const decodedA = batchEncoder.decode(plainText)
    const Cipher = cipherText.save()
    const decodedD = batchEncoder.decode(plainTextD)

    console.log('Input:\n',decodedA);
    console.log('Input + Input:\n',decodedD);
    console.log('cipher:\n',Cipher);

}
Add();

実行方法

実行結果として平文(input)、計算結果(input + input)、暗号文(cipher)を出力します。作成した公開鍵の出力はコメントアウトしてあるので、欲しい方は解除してください。

めっちゃ長くなるので別ファイルに出力することを推奨します。

# node hoge.js > result.txt

webブラウザ上で動かす

node-sealを使う(恐らく)最大の目的であるブラウザ上で動作させるための手順を書きます。とはいっても、大体はwebpackの作業です。

ディレクトリを作成する

まずは作業するディレクトリを作りましょう。

コマンド

# mkdir nodeseal_test
# cd nodeseal_test

webpackの準備

コマンド

# npm init -y
# mkdir src
# touch src/index.js src/bfv.js

src/index.js

import {Add} from "./bfv.js";

Add();

src/bfv.js

先ほどのソースコードの末尾に以下の3行を追記し、関数を実行していた行をコメントアウト(または削除)してください。

-- 省略 --
    document.getElementById('plaintext').value = decodedA;
    document.getElementById('ciphertext').value = Cipher;
    document.getElementById('results').value = decodedD; 

}
//Add();

webpackでまとめる

コマンド

#npx webpack

distディレクトリが作成され、その配下にmain.jsファイルが作成されます。

HTMLからはこのファイルを呼び出すことで、jsに外部ライブラリを用いていてもインポートを解決して動作するようになります。

HTMLファイルを作成する

コマンド

#touch dist/index.html

dist/index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- script -->
    <script src="main.js"></script>

    <title>SEAL Test</title>
</head>
<body>
    <div class="title ">
        <h1>SEAL Test</h1>
    </div>
    <div class="input">
        <h1>Input</h1>
        <textarea id="plaintext" disabled readonly></textarea>
    </div>
    <div class="cipher">
        <h1>Cipher</h1>
        <textarea id="ciphertext"  disabled readonly></textarea>
    </div>
    <div class="output">
        <h1>Input+Input</h1>
        <textarea id="results" disabled readonly></textarea>
    </div>
</body>
</html>

確認する

HTMLファイルを開いてみてください。

以下の画像は記事用に少し整形していますが、おおよそ同じような表示が得られたのではないかと思います。

実行結果

まとめ

完全準同型暗号ライブラリであるMicrosoft SEALをラップしたライブラリであるnode-sealの使い方についてまとめました。

完全準同型暗号をブラウザ上で動かしてみたい、という方の助けになれば幸いです(ただし、各種処理にはそこそこの時間がかかるため、あまり重い処理を複数行うとリクエストタイムエラーになることに注意してください)