Python
ブロックチェーン
bbc1

BBc−1 version1.0っていうのをリリースしました(5)

前回は、ちょっと寄り道してBBc-1のgithubリポジトリの内容について書きました。今回は、BBc-1アプリケーションを開発する上で最も重要なトランザクション生成方法について書きます。

前回同様、下記の要領でgit cloneしておいてください。

git clone https://github.com/beyond-blockchain/bbc1

BBcTransactionクラス

BBc-1のトランザクションのデータ構造およびデータの操作の定義は、全てbbc1/core/bbclib.pyに入っています。

ここでも書きましたが、トランザクションは以下のような構造になっています。

transaction_structure.png

この全体構造は、BBcTransactionクラスとして定義されています。

class BBcTransaction:
    """Transaction object"""
    def __init__(self, version=0, deserialize=None, jsonload=None, format_type=BBcFormat.FORMAT_BINARY):
        self.format_type = format_type
        self.version = version
        self.timestamp = int(time.time())
        self.events = []
        self.references = []
        self.relations = []
        self.witness = None
        self.cross_ref = None
        self.signatures = []
        self.userid_sigidx_mapping = dict()
        self.transaction_id = None
        self.transaction_base_digest = None
        self.transaction_data = None
        self.asset_group_ids = dict()
        if deserialize is not None:
            self.deserialize(deserialize)
        if jsonload is not None:
            self.jsonload(jsonload)

今回の説明には出てこないものもありますが、self.events=[]のようになっていることからわかるように、図で「複数の〜の集合」とあるところは、要はオブジェクトリストになっているということです。(setではなくlistです。紛らわしくてすみません)

このBBcTrasactionクラスを含め、これから出てくるすべてのクラスには、addメソッドが実装されています。これはオブジェクトに情報を追加するためのメソッドです。BBcTransaction.add()はBBcEvent, BBcRelation, BBcRelation, BBcWitness, BBcCrossRef, BBcSignatureオブジェクトをトランザクションの中に追加します。

BBcTransactionには、serialize, deserializeメソッドがあり、これらはオブジェクトをバイナリ化またはその逆にバイナリからオブジェクト化します。なおformat_typeを変更すれば、bson形式のバイナリにすることもできます。他のユーザにトランザクションを送ったり、coreプロセスにinsertするときは必ずserializeする必要があります。

また、このクラスにはdigestメソッドがあり、これを呼ぶことでtransaction_idを計算し、self.transaction_idに格納します。

各クラスには、__str__メソッドが定義されており、文字列としてオブジェクトを評価すると、中身の情報が文字列として得られます。

今回はBBcTransactionの他に、BBcRelation, BBcWitness, BBcSignatureおよびそれらに関連するクラスについて説明します。

BBcRelationクラス

ここに例を上げていた、BBcRelationというクラスは、アセット(デジタル資産本体)と他のトランザクションへの参照を持ち、以下のように定義されています。

class BBcRelation:
    """Relation part in a transaction"""
    def __init__(self, asset_group_id=None, format_type=BBcFormat.FORMAT_BINARY):
        self.format_type = format_type
        self.asset_group_id = asset_group_id
        self.pointers = list()
        self.asset = None

asset_group_idはこのオブジェクトが取り扱うアセット種別を示します。

その後に、pointersというリストがありますが、ここにはBBcPointerオブジェクトというものを入れます。BBcPointerオブジェクトは他のトランザクションへの参照情報を保持し、それをリストとして複数入れられるということです。

またassetには、BBcAssetオブジェクトを入れます。BBcAssetオブジェクトはデジタル資産情報が格納されており、一つのBBcRelationに一つのBBcAssetしか登録できません。もし複数のBBcAssetを扱いたければ複数のBBcRelationオブジェクトを作る必要があります。

BBcPointerクラス

BBcPointerクラスは以下のように定義されます。

class BBcPointer:
    """Pointer part in a transaction"""
    def __init__(self, transaction_id=None, asset_id=None, format_type=BBcFormat.FORMAT_BINARY):
        self.format_type = format_type
        self.transaction_id = transaction_id
        self.asset_id = asset_id

これは比較的単純で、参照すべきtransaction_idとasset_idの情報を持ちます。ここで指定されたトランザクションやアセットと関連があるということを表現します。なお、asset_idはNoneでも構いません(トランザクション全体との関連性を表したいなら)。

BBcAssetクラス

BBcAssetクラスは、アセット、つまりデジタル資産そのものの情報を保持する最も重要なクラスで、以下のように定義されます。

