前回 に引き続き、file_proof.pyに沿ってBBc-1プログラミングの概要を説明します。今回はBBc-1の中で最も重要なポイントの1つである「合意」について、具体的な方法を説明します。なお、file_proof.pyのプログラムは、下記URLから確認いただけますが、bbc1リポジトリをgit cloneしていただけば、examples/file_proof/ディレクトリの中に格納されています。
また、BBc-1のアプリケーションプログラミングに関するドキュメント群につきましては、第11回冒頭リード文 にまとめていますので、ご参考ください。
トランザクション承認依頼(合意)
これまでの記事で何度も述べてきましたが、BBc-1における合意とは、当事者全員がトランザクションの情報が正しいと認めているということです。他のブロックチェーン技術でマルチシグと呼ばれているものと概念としては同じです。BBc-1ではこの意味での合意がすべて、と呼んでも過言ではないほど重要なポイントであり、この合意はアプリケーションで実現されます。ここで紹介する方法は、coreノードが提供するメッセージング機能を利用します。このメッセージング機能によりトランザクションを相手に送付したり、BBcSignatureオブジェクトデータを返答したりできます。
file_proof.pyでは、sendモードとwaitモードで起動したときに、ファイルの所有権をsend側からwait側のユーザに移転します。このとき、双方のユーザがファイル授受について合意し、それをトランザクションとして登録します。つまり、send側とwait側のユーザによる2つの署名が1つのトランザクションに格納されます。sendモードとwaitモードの実行例については、連載第10回の「ファイル所有権の移転 」の解説をご覧ください。
file_proof.py166~190行目(send_signreq関数内)
def send_signreq(receiver_name, receiver_user_id, ref_txids=None, file_data=None, bbc_app_client=None):
transaction = bbclib.make_transaction(relation_num=1, witness=True)
user_info_msg = "Ownership is transfered from %s to %s" % (user_name, receiver_name)
bbclib.add_relation_asset(transaction, relation_idx=0, asset_group_id=asset_group_id,
user_id=receiver_user_id, asset_body=user_info_msg, asset_file=file_data)
transaction.witness.add_witness(user_id)
transaction.witness.add_witness(receiver_user_id)
(中略)
asset_id = transaction.relations[0].asset.asset_id
asset_files = {asset_id: file_data}
ret = bbc_app_client.gather_signatures(transaction, destinations=[receiver_user_id],
asset_files=asset_files)
if not ret:
print("Failed to send sign request")
sys.exit(0)
return transaction
このコードは、send側で実行されます。transactionというのは、make_transaction関数で作成したBBcTransactionオブジェクトです。ここで重要なポイントは、transaction.witness.add_witness(receiver_user_id)という部分です。これは、あらかじめ署名してもらうユーザのユーザ識別子(ここではreceiver_user_id)を指定して、トランザクション内のBBcWitnessオブジェクトに「このユーザが署名をします」ということを宣言しています。これによって、無関係の人が勝手に署名することを防げます。後はこれまでに説明したとおりにトランザクションを作ります。
そして、gather_signatures関数を使ってそのトランザクションを他のユーザに送ります。送り先はdestinationsという引数で指定します。リストで複数のユーザを指定することも可能です。coreノードは、指定されたユーザ識別子を持つクライアントを探してメッセージを送ります。もちろん、ドメインが複数のcoreノードで構成されていても大丈夫です。
この処理の後、wait側からの返答を待ちますが、その前にwait側の処理を説明します。
file_proof.py 442~451行目
def enter_file_wait_mode():
bbc_app_client = setup_bbc_client()
recvdat = wait_for_transaction_msg(bbc_app_client=bbc_app_client)
transaction, source_id = pick_valid_transaction_info(received_data=recvdat,
bbc_app_client=bbc_app_client)
prompt_user_to_accept_the_file(bbc_app_client=bbc_app_client, source_id=source_id,
transaction_id=transaction.transaction_id)
signature = transaction.sign(keypair=key_pair)
bbc_app_client.sendback_signature(source_id, transaction.transaction_id, -1, signature)
このコードはwait側で実行されます。処理を関数にまとめているためシンプルに見えますが、実際の細かい処理は、それぞれの関数の中を参照ください。ここで注目していただきたいのは、最後の2行です。これは、メッセージとして受け取ったトランザクションの中身を確認した後の処理です。トランザクションに書かれた内容に合意する場合に、transaction.sign関数で署名を作成して、sendback_signature関数で署名(signature)を応答メッセージとしてsend側に返します。合意できない場合は、sendback_denial_of_sign関数(240,254,273行目)で拒絶メッセージを返します(prompt_user_to_accept_the_file関数の中で呼ばれます) 。
なお、sendback_signature関数では、署名オブジェクト(BBcSignature)だけが返答されます。トランザクション本体は相手が持っているので送る必要がないためです。
合意したトランザクションの完成
最後に、登録するトランザクションを完成させる処理です。
file_proof.py 193~206行目(wait_for_signs関数内)
def wait_for_signs(transaction, bbc_app_client):
response_data = bbc_app_client.callback.synchronize()
if response_data[KeyType.status] < ESUCCESS:
print("Rejected because ", response_data[KeyType.reason].decode(), "")
sys.exit(0)
result = response_data[KeyType.result]
transaction.witness.add_signature(user_id=result[1], signature=result[2])
sig_mine = transaction.sign(private_key=key_pair.private_key,
public_key=key_pair.public_key)
transaction.witness.add_signature(user_id=user_id, signature=sig_mine)
transaction.digest()
return transaction
このコードは、send側で実行されます。署名応答メッセージを待ち受けて、トランザクションにそれを組み込みます。前半部分が署名応答メッセージを待ち受ける処理です。file_proof.pyは簡易アプリケーションであるため、必ず署名応答メッセージが返ってくることが前提になっており、bbc_app_client.callback.synchronize関数で無条件に署名応答メッセージだと判断しています。実際のアプリケーションでは、非同期型のメッセージ処理を書く必要がありますので、詳しくはこちら のドキュメントをご参考ください。
result = response_data[KeyType.result]で署名応答メッセージの中身を取得します。これは2つの要素を含む配列で、1つ目がユーザ識別子、2つ目が署名データです。transaction.witness.add_signature(user_id=result[1], signature=result[2])でトランザクションに組み込みます。先に説明したように、事前にadd_witness(receiver_user_id)でこのユーザの署名が入ることを宣言しているのでうまく組み込めます。
それ以降の処理は、自分自身の署名もトランザクションに組み込んでトランザクションを完成させます。以下に示すinsert_signed_transaction_to_bbc_core関数の中のinsert_transaction関数でcoreノードにトランザクションを登録し、所有権移転が完了します。さらに、file_proof.pyでは所有権移転完了後に、その旨を伝えるメッセージをsend側からwait側に送っています(BBc-1としては必須の処理ではありません) 。
file_proof.py 193~206行目(insert_signed_transaction_to_bbc_core 関数内)
def insert_signed_transaction_to_bbc_core(transaction=None, bbc_app_client=None,
file_name=None):
print("Insert the transaction into BBc-1")
ret = bbc_app_client.insert_transaction(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)
ここで紹介したメッセージング機能(gather_signaturesやsendback_signature)を必ず利用しなければならないわけではありません。今後の発展によって、機能が拡充されたり、まったく別のメッセージングの仕組みが導入されたりする可能性もありますし、システム独自の方法を実装しても構いません。繰り返しになりますが、合意の証として当事者全員の署名オブジェクト(BBcSignature)がトランザクションに格納されればよいのです。
まとめ
今回は、合意の仕方について、file_proof.pyに沿ってBBc-1プログラミングの概要を説明しました。coreノードが提供するメッセージング機能を利用して、未署名のトランザクションや署名本体をやり取りすることで、当事者が合意したトランザクションを完成させます。
次回は、トランザクションに格納するアセットについて説明する予定です。