English here. Special thanks to Take san for translating it!
過去の記事、*IOTA:【技術解説】トランザクション大解剖!ウォレットは裏で何をやっているか。*で説明しきれなかったマニアックだが痒いところに手がとどく内容にした。というのも、今後IOTAの注目技術、MAMやマルチ署名を理解していく際に前提知識となるのがこのBundleの基礎だからである。
なお、記事内のソースコードはJavaを使用した。理由としてはJavaが筆者のお気に入りであることと、変数の型が見える方が理解しやすいからである。もちろん、IOTAでは様々な環境で開発することができるので気になる方は IOTA公式Githubをチラ見しておこう。
#IOTA(アイオータ)とは
まだIOTAがあまり認知されていないため、IOTAの基本をおさえるのに役立つリンク集。
IOTA日本語ファンサイト 実質IOTA日本公式サイト。というのもIOTA公式の情報を日本語訳して掲載しているからである。初心者向けの情報も多く掲載されている。
ホワイトペーパー英語 日本語 Tangleについての概要は序盤までで、それ以降は安全性、想定される攻撃とその耐性について高度な数学で説明している。後半は初心者向きではない。
Redditの初心者向けスレッド(英語) 僕はここから始まった。
IOTA Guide(英語) ホワイトペーパーが理学部なら、これは工学部。
IOTAのコミュニティはSlackベースに育ってきた。日本語のチャンネル**#japanese**もあるので気軽に参加・質問してみよう。
トランザクションの構造
過去の記事も見ると分かりやすいかもしれない。下のソースは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つの部分がある。
①出力部 - 'output'(どこへいくら送金するかを指定)
②入力部 - 'input'(どこからその金額を持ってくるかを指定)
③差額出力部 - 'remainder'(お釣りをどこへ送金するかを指定)
アドレスDEFBQV...
に13i
送金する例。(security=2
)
##出力部 - output
指定された宛先アドレスに送金することを出力という。その出力を請け負うのがBundleの前半である出力部という部分である。出力部には以下の役割がある。
①送金額の指定
②送金先の指定
③任意のメッセージ(message)の保管
先ほどの**「AさんがBさんにメッセージを載せて100通貨送金する」**という例だと以下のようになる。
*sigF
というのは上記Transaction内のsignatureFragments
を意味する。
*分かりやすさのためにaddress
にはBさんが代入されているが、これはBさんのアドレスという風に見て欲しい。
- Tx.0、Tx.1 の0や1は上記Transaction内の
currentIndex
を示す。
図を見てわかる通り、送金額は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上の残高のあるアドレスから送金額を集めてくることを入力と呼ぶ。出力部で指定した出力を実行するにはそれに十分な額をどこかで保有していないといけない。また、アドレスから支払われたことを署名することによって、二重支払いを防がないといけない。それらを請け負う入力部には以下の役割がある。
①入力額の指定
②入力アドレスの指定
③入力アドレスの署名
**「AさんがBさんにメッセージを載せて100通貨送金する」**という例で引き続き考えると、100通貨以上をどこから持ってくるかをここで指定する。ここで"以上"と言ったのは、余剰分は次の差額出力部で回収できるからだ。
今回の例だと入力は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が入力部に生成される。公式化すると以下のようになる。
$Transaction数 = security*input数$
また、署名の方法については過去の記事で言及したので参照していただけると幸いだ。
##差額出力部 - remainder
出力額ぴったりの残高をもつ入力アドレスは大抵存在しないため、出力額以上の残高を複数の入力アドレスから集めることになる。そのためお釣りが発生する。IOTAでは一度署名されたアドレスを再度入力アドレスとして新しく署名するとPrivate Keyの漏洩に繋がる。言い換えると、一度署名されたアドレスで金額を受け取ってはいけない。もし、お釣りを元あった入力アドレスに戻すことはその原則に違反する。そこでお釣りは新しい別のアドレスに出力される。
差額出力部は以下の役割がある。
①差額の指定
②出力アドレスの指定
「AさんがBさんにメッセージを載せて100通貨送金する」という例では、最終的に44通貨の余りが出た。下の図を見てみよう。
とてもシンプルである。差額出力部は差額がある場合は1つだけのTXを生成する。お釣りの金額が新しいAさんのアドレス(Aさん③)に出力されるだけである。新しいアドレスとはなんぞやという方は、過去の記事を読み返してほしい。
ちなみにもしお釣りがゼロ(ぴったりの入力アドレスが見つかった)場合は差額出力部自体が生成されない。
##複数アドレスに送金する場合
複数の別のアドレスに送金する場合は、出力部をアドレスの数だけ増やす。
入力部、差額出力部の扱い方は簡単だ。入力部では今回の例だと(100+1000+634=1734)以上分の入力アドレスを探し、差額はお釣りをまた1つのTxに入れるだけで特に特別なことはしない。
#Bundleの全容
さてIOTAのTransactionはただ生成するだけでなく、trunkTransaction
とbranchTransaction
と呼ぶ二つのTransactionを承認するという仕事がある。
##承認するTipsの選択
承認するTipsと呼ばれるTransactionはgetTransactionToApprove
という関数で簡単に取得することができる。あまりに簡単だが、その裏には高度な数学が潜んでおり、筆者のような素人の頭を悩ませる。とても手に負えないため、この説明は誰か数学のできる人にお願いしたい。
##Bundleの承認関係
以前の記事でも触れたがもう一度あえて図を描き直した。
getTransactionsToApprove
関数が返すtrunkTransaction
、branchTransaction
をそれぞれ図内のMilestone
とother Tx
に当てはめる。
https://iotasear.ch/などのTangle Explorerで適当なトランザクションとBundleを見てみよう。この記事で紹介した通りになっているはずだ。(違っていたら報告して下さい)
#チートシート
印刷できるサイズ。随時更新して美しいものに仕上げていきたい。
#参考文献
IOTA Java ライブラリ JOTA iotaledger/iota.lib.java
毎度のごとく、記事に対するフィードバックお待ちしています。