送金APIから紐解くBundleの全容

過去の記事、「トランザクション大解剖!ウォレットは裏で何をやっているか。」で説明しきれなかったマニアックだが痒いところに手がとどく内容にした。というのも、今後IOTAの注目技術、MAMやマルチ署名を理解していく際に前提知識となるのがこのBundleの基礎だからである。

なお、記事内のソースコードはJavaを使用した。理由としてはJavaが筆者のお気に入りであることと、変数の型が見える方が理解しやすいからである。もちろん、IOTAでは様々な環境で開発することができるので気になる方は IOTA公式Githubをチラ見しておこう。

IOTAの理解に役立つリンク集

Tangleについての概要は序盤までで、それ以降は安全性、想定される攻撃とその耐性について高度な数学で説明している。後半は初心者向きではない。

僕はここから始まった。

ホワイトペーパーが理学部なら、これは工学部。

IOTAのコミュニティはSlackベースに育ってきた。日本語のチャンネル#japaneseもあるので気軽に参加・質問してみよう。
参加の方法は下記から。

IOTAコミュニティへの参加方法(Discord)

2017.09.05

トランザクションの構造

過去の記事も見ると分かりやすいかもしれない。下のソースはTransaction.javaから抜粋した。主に重要なものにコメントをふっておいた。

public class Transaction {

    private static final transient Logger log = LoggerFactory.getLogger(Transaction.class);

    private transient ICurl customCurl;

    private String hash;                //  トランザクションのハッシュ(アイデンティティ)
    private String signatureFragments;  //  署名等に使われる2187トライトの長さを持つフィールド
    private String address;             //  アドレスを指定する
    private long value;                 //  金額を指定する
    private String obsoleteTag;
    private long timestamp;             //  必須になった
    private long currentIndex;          //  このTxはBundle内で何番目?(0からスタート)
    private long lastIndex;             //  Bundleの最後のTxのindexは何?
    private String bundle;              //  このトランザクションはどのBundleに所属するかを指定
    private String trunkTransaction;    //  承認するTxその1
    private String branchTransaction;   //  承認するTxその2
    private String nonce;               //  PoWのとき探す
    private Boolean persistence;        //  承認されたらTrue。未承認だとfalse
    private long attachmentTimestamp;
    private String tag;                 //  任意の27トライトのタグ
    private long attachmentTimestampLowerBound;
    private long attachmentTimestampUpperBound;

送金には受取アドレス(宛先)送金元アドレス(送り主)という最低二つが必要である。しかし、ご覧の通り一つのTransactionオブジェクトにはアドレスが一つしかない。そこで、IOTAではBundleという概念を導入した。BundleにはTransactionオブジェクトを複数含むことで受取アドレスと送金元アドレス、また署名などの様々な必要事項を別々のTransactionオブジェクトで役割分担して管理する。

送金のAPI

送金をするAPIは見るだけなら簡単である。

Transferクラス

送金情報を保持する。

public class Transfer {

    private String timestamp;      // タイムスタンプ
    private String address;        // 受け取りアドレス(宛先)
    private String hash;
    private Boolean persistence;   // true:承認済み、false:未承認
    private long value;            // 送金額
    private String message;        // 任意のトライト
    private String tag;            // タグ(27トライト)

    // Transferオブジェクト生成
    // 宛先アドレス、送金額、任意のメッセージトライト、タグ
    public Transfer(String address, long value, String message, String tag) {
        this.address = address;
        this.value = value;
        this.message = message;
        this.tag = tag;
    }
...

sendTransfer関数

送金に使われるのはsendTransferという関数だ。引数にTransferオブジェクトのリストを取ることで複数の送金を一度に行う。

/**
 * Wrapper function that basically does prepareTransfers, as well as attachToTangle and finally, it broadcasts and stores the transactions locally.
 *
 * @param seed               Tryte-encoded seed
 * @param security           The security level of private key / seed.
 * @param depth              The depth.
 * @param minWeightMagnitude The minimum weight magnitude.
 * @param transfers          Array of transfer objects.
 * @param inputs             Optional: List of inputs used for funding the transfer.
 * @param remainderAddress   Optional: If defined, this remainderAddress will be used for sending the remainder value (of the inputs) to.
 * @param validateInputs     Whether or not to validate the balances of the provided inputs.
 * @param validateAddresses  Whether or not to validate if the destination address is already used and if a key reuse is detect.
 * @return Array of valid Transaction objects.
 * @throws ArgumentException is thrown when the specified input is not valid.
 */
public SendTransferResponse sendTransfer(
        String seed,
        int security, int depth, int minWeightMagnitude,
        final List<Transfer> transfers,
        List<Input> inputs,
        String remainderAddress,
        boolean validateInputs, boolean validateAddresses) throws ArgumentException {
....
}

送金

「AさんがBさんにメッセージを載せて100通貨送金する」という例をざっくり見てみると。

// メッセージは任意のサイズのトライト
String message = "MESSAGE..."

// タグは27トライト
String tag = "SAMPLE9TAG99999999999"

// アドレスは81トライト。Bさんのアドレス。
String address = "B9ADDRESS..."

// Transferオブジェクトの生成(Bさんのアドレスに100通貨をmessage付きで送金)
Transfer transfer = new Transfer(address,100,message,tag);
List<Transfer> list = new List<>();
list.add(transfer);

// 送金
sendTransfer("Aさんのseedを指定",security,depth,minWeightMagnitude,list,null,null,true,true);

公式ウォレットのソースコードの送金部分を単純化してみた。これで試してはいないのでなんとも言えないが送金は上記ようなの流れで行われる。

この記事はこのsendTransfer関数の中身がどうなっているかに注目していきたい。正しい送金の仕方は公式のソースを見るのが一番正しいのでリンクを貼っておく。

  • Python

iota.lib.py/examples/send_transfer.py

  • Android

android-wallet-app/app/src/main/java/org/iota/wallet/ui/fragment/NewTransferFragment.java

Bundleの構造

まず、送金のためにIOTAではBundleというものを生成する。BundleとはIOTAの最小単位であるTransactionをまとめたものである。送金額や署名等の1つの送金に必要な情報をまとめている。

Bundleには大きく分けて3つの部分がある。

  1. 出力部 – ‘output’(どこへいくら送金するかを指定)
  2. 入力部 – ‘input’(どこからその金額を持ってくるかを指定)
  3. 差額出力部 – ‘remainder’(お釣りをどこへ送金するかを指定)

出力部 – output

指定された宛先アドレスに送金することを出力という。その出力を請け負うのがBundleの前半である出力部という部分である。出力部には以下の役割がある。

  1. 送金額の指定
  2. 送金先の指定
  3. 任意のメッセージ(message)の保管

先ほどの「AさんがBさんにメッセージを載せて100通貨送金する」という例だと以下のようになる。

  • sigFというのは上記Transaction内のsignatureFragmentsを意味する。
  • 分かりやすさのためにaddressにはBさんが代入されているが、これはBさんのアドレスという風に見て欲しい。
  • Tx.0、Tx.1 の0や1は上記Transaction内のcurrentIndexを示す。

出力部 – output

図を見てわかる通り、送金額はTx.0のvalueに宛先はaddressに指定する。そして、message部分はサイズ2187トライトのチャンクごとに分けられ、前から順番にTx.0、Tx.1、…のsigFに保管される。もし、messageが2187トライトより短ければ出力部はTx.0のみ、つまり1つのTransactionで足りる。また、もしmessageが長ければその分Transactionが増えて出力部自体も長くなる。messageが2187の倍数ぴったりになることはほぼないので、大抵sigFの端数を’9’で埋める。

また、messageはそれぞれsigFにトライトで表されているものの、もちろんトライトから文字に変換することができるので、文章の中身はTangle上で筒抜けである。そこでこのsigFを暗号化することでTangle上にプライベート空間を提供するMAM(Masked Authenticated Message)という活用法も提案され研究・開発中である(次項)。

MAMとの関連性

MAMとはIOTAでも最も期待されている機能の一つであり、Tangle上にプライバシーを保ちつつデータを保存できる機能である。要するに、今のままではTangle上でむき出しになっている誰でも閲覧できるこのsigFの中身を暗号化する。手数料が無料のIOTAではゼロ円送金ができるため、そのトランザクションのsigFをプライベートなデータ領域として積極的に利用出来る。

解説記事を公開するのが今年最後の夢だ。

入力部 – input

Tangle上の残高のあるアドレスから送金額を集めてくることを入力と呼ぶ。出力部で指定した出力を実行するにはそれに十分な額をどこかで保有していないといけない。また、アドレスから支払われたことを署名することによって、二重支払いを防がないといけない。それらを請け負う入力部には以下の役割がある。

  1. 入力額の指定
  2. 入力アドレスの指定
  3. 入力アドレスの署名

AさんがBさんにメッセージを載せて100通貨送金する」という例で引き続き考えると、100通貨以上をどこから持ってくるかをここで指定する。ここで”以上”と言ったのは、余剰分は次の差額出力部で回収できるからだ。

入力部 – input

今回の例だと入力は2つ(入力1と入力2)ある。なぜ、2つかというとそれぞれのaddressの保有額を合計(Aさん①+Aさん②=-144)してやっと送金額の100を賄えるからだ。余った44は次項、差額出力部で扱う。もし、Aさんのとあるアドレスに例えば100以上の残高があれば、その一つを入力とすれば足りるため、入力部の長さは結果的に短くなる。

それよりも重要なのは、sigFである。1つの入力アドレスに対して署名が2つにわかれている。これはsendTransfer関数のsecurity引数によって変わる。security=1なら「その1」だけしか生成されない。つまり、今回の図だとAさん①、Aさん②のために合計2つの署名で済む。もし、security=3ならそれぞれの入力アドレスに「その3」まで署名が必要で合計2*3=6個のTransactionが入力部に生成される。公式化すると以下のようになる。

\({\rm Transaction数}={\rm security} \times {\rm input数}\)

また、署名の方法については過去の記事で言及したので参照していただけると幸いだ。

差額出力部 – remainder

出力額ぴったりの残高をもつ入力アドレスは大抵存在しないため、出力額以上の残高を複数の入力アドレスから集めることになる。そのためお釣りが発生する。IOTAでは一度署名されたアドレスを再度入力アドレスとして新しく署名するとPrivate Keyの漏洩に繋がる。言い換えると、一度署名されたアドレスで金額を受け取ってはいけない。もし、お釣りを元あった入力アドレスに戻すことはその原則に違反する。そこでお釣りは新しい別のアドレスに出力される。

差額出力部は以下の役割がある。

  1. 差額の指定
  2. 出力アドレスの指定

AさんがBさんにメッセージを載せて100通貨送金する」という例では、最終的に44通貨の余りが出た。下の図を見てみよう。

差額出力部 – remainder

とてもシンプルである。差額出力部は差額がある場合は1つだけのTXを生成する。お釣りの金額が新しいAさんのアドレス(Aさん③)に出力されるだけである。新しいアドレスとはなんぞやという方は、過去の記事を読み返してほしい。

ちなみにもしお釣りがゼロ(ぴったりの入力アドレスが見つかった)場合は差額出力部自体が生成されない。

複数アドレスに送金する場合

複数の別のアドレスに送金する場合は、出力部をアドレスの数だけ増やす

出力部 – output

入力部、差額出力部の扱い方は簡単だ。入力部では今回の例だと(100+1000+634=1734)以上分の入力アドレスを探し、差額はお釣りをまた1つのTxに入れるだけで特に特別なことはしない。

Bundleの全容

さてIOTAのTransactionはただ生成するだけでなく、trunkTransactionbranchTransactionと呼ぶ二つのTransactionを承認するという仕事がある。

承認するTipsの選択

承認するTipsと呼ばれるTransactionはgetTransactionToApproveという関数で簡単に取得することができる。あまりに簡単だが、その裏には高度な数学が潜んでおり、筆者のような素人の頭を悩ませる。とても手に負えないため、この説明は誰か数学のできる人にお願いしたい。

Bundleの承認関係

以前の記事でも触れたがもう一度あえて図を描き直した。

Bundle

getTransactionsToApprove関数が返すtrunkTransactionbranchTransactionをそれぞれ図内のMilestoneother Txに当てはめる。

https://iotasear.ch/などのTangle Explorerで適当なトランザクションとBundleを見てみよう。この記事で紹介した通りになっているはずだ。(違っていたら報告して下さい)

チートシート

印刷できるサイズ。随時更新して美しいものに仕上げていきたい。

チートシート

参考資料

IOTA Java ライブラリ JOTA
iotaledger/iota.lib.java

毎度のごとく、記事に対するフィードバックお待ちしています。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です