[LightningNetwork] ペイメントチャネル

チャネルパートナーとは2つのノード間の金銭関係、2of2マルチシグの暗号プロトコルによって管理(自分と相手)
チャネルの残高がエンコードされ、その残高をどのように分配するかが定義される
トランザクションシーケンスのトランザクションはスプリクト言語を使う
チャネルを開いた時にコミットされるビットコインの量
チャネル経由で同時に送信できる未実行・実行中のルーティングペイメントの数も制限される
ビットコインブロックチェーンに記録されるのは最終残高のみ

### ファンディングトランザクション
1人がマルチシグアドレスにbitcoinを送金することでペイメントチャネルに資金を預ける
これをchannel capacityと呼ぶ

### チャネルの開き方
2つの公開鍵を使って2of2マルチシグアドレスを作成
払い戻しトランザクションはコミットメントトランザクションと呼ばれる

1. open_channelメッセージでBobに通知
2. accept_channelメッセージでAliceに送信
3. pubkey pubkey 2 CHECKMULTISIGでトランザクションを作成
4. funding_createdでBobに送信
5. コミットメントトランザクションを作成
6. Bobが funding_signedメッセージでAliceに返す
7. 署名交換後にAliceがブロードキャスト

### 古い状態を使った不正
古いコミットメントトランザクションが送信された場合は、罰を与えることができるように作られれている
失効のメカニズム:timelock delayとrevocation secretがペイメントにある
新しいコミットメントの際に、古いコミットメントトランザクションを失効させる
※失効シークレットの管理と格納はLNの最も複雑な部分の1つである

### チャネルを公表・クローズ
チャネルを公表するとルーティング手数料を獲得できる
チャネルを閉じるときはトランザクション手数料がかかる
クローズには相互クローズ、強制クローズ、プロトコル違反などある

LightningNetwork 概要

LNを使うには、ニーズに合ったLNウォレットを選ぶ必要がある
L Eclairウォレットを選択する

ライトニングネットワークノードはウォレットである
他のライトニングノードとP2P方式で通信しなければならない
LNはbitcoin nodeにアクセスし資金を保全する必要がある

ライトニングエクスプローラ
https://1ml.com/
その他にもACINQ, Amboss Space, Fiatjaf, hashXPなどLNエクスプローラがある

### ライトニングウォレット
keystore: secret keyを保持
Lightning Network Node
Bitcoin node
database map: ノードとチャネルのマップ
channel manager: LNチャネルを開閉できるコンポーネント
close-up system: チャネルのパスを調べるコンポーネント
※鍵の管理を外部に委託するウォレットをcustodial walletと呼ぶ。鍵をどのように管理するかでウォレットを選択する。またLN Walletが使うのはfull nodeか第三者のnodeかなどを見極める必要がある
Lightweight, Noneなどはcustodial walletになる。自己管理型かどうかを選択する

Neutrino: ビットコインノードを実行しないwallet, neutrinoプロトコルを使ってアクセス
Electrum: ビットコインノードを実行しないwallet

[bitcoin] ライトニングネットワーク概要

ライトニングネットワークはビットコインの支払いをオフチェーンで実行できるようにするP2Pネットワーク
ビットコインのブロックチェーンにコミットされない、オフチェーンペイメントと呼ぶ
ルーティングネットワークで、支払人から受取人までのパスに沿って各ペイメントチャネルをホップしていく。
LNと略される
ビットコインをベースとしたセカンドレイヤーテクノロジ
チャネルを書き換える1種類のトランザクションにスプライシングと呼ばれるものがあり、チャネルにコミットされている資金を追加または削除に使うことができる
LNの2つのノード間の金銭関係をペイメントチャネルと呼ぶ
ペイメントは送信者から受信者までのパスを辿る1つ以上のペイメントチャネル経由でルーティングされる
LNではビットコイントランザクションを使って資金を移動する

### fairness protocol
互いに信頼していない参加者が公正な結果を得られるようにするためにインセンティブ、ディスインセンティブを利用する
e.g. 2人で均等に分けたい場合は、参加者で分割者と選択者の役割に分ける, hash function, digital signature, encryption/decryption

– チャネルに資金を預けるユーザは、ファンディングトランザクションを公開する前に払い戻しトランザクションが署名されていることを確認する
– チャネルが新しい状態に遷移する度に、誰かが古い状態をブロードキャストしようとしたら残高を全て失って罰を受ける
– ペイメントを転送するユーザは、資金の転送をコミットした場合、1つ前のノードから返金または報酬を受け取れる
– ビットコインを利用するのは最初に読み込む時と、決済(settle)時にビットコインをライトニングから取り出す時のみ。トランザクションは公開されないのでプライベートになる
– LMはThe Onion Routerのオニオンルーティングを使う

bitcoin.confの書き方

mainnet=1
txindex=1
server=1
rest=1
rpcuser="username"
rpcpassword="password"
rpcport=8332

testnet=1
txindex=1
server=1
rest=1
rpcuser="username"
rpcpassword="password"
rpcport=8332

mainnet or testnetの利用
indexを作成して全てのトランザクションIDを参照可能とする
json rpcサーバとしてコマンドを受け付ける
rest インターフェイスを有効にする
json rpcのためのユーザ名、パスワード、rpcポート
※JSON-RPCを利用することで、一般的なHTTPライブラリを使用してbitcoin coreと対話できる

testnet=3
server=1
rpcbind=localhost
rpcport=18332
rpcuser=hoge
rpcpassword=piyo

testnet=3とは現在のバージョン
regtestに接続する場合は regtest=1

ニモニックコード

ニモニックコードも乱数に加えてチェックサムも入力される
BIP0039で定義されており、この乱数をエントロピーと呼ぶ

1. エントロピーを作成(乱数)
2. エントロピーに応じたチェックサムを生成
3. エントロピーとチェックサムを連結
4. 連結したデータを11ビットごとに分割
5. 11ビットで表現されている数字をインデックスとして、用意されている単語リストから単語を取得して空白区切りで連結

### ニモニックコードからシードを生成
シードの生成にはPBKDF2を利用
saltには定数”mnemoic”を使用し、2048回ストレッチングする
seed=hashlib.pbkdf2_hmac(‘sha512’, mnemonic, salt, 2048)
※パスフレーズを使用する際には、saltにパスフレーズを生成する

HD Wallet

privateKey * G = publicKey
(privateKey + i) * G = privateKey * G + i * G

i は推定不可能にするため、インデックスに加えて256ビットの巨大な乱数を1つ追加する
さらにハッシュ関数を適用する

乱数cとインデックスiを入力してハッシュ値をとったものをインデックス代わりに使用する
このインデックスと乱数を含んだハッシュ値をchain codeと呼ぶ

chain_code = h(i, c)

決定性ウォレットは最初の親秘密鍵と最初のチェーンコードだけをバックアップしておくだけで良い
最初の秘密鍵mはマスター秘密鍵、 M = mGはマスター公開鍵と呼ばれる
この二つの情報を生成するルールシードというデータを秘密情報の基点にしている。

マスターチェーンコード: mcc
インデックス: i=0, i=1,
インデックスiの子秘密鍵: priv0, …
インデックスiの子公開鍵: pub0, …
インデックスiのチェーンコードの左256ビット: cc0L, cc1L
インデックスiのチェーンコードの右256ビット: cc0R, cc1R

HMACとは暗号学的ハッシュ関数を使ったMAC
ビットコインではHMAC-SHA512のHMACが利用されている
マスターチェーンコードmccとマスター公開鍵M=mGからインデックスi=0の時のチェーンコードを計算し、その左256ビットを取り出す
このチェーンコードの左256ビットとマスター秘密鍵mの和がインデックスi=0の子秘密鍵になる。
iを0, 1, 2と変えることで、子秘密鍵・公開鍵を次々と作ることができる。

作成したチェーンコードの右側に256ビットのccRとインデックスからHMAC-SHA512からccL(左256ビット)と子秘密鍵で孫秘密鍵を作成できる
同様に子公開鍵から孫公開鍵を作ることもできる。

Ubuntuにbitcoin exploreをinstallしたい

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.3 LTS
Release: 22.04
Codename: jammy

$ g++ –version
g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ wget https://github.com/libbitcoin/libbitcoin-explorer/releases/download/v3.2.0/bx-linux-x64-qrcode
–2023-12-31 17:57:56– https://github.com/libbitcoin/libbitcoin-explorer/releases/download/v3.2.0/bx-linux-x64-qrcode
Resolving github.com (github.com)… 20.27.177.113
Connecting to github.com (github.com)|20.27.177.113|:443… connected.
HTTP request sent, awaiting response… 404 Not Found
2023-12-31 17:57:57 ERROR 404: Not Found.

bx seedで脆弱性があるのかDLできない…
v3.8.0のタグをDLする
https://github.com/libbitcoin/libbitcoin-explorer/releases
$ wget https://github.com/libbitcoin/libbitcoin-explorer/archive/refs/tags/v3.8.0.tar.gz
$ tar -xvf v3.8.0.tar.gz

$ sudo apt install g++-9
$ sudo ./install.sh –with-icu
//
/usr/bin/ld: cannot find libprotokit.a: No such file or directory
/usr/bin/ld: cannot find -llzma: No such file or directory
collect2: error: ld returned 1 exit status
make: *** [Makefile:873: src/libbitcoin-protocol.la] Error 1

real 7m11.463s
user 11m48.472s
sys 1m7.459s

うーむ
$ wget https://github.com/libbitcoin/libbitcoin-explorer/archive/refs/tags/v3.2.0.tar.gz
$ tar -xvf v3.2.0.tar.gz
$ g++ –version
g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
$ sudo apt-get install build-essential autoconf automake libtool pkg-config git
$ sudo apt-get install libboost-all-dev
$ cd libbitcoin-explorer-3.2.0
$ chmod +x install.sh
$ sudo apt install libsecp256k1-dev
$ sudo ./install.sh

圧縮公開鍵の作成

### パラメータファイルの作成
$ openssl ecparam -name secp256k1 -out secp256k1.pem
$ openssl ecparam -in secp256k1.pem -text -noout
EC-Parameters: (256 bit)
ASN1 OID: secp256k1

$ openssl ecparam -in secp256k1.pem -text -param_enc explicit -noout
EC-Parameters: (256 bit)
Field Type: prime-field
Prime:
00:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:fe:ff:
ff:fc:2f
A: 0
B: 7 (0x7)
Generator (uncompressed):
04:79:be:66:7e:f9:dc:bb:ac:55:a0:62:95:ce:87:
0b:07:02:9b:fc:db:2d:ce:28:d9:59:f2:81:5b:16:
f8:17:98:48:3a:da:77:26:a3:c4:65:5d:a4:fb:fc:
0e:11:08:a8:fd:17:b4:48:a6:85:54:19:9c:47:d0:
8f:fb:10:d4:b8
Order:
00:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:ff:
ff:fe:ba:ae:dc:e6:af:48:a0:3b:bf:d2:5e:8c:d0:
36:41:41
Cofactor: 1 (0x1)

### 秘密鍵
$ openssl ecparam -in secp256k1.pem -genkey -noout -out secp256k1-private.pem
$ cat secp256k1-private.pem
16進数表記で出力
$ openssl ec -in secp256k1-private.pem -outform DER | tail -c +8 | head -c 32 | xxd -p -c 32

### 公開鍵
$ openssl ec -in secp256k1-private.pem -pubout -out secp256k1-public.pem ; cat secp256k1-public.pem
16進数表記で出力
$ openssl ec -in secp256k1-private.pem -pubout -outform DER | tail -c 65 | xxd -p -c 65
$ pubKey=04bf03ed7464908e5ee11ca694d3699cef8f27b51d6bd4294576ac30f7a5ebf84346ec91d2fae812d2d8e9528838bc929d6f5eab143214864e8dc719b976c9bbd1

$ prefix=`echo $pubKey | cut -c1-2` ; echo “prefix = $prefix”
prefix = 04
$ x=`echo $pubKey | cut -c3-66` ; echo “x=$x”
x=bf03ed7464908e5ee11ca694d3699cef8f27b51d6bd4294576ac30f7a5ebf843
$ y=`echo $pubKey | cut -c67-130` ; echo “y = $y”
y = 46ec91d2fae812d2d8e9528838bc929d6f5eab143214864e8dc719b976c9bbd1

$ pubKeyHash=`echo $pubKey | hash160` ; echo $pubKeyHash
$ checksum=`echo 00${pubKeyHash} | sha256d | cut -c1-8` ; echo $checksum
$ echo 00${pubKeyHash}${checksum} | b58e

opensslのrmd160がうまくいかん…

Base58 decode

Base58decodeはBase58encodeの逆の処理を行う

import sys
import re

matrix=list('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz')

pattern=r"^1*"

for line in sys.stdin:
    target=line.strip('\n')
    match=re.match(pattern, target)
    ones=match.group()
    target=target.lstrip(ones)
    targetChars=list(target)
    q=0
    for c in targetChars:
        q=q*58+matrix.index(c)
    decodedHex=str(hex(q)).lstrip('0x')
    decodedHex=ones.replace('1','00') + decodedHex
    hexlen=len(decodedHex)
    if hexlen % 2 == 1:
        print('0'+decodedHex)
    else:
        print(decodedHex)

$ echo 18N | python3 base58decode.py
0001ab

Base58 encode

1. 対象データの先頭に0が続く場合、0以外になるまでそのバイトを除外(e.g. 最初の3バイトが全て0の場合は、先頭の3バイトを除外)
2. 1の結果を58で割る
3. 剰余をBase58に変換
4. 変換された剰余を連結し、1で除外した0の数だけ先頭に1を連結

import sys
import re

matrix = list('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz')

pattern=r"^(00)*"

for line in sys.stdin:
    target = line.rstrip('\n')
    match = re.match(pattern, target)
    zeros=match.group()
    target = target.lstrip(zeros)
    q = int(target, 16)
    r = 0
    b58str = ''
    while q > 0:
        q, r = divmod(q, 58)
        b58str = matrix[r] + b58str

    print(zeros.replace('00', '1') + b58str)

$ echo 0001ab | python3 base58encode.py
18N