class BBcAsset:
    """Asset part in a transaction"""
    def __init__(self, user_id=None, asset_file=None, asset_body=None, format_type=BBcFormat.FORMAT_BINARY):
        self.format_type = format_type
        self.asset_id = None
        self.user_id = user_id
        self.nonce = get_random_value()
        self.asset_file_size = 0
        self.asset_file = None
        self.asset_file_digest = None
        self.asset_body_size = 0
        self.asset_body = None
        if user_id is not None:
            self.add(user_id, asset_file, asset_body)

asset_idはこのBBcAssetオブジェクトをserializeしたときのSHA256ダイジェスト(ただしasset_id自身を除く)がセットされ、このアセットの識別子になります。

user_idはこのアセットの所有者を表します。

nonceには乱数が設定されます。同じ内容でも別でインスタンス化されたものは別のアセットとして扱うためです。

asset_file_sizeとasset_file_digestは、アセットの本体を外部ファイルとして別管理したいときに指定します。それぞれ、ファイルサイズとそのファイルのSHA256ダイジェストです。外部ファイル名はasset_idの値のHEX文字列にすべきです。なお、coreプロセスをデフォルト設定で起動した場合、外部ファイルといいつつもcoreプロセスが管理するワーキングディレクトリ内に自動的に保存されます。self.asset_fileは内部的に利用するもので、ファイルそのものを読み込んだときにここに格納されます。asset_idを計算するときにも必要になります。(2018/6/15 この段落の説明に一部不備があったので修正しました)

asset_bodyは、このBBcAssetオブジェクト自体で保持するアセット本体の情報で、asset_body_sizeはそのバイト数です。asset_bodyが取りうる型は、文字列、数値、バイト列です。なお、pythonのディクショナリ型データをasset_bodyにすることも可能ですが、その場合はformat_typeをbsonにしなければなりません。

BBcWitnessクラス

BBcWitnessクラスは、ユーザが署名をトランザクションに付与したことを示すためのもので、以下のように定義されます。

class BBcWitness:
    """Witness part in a transaction"""
    def __init__(self, format_type=BBcFormat.FORMAT_BINARY):
        self.format_type = format_type
        self.transaction = None
        self.user_ids = list()
        self.sig_indices = list()

transactionはこのオブジェクトを内包しているBBcTransactionオブジェクトで、内部処理で使うものです。user_idsは、署名を付与したユーザのuser_idのリストです。そして、sig_indicesは、BBcTransactionオブジェクトのsignaturesリストの何番目の署名なのかを指定します。つまり下図のような関係になります。

Witness.png

userAがindex=0で、userBがindex=1という意味です。user_idsの並びとsig_indicesの並びは同じ順番になっています。そして、BBcTransactionオブジェクトのsignaturesリストに格納された最初の署名(配列要素番号0)がuserAのもの、次がuserBのものということを示しています。

なぜこのようなことをしているかというと、署名を付けたという事実も署名の対象にしたいからです。もともと署名というのは署名したい範囲のデータのダイジェストに対して秘密鍵を作用させて計算した値です。つまり、署名される範囲のデータと署名自身は完全に分離されています。なので、あとから署名をくっつけたり取り外したりできてしまいます。署名自身を署名したいところではありますが、それは不可能なので、上述のsig_indicesのように、どの署名がどのユーザのものかということを配列要素番号だけで表現します。こうすることによって、あとから署名部分だけくっつけられたり削除されたとしても、BBcWitnessの内容と合わなくなってしまうので、改ざんを検出することができます。今回は説明しませんが、同様の仕組みがBBcReferenceにも入っています。

ただ、いちいち配列要素番号を気にするのは面倒なので、BBcWitnessには、add_witnessメソッドが用意されています。add_witness(user_id)とすることで、user_idsリストへの追加と、sig_indicesリストへの配列要素番号の追加、さらにはBBcTransactionオブジェクトのsignaturesリストの該当場所に署名オブジェクト(BBcSignature)の領域を確保します。

そして、BBcSignatureオブジェクトが入手できたら、add_signatureメソッドで実際にBBcTransactionオブジェクトのsignaturesリストの中に署名を格納できます。大雑把なコードですが、下記のようなイメージです。なお、txobjがBBcTransactionオブジェクトです。

txobj.witness.add_witness(userX)

..snip.. # BBcSignatureオブジェクトsig_objを入手する

txobj.witness.add_signature(user_id=userX, signature=sig_obj)

なお、add_signatureメソッドはBBcTranasctionにも実装されているため、txobj.witness.add_signatureの代わりにtxobj.add_signatureとすることも可能です。

BBcSignatureクラス

BBcSignatureクラスは、署名および検証用の公開鍵を含み、以下のように定義されています。

class BBcSignature:
    """Signature part in a transaction"""
    def __init__(self, key_type=KeyType.ECDSA_SECP256k1, deserialize=None, format_type=BBcFormat.FORMAT_BINARY):
        self.format_type = format_type
        self.key_type = key_type
        self.signature = None
        self.pubkey = None
        self.keypair = None
        self.not_initialized = True
        if deserialize is not None:
            self.not_initialized = False
            self.deserialize(deserialize)

key_typeは現在は一種類のみ(KeyType.ECDSA_SECP256k1)対応しています。

signatureはECDSAによる署名のバイナリデータ(生データ)です。

pubkeyは検証用の公開鍵(ECC)のバイナリデータです。X(32バイト)とY(32バイト)を直結して先頭にフラグ1バイトを付加した計65バイトのデータです。Xのみとしてフラグと併せて33バイトにすることも可能です。

実際に署名や検証するためには、KeyPairオブジェクトに格納された秘密鍵、公開鍵を使います。self.keypairはそのKeyPairオブジェクトで、pubkeyはそのKeyPairオブジェクトから取得した値です。

KeyPairクラス

KeyPairクラスは、署名の計算と検証を行うための鍵ペアを管理ます。実際KeyPair内の変数を気にする必要はなく、メソッドを理解しておけば十分です。

class KeyPair:
    """Key pair container"""
    def __init__(self, curvetype=KeyType.ECDSA_SECP256k1, privkey=None, pubkey=None):
    def generate(self):
    def mk_keyobj_from_private_key(self):
    def mk_keyobj_from_private_key_der(self, derdat):
    def mk_keyobj_from_private_key_pem(self, pemdat_string):
    def get_private_key_in_der(self):
    def get_private_key_in_pem(self):
    def sign(self, digest):
    def verify(self, digest, sig):

generate()は新しい鍵ペアを生成します。mk_keyobj_from_private_key()は秘密鍵の生データからKeyPairオブジェクトを生成します。mk_keyobj_from_private_key_der()とmk_keyobj_from_private_key_pem()はそれぞれDERおよびPEM形式のデータ(文字列)からKeyPairオブジェクトを生成します。

get_private_key_in_der()とget_private_key_in_pem()は、KeyPairオブジェクトに含まれている秘密鍵情報をDER形式およびPEM形式で出力します。

sign()は署名計算、verify()は署名検証を行うメソッドです。

ユーティリティメソッド

ここまでに説明したクラスをインスタンス化してBBcTransactionオブジェクトを作成すれば、トランザクションが出来上がります。だいたいいつもやるべき作業が決まっているので、bbclib.pyにはいくつかのユーティリティメソッドが用意されています。この記事で説明したものに関連するものは以下のとおりです。

def make_transaction(event_num=0, relation_num=0, witness=False, format_type=BBcFormat.FORMAT_BINARY):
def add_relation_asset(transaction, relation_idx, asset_group_id, user_id, asset_body=None, asset_file=None):
def add_relation_pointer(transaction, relation_idx, ref_transaction_id=None, ref_asset_id=None):
def recover_signature_object(data, format_type=BBcFormat.FORMAT_BINARY):

make_transactionメソッドは、BBcTransactionオブジェクトを作成し、さらにその中に指定個数のオブジェクトを作成してBBcTransactionオブジェクトの中に格納します。例えばrelation_num=2とすれあば、2つのBBcRelationオブジェクトを作成してBBcTransactionオブジェクト内に格納します。witness=TrueとすればBBcWitnessオブジェクトが作成されます。make_transactionメソッドの戻り値はトランザクションオブジェクトになります。

add_relation_assetメソッドは、BBcAssetオブジェクトを作成してBBcRelationオブジェクトの中に格納します。同じ様に、add_relation_pointerメソッドは、BBcPointerオブジェクトを作成してBBcRelationオブジェクトの中に格納します。

recover_signature_objectメソッドは、他のクライアントから書名データ(バイナリデータ)を受信したときに、それをBBcSignatureオブジェクトに変換します。

まとめ

トランザクションのデータ構造の定義と、トランザクションの作成の仕方を、ソースコードに沿って説明しました。実際の使い方は、bbc1/examples/file_proof/file_proof.pyや、examplesリポジトリを見てみてください。今後examplesは拡充していきたいと考えています。

次回は、トランザクションの登録などbbc_app.pyに関わるところについて書こうと思います。

以前の記事

BBc−1のアプリケーションプログラミング
BBc−1 version1.0っていうのをリリースしました(4)
BBc−1 version1.0っていうのをリリースしました(3)
BBc−1 version1.0っていうのをリリースしました(2)
BBc−1 version1.0っていうのをリリースしました(1)