実践Rust入門を読んだ

実践Rust入門 言語仕様から開発手法まで, κeen, 河野達也, 小松礼人を読みました。本書の特徴は以下の3つでしょう。

  • 2018 Editionに対応している
  • FFIについて日本語で書かれた書籍
  • 実践 を意識した内容になっている

本記事では、特に3つ目の実践的という観点で感想を述べようと思います。

Rustの言語仕様という観点で言えば、プログラミングRustのほうが網羅性は高いでしょう。 しかし、Rustは入門のハードルがとても高い言語です。1

個人的な経験でいえば、mapfilterでさえHaskellを勉強していなかったら、とっつきにくかったんじゃないかと思います。他言語の経験が豊富であればまだしも、いきなりRustを始めると挫折してしまうでしょう。 だからこそ、本書のような実践的な入門書は重要だと思います。「あれもこれもやらなきゃいけない」とならずに、実際に利用されるトピックから取り組めるのはとても良いことだと思います。

また、わかってる人からすると当たり前だけど初学者はわからないというようなことが、言語化されていたのもよかったです。例えばP.151では、桁あふれのメソッドについて

  • 検査付き演算: checked_
  • 飽和演算: saturating_
  • ラッピング演算: wrapping_
  • 桁あふれ演算: overflowing_

のprefixがつく、と言及されていました。他にもP.169のボックス化されたスライスでは、

使う機会はあまりなさそうですが、

と書かれていたりして、力の抜きどころもわかって良かったです。 プログラミングRustを読んでいると網羅性が高いのはいいんですが、これはいつ使うんだろう?となることがあるのでこういう配慮は助かります。

他にも、プログラミングRustにも載っている内容ではあるものの、Stringstrの違いは最初にわからなくなりがちなところなので、メモリ表現が図解されているのもわかりやすいと思います。
また、ユニット型()がサイズゼロだと知ってるとcollectionでSet作りたいときに()使えば良さそうと思えるのも良かったです。以前、GoでSetライブラリを自作しようとしたときにstruct{}{}だらけになったのでRustのほうが同じサイズゼロで実装するのにきれいに書けますね。 2

いいところばかりなのもあれなので、気になったところも述べます。 内容ではないですが、誤植が気になりました。 例えば、ch07_10_closures.rslet lookup = || assert!(s1.find('d').is_some()); に閉じカッコがないなど、コンパイルが通らないエラーは潰し込んでほしかったなと。 とはいえ、誤植はrust-jpのslackでも報告、対応してくださっているので、大きな問題はなかったです。 また、GitHubのコードは問題なく動くのでこちらも見ながら進めれば大丈夫でした。

構成で気になったのが、P.271の最後です。ここはgrowを実装する前だと動かないので、初学だと動かなくてちょっと困るかもと思いました。newしただけだとサイズ0しかヒープに領域確保してないので、動かないと思います。一度読み飛ばして、growを実装後に、戻ってくれば実行できるので、順序が逆だとわかりやすそうです。

また、本書でもマクロが使われているので、簡単な紹介くらいあるともっと良かったかと思います。

特に良かった章

最後に、個人的に勉強になった・楽しかった章の感想を残します。

第7章 所有権システム

Rust特有の概念である所有権やライフタイムについて第1部の中で特に実践的な章でした。 簡易版のVecを実際に自分の手で実装することで、利用頻度の高いVecの内部実装がイメージできるのは大きいと思います。 また、CopyとClone、RcとArc、FnとFnMutとFnOnceの違いが書かれていて、頭の中が整理できました。

第9章 パーサを作る

本書で一番読みたかった章です。 再帰下降パーサはmonkeyで実装したことがありましたが、Annotationをつけたことがなかったのと、エラー処理をちゃんとできるのはとても楽しかったです。

第10章 パッケージを作る

パッケージの作成、CI、crate.ioへの公開と一通り触れてよかったです。特にcrate.ioへの公開は初めてだったので勉強になりました。

第11章 Webアプリケーション、データベース接続

リクエストの受付からDBへの接続、レスポンスを返すところまで一式という章です。 Futuresに振れることができたのも大きな収穫でした。 また、CLIがこんなに簡単に書けるのかという驚きもありました。

References

実践Rust入門[言語仕様から開発手法まで]

実践Rust入門[言語仕様から開発手法まで]

プログラミングRust

プログラミングRust


  1. 本書の「はじめに」でも学習コストが高いことに言及されています

  2. RustはCollectionがstdにありますが。

Rustでsliceをシャッフルする

メモ書きです。

O'Reilly Japan - プログラミングRustを読んでいたら rand::Rng - Rustでsliceのシャッフルができるとあったので試したところrand::Rng::shuffleがdeprecatedになっていました。 SliceRandom::shuffleを使った例を残します。

Cargo.tomlは以下のとおりです。

[dependencies]
rand = "0.6.5"

実装例

use rand::seq::SliceRandom;

fn main() {
    let mut v = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

    let mut rng = rand::thread_rng();
    v.shuffle(&mut rng);
    println!("{:?}", v); // [3, 4, 7, 6, 2, 0, 5, 9, 1, 8] 
}

deprecatedな実装

use rand::Rng;

fn main() {
    let mut v = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

    let mut rng = rand::thread_rng();
    rng.shuffle(&mut v);
    println!("{:?}", v);
}
❯ cargo run
warning: use of deprecated item 'rand::Rng::shuffle': use SliceRandom::shuffle instead
 --> src/main.rs:7:9
  |
7 |     rng.shuffle(&mut v);
  |         ^^^^^^^
  |
  = note: #[warn(deprecated)] on by default

    Finished dev [unoptimized + debuginfo] target(s) in 0.64s
     Running `target/debug/rust-shuffle-slice`
[1, 2, 4, 8, 3, 5, 7, 0, 9, 6]

References

メモリをダンプしてRustのsliceとVecを理解する

タイトルの通りなんですが、RustのsliceやVecがしっくり来ていないので、メモリ上でどのように表現されているのかという観点で理解を深めようと思います。

環境

❯ cargo version
cargo 1.32.0

❯ rustc --version
rustc 1.33.0

また今回の環境ではusizeは8バイト長です。

println!("size of usize: {}", std::mem::size_of::<usize>());
// size of usize: 8

事前準備

メモリ上でどのように表現されているか確認するため、RustのSizedとfatポインタ - 簡潔なQas_raw_bytesを利用します。 引数x*const Tでキャストして、生ポインタからstd::mem::size_of_val(x)で得たバイト長を読み出します。

fn as_raw_bytes<'a, T: ?Sized>(x: &'a T) -> &'a [u8] {
    unsafe {
        std::slice::from_raw_parts(
            x as *const T as *const u8,
            std::mem::size_of_val(x))
    }
}

結論

  • [T]はスタック領域に連続して表現される
  • Vec<T>はスタック領域に「実データへのポインタ」「len」「cap」を持つ
  • &strはスタック領域に「実データへのポインタ」「len」を持つ

具体例

[i32]

まずは[i32]i32のslice)でメモリ上の内容を見てみましょう。

let a: [i32; 5] = [255, 256, 1023, 1024, 1025];
println!("{:x?}", as_raw_bytes(&a));

実行結果は以下のようになります。 読みやすいように改行などの整形を入れています。 以降でも同じように整形して読みやすくしています。

[ff, 0, 0, 0,
 0, 1, 0, 0,
ff, 3, 0, 0,
 0, 4, 0, 0,
 1, 4, 0, 0]

これだけだと分かりづらいので、as_raw_bytesの返り値の型が[u8]であることを考慮して0を補完します。

[ff, 00, 00, 00,
 00, 01, 00, 00,
 ff, 03, 00, 00,
 00, 04, 00, 00,
 01, 04, 00, 00]

トルエンディアンで表現されていることに注意すれば、 

0d255 = 0x000000ff
0d256 = 0x00000100
0d1023 = 0x000003ff
0d1024 = 0x00000400
0d1025 = 0x00000401

となることがわかると思います。 またヒープ領域へのポインタなどもなく、スタック領域に確保もされていることも確認できました。

Vec<i32>

次にVec<i32>を見ていきます。

let a: Vec<i32> = vec![1, 2, 3, 4];
println!("{:x?}", as_raw_bytes(&a));

結果は以下のようになりました。

[d0, 2c, c0, d5, a8, 7f, 00, 00,
 04, 00, 00, 00, 00, 00, 00, 00,
 04, 00, 00, 00, 00, 00, 00, 00]

ここでVec<T>の実装を見てみましょう。 vec.rsの実装は以下のようになっています。

#[stable(feature = "rust1", since = "1.0.0")]
pub struct Vec<T> {
    buf: RawVec<T>,
    len: usize,
}

またRawVecraw_vec.rsで実装されています。

#[allow(missing_debug_implementations)]
pub struct RawVec<T, A: Alloc = Global> {
    ptr: Unique<T>,
    cap: usize,
    a: A,
}

このようにVec<T>lencapを持ちます。

改めて結果を見てみると後半の4から始まる8バイト2行はlencapであることがわかります。

// (再掲)
[d0, 2c, c0, d5, a8, 7f, 00, 00,
 04, 00, 00, 00, 00, 00, 00, 00,
 04, 00, 00, 00, 00, 00, 00, 00]

最初の8バイトが実データへのポインタになっているのですが、本当にそうなっているか参照外しをして確認しましょう。

let a = vec![4, 1, 2, 3];

unsafe {
    let p = a.as_ptr();
    println!("{:?}", p); // 0x7feba05027c0
    println!("{:?}", *p); // 4 <- 先頭のデータ
    let data: &[u8] = std::slice::from_raw_parts(p, a.len());
    println!("{:?}", data); // [4, 1, 2, 3]
}

*pdataの出力結果から、確かに実データへのポインタとなっていました。

文字列まわり

[i32]Vec<i32>を確認できたので、おまけとして文字列を扱う際に登場する以下3つのメモリ内容も確認してみます。

  • [char]Vec<char>
  • [&str]Vec<&str>
  • StringVec<String>

char

[char]から見ていきましょう。

let a = ['a', 'b', 'c'];

println!("{:x?}", as_raw_bytes(&a));
// [61, 0, 0, 0, 62, 0, 0, 0, 63, 0, 0, 0]

[i32]と同じでスタック領域のみで表現されていることがわかります。 ちなみに、char - Rustにあるようにcharは4バイト長です。

char is always four bytes in size.

次にVec<char>です。

let a = vec!['a', 'b', 'c'];

println!("{:x?}", as_raw_bytes(&a));
// [d0, 2c, 40, d7, ad, 7f, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]

unsafe {
    let p = a.as_ptr();
    println!("{:?}", p);
    // 0x7fadd7402cd0

    println!("{:?}", *p);
    // 'a'

    let data = std::slice::from_raw_parts(p, a.len());
    println!("{:?}", data);
    // ['a', 'b', 'c']
}

as_raw_bytes(&a)の出力結果を整形します。

[d0, 2c, 40, d7, ad, 7f, 00, 00,
 03, 00, 00, 00, 00, 00, 00, 00,
 03, 00, 00, 00, 00, 00, 00, 00]

lencap3(うしろ2行)で、1行目はヒープにある実データへのポインタになっています。 実際、unsafeの中で参照外しを行うと'a'が得られるため、実データの先頭アドレスであったことがわかります。

[&str]

続けて[&str]です。

let a: [&str; 3] = ["a", "b", "c"];

println!("{:x?}", as_raw_bytes(&a));
// [82, 59, 85, 4, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 80, 59, 85, 4, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 81, 59, 85, 4, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0]

今までとは様子が違いそうです。 理解を深めるため、別の例をもうひとつ表示してみます。

let a: [&str; 3] = ["a", "ab", "abc"];

println!("{:x?}", as_raw_bytes(&a));
// [35, 77, 46, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 30, 77, 46, 1, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 32, 77, 46, 1, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]

上記2つの結果を見やすいように整形しました。

[82, 59, 85, 04, 01, 00, 00, 00,
 01, 00, 00, 00, 00, 00, 00, 00,
 80, 59, 85, 04, 01, 00, 00, 00,
 01, 00, 00, 00, 00, 00, 00, 00,
 81, 59, 85, 04, 01, 00, 00, 00,
 01, 00, 00, 00, 00, 00, 00, 00]
[35, 77, 46, 01, 01, 00, 00, 00,
 01, 00, 00, 00, 00, 00, 00, 00,
 30, 77, 46, 01, 01, 00, 00, 00,
 02, 00, 00, 00, 00, 00, 00, 00,
 32, 77, 46, 01, 01, 00, 00, 00,
 03, 00, 00, 00, 00, 00, 00, 00]

実は、str - Rustにあるように&strは実データへのポインタとlenで構成されます。

A &str is made up of two components: a pointer to some bytes, and a length.

今回の表示した対象は[&str]&strのslice)です。[i32]の例でみたようにsliceはスタック領域に連続してデータを格納します。 そして&strは上記の通り、実データへのポインタとlenで表現されます。 これらの組み合わせで、ポインタ+lenが3つ並ぶ結果となったのです。

念のため、3つの要素のうち最初の8バイトが実データへのポインタであることも確かめておきましょう。

let a = ["a", "ab", "abc"];

println!("{:x?}", as_raw_bytes(&a));
// [55, 8e, f9, 7, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 50, 8e, f9, 7, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 52, 8e, f9, 7, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]

unsafe {
    for i in 0..a.len() {
        println!("-------");
        let p = a[i].as_ptr();
        println!("{:?}", p);
        println!("{:?}", *p);
        let data = std::slice::from_raw_parts(p, a[i].len());
        println!("{:?}", data);
    }
    // -------
    // 0x107f98e55
    // 97
    // [97]
    // -------
    // 0x107f98e50
    // 97
    // [97, 98]
    // -------
    // 0x107f98e52
    // 97
    // [97, 98, 99]
}

確かに、3つに分かれた各要素の先頭8バイトが実データへのポインタになっていました。

String

続いてStringです。

let a: [String; 3] = [
    "a".to_string(),
    "ab".to_string(),
    "abc".to_string()];

println!("{:x?}", as_raw_bytes(&a));
// [90, 2c, c0, 4a, 9b, 7f, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, a0, 2c, c0, 4a, 9b, 7f, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, b0, 2c, c0, 4a, 9b, 7f, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]

例によって整形します。

[90, 2c, c0, 4a, 9b, 7f, 00, 00,
 01, 00, 00, 00, 00, 00, 00, 00,
 01, 00, 00, 00, 00, 00, 00, 00,
 a0, 2c, c0, 4a, 9b, 7f, 00, 00,
 02, 00, 00, 00, 00, 00, 00, 00,
 02, 00, 00, 00, 00, 00, 00, 00,
 b0, 2c, c0, 4a, 9b, 7f, 00, 00,
 03, 00, 00, 00, 00, 00, 00, 00,
 03, 00, 00, 00, 00, 00, 00, 00]

[String][&str]と同じようにスタック領域に3つのStringが並んでいます。 上述の通り&strは実データへのポインタとlenを持っていますが、Stringはこれに加えてcapを持ちます。 ちょうどVecに対応しています。

そうとなれば実データを指しているのか確認したくなりますね。

let a: [String; 3] = [
    "a".to_string(),
    "ab".to_string(),
    "abc".to_string()];

println!("{:x?}", as_raw_bytes(&a));
// [90, 2e, 40, 6f, 8c, 7f, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, a0, 2e, 40, 6f, 8c, 7f, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, b0, 2e, 40, 6f, 8c, 7f, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]

unsafe {
    for i in 0..a.len() {
        println!("-------");
        let p = a[i].as_ptr();
        println!("{:?}", p);
        println!("{:?}", *p);
        let data = std::slice::from_raw_parts(p, a[i].len());
        println!("{:?}", data);
    }
    // -------
    // 0x7f8c6f402e90
    // 97
    // [97]
    // -------
    // 0x7f8c6f402ea0
    // 97
    // [97, 98]
    // -------
    // 0x7f8c6f402eb0
    // 97
    // [97, 98, 99]
}

確かに実データへのポインタとなっていました。

続けて、Vec<String>です。

let a: Vec<String> = vec![
    "a".to_string(),
    "ab".to_string(),
    "abc".to_string()];

println!("{:x?}", as_raw_bytes(&a));
// [a0, 2d, c0, d0, d5, 7f, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0]

unsafe {
    let p = a.as_ptr(); // p: *const String
    println!("{:?}", p);
    // 0x7fd5d0c02da0

    println!("{:?}", *p);
    // "a"

    let data = std::slice::from_raw_parts(p, a.len()); // data: &[String]
    println!("{:?}", data);
    // ["a", "ab", "abc"]

    for i in 0..a.len() {
        println!("-------");
        let p = a[i].as_ptr(); // p: *const u8
        println!("{:?}", p);
        println!("{:?}", *p);
        let data = std::slice::from_raw_parts(p, a[i].len()); // data: &[u8]
        println!("{:?}", data);
    }
    // -------
    // 0x7fd5d0c02cd0
    // 97
    // [97]
    // -------
    // 0x7fd5d0c02ce0
    // 97
    // [97, 98]
    // -------
    // 0x7fd5d0c02cf0
    // 97
    // [97, 98, 99]
}

一番最初のas_raw_bytes(&a)の結果を整形します。

[a0, 2d, c0, d0, d5, 7f, 00, 00,
 03, 00, 00, 00, 00, 00, 00, 00,
 03, 00, 00, 00, 00, 00, 00, 00]

aVec<String>型のため、実データへのポインタ、lencapを持つ結果となっていることがわかります。

a.as_ptr()(0x00007fd5d0c02da0)を参照外しすると"a"が得られました。 a[0].as_ptr()(0x00007fd5d0c02cd0)は違うアドレスですが、同じ"a"を参照しています。 図にすると以下のようになります。

f:id:cipepser:20190317174507p:plain

References

プログラミングRust

プログラミングRust

BLS署名について

以下を書いたので、BLS署名についてもまとめておく。

cipepser.hatenablog.com

なお、本内容はブロックチェーン系プロジェクトで着目される暗号技術 のP.22に書かれているBLS署名について自分なりに理解するためのメモである。1

BLS署名はその名の通り、署名をどうやるかという話なので、以下3フェーズで考える。

  • 鍵生成
  • 署名
  • 検証

鍵生成

 G_1, G_2素数位数 pの加法巡回群とする。

ハッシュ関数を以下のように定める。

 H: \lbrace 0, 1 \rbrace^* \to G_1

 \lbrace 0, 1 \rbrace^* は任意長の入力を表している。 後述の双線形写像を使うために、写像した先が G_1になるようにする。

また、公開パラメータとして、 Q \in G_2を選ぶ。

次に有限体 \mathbb{F}_pから秘密鍵を以下のように選ぶ。

 s \in \mathbb{F}_p

公開パラメータ Qを生成元として、公開鍵を sQとする。  sQ \in G_2であることに注意。

署名

メッセージ mに対して、以下のように署名 Signを行う。

 Sign(s, m) = s H(m)

 sH(m) \in G_1であることに注意。

検証

BLS署名における双線形性(ペアリング)について - 逆さまにしたで述べた以下の双線形写像 eを使う。

 e: G_1 \times G_2 \to G_T

ここで G_T素数位数 pの乗法巡回群である。

検証する内容は以下。等号が成り立つなら正当な署名である。

 e(H(m), sQ) = e(Sign(s, m), Q)

補足

署名の検証までであれば、上記で終わりなのでおまけ。  e(H(m), sQ) = e(Sign(s, m), Q)を式展開して、ちゃんと成り立つことを確認する。

署名のところで記載したように Sign(s, m) = s H(m) なので、

 (l.h.s) = e(sH(m), Q)

である。あとは sを第一引数から第二引数に移せることが言えれば、右辺と一致する。これについては、BLS署名における双線形性(ペアリング)について - 逆さまにしたで述べた通りなので、そちら参照(一番最後のところ)。

もうちょっと補足しておくと、 H(m) G_1の元としたことで、 sH(m) \in G_1である。 同様に Q \in G_2としたので、 sQ \in G_2である。 双線形写像 e e: G_1 \times G_2 \to G_Tとなるような写像であることも理解しておくとよいと思う。

References


  1. 今回も記号を自分の理解に合わせて出典元と変えている箇所がある

BLS署名における双線形性(ペアリング)について

ブロックチェーン系プロジェクトで着目される暗号技術のP.21-22を理解するために双線形性(ペアリング)について調べた。自分が理解するに至った軌跡をメモベースで残す。

余談だが、調べる過程で双線形写像(Bilinear maps)とペアリングの違いが気になった。個人的な印象としては、ペアリングは暗号分野寄りに使われる用語だと思ったが、Intro to Bilinear Mapsにも以下のように書いてあるので、本記事では大体同じものとして扱う。

Bilinear maps are called pairings because they associate pairs of elements from G1 and G2 with elements in Gt.

(拙訳) 双線形写像はペアリングとも呼ばれる。 G1とG2からの要素をGtでペアとして関連付けるからだ。

以下、調べた内容。1

双線形とは何なのか

本節は、ブロックチェーン系プロジェクトで着目される暗号技術がベースとなっている。

双線形を定義するにあたり、まず2種類の楕円曲線の生成元を  P, Qとする。このとき、素数位数  pの加法巡回群  G_1, G_2は、以下のように表現できる。

  G_1 = \lbrace 0, P, 2P, ..., (p-1) P \rbrace
  G_2 = \lbrace 0, Q, 2Q, ..., (p-1) Q \rbrace

また、有限体  \mathbb{F}_pと、位数  pの乗法巡回群  1  p乗根の集合)として  G_Tを定める。

このとき、 a, b \in \mathbb{F}_pなる  a ,bに対して、写像  e: G_1 \times G_2 \to G_Tが以下を満たすことを双線形という。

  e(aP, bQ) = e(P, Q)^{ab}

個人的に本記事を書く要因となったのが、ブロックチェーン系プロジェクトで着目される暗号技術P.22「正当性」における以下の数式。上記定義と表記が違ったので疑問に思って調べ始めた。なので本記事では下記数式が成立するところまでを追う。

  e(sH(m), Q) = e(H(m), sQ)

ベクトル空間における線形性(その1)

本節は、双線形関数 物理のかぎしっぽがベースになっている。

まず、復習として一変数関数に対する線形性を考える。 これはベクトル空間  V  x, y \in Vなる元に対して、写像  \phi: V \to \mathbb{R}が以下を満たすことだった。

  \phi(x + y) = \phi(x) + \phi(y)
  \phi(\alpha x) = \alpha \phi(x)  , \alpha \in \mathbb{R}

では、二変数ではどうなるか。 ベクトル空間  Vの直積集合  V \times Vの元  (x, y)に対して、写像  \psi(x,y): V \times V \to \mathbb{R}が以下を満たすことを双線形という。

  \psi(\alpha x + \beta x, y) = \alpha \psi(x, y) + \beta \psi(x,y)
  \psi(x, \alpha y + \beta y) = \alpha \psi(x, y) + \beta \psi(x,y)

双線形関数 物理のかぎしっぽ本文には、以下のように記載されているが、理解が及ばず腹落ちしなかった。

双線形関数は 個々の変数に対して別々に線形性を持っている と言えそうです.これが双線形という名前の由来でもあります なるほど。確かにそう見える。

ベクトル空間における線形性(その2)

本節は、岸本研究室 - 双線形写像についてがベースとなっている。

上記議論では、理解が及ばなかったので、もう少しシンプルに考える。 「ベクトル空間における線形性(その1)」の定義に比べて、式の数は多いが、以下で双線形を定義したほうがシンプルで、学習にあたっては、理解しやすかった。

双線形を定義する。ベクトル空間  Vを考え、  x, x_1, x_2, y, y_1, y_2 \in Vなる元を考える。このベクトル空間  Vに対して、  f: V \times V \to \mathbb{R}なる写像  fを定義する。また、  c \in \mathbb{R}とする。 このとき双線形は以下の性質を満たすことをいう。

  f(x_1 + x_2, y) = f(x_1, y) + f(x_2, y)
  f(cx, y) = c f(x, y)
  f(x, y_1 + y_2) = f(x, y_1) + f(x, y_2)
  f(x, cy) = c f(x, y)

こちらのほうが「ベクトル空間における線形性(その1)」で引用した個々の変数に対して別々に線形性を持っているというのが理解しやすいと思う。

双線形の例

「例示は理解の試金石」ということで双線形を満たす写像  fの具体例を考えてみる。

双線形な例

  f(x, y) = xy

「ベクトル空間における線形性(その2)」で定義した4式を一つずつ確かめていく。

  f(x_1 + x_2, y) = (x_1 + x_2) y = x_1 y + x_2 y = f(x_1, y) + f(x_2, y)
  f(cx, y) = (cx)y = c xy =  c f(x, y)
  f(x, y_1 + y_2) = x(y_1 + y_2) = x y_1 + x y_2 = f(x, y_1) + f(x, y_2)
  f(x, cy) = x(cy) = c xy = c f(x, y)

となり、すべて満たすので双線形。

双線形じゃない例2

  f(x, y) = x + y

実は最初にこれを念頭に考えていた。パッと見、線形性持ってそうだし。でも双線形じゃない。 4式中の一番上の式で双線形を満たさないことを確認する。

 \begin{eqnarray} (l.h.s) &=& f(x_1 + x_2, y) \\ &=& (x_1 + x_2) + y \end{eqnarray}  
  \begin{eqnarray} (r.h.s) &=& f(x_1, y) + f(x_2, y) \\ &=& (x_1 + y) + (x_2 + y) \\ &=& (x_1 + x_2) + y + y \\ &=& f(x_1 + x_2, y) + y \end{eqnarray}

となり、左辺と右辺が一致しない。

BLS署名における双線形

話をベクトル空間から乗法巡回群で考えているBLS署名の話に戻す。それにあたり「ベクトル空間における線形性(その2)」で登場した双線形性の定義を再掲する。

  f(x_1 + x_2, y) = f(x_1, y) + f(x_2, y)
  f(cx, y) = c f(x, y)
  f(x, y_1 + y_2) = f(x, y_1) + f(x, y_2)
  f(x, cy) = c f(x, y)

これらはベクトル空間  Vと実数  \mathbb{R}で考えていたので、乗法巡回群  G_Tと有限体  \mathbb{F}_pで考え直す。Intro to Bilinear Mapsより双線形性の定義を引用すると、双線形性とは  e: G_1 \times G_2 \to G_Tなる写像について、 u \in G_1, v \in G_2, a, b \in \mathbb{Z}に対して、

 e(u^{a}, v^{b}) = e(u, v)^{ab}

が成り立つことである。ただし、このとき  eは非退化である。また本式の表現方法にはIntro to Bilinear Mapsの7ページ目に書いてある以下の表記もある。

 \forall P \in G_1, \forall Q \in G_2, \forall a, b \in \mathbb{Z}に対して、

 e(aP, bQ) = e(P, Q)^{ab}

いきなり上式が出てくると、ベクトル空間から乗法巡回群への議論で少し飛んでしまっているように感じられるので補足する。

4式のうち、上2式について対応を示す。3

 f(x_1 + x_2, y) = f(x_1, y) + f(x_2, y) \tag{1}
 f(cx, y) = c f(x, y) \tag{2}

式(2)から示す。 e(aP, bQ) = e(P, Q)^{ab}において、 a = c, b = 1とすると

 e(cP, Q) = e(P, Q)^{c}

と表現できる。ここで e(P, Q) \in G_T G_Tが乗法巡回群であることを考えると自然な表記であろう。

次に式(1)を示す。 P \in G_1 G_1が加法巡回群であることを考えると

 e(P_1 + P_2, Q) = e(P_1, Q)e(P_2, Q)

となることを示せればよい。ここで P_1, P_2 G_1の元であることから生成元 Pを用いて、

 P_1 = a_1 P
 P_2 = a_2 P

と表現できる。ここで a_1, a_2 \in \mathbb{Z}である。これらを用いると

 \begin{eqnarray} e(P_1 + P_2, Q) &=& e(a_1P+a_2P, Q) \\ &=& e((a_1+a_2)P, Q) \\ &=& e(P, Q)^{(a_1+a_2)} \\ &=& e(P,Q)^{a_1} e(P,Q)^{a_2} \\ &=& e(a_1 P,Q) e(a_2 P, Q) \\ &=& e(P_1, Q) e(P_2, Q) \end{eqnarray}

が得られる。

ここまでの議論は整数 \mathbb{Z}上のものだったが、有限体 \mathbb{F}_p上でも成り立つとして、

 \forall P \in G_1, \forall Q \in G_2, \forall a, b \in \mathbb{F}_pに対して、

 e(aP, bQ) = e(P, Q)^{ab}

である。ここから a, bが可換であることがわかるが、もう少し詳細に書いておく。

 c \in \mathbb{F}_pに対して、 a = c, b = 1とすると

 e(cP, Q) = e(P, Q)^{c}

である。同様に  a = 1, b = cとすると

 e(P, cQ) = e(P, Q)^{c}

である。これらを合わせると

 e(cP, Q) = e(P, cQ)

が得られる。これを使うとブロックチェーン系プロジェクトで着目される暗号技術のP.22では、以下の変形が成り立つことがわかる。

 e(sH(m), Q) = e(H(m), sQ)

References


  1. 自分が理解しやすいように記号を変えているので出典元と異なる箇所がある

  2. 改行を含む数式にpreタグを使った。markdownでも&がエスケープされないようにならないかなー

  3.  yについては同様なので省略

Raspberry Pi 3 Model b+でビットコインフルノードを構築する

背景

フルノードの重要性とLightning NetworkやProof of Stakeがノード運営インセンティブに与える影響 - YouTubeフルノードの重要性とフルノードが広がった世界について改めて考える - ビットコインダンジョン2.0 を読んで(見て)、ビットコインルノードを立てたくなりました。
とはいえ以前、下記記事を書いたときに一度ノードを立てたことはあったので、二度目の構築です。

cipepser.hatenablog.com

前回はMacBook Pro上で構築したためストレージ容量を節約したかったので、 当時の構築メモの通り、pruneモードで構築しました。
今回はフルノードです。 My first impressions of the Casa Bitcoin Node – The Startup – Medium のようにCasaもありかなぁと思ったいたのですが、Casaのホームページを見たら2019年2月7日時点で出荷に1ヶ月掛かるとありました。

1ヶ月は待てなかったので、Casaは諦め1、以下のRaspberry Pi 3 Model b+2を購入しました。

なお、HDD、モニタ、キーボード、マウスは家にあったものを流用したので、今回発生した費用は本セットの約1万円でした。

開封の儀

届いたやつです。開封し、基盤を取り出します。 f:id:cipepser:20190212175033j:plain f:id:cipepser:20190212175045j:plain

付属のmicroSDカードを挿します。

f:id:cipepser:20190212175057j:plain

BluetoothマウスのUSBを取り付けます。

f:id:cipepser:20190212175110j:plain

HDMIケーブルを挿します。

f:id:cipepser:20190212175124j:plain

外付けHDD(USBケーブル)を挿します。

f:id:cipepser:20190212175138j:plain

電源ケーブルを挿し、スイッチを入れます。

f:id:cipepser:20190212175150j:plain

今回のキットにはSDカードにOSが入っているので、Raspbianをインストールしました。

f:id:cipepser:20190212175205j:plain

キーボードはBluetoothキーボードしか持っていなかったので、ここでやっとペアリングです。

f:id:cipepser:20190212175227j:plain

Wi-Fiの設定も上図の赤い☓のところからいけます。

Bitcoin Coreのインストール

構築には、ラズパイでビットコインフルノードを動かしてみる(2017年) – Crypto Developer Blogを参考にさせていただきました。 2019年になりBitcoin Coreのバージョンも上がっており、コマンドが異なる部分もあるので、自分でやった手順を残します。3

まず、SWAP領域を拡張します。

# /etc/dphys-swapfile
CONF_SWAPSIZE=1000

# termitanl
$ sudo dphys-swapfile setup
$ sudo dphys-swapfile swapon

apt-getを最新の状態にします。

# termitanl
$ sudo apt-get update
$ sudo apt-get upgrade -y

ツール群をインストールします。

# termitanl
$ sudo apt-get install autoconf libevent-dev libtool libssl-dev libboost-all-dev libminiupnpc-dev -y
$ sudo apt-get install git -y
$ sudo apt-get install qt4-dev-tools libprotobuf-dev protobuf-compiler libqrencode-dev -y

Bitcoin Coreをインストールします。本記事時点で最新の0.17をインストールします。

# termitanl
$ mkdir ~/bin
$ cd ~/bin
$ git clone -b 0.17 https://github.com/bitcoin/bitcoin.git
$ cd bitcoin/
$ ./autogen.sh
$ ./configure --enable-upnp-default --disable-wallet
$ make -j2 # ここがとても長い
$ sudo make install

HDDのマウント

HDDの容量はどれくらいがいいのか見積もっておきましょう。 ブロックチェーンサイズは、Blockchain Size - Blockchainで確認できます。グラフを引用すると以下のようになります。

f:id:cipepser:20190212174022p:plain

2019年2月10日現在で200GBを超えたくらいですね。 ここ4年ほどは概ね線形にサイズが増加しており、約50GB/年くらいのスピードです。 フルノードで運用することを考えると500GBくらいあれば5年くらいは大丈夫そうですね。

かなりバッファを見て(手元にあったHDDなだけ)1TBのHDDでマウントします。 手順やハマった記録は以下記事参照。

cipepser.hatenablog.com

Bitcoin Coreの設定

マウント先に/volumes/BTC/blocksを作り、ここにデータを格納することにします。4

bitcoin.confに設定を書き込みます。どこにbitcoin.confを配置すべきかというと、Running Bitcoin - Bitcoin Wiki に以下のように書いてあるので、/volumes/BTC/blocksに配置します。

By default, Bitcoin (or bitcoind) will look for a file named 'bitcoin.conf' in the bitcoin data directory

デフォルトでは、bitcoinデータを格納するフォルダにあるbitcoin.confを見に行く

設定した内容は以下の通りです。

# bitcoin.conf
rpcuser=bitcoinrpc
rpcpassword=<PASSWORD>
# ~/.bashrc (追記分のみ)
PATH="$PATH:/home/pi/bin/bitcoin/src"
alias bitcoin-cli='bitcoin-cli -datadir=/volumes/BTC/blocks'

ラズパイでビットコインフルノードを動かしてみる(2017年) – Crypto Developer Blog のように設定すると、bash~/.profileを読みにいってくれないので、~/.bashrcに集約しています。 本当はaliasとか.bash_aliasesに書くべきかもしれないですが、どうせフルノード専用機にするので管理箇所を集約することにしました。

Bitcoin Coreの起動

以下で起動します。

# termitanl
$ bitcoind -datadir=/volumes/BTC/blocks -daemon

バックグラウンドで実行されているので同期がされていることをCLIで確認します。 v0.16.0getinfoは削除されてしまったので、getblockchaininfoを確認しましょう。

# termitanl
pi@raspberrypi:~ $ bitcoin-cli getblockchaininfo
{
  "chain": "main",
  "blocks": 181901,
  "headers": 563103,
  "bestblockhash": "000000000000075b5e5ee7573921a3b48e45ace6d9020af7777f32fd99737f14",
  "difficulty": 1591074.961847305,
  "mediantime": 1338165561,
  "verificationprogress": 0.009423622364504645,
  "initialblockdownload": true,
  "chainwork": "000000000000000000000000000000000000000000000012406bed1889e1f934",
  "size_on_disk": 1702373506,
  "pruned": false,
  "softforks": [
    {
      "id": "bip34",
      "version": 2,
      "reject": {
        "status": false
      }
    },
    {
      "id": "bip66",
      "version": 3,
      "reject": {
        "status": false
      }
    },
    {
      "id": "bip65",
      "version": 4,
      "reject": {
        "status": false
      }
    }
  ],
  "bip9_softforks": {
    "csv": {
      "status": "defined",
      "startTime": 1462060800,
      "timeout": 1493596800,
      "since": 0
    },
    "segwit": {
      "status": "defined",
      "startTime": 1479168000,
      "timeout": 1510704000,
      "since": 0
    }
  },
  "warnings": ""
}

blocksheadersが一致したら同期完了です。

最新ブロックが「いつマイニングされたものなのか」を確認するなら、debug.logを見たほうがわかりやすいです。

$ tail -f /volumes/BTC/blocks/debug.log
2019-02-15T21:31:40Z UpdateTip: new best=000000000000000009bfca5a27f95c6212923139b0f43888d8581f49b3542fd4 height=338070 version=0x00000002 log2_work=81.95771 tx=56124782 date='2015-01-08T16:27:25Z' progress=0.150469 cache=497.6MiB(4300081txo)
2019-02-15T21:31:43Z UpdateTip: new best=00000000000000001177c70e7b05f9790c645202b6714b8dce38a10c1ddd9b0e height=338071 version=0x00000002 log2_work=81.957763 tx=56126029 date='2015-01-08T16:32:46Z' progress=0.150472 cache=497.6MiB(4300049txo)

同期開始(bitcoindの起動)は2019/02/15 12:20頃です。 ルータで通信量を確認したところ、1日経過時点で40GB程度(その他の通信も込み)だったので、一週間くらい掛かりそうですね。。。 気長に待ちます。

(2019/4/10追記) wimaxの3日10GB制限あり、かつ、途中で同期を停止していたタイミングもあったので参考になるかわかりませんが、4/10早朝にprogressが1となりました。 制限ありの状況で2ヶ月くらい生活するとネットすら見れずかなりつらい日々でした。固定回線で同期しましょう。。。

References


  1. 自前で構築する分、こちらのほうが安い。需給もあると思いますが。

  2. SDカードだけ違う安いショップもあったのですが、レビューでACアダプタの電圧が足りないようなレビューが多かったので割高でもこちらにしました。バッテリーが少ないようなエラーも出ないのでよかったです。

  3. 大きな流れは変わりません。2019年でも手順変わってないよくらいの感じで見てもらえるとうれしいです。

  4. blocksの中にblocksディレクトリができてしまったので、別の名前のほうがよかった。反省。

【Raspberry Pi】マウントするファイルシステムを間違えて起動できなくなった

ラズパイを買ったので RaspberryPi 3 にUSBの外付けHDD(NTFSフォーマット)を接続する - min117の日記を参考に、外付けHDDをマウントしたところ、ntfs-3gをインストール後rebootしたらラズパイの起動ができなくなりました。 ファイルシステムに明るくないため、手こずりましたが、無事復旧したのでログを残します。

環境情報

Raspberry Pi 3 Model b+
pi@raspberrypi:~ $ lsb_release -a
No LSB modules are available.
Distributor ID:   Raspbian
Description:  Raspbian GNU/Linux 9.6 (stretch)
Release:  9.6
Codename: stretch

発生事象

etc/fstabに以下を追記後、再起動したら起動できなくなりました。

# etc/fstab
UUID="<MY UUID>"  /volumes/  ntfs-3g defaults,iocharset=utf8,umask=000 0 0

より具体的な事象は、raspi 3 が起動しなくなった。 - Raspberry Pi Forums と同じで、起動後、以下メッセージが出るもののctrl + DEnterを押しても同メッセージが出続け、先に進めなくなりました。

You are in emergency mode, After loging in, type "jounalctl -xb"to view system logs,
"systemctl reboot" to rebott, "systemctl default" or ^D to try again to boot into defalult mode.

Cannot open access to console, the root account is locked. See sulongin(8) man page
for more details.

Press Enter to continue.

完全に/etc/fstabファイルシステムの指定を間違えてしまい、起動できなくなってしまったパターンです。やらかしです。 /etc/fstabを戻そうにも起動すらできないので、Macで復旧します。

復旧

方針

幸い所有しているMacBook ProにSDカードスロットがあったので、VirtualBox上のUbuntuにSDカードを認識させる · Yoshi's Notesを参考にVirtualBoxUbuntuを立ち上げて、/etc/fstabを編集できるようにします。

環境情報

# VirtualBox
バージョン 5.2.26 r128414 (Qt5.6.3)

SDカードを認識させるのにExtension Packが必要なので、Download VirtualBox (Old Builds)から自身のVirtualBoxのバージョンに合ったExtension Packをインストールします。 なお、バージョンが異なるとインストールに失敗します。

UbuntuのOS情報は以下の通りです。

vagrant@vagrant-ubuntu-trusty-64:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=14.04
DISTRIB_CODENAME=trusty
DISTRIB_DESCRIPTION="Ubuntu 14.04.5 LTS"

復旧手順

Mac

SDカードをMacに挿入します(MicroSDなので変換アダプタ使いました)。

Macからdiskutilを確認します。 以下のように当環境では、SDカードは/dev/disk2で認識されていることが確認できます。

# Mac
❯ diskutil list
(中略)
/dev/disk2 (internal, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *31.9 GB    disk2
   1:             Windows_FAT_16 RECOVERY                1.7 GB     disk2s1
   2:                      Linux                         33.6 MB    disk2s5
   3:             Windows_FAT_32 boot                    72.4 MB    disk2s6
   4:                      Linux                         30.1 GB    disk2s7

mountコマンドでも確認したところ、/dev/disk2に関連するパーティションは以下の2つ(RECOVERYboot)でした。

# Mac
❯ mount
(中略)
/dev/disk2s1 on /Volumes/RECOVERY (msdos, local, nodev, nosuid, noowners)
/dev/disk2s6 on /Volumes/boot (msdos, local, nodev, nosuid, noowners)

ディスクユーティリティからも以下のように確認できます。

f:id:cipepser:20190212161634p:plain f:id:cipepser:20190212161628p:plain

次にディスクユーティリティからRECOVERY/bootマウント解除します。
(スクショを残し忘れましたが、上記2画面RECOVERY/bootの画面上部にマウント解除のボタンがあります)

続いて、VirtualBox vmdkファイルを作成し、権限を変更します。
(本当は777にしたくないですが、元記事と変えてハマるのも嫌なので、777で。。。)

# Mac
❯ sudo VBoxManage internalcommands createrawvmdk -filename ./sd-card.vmdk -rawdisk /dev/disk2
❯ sudo chmod 777 sd-card.vmdk
❯ sudo chmod 777 /dev/disk2

作成したvmdkファイルについて、VirtualBox上でハードディスクの追加を行います。

f:id:cipepser:20190212164933p:plain

既存のディスクを選択します。

f:id:cipepser:20190212164940p:plain

先ほど作成したsd-card.vmdkを開きます。

f:id:cipepser:20190212164945p:plain

ちなみにマウント解除したはずなんですが、少し時間を開けたら再度マウントされてしまっていて、以下のように失敗しました。

f:id:cipepser:20190212164811p:plain

慌てず、ディスクユーティリティから再度マウント解除すればOKです。

成功すると以下のようにsd-card.vmdkが認識されます。

f:id:cipepser:20190212165542p:plain

Ubuntu

無事追加できたので、vagrant upしましょう。

参考までですが、一度以下のように起動に失敗しました。

❯ vagrant up
(中略)
==> default: Booting VM...
There was an error while executing `VBoxManage`, a CLI used by Vagrant
for controlling VirtualBox. The command and stderr is shown below.

Command: ["startvm", "84b91ede-c9fc-45bb-a45a-1f2dfbddee39", "--type", "headless"]

Stderr: VBoxManage: error: VD: error VERR_RESOURCE_BUSY opening image file '/Users/xxx/sd-card.vmdk' (VERR_RESOURCE_BUSY).
VBoxManage: error: Failed to open image '/Users/xxx/sd-card.vmdk' in read-write mode (VERR_RESOURCE_BUSY).
VBoxManage: error: AHCI: Failed to attach drive to Port1 (VERR_RESOURCE_BUSY)
VBoxManage: error: Details: code NS_ERROR_FAILURE (0x80004005), component ConsoleWrap, interface IConsole

こちらもRECOVERY/bootMacにマウントされてしまっていることが原因なので、ディスクユーティリティから再度マウント解除すればOKです。

さて、vagrant sshしてからlsblkを実行し、Ubuntu上でSDカードが認識できたことを確認します。

vagrant@vagrant-ubuntu-trusty-64:~$ lsblk
NAME   MAJ:MIN RM   SIZE RO TYPE MOUNTPOINT
sda      8:0    0    40G  0 disk
└─sda1   8:1    0    40G  0 part /
sdb      8:16   0  29.7G  0 disk
├─sdb1   8:17   0   1.6G  0 part
├─sdb2   8:18   0     1K  0 part
├─sdb5   8:21   0    32M  0 part
├─sdb6   8:22   0    69M  0 part
└─sdb7   8:23   0  28.1G  0 part

sdbとして認識できていますね。これをマウントしていきます。 ラズパイ上では何も考えずにntfs-3gにマウントしようとして失敗したので、今回はちゃんとファイルシステムのTYPEを確認します。

vagrant@vagrant-ubuntu-trusty-64:/volumes$ sudo blkid
/dev/sda1: LABEL="cloudimg-rootfs" UUID="xxx" TYPE="ext4"
/dev/sdb1: LABEL="RECOVERY" UUID="xxx" TYPE="vfat"
/dev/sdb5: LABEL="SETTINGS" UUID="xxx" TYPE="ext4"
/dev/sdb6: LABEL="boot" UUID="xxx" TYPE="vfat"
/dev/sdb7: LABEL="root" UUID="xxx" TYPE="ext4"

今回のゴールは「/etc/fstabを編集すること」なので、LABELがrootになっている/dev/sdb7を見ます。 TYPEはext4なので、ext4として/volumes/sdcard7にマウントします。
/volumes/sdcard7が存在しないと思うので、事前にmkdirしておいてください)

vagrant@vagrant-ubuntu-trusty-64:/volumes$ sudo mount -t ext4 /dev/sdb7 /volumes/sdcard7

無事マウントできました。
今回のゴールであるetc/fstabが編集できます。以下記述を削除しましょう。

UUID="<MY UUID>"  /volumes/  ntfs-3g defaults,iocharset=utf8,umask=000 0 0

この状態でSDカードをラズパイに挿し、起動したら復旧しました。

etc/fstabを正しく設定する

復旧できたので正しい設定をしましょう。
先ほど同じようにラズパイ上でsudo blkidしてファイルシステムのTYPEを確認します。

$ sudo blkid
/dev/sda1: UUID="<MY UUID>" TYPE="vfat" PARTUUID="xxx"

vfatなので、ntfs-3gではなくvfatでマウントします。

# etc/fstab
UUID="<MY UUID>"  /volumes/  vfat defaults,iocharset=utf8,umask=000 0 0

これで再起動すれば、途中で止まることなく起動でき、/volumes/BTC/に外付けHDDがマウントされているはずです。

References