More Related Content Similar to BLS署名の実装とその応用 (20) More from MITSUNARI Shigeo (15) BLS署名の実装とその応用3. • ペアリングベースの短い署名
• Boneh, Lynn, Shacham(2001)
• 実装では高速性のため(非対称)type-3ペアリングを利用
• ペアリング
• 𝑒: 𝐺1 × 𝐺2 → 𝐺 𝑇 ; 𝐺𝑖は位数𝑟の巡回群
• 𝐺𝑖 = ⟨𝑃𝑖⟩, 𝑔 ≔ 𝑒(𝑃1, 𝑃2) ; 𝐺 𝑇の生成元
• 𝑒 𝑎𝑃1, 𝑏𝑃2 = 𝑔 𝑎𝑏 for 𝑎, 𝑏 ∈ 𝔽 𝑟
• type-3; 𝐺𝑖から𝐺3−𝑖(𝑖 = 1,2)への効率的に求まる同型写像がない
• 128ビットセキュリティの曲線
• BN254は100~110ビットセキュリティ(by Menezesら)
• BLS12-381がよく利用されている(2017 by S. Boweら)
• BLS=Barreto, Lynn, Scott
BLS署名
3 / 19
4. • 記号
• 𝐻𝑖: 0,1 ∗
→ 𝐺𝑖 ; ハッシュ関数
• 添え字 𝐼, 𝐼′ = (1,2) or (2,1) ; 非対称なので2種類ある
• ൗ𝐺𝑖 𝔽 𝑝 𝑖なので𝐺2の元の𝐺1の2倍
• 鍵生成
• 𝑠 ՚
𝑅
𝔽 𝑟(一様ランダム); 署名鍵, 𝑄 ≔ 𝑠𝑃𝐼′ ; 検証鍵
• メッセージ𝑚 ∈ 0,1 ∗
に対する署名
• 𝜎 ≔ 𝑠𝐻𝐼 𝑚 ∈ 𝐺𝐼
• 署名𝜎, 検証鍵𝑄, メッセージ𝑚に対する検証
• 𝐼, 𝐼′ = (1,2)なら𝑒 𝜎, 𝑃2 = 𝑒 𝐻1 𝑚 , 𝑄 ; 署名-小/検証鍵-大
• 𝐼, 𝐼′
= (2,1)なら𝑒 𝑃1, 𝜎 = 𝑒(𝑄, 𝐻2 𝑚 ) ; 署名-大/検証鍵-小
BLS署名のアルゴリズム
4 / 19
5. • 検証鍵と署名が「スカラー×群要素」の形
• 𝑄 ≔ 𝑠𝑃𝐼′, 𝜎 ≔ 𝑠𝐻𝐼 𝑚
• 集約署名
• ℎ: 0,1 ∗ → 𝔽 𝑟 ; ハッシュ関数
• 𝑛個の署名を集約して検証コストを減らす仕組み
• 𝑠𝑖 ; 秘密鍵, 𝑆𝑖 ≔ 𝑠𝑖 𝑃𝐼′, 𝑄𝑖 ≔ ℎ 𝑆𝑖 𝑆𝑖 ; 公開鍵
• 𝑚に対する署名𝜎𝑖 ≔ ℎ 𝑆𝑖 𝑠𝑖 𝐻𝐼(𝑚)
• 集約
• 署名の集約𝜎 ≔ σ𝑖 𝜎𝑖 = σ𝑖 ℎ 𝑆𝑖 𝑠𝑖 𝐻𝐼 𝑚
• 公開鍵の集約𝑄 ≔ σ𝑖 𝑄𝑖 = σ𝑖 ℎ 𝑆𝑖 𝑆𝑖 = σ𝑖 ℎ 𝑆𝑖 𝑠𝑖 𝑃𝐼′
• 検証
• {𝜎, 𝑄, 𝑚}に対する通常のBLS署名の検証と同じ
BLS署名の特長
5 / 19
6. • 𝑛個の署名のうち任意の𝑡個の署名を集めるとマスター
検証鍵で復号可能な署名を作成できる
• 鍵配付 : ディーラーが𝑡 − 1次𝔽 𝑟係数多項式𝑓(𝑥)を選ぶ
• 𝑠 ≔ 𝑓(0)がマスター署名鍵, 𝑄 ≔ 𝑠𝑃𝐼′がマスター検証鍵
• 各メンバー𝑖に鍵シェア𝑠𝑖 = 𝑓(𝑖)を配付(𝑖 = 1, … , 𝑛)
• 𝑠𝑖が署名鍵, 𝑄𝑖 ≔ 𝑠𝑖 𝑃𝐼′が検証鍵
• {𝑠𝑖}は𝑠に対するShamirの秘密分散
• 署名 : 𝑚に対する通常のBLS署名𝜎𝑖 ≔ 𝑠𝑖 𝐻𝐼(𝑚)
• 復元 : メンバーのうち任意の𝑡人の集合𝑆
• {𝜎𝑖: 𝑖 ∈ 𝑆}から𝑓 0 𝐻𝐼 𝑚 = 𝑠𝐻𝐼(𝑚)を復元できる
• ここから𝑠は求められない
• この署名の正しさはマスター検証鍵で検証可能
• ブロックチェーン上での多数決に利用可能
𝑡-of-𝑛閾値署名
6 / 19
7. • 複数のBLS署名をまとめて検証する
• Ethereumでは400個程度の署名{𝜎𝑖}を検証
• 素朴な実装
• 一つ一つ検証を行う
• 一つの検証の改良
• ペアリングの性質による式変形
• 𝑒 𝑃, 𝑄 = 𝐹𝐸 𝑀𝐿 𝑃, 𝑄
• ML ; Miller Loop, 𝐹𝐸 𝑥 = 𝑥 𝑟0, 𝑟0 ; constant factor
• 処理コスト ML : FE = 2 : 3
• verification : 𝑒 𝑃1, 𝜎 = 𝑒(𝑄, 𝐻2 𝑚 ) --- (*) ; 2ML + 2FE
• (*)⇔ 𝑒 𝑄, 𝐻2 𝑚 𝑒 −𝑃1, 𝜎 = 1
⇔ 𝐹𝐸 𝑀𝐿 𝑄, 𝐻2 𝑚 𝑀𝐿 −𝑃1, 𝜎 = 1 ; 2ML + 1FE
multiVerify
7 / 19
8. • 検証式を統合
• 𝑒 𝑃1, 𝜎𝑖 = 𝑒(𝑄𝑖, 𝐻 𝑚𝑖 ) for 𝑖 = 1, … , 𝑛
• ランダムな𝑟𝑖に対してς𝑖 𝑒 𝑃1, 𝜎𝑖
𝑟 𝑖 = ς𝑖 𝑒(𝑄𝑖, 𝐻(𝑚𝑖)) 𝑟 𝑖 -- (*)
が成立→無視出来る確率(~1/|𝑟𝑖|)を除いて全ての等式が成立
• (*)⇔ 𝐹𝐸 ς𝑖 𝑀𝐿 𝑄𝑖, 𝐻2 𝑚𝑖
𝑟 𝑖
𝑀𝐿 −𝑃1, 𝜎𝑖
𝑟 𝑖 = 1
⇔ 𝐹𝐸 ෑ
𝑖
𝑀𝐿 𝑟𝑖 𝑄𝑖, 𝐻 𝑚𝑖 𝑀𝐿 −𝑃1,
𝑖
𝑟𝑖 𝜎𝑖 = 1
• 𝑛(2ML+1FE) → 𝑛 + 1 ML + 1FE
• σ𝑖 𝑟𝑖 𝜎𝑖 ; multi scalar演算, ς𝑖 𝑀𝐿 ; multi Miller Loop
• この変形によりシングルスレッドで約2倍の高速化
• ς𝑖 𝑀𝐿をマルチコアで実行
• 4コアで5.7倍、12コアで12.8倍
multiVerify
8 / 19
9. • OSS(オープンソースソフトウェア)
• mcl ; ペアリングライブラリ https://github.com/herumi/mcl
• bls ; mclを用いたBLS署名ライブラリ
• 特長
• Windows, Linux, macOS, Android, iPhone, Node.js対応
• x64, aarch64, WebAssembly, ARM, MIPS対応
• Go, Rust, C#, JavaScriptなどの言語バインディング対応
• 階層構造
実装
BLS署名bls
ペアリングmcl
楕円・有限体
固定長整数演算
Xbyak
LLVM
GMP
pure C++
9 / 19
11. • pure C++版
• 移植性優先
• GMP
• さまざまなアーキテクチャ向けに最適化された
多倍長演算ライブラリ
• mpn_*という低レイヤの高速な関数を呼び出す形で利用
• 動的メモリ確保は行わない
• GPLのため利用したくないケースがある
固定多倍長演算の実装
11 / 19
12. • LLVM
• さまざまな言語を開発するための開発基盤
• 独自の仮想機械(VM)上の実行環境で動作するコンパイラ
• VM用コードから実際のアーキテクチャ用コードへの変換機構
• aarch64, MIPS, WebAssemblyなど対応
• 高度な最適化機構
• 任意ビットサイズの整数演算レジスタ
LLVMを利用した固定多倍長演算の実装
12 / 19
13. • LLVMビットコードによる加算の例
• 型情報の表現が冗長
• 静的単一代入系SSAのため複雑なコードは変数が増大
• 手書きが大変
LLVMビットコードの例
define void @add(i192* %pz,i192* %px,i192* %py) {
%x = load i192, i192* %px
%y = load i192, i192* %py
%z = add i192 %x, %y
store i192 %z, i192* %pz
ret void
}
13 / 19
14. • DSL(ドメイン特化言語)
• C++で書きやすいDSLを開発
• DSLを用いて有限体の演算やMontgomery乗算などを実装
• LLVMのツールにより各CPU向けのアセンブリ言語を出力
LLVMのための簡易DSL
codeGen
LLVMビットコード
code written by
DSL for LLVM
x64 asm
aarch64
asm
wasm
...LLVM opt+llc
14 / 19
15. • 有限体𝔽 𝑟上の引き算
• 擬似コード sub(x, y)={ t := x - y; if (t < 0) t += r; return t; }
DSL for LLVMの例
N ; ビット長(bit) / word長
Operand x = loadN(px, N); // アドレスpxから読みxに代入
Operand y = loadN(py, N); // アドレスpyから読みyに代入
Operand v = sub(x, y); // v = x - y
Operand c;
c = trunc(lshr(v, bit - 1), 1); // c = (v>>(bit-1)) & 1
Operand p = loadN(pp, N); // アドレスppから位数pを読む
c = select(c, p, makeImm(bit, 0)); // c = c ? p : 0
Operand t = add(v, c); // t = v + c
storeN(t, pz); // アドレスpzにtを書き込む
15 / 19
16. • bit=128, N=2のとき
DLSから出力されたLLVMビットコード
define void @mcl_fp_sub2L(
i64* noalias %r1,
i64* noalias %r2,
i64* noalias %r3, i64* noalias %r4) {
%r5 = load i64, i64* %r2
%r6 = zext i64 %r5 to i128
%r8 = getelementptr i64, i64* %r2, i32 1
%r9 = load i64, i64* %r8
%r10 = zext i64 %r9 to i128
%r11 = shl i128 %r10, 64
%r12 = or i128 %r6, %r11
%r13 = load i64, i64* %r3
%r14 = zext i64 %r13 to i128
%r16 = getelementptr i64, i64* %r3, i32 1
%r17 = load i64, i64* %r16
...
%r48 = getelementptr i64, i64* %r1, i32 1
%r49 = trunc i128 %r46 to i64
store i64 %r49, i64* %r48
mcl_fp_sub2L:
movq (%rsi), %rax
movq 8(%rsi), %r8
xorl %esi, %esi
subq (%rdx), %rax
sbbq 8(%rdx), %r8
movq %rax, (%rdi)
movq %r8, 8(%rdi)
sbbq $0, %rsi
testb $1, %sil
jne .carry
retq
.carry:
movq 8(%rcx), %rdx
addq (%rcx), %rax
movq %rax, (%rdi)
adcq %r8, %rdx
movq %rdx, 8(%rdi)
retq
LLVMビットコード x64 asm
16 / 19
17. • JIT(実行時コード生成)の開発を補助するライブラリ
• C++の文法でx64アセンブリ言語のDSLで記述可能
• CPUの特性に応じた処理の変更が可能
• Intel CPU向けに多倍演算をxbyakで実装
Xbyak
// mulx命令が利用可能なら使う
if (useMulx_) {
mov(rdx, x);
mulx(rax, t0, ptr [py]);
mulx(rdx, x, ptr [py + 8]);
add(x, rax);
adc(rdx, 0);
} else { // 古いCPUでの処理...
mov(rax, ptr [py]);
mul(x);
...
17 / 19
18. • x64(3.2GHz)とaarch64で測定(msec)
• DSL for LLVMがGMPの1.8倍高速
• x64/aarch64用のアセンブリコードを1行も書いていない
• 比較的抽象度の高い記述で高速なコードを開発できる
• cf. relic-toolkit/relic(bb41dc88 ; preset/x64-pbc-bls12-381.sh)
• pp/bench_ppのpp_map_oatep_k12 0.687msec
ベンチマーク
mode x64(64bit) aarch64
Xbyak 0.510 -
DSL for LLVM 0.689 3.466
GMP 1.253 6.393
pure C++ 1.948 7.445
WebAssembly 5.98 20.16
18 / 19
21. • 利点
• 柔軟なコード生成
• 欠点
• SELinuxやmacOSなどでJITを許可する設定が必要なことがある
• Ethereumで利用するペアリングパラメータは固定
• mulx, adox, adcxなどの命令は利用すると仮定
• 古いCPUはLLVMやGMPによるコードにフォールバック
• JITではなくxbyakを通常のアセンブラとして利用する
• JITコードの静的化
JITコード
21 / 19
22. • 方法
• JIT生成したコードをバイナリダンプしてasmファイルに出力
• コンパイル時にそのファイルをリンクする
• 考慮すべきこと
• JIT codeは実行時にアドレスを解決し終える
• static codeをリンクするときに異なるアドレスに配置される
• 位置独立コードPICで動くようにJIT codeを修正する
• global変数への参照に注意する
JITコードの静的化
mcl
JIT code
実行時コード生成
objファイル
として保存
mcl'
static code 通常のリンク
左と異なるアドレス
22 / 19
23. • ライブラリを他の言語から使えるようにすること
• 実アプリケーション利用でのバインディングの知見の共有
• 動的メモリ確保は可能な限り避ける
• C APIでは構造体を隠蔽するために不定型のvoid*をハンドルと
して生成、処理、破棄を担当するAPIを用意することが多い
• GC(ガベージコレクション)のある言語ではdestroyを呼ぶタ
イミングが難しい
• API内部で確保されるメモリはGCの管理外
言語バインディング
struct SecretKey *sec = SecretKey_create(); // 内部でメモリ確保
SecretKey_init(sec);
...
SecretKey_destroy(sec); // メモリ破棄
23 / 19
25. • objの配列
• シンプル
• C側で処理が容易
• objの間に隙間(padding)がないように注意する必要あり
• 呼び出す言語側の配列と対応しないことがある
• その場合メモリコピーが必要
• objへのポインタ配列
• ポインタ配列を作る
コストはobjの配列より
低い
• Goのversionがあがるとポインタ配列は扱えなくなった
ポインタ配列を使わない
sec0 sec1 sec2...
sec0 sec1 sec2...
psec0 psec1 psec2...
25 / 19
26. • ブラウザ上で実行できる仮想マシン低水準言語
• Ethereumのスマートコントラクトの実行レイヤで利用予定
• 制約
• 標準ではmalloc/freeがない
• C++では-ffreestanding -fno-exceptions -nodefaultlibs -
nostdlib -fno-use-cxa-atexit -fno-unwind-tables -fno-rtti -
nostdinc++でコンパイル出来るように開発
• wasmが扱うメモリ𝑀 𝑊とJavaScriptが扱うメモリ𝑀𝐽が異なる
• データ保持の選択
• JavaScriptのクラスが𝑀𝐽を持つ
• オブジェクトの寿命がきたときに手動で破棄する必要性
• JavaScriptプログラマには慣れないスタイルのため採用せず
WebAssembly (wasm)
26 / 19
27. • JavaScript側ではUint32Arrayの配列を持ちwasmの関数
を呼ぶときに𝑀𝐽から𝑀 𝑊へメモリコピーをする
• 不要なメモリコピーが発生するがメモリリークは発生しない
安全なデータ保持
Signature.add(y) {
const xPos = this._allocAndCopy() // 𝑀 𝑊を確保し𝑀𝐽 → 𝑀 𝑊
const yPos = y._allocAndCopy() // 𝑀 𝑊′を確保し𝑀𝐽′ → 𝑀 𝑊′
mod.mcl_addSignature(xPos, yPos) // wasmのaddSignatureをcall
_free(yPos) // 𝑀 𝑊
′
を解放
this._saveAndFree(xPos) // 𝑀 𝑊 → 𝑀𝐽のあと𝑀 𝑊を解放
}
sec:Uint32Array 𝑀 𝑊
JavaScript
SecretKey Memory
call wasm function
27 / 19