ブロックチェーンの課題と可能性~BBc-1(Beyond Blockchain One)から学ぶブロックチェーン開発

第11回BBc-1のアプリケーションプログラミング

前回は、githubに公開されているbbc1のfile_proof.pyというサンプルアプリケーションの使い方を紹介しました。file_proof.pyは、ファイルをcoreノードに書き込み、改ざんがないかを検証し、ファイルの所有権を移転できるツールで、BBc-1アプリケーションの主要な要素を含んでいます。そのため、アプリケーションの開発においても参考になります。今回から数回の記事は、file_proof.pyのソースコードを中心に、BBc-1のアプリケーションプログラミングについて解説します。

なお、BBc-1のアプリケーションプログラミングについては、github内にもドキュメントがありますのでご参考ください。

file_proof.pyのプログラムは、下記URLから確認いただけますが、bbc1リポジトリをgit cloneしていただけば、examples/file_proof/ディレクトリの中に格納されいます。

アプリケーションプログラムの処理の流れの全体像

BBc-1アプリケーションの処理は、主に次のような3つの流れがあります。

図1 BBc-1アプリケーション処理の主な流れ
図1 BBc-1アプリケーション処理の主な流れ

ドメイン作成は、coreノードごとに1度だけ実行します。また鍵生成も1度実行すれば、署名に使う鍵を変更しない限り再実行の必要はありません。一方、トランザクション登録は、アプリケーションの中で何度も実行する処理です。

トランザクションを作成するためには、過去のトランザクション情報が必要になることがほとんどであるため、トランザクション検索は登録の処理の流れの中に含んでいます。なお、このトランザクション登録という処理は、他のブロックチェーンプラットフォームにおいて「スマートコントラクト」と呼ばれる部分になります。

それでは、図1に沿って順番にみていきましょう。

まず、pipコマンドを用いてbbc1をインストールし、新しいターミナルを立ち上げて、coreノードを起動しておきましょう。第10回の環境整備とBBc-1 Coreのインストールや起動などをご参照ください。

BBc-1の機能を利用するには、アプリケーションプログラムの冒頭で必要なモジュールをimportしておく必要があります。file_proof.pyの中では以下のように宣言しています。ご自身でアプリケーションを開発される際にも、プログラムの冒頭に同様の宣言を記載するようにしてください。

file_proof.py 31~35行目
from bbc1.core import bbc_app
from bbc1.core.bbc_config import DEFAULT_CORE_PORT
from bbc1.core import bbclib
from bbc1.core.message_key_types import KeyType
from bbc1.core.bbc_error import *

ドメインの作成

ドメインとはデータを共有する範囲を表します。ドメインの作成は、アプリケーションの管理者(サービス提供者)が行うべき作業です。coreノード上にドメインを作成することにより、coreノードはドメインごとのデータベースを作成したりストレージを確保したりします。また接続関係にあるcoreノードに同じドメインが設定されていれば、他のcoreノードに自分の存在を知らせます。つまり、coreノードごとにドメイン作成を実施する必要があります。

ドメインを作成するコードは、file_proof.pyの129行目のdomain_setup()という関数で行われます。中を見てみましょう。

file_proof.py 129~137行目
def domain_setup():
    tmpclient = bbc_app.BBcAppClient(port=DEFAULT_CORE_PORT, multiq=False, loglevel="all")
    if os.path.exists("node_key.pem"):
        tmpclient.set_node_key("node_key.pem")
    tmpclient.domain_setup(domain_id)
    tmpclient.callback.synchronize()
    tmpclient.unregister_from_core()
    print("Domain %s is created." % (binascii.b2a_hex(domain_id[:4]).decode()))
    print("Setup is done.")

この関数では、まずcoreノードに接続します。同じホストでbbc_core.pyが動作していれば、bbc_app.BBcAppClient()という関数を呼ぶことで接続できます。引数のmultiqは、bbc_coreに対して複数の機能を同時に要求する(非同期処理)場合にTrueに指定しますが、シンプルなアプリケーションではFalseで構いません。戻り値がクライアントオブジェクトになっており(tmpclient⁠⁠、このオブジェクトがbbc_coreに対するさまざまな機能を提供します。

tmpclient.domain_setup(domain_id)というところで、接続しているcoreノード上にdomain_idで指定された識別子のドメインを作成します。なお、domain_idはPythonのbytes型の32バイトの適当な値です(file_proof.pyの42行目で設定しています⁠⁠。なお32バイトは256ビットで、BBc-1では識別子の長さを256ビットとしています第4回の「BBc-1の識別子たち」の節を参照⁠⁠。 tmpclient.callback.synchronize()は、戻り値を待ちます。いわゆる同期処理のための関数です。 また、tmpclient.unregister_from_core()でcoreノードから切断します。

鍵作成

BBc-1のトランザクションには必ず署名(つまり電子署名)が必要です。署名を作るためには公開鍵ペアが必要で、ユーザ1人ひとりが異なる鍵ペアを作り、管理しておく必要があります。署名を付与する本人だけが秘密鍵を持っていて、トランザクションには対応する公開鍵と署名本体を付加します。実際には、第4回の「トランザクションの構造」の節で解説したBBcSignatureオブジェクトにこれらの情報が格納されます。

bbc1モジュールでは、KeyPairというクラスを使うことで、鍵ペアを簡単に取り扱うことができます。file_proof.pyでは432行目のcreate_keypair()という関数で鍵を生成し、保存しています。

file_proof.py 432~439行目
def create_keypair():
    keypair = bbclib.KeyPair()
    keypair.generate()
    with open(PRIVATE_KEY, "wb") as fout:
        fout.write(keypair.private_key)
    with open(PUBLIC_KEY, "wb") as fout:
        fout.write(keypair.public_key)
    print("created private_key and public_key : %s, %s" % (PRIVATE_KEY, PUBLIC_KEY))

keypair = bbclib.KeyPair()で鍵ペアオブジェクトを作成し、keypair.generate()で実際に秘密鍵と公開鍵のペアを生成します。生成された鍵は、この鍵ペアオブジェクト(keypair)が保持しています。鍵本体のバイナリデータはkeypair.private_keyやkeypair.public_keyで取得できます。また、file_proof.pyでは使っていませんが、KeyPairクラスはPEM形式やDER形式で鍵を読み書きすることも可能です。公開鍵についてはX509形式の公開鍵証明書を読み込むことも可能です。

トランザクション登録

いよいよトランザクションの作成と登録です。トランザクションに含めるアセットの内容はアプリケーションごとさまざまですが、トランザクションのデータ構造の作り方はどんなアプリケーションでも共通です。今回の記事ではその共通部分について説明します。

file_proof.pyの中ではいくつかの関数でトランザクションを作成していますが、基本は292行目のstore_proc()関数です。

file_proof.py 292~327行目
def store_proc(file, txid=None):
    with open(file, "rb") as fin:
        data = fin.read()
    bbc_app_client = setup_bbc_client()

    store_transaction = bbclib.make_transaction(relation_num=1, witness=True)
    user_info = "Owner is %s" % user_name
    bbclib.add_relation_asset(store_transaction, relation_idx=0, asset_group_id=asset_group_id,
                              user_id=user_id, asset_body=user_info, asset_file=data)
    store_transaction.witness.add_witness(user_id)

    if txid:
        bbc_app_client.search_transaction(txid)
        response_data = bbc_app_client.callback.synchronize()
        if response_data[KeyType.status] < ESUCCESS:
            print("ERROR: ", response_data[KeyType.reason].decode())
            sys.exit(0)
        prev_tx, fmt_type = bbclib.deserialize(response_data[KeyType.transaction_data])
        bbclib.add_relation_pointer(transaction=store_transaction, relation_idx=0,
                                    ref_transaction_id=prev_tx.transaction_id)
    sig = store_transaction.sign(private_key=key_pair.private_key,
                                 public_key=key_pair.public_key)
    store_transaction.get_sig_index(user_id)
    store_transaction.add_signature(user_id=user_id, signature=sig)
    store_transaction.digest()
    print(store_transaction)

    ret = bbc_app_client.insert_transaction(store_transaction)
    assert ret
    response_data = bbc_app_client.callback.synchronize()
    if response_data[KeyType.status] < ESUCCESS:
        print("ERROR: ", response_data[KeyType.reason].decode())
        sys.exit(0)

    store_id_mappings(os.path.basename(file),
asset_group_id, transaction_id=response_data[KeyType.transaction_id],
                          asset_ids=store_transaction.relations[0].asset.asset_id)

store_proc()関数は、ファイルと過去のトランザクションIDを引数に取ります。ファイルは登録したいファイルで、トランザクションIDはファイルに変更を加えるときに変更前の情報をもつトランザクションIDです。ファイルを読んでdata変数(294行目)に情報を格納します。つぎにbbc_app_client = setup_bbc_client()で(295行目⁠⁠、coreノードに接続します。setup_bbc_client()という関数は140行目で定義されています。

トランザクションの作成

処理の流れの詳細を説明していきます。 次の部分がトランザクションを作成する部分です。

file_proof.py 297~301行目
store_transaction = bbclib.make_transaction(relation_num=1, witness=True)
user_info = "Owner is %s" % user_name
bbclib.add_relation_asset(store_transaction, relation_idx=0, asset_group_id=asset_group_id,
                          user_id=user_id, asset_body=user_info, asset_file=data)
store_transaction.witness.add_witness(user_id)

make_transaction()というのはユーティリティ関数で、relation_numで指定した数だけBBcRelationオブジェクトを含み、BBcWitnessオブジェクトも含むようなトランザクションオブジェクトを作成します。さらに、add_relation_asset()関数は、第1、第2引数でトランザクションオブジェクトとその中のrelationsリストの何番目のBBcRelationオブジェクトなのかを指定して、実際にその中身を設定します。上記コードではrelation_idx=0の部分です。add_relation_asset()関数はさらに、BBcRelationオブジェクトの中にBBcAssetオブジェクトを含めます。user_id、asset_body、asset_fileという引数は、BBcAssetオブジェクト内に設定されます。 そして、witness.add_witness()関数では、BBcWitnessオブジェクトに引数で指定したuser_idの署名をトランザクションに付加することを宣言します。第5回から第7回で解説したトランザクションのデータ構造はこのコードで作成されています。

トランザクションの検索

file_proof.py 303~311行目
if txid:
    bbc_app_client.search_transaction(txid)
    response_data = bbc_app_client.callback.synchronize()
    if response_data[KeyType.status] < ESUCCESS:
        print("ERROR: ", response_data[KeyType.reason].decode())
        sys.exit(0)
    prev_tx, fmt_type = bbclib.deserialize(response_data[KeyType.transaction_data])
    bbclib.add_relation_pointer(transaction=store_transaction, relation_idx=0,
                                ref_transaction_id=prev_tx.transaction_id)

if txid:の条件分岐で、過去のトランザクションIDが指定されているときは、bbc_app_client.search_transaction(txid)によってcoreノードから過去のトランザクションを取得します。bbc_app_client.callback.synchronize()でcoreノードからの応答を待ち受けています。 この段階ではまだオブジェクトではなくデータの塊しか取得できていないため、bbclib.deserialize()関数を呼んでBBcTransactionオブジェクトに変換します。そして、得られた過去トランザクションを、bbclib.add_relation_pointer()関数を使って、新しいトランザクションのBBcRelationオブジェクト内にBBcPointerオブジェクトを追加します。このBBcPointerオブジェクトは、関連する過去のトランザクションIDやアセットIDを参照するためのものです。

ここまでで、トランザクションのうち署名以外の部分が完成します。

トランザクションへの署名付与

file_proof.py 312~313行目
sig = store_transaction.sign(private_key=key_pair.private_key,
                             public_key=key_pair.public_key)

あとは、sig = store_transaction.sign()を用いて署名オブジェクトsigを作成します。そのとき必ず自分の秘密鍵を指定するようにしてください。

file_proof.py 315行目
store_transaction.add_signature(user_id=user_id, signature=sig)

先のwitness.add_witness(user_id)という行(301行目)で、user_idというユーザの署名を付与する宣言をしていましたので、作成したsigオブジェクトをそのuser_idというユーザの署名としてトランザクションに付与します。なお、この例では、署名は1つしか付与していませんが、複数付与することも可能ですし、他のユーザの署名を付けても構いません。

file_proof.py 316~317行目
store_transaction.digest()
print(store_transaction)

の部分は、このトランザクションのトランザクションIDを計算したり、トランザクションの内容をテキストで表示したりするためのものです(デバッグ用出力です⁠⁠。

トランザクションの登録と後処理

file_proof.py 319~327行目
ret = bbc_app_client.insert_transaction(store_transaction)
assert ret
response_data = bbc_app_client.callback.synchronize()
if response_data[KeyType.status] < ESUCCESS:
    print("ERROR: ", response_data[KeyType.reason].decode())
    sys.exit(0)

store_id_mappings(os.path.basename(file),
asset_group_id, transaction_id=response_data[KeyType.transaction_id],
                      asset_ids=store_transaction.relations[0].asset.asset_id)

完成したトランザクションは、ret = bbc_app_client.insert_transaction(store_transaction)によってcoreノードに登録されます。 if response_data[KeyType.status] < ESUCCESS:という条件分でエラー処理を行っています。

最後のstore_id_mappings()は、このfile_proof.py独自のやり方で、ファイル名やパスと、トランザクションIDをファイルにjson形式で書き出すユーティリティです。過去のトランザクションを取得するためには、トランザクションIDまたはアセットIDを指定する必要があるため、ファイル名とそれを登録したトランザクションのトランザクションIDのマッピングを記録しています。目的とする情報を含むトランザクションをどのように特定するかは、さまざまなアプリケーションに共通の課題です。file_proof.pyはサンプルアプリケーションであることから、store_id_mappings()で記録したマッピング情報を、get_id_from_mappings()関数で読み出すという簡易的な実装になっています。

まとめ

今回は、file_proof.pyに沿ってBBc-1プログラミングの概要を説明しました。トランザクションの検証方法、およびBBc-1において最も重要な「相手との合意」を実現する方法や「どのような内容のトランザクションを作成するか」については次回の記事で説明する予定です。

おすすめ記事

記事・ニュース一覧