実践Rust入門を読んだ
実践Rust入門 言語仕様から開発手法まで, κeen, 河野達也, 小松礼人を読みました。本書の特徴は以下の3つでしょう。
- 2018 Editionに対応している
- FFIについて日本語で書かれた書籍
- 実践 を意識した内容になっている
本記事では、特に3つ目の実践的という観点で感想を述べようと思います。
Rustの言語仕様という観点で言えば、プログラミングRustのほうが網羅性は高いでしょう。 しかし、Rustは入門のハードルがとても高い言語です。1
個人的な経験でいえば、map
やfilter
でさえHaskellを勉強していなかったら、とっつきにくかったんじゃないかと思います。他言語の経験が豊富であればまだしも、いきなりRustを始めると挫折してしまうでしょう。
だからこそ、本書のような実践的な入門書は重要だと思います。「あれもこれもやらなきゃいけない」とならずに、実際に利用されるトピックから取り組めるのはとても良いことだと思います。
また、わかってる人からすると当たり前だけど初学者はわからないというようなことが、言語化されていたのもよかったです。例えばP.151では、桁あふれのメソッドについて
- 検査付き演算:
checked_
- 飽和演算:
saturating_
- ラッピング演算:
wrapping_
- 桁あふれ演算:
overflowing_
のprefixがつく、と言及されていました。他にもP.169のボックス化されたスライスでは、
使う機会はあまりなさそうですが、
と書かれていたりして、力の抜きどころもわかって良かったです。 プログラミングRustを読んでいると網羅性が高いのはいいんですが、これはいつ使うんだろう?となることがあるのでこういう配慮は助かります。
他にも、プログラミングRustにも載っている内容ではあるものの、String
とstr
の違いは最初にわからなくなりがちなところなので、メモリ表現が図解されているのもわかりやすいと思います。
また、ユニット型()
がサイズゼロだと知ってるとcollectionでSet作りたいときに()
使えば良さそうと思えるのも良かったです。以前、GoでSetライブラリを自作しようとしたときにstruct{}{}
だらけになったのでRustのほうが同じサイズゼロで実装するのにきれいに書けますね。 2
いいところばかりなのもあれなので、気になったところも述べます。
内容ではないですが、誤植が気になりました。
例えば、ch07_10_closures.rs
の let 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をつけたことがなかったのと、エラー処理をちゃんとできるのはとても楽しかったです。
実践Rust入門9章終わった。どこでエラー起きたかもわかるし最高だ。 pic.twitter.com/bIMJyxuF3i
— さいぺ (@cipepser) May 5, 2019
第10章 パッケージを作る
パッケージの作成、CI、crate.ioへの公開と一通り触れてよかったです。特にcrate.ioへの公開は初めてだったので勉強になりました。
第11章 Webアプリケーション、データベース接続
リクエストの受付からDBへの接続、レスポンスを返すところまで一式という章です。 Futuresに振れることができたのも大きな収穫でした。 また、CLIがこんなに簡単に書けるのかという驚きもありました。
References
- 実践Rust入門 言語仕様から開発手法まで, κeen, 河野達也, 小松礼人
- ghmagazine/rustbook
- プログラミングRust | Jim Blandy, Jason Orendorff, 中田 秀基
- Join rust-jp on Slack!
- cipepser/monkey
- 作者: κeen,河野達也,小松礼人
- 出版社/メーカー: 技術評論社
- 発売日: 2019/05/08
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
- 作者: Jim Blandy,Jason Orendorff,中田秀基
- 出版社/メーカー: オライリージャパン
- 発売日: 2018/08/10
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
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ポインタ - 簡潔なQのas_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, }
またRawVec
はraw_vec.rs
で実装されています。
#[allow(missing_debug_implementations)] pub struct RawVec<T, A: Alloc = Global> { ptr: Unique<T>, cap: usize, a: A, }
このようにVec<T>
はlen
とcap
を持ちます。
改めて結果を見てみると後半の4
から始まる8バイト2行はlen
とcap
であることがわかります。
// (再掲) [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] }
*p
やdata
の出力結果から、確かに実データへのポインタとなっていました。
文字列まわり
[i32]
とVec<i32>
を確認できたので、おまけとして文字列を扱う際に登場する以下3つのメモリ内容も確認してみます。
[char]
、Vec<char>
[&str]
、Vec<&str>
String
、Vec<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]
len
とcap
が3
(うしろ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]
a
はVec<String>
型のため、実データへのポインタ、len
、cap
を持つ結果となっていることがわかります。
a.as_ptr()
(0x00007fd5d0c02da0
)を参照外しすると"a"
が得られました。
a[0].as_ptr()
(0x00007fd5d0c02cd0
)は違うアドレスですが、同じ"a"
を参照しています。
図にすると以下のようになります。
References
- Rustのvtableの内部構造 - 簡潔なQ
- RustのSizedとfatポインタ - 簡潔なQ
- Function std::mem::size_of - rust-lang
- char - Rust
- str - Rust
- 生ポインタ
- プログラミングRust
- 作者: Jim Blandy,Jason Orendorff,中田秀基
- 出版社/メーカー: オライリージャパン
- 発売日: 2018/08/10
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
BLS署名について
以下を書いたので、BLS署名についてもまとめておく。
なお、本内容はブロックチェーン系プロジェクトで着目される暗号技術 のP.22に書かれているBLS署名について自分なりに理解するためのメモである。1
BLS署名はその名の通り、署名をどうやるかという話なので、以下3フェーズで考える。
- 鍵生成
- 署名
- 検証
鍵生成
ハッシュ関数を以下のように定める。
は任意長の入力を表している。 後述の双線形写像を使うために、写像した先がになるようにする。
また、公開パラメータとして、を選ぶ。
次に有限体から秘密鍵を以下のように選ぶ。
公開パラメータを生成元として、公開鍵をとする。 であることに注意。
署名
メッセージに対して、以下のように署名を行う。
であることに注意。
検証
BLS署名における双線形性(ペアリング)について - 逆さまにしたで述べた以下の双線形写像を使う。
検証する内容は以下。等号が成り立つなら正当な署名である。
補足
署名の検証までであれば、上記で終わりなのでおまけ。 を式展開して、ちゃんと成り立つことを確認する。
署名のところで記載したようになので、
である。あとはを第一引数から第二引数に移せることが言えれば、右辺と一致する。これについては、BLS署名における双線形性(ペアリング)について - 逆さまにしたで述べた通りなので、そちら参照(一番最後のところ)。
もうちょっと補足しておくと、をの元としたことで、である。 同様にとしたので、である。 双線形写像はとなるような写像であることも理解しておくとよいと思う。
References
-
今回も記号を自分の理解に合わせて出典元と変えている箇所がある↩
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.22「正当性」における以下の数式。上記定義と表記が違ったので疑問に思って調べ始めた。なので本記事では下記数式が成立するところまでを追う。
ベクトル空間における線形性(その1)
本節は、双線形関数 物理のかぎしっぽがベースになっている。
まず、復習として一変数関数に対する線形性を考える。 これはベクトル空間のなる元に対して、写像が以下を満たすことだった。
では、二変数ではどうなるか。 ベクトル空間の直積集合の元に対して、写像が以下を満たすことを双線形という。
双線形関数 物理のかぎしっぽ本文には、以下のように記載されているが、理解が及ばず腹落ちしなかった。
双線形関数は 個々の変数に対して別々に線形性を持っている と言えそうです.これが双線形という名前の由来でもあります なるほど。確かにそう見える。
ベクトル空間における線形性(その2)
本節は、岸本研究室 - 双線形写像についてがベースとなっている。
上記議論では、理解が及ばなかったので、もう少しシンプルに考える。 「ベクトル空間における線形性(その1)」の定義に比べて、式の数は多いが、以下で双線形を定義したほうがシンプルで、学習にあたっては、理解しやすかった。
双線形を定義する。ベクトル空間を考え、なる元を考える。このベクトル空間に対して、なる写像を定義する。また、とする。 このとき双線形は以下の性質を満たすことをいう。
こちらのほうが「ベクトル空間における線形性(その1)」で引用した個々の変数に対して別々に線形性を持っている
というのが理解しやすいと思う。
双線形の例
「例示は理解の試金石」ということで双線形を満たす写像の具体例を考えてみる。
双線形な例
「ベクトル空間における線形性(その2)」で定義した4式を一つずつ確かめていく。
となり、すべて満たすので双線形。
双線形じゃない例2
実は最初にこれを念頭に考えていた。パッと見、線形性持ってそうだし。でも双線形じゃない。 4式中の一番上の式で双線形を満たさないことを確認する。
となり、左辺と右辺が一致しない。
BLS署名における双線形
話をベクトル空間から乗法巡回群で考えているBLS署名の話に戻す。それにあたり「ベクトル空間における線形性(その2)」で登場した双線形性の定義を再掲する。
これらはベクトル空間と実数で考えていたので、乗法巡回群と有限体で考え直す。Intro to Bilinear Mapsより双線形性の定義を引用すると、双線形性とはなる写像について、に対して、
が成り立つことである。ただし、このときは非退化である。また本式の表現方法にはIntro to Bilinear Mapsの7ページ目に書いてある以下の表記もある。
に対して、
いきなり上式が出てくると、ベクトル空間から乗法巡回群への議論で少し飛んでしまっているように感じられるので補足する。
4式のうち、上2式について対応を示す。3
式(2)から示す。において、とすると
と表現できる。ここででが乗法巡回群であることを考えると自然な表記であろう。
次に式(1)を示す。でが加法巡回群であることを考えると
となることを示せればよい。ここではの元であることから生成元を用いて、
と表現できる。ここでである。これらを用いると
が得られる。
ここまでの議論は整数上のものだったが、有限体上でも成り立つとして、
に対して、
である。ここからが可換であることがわかるが、もう少し詳細に書いておく。
に対して、とすると
である。同様にとすると
である。これらを合わせると
が得られる。これを使うとブロックチェーン系プロジェクトで着目される暗号技術のP.22では、以下の変形が成り立つことがわかる。
References
Raspberry Pi 3 Model b+でビットコインフルノードを構築する
背景
フルノードの重要性とLightning NetworkやProof of Stakeがノード運営インセンティブに与える影響 - YouTube
やフルノードの重要性とフルノードが広がった世界について改めて考える - ビットコインダンジョン2.0
を読んで(見て)、ビットコインフルノードを立てたくなりました。
とはいえ以前、下記記事を書いたときに一度ノードを立てたことはあったので、二度目の構築です。
前回は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万円でした。
開封の儀
届いたやつです。開封し、基盤を取り出します。
付属のmicroSDカードを挿します。
BluetoothマウスのUSBを取り付けます。
HDMIケーブルを挿します。
外付けHDD(USBケーブル)を挿します。
電源ケーブルを挿し、スイッチを入れます。
今回のキットにはSDカードにOSが入っているので、Raspbian
をインストールしました。
キーボードはBluetoothキーボードしか持っていなかったので、ここでやっとペアリングです。
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で確認できます。グラフを引用すると以下のようになります。
2019年2月10日現在で200GBを超えたくらいですね。 ここ4年ほどは概ね線形にサイズが増加しており、約50GB/年くらいのスピードです。 フルノードで運用することを考えると500GBくらいあれば5年くらいは大丈夫そうですね。
かなりバッファを見て(手元にあったHDDなだけ)1TBのHDDでマウントします。 手順やハマった記録は以下記事参照。
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.0
でgetinfo
は削除されてしまったので、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": "" }
blocks
とheaders
が一致したら同期完了です。
最新ブロックが「いつマイニングされたものなのか」を確認するなら、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
- フルノードの重要性とLightning NetworkやProof of Stakeがノード運営インセンティブに与える影響 - YouTube
- フルノードの重要性とフルノードが広がった世界について改めて考える - ビットコインダンジョン2.0
- ビットコインとブロックチェーン:暗号通貨を支える技術を読んだ - 逆さまにした
- My first impressions of the Casa Bitcoin Node – The Startup – Medium
- Casa Node for Lightning and Bitcoin
- ラズパイでビットコインフルノードを動かしてみる(2017年) – Crypto Developer Blog
- Blockchain Size - Blockchain
- bitcoin/bitcoin: Bitcoin Core integration/staging tree
【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 + D
やEnter
を押しても同メッセージが出続け、先に進めなくなりました。
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を参考にVirtualBoxでUbuntuを立ち上げて、/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つ(RECOVERY
とboot
)でした。
# Mac ❯ mount (中略) /dev/disk2s1 on /Volumes/RECOVERY (msdos, local, nodev, nosuid, noowners) /dev/disk2s6 on /Volumes/boot (msdos, local, nodev, nosuid, noowners)
ディスクユーティリティからも以下のように確認できます。
次にディスクユーティリティから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上でハードディスクの追加
を行います。
既存のディスクを選択
します。
先ほど作成したsd-card.vmdk
を開きます。
ちなみにマウント解除
したはずなんですが、少し時間を開けたら再度マウントされてしまっていて、以下のように失敗しました。
慌てず、ディスクユーティリティから再度マウント解除
すればOKです。
成功すると以下のようにsd-card.vmdk
が認識されます。
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
/boot
がMacにマウントされてしまっていることが原因なので、ディスクユーティリティから再度マウント解除
すれば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がマウントされているはずです。