One or Zero?

興味のある様々なことについて

BOLT#11 Lightning Networkによる支払いの請求書プロトコル

前回の記事ではLightning Networkで買い物したが、以下のようにQRコードとlnbcから始まる文字列が表示され、それを用いて支払いを行う。

f:id:johntas:20180216235114p:plain

このlnbcから始まる invoiceを作成するためのプロトコルが、BOLT(Basis of Lightning Technology)の11番で定義されている。このプロトコルをTypeScriptで実装したものをびりあるさんがGithubで公開していたので、マネしてRubyで書いてみよう、と思ったのでそのメモ。 

invoiceの構造

invoiceは、大きく分けてHuman-Readable Part(hrp)と Data Part(data)の2つに分かれ、それらがBech32というエンコーディングプロトコルエンコードされる。ここで注意なのが、Bech32の定義ではエンコードされた後の文字列は90文字以下でなければならないと決められている。しかし、BOLT#11では

with the exception that the Bech32 string MAY be longer than the 90 characters

と例外が認められているので、実装には注意が必要だ。RubyでBOLT#11の実装を試みた際、安土さんのgemを使わせていただいたのだが、そこには90文字以上のBech32はデコードできないようになっていたので、issueを出したら反映してくれたのがちょっと嬉しかった()。リリースはまだされていないようなので、Gemfileに書く際にはgithubのURLから引っ張ってくるとよいと思う。

追記:リリースしていただきました!

話が逸れたが、hrpは文字通り人間が容易に読み取れる部分である。ここの例を使わせてもらうと、

lnbc2500u1pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp

このinvoiceの

lnbc2500u

である。これは、LNのmainnet(bc)で2500 micro btc を払ってくださいということを言っている。そして、dataは、

pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdq5xysxxatsyp3k7enxv4jsxqzpuaztrnwngzn3kdzw5hydlzf03qdgm2hdq27cqv3agm2awhz5se903vruatfhq77w3ls4evs3ch9zw97j25emudupq63nyw24cg27h2rspfj9srp

の部分で人間が解読するには時間がかかる(できないこともないが)。hrpとdataの間の1はセパレータで、どこからdata partが始まるかを指し示すものである(支払額の数字に1が含まれることもあるが、セパレータの定義は後ろから数えて最初の1なので一意に定まる。dataは1を含まない)。

おわかりのように、hrpの解読は容易なのだが、dataの解読で手間取っている。ここではdata partの解読について整理する。

Data Partの解読 

The data part of a Lightning invoice consists of multiple sections:

timestamp: seconds-since-1970 (35 bits, big-endian)
zero or more tagged parts
signature: bitcoin-style signature of above (520 bits)

とあるように、、data partは3つの部分からなるが、timestampのデコードは比較的すぐに実装できた。

まず、Bech32のgemを用いてinvoiceをhrpとdataに分ける。

hrp, data = Bech32.decode(invoice)

これによって、hrpはそのまま lnbc2500uと、dataは0から31の数字の配列に変換される(参照)。

timestamp のデコード

timestampの長さは35 bitsと決まっているので、1番目から数えて7番目まで(32=>5bit)を10進数に変換してあげればよい。

timestamp = from_base32_to_base10(data[0..6])

tagged filedsのデコード

tagged fieldsは、

1. type
2. data_length
3. data

からなっている。デコードするときは、最初から順に読んでいき、type(1文字)、data_length(10 bits 2文字)から残りの本データの長さがわかり、その後また次のtagged fieldのtypeが始まる、と言った次第。

ここの実装で手間取っており、Pythonだとbitstringなる便利なライブラリを使えば容易に実装できるのだが、、どう実装しようか。

signature

signatureの長さは520 bitsと決まっているので、data配列で言うと520 / 5 = 104

signature = data[-1..-104]

これをhexにしたりする。Bitcoinのsignatureについても勉強不足。

最後に

BOLT#11には、MUST, MAY, SHOULDなどの動詞を使って、Reader(decoder)とWriter(encoder)がどのようなことをするべきがが書かれているのでそこも参照するべき(MUST)。

また、実装したところまでのコードはいまのところWIPブランチにあるので、「コードが汚いからここをこう直すといい」とか「Rubyらしくない」、「このように実装したほうがシンプル」などの改善案をissueでくれると喜びます。 現在、WIPブランチはmasterにマージ済みです。途中ですが。。。 では。