Edited at

IOTA:【技術解説】MAMの全容。

English here

 去年の終わりに公開した記事『IOTA:【旧版】MAMとは。IOTAのIoT性。』が早くも古くなってしまったので最新版を書くことにした。また、そちらの記事はいろんなアイデアがゴチャゴチャしてMAMの実態を掴みにくい構造になっていたので頑張って分かりやすくなるように努力した。

 本記事のMAMを試したこちらの記事『IOTAのMAMを試してみる。』ではライブラリの使い方を説明した。


IOTA(アイオータ )とは

おなじみ初心者向けの解説サイトを念のためここに列挙しておく。


IOTA日本語ファンサイト 実質IOTA日本公式サイト。というのもIOTA公式の情報を日本語訳して掲載しているからである。初心者向けの情報も多く掲載されている。

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

Redditの初心者向けスレッド(英語) 僕はここから始まった。

IOTA Guide(英語) ホワイトペーパーが理学部なら、これは工学部。



前提知識


IOTA:【入門】トランザクション大解剖!ウォレットは裏で何をやっているか。

IOTA【技術解説】署名と承認。 - 改訂版



MAM とは

 MAMはTangle上の情報のストリームである。世の中の情報を発信する色々なメディア、ラジオやテレビ、新聞、天気予報や監視カメラを想像してみる。それらは時々刻々と新しい情報を名前のついた一括りのコンテンツとして発信しているはずだ。


チャンネル

 例えばYoutubeのチャンネルのように、MAMもその単位をチャンネルと呼ぶ。MAMを閲覧するときは見たいチャンネルにアクセスし、また投稿するときは自分のチャンネルに投稿する。ここで自分というのは、自分のSeedという意味である。つまり、チャンネルのアイデンティティもまたSeedに由来する。(IOTAにおけるSeedは全てである。プライバシーと個人の所有権の全てがここに詰まっている。)


チャンネルのモード

 チャンネルにMAMを発行する際、チャンネル所有者は3つの投稿タイプを選ぶことができる。誰でも見れるPublic(公開)、自分(Seed所有者)しか見れないPrivate(非公開)、特定の人にだけみることのできるRestricted(限定公開)。ここでいう特定の人というのは、チャンネル所有者が設定したsideKey(チャンネル鍵)を教えられた人のみという意味である。


Public(公開):保管されたメッセージはアドレスを使って復号。(アドレス自身が復号に使われるため、アドレスが分かればメッセージも見れる。)

Private(非公開)アドレス=hash(root)rootを使って復号。(アドレスはハッシュ化されており、ハッシュ化前の生rootを知っていないと、例えアドレスにアクセスしてもそのメッセージを復号できない。)

Restricted(限定公開)アドレス=hash(root)keyを使って復号。(rootを教えることはPrivateチャンネルを公開することになるため、あくまでroot+keyを限定公開する。そのroot+keyを知っている人だけがRestrictedチャンネルにアクセスしてメッセージを復号できる。)



メッセージチェーン

 IOTA自体に元々メッセージをトランザクションに貼り付ける機能はあった。しかし、そのままだとメッセージの連続性は表現できない。例えば、15分ごとに気温をTangleに記録していくとする。最初にアドレスAにメッセージ12:15 06/02, 18.2という気温情報を送った。しかし、次のメッセージをまたアドレスAに送るとすると、この気温情報のストリームはアドレスAがずっと保持しなければならず第三者にその情報が筒抜けである。また、誰かからの妨害メッセージ付きトランザクションをアドレスA宛に送金されるなどの恐れも出てくる。

 しかしMAMは一つの投稿を別々のアドレスに送っても、紐解くように連続した情報のストリームを取り出すことができるように設計されているためそれらの心配はいらない。

 MAMのメッセージチェーンには言うなれば世代の概念がある。ある世代から次の世代へとMAMメッセージは一方通行に鎖に繋がれていく。


基礎

 MAMも送金と同じようにBundleに必要情報をまとめてTangleに保管される。MAMのBundleは大きく分けて署名部MAM部に分かれる。それらはトランザクションのsignatureFragmentとして保管される。署名というのはMAMの発行者を見分けるため=MAMメッセージの本人確認に使われる。詳しくは後述する。

mam_bundle.png

 MAMを閲覧する際、閲覧者はMAMをTangle上から探すためにrootsideKeyと呼ばれる値をMAM発行者から与えられる。公開モードではrootのみ。限定公開モードではrootsideKeyを与えられる。


アドレス生成

 アドレスというのはMAMメッセージが保管されるTangle上の場所である。これはrootを使って計算される。


mam.node.js

if (channel.mode !== 'public') {

// 公開モード以外は アドレス=hash(root)
address = Crypto.converter.trytes(Encryption.hash(81, Crypto.converter.trits(mam.root.slice())));
} else {
address = mam.root;
}


おことわり

Paul Handyさんの記事では限定公開のアドレス=hash(root+sideKey)となっているがソースコード内に認められないので、本記事ではソースコードにのっとり、アドレス=hash(root)で統一した。これは誤っている可能性があるので、随時確認いたしますが詳しい方にコメント欄等で報告していただけると大変助かります。(追記:実際にAPIで試して比べてみましたが、やはりアドレス=hash(root)のようです。)



公開モード


rootがメッセージの保管アドレス。

rootでメッセージを復号する。


public.png

 ここでnextRootというのは、MAMのメッセージチェーンのつなぎ目となる値である。一つのメッセージを復号するとそれに含まれるnextRootという値によって閲覧者は続きのメッセージにたどり着くことができる。RPG風に例えるなら、最初の鍵で開けた宝箱の中に、次の宝箱を開ける鍵を入れるようなものだ。これを繰り返すことで、最初のジェネシス世代のrootを知っている人はメッセージの復号によって得られるnextRootを使って、チャンネルのメッセージチェーン上の全ての世代を連鎖的に追っていくことができる。

 また、チェーン上の途中の世代のメッセージを開けるnextRootを所有している人は、それを使って途中からチャンネル閲覧を始めることができる。しかしその場合、過去の世代のメッセージに遡ることはできない。


限定公開モード


hash(root)がメッセージの保管アドレス。

sideKeyでメッセージを復号する。


restricted.png

 メッセージチェーンの仕組みは公開モードと変わらない。ただ、sideKeyという復号鍵を閲覧者は所有していなければメッセージの内容を読むことができない。


チャンネルの所有権問題

 チャンネルに初投稿したAさんはBさんにチャンネルを見て欲しいと考えた。そこで、AさんはBさんにrootを教えた。BさんはAさんにもらったrootでアドレス=hash(root)を訪れ、メッセージをsideKeyで復号しAさんの投稿を閲覧した。続きが気になったBさんは復号の際手に入ったnextRootで次のアドレス=hash(nextRoot)を取得し、次のメッセージを見ることにした。しかし、Aさんはまだ次の投稿をしていなかったので、そのアドレス宛てにはまだ何のトランザクションも発行されていなかった。

 BさんはAさんに嫌がらせをしたいと突然思いついた。見渡すとBさんの手には、nextRootsideKeyがあった。これがあれば、Bさんは次のメッセージをBさんが勝手に決めて、sideKeyで暗号化して次のアドレス=hash(nextRoot)宛てにAさんより先に投稿すれば、Aさんのチャンネルを乗っ取れると考えた。

 これは上手くいくだろうか?今までの説明ではこの盲点をカバーできていない。ここでMAMにも送金と同じように署名という概念が必要になる。

 ここでIOTAにおける、アドレスの残高の所有権はどう証明したかを思い出してみよう。アドレスの残高を引き出す際には、Seedから生成されるPrivate Key署名したトランザクションが承認されることで残高を引き出すことができた。

 MAMではPrivate Keyroot生成に使ったMerkle Treeと組み合わせることでチャンネルの所有権を証明するのに利用する。


Merkle tree based signature scheme

 MAMにおけるMerkle Tree(マークル木)を使った独特な署名方法について説明をしたい。Merkle tree based signature schemeと言う技術らしいので興味ある人はリンク先を読んで見ると良い。


投稿 - 基礎

 MAMを投稿するにあたってまず必要となるのは投稿する場所=アドレスだ。MAMは上で説明した通り、rootという値がアドレスに直接関係するため、まずrootの生成方法から説明する。ここで先ほどちらっと出てきたマークル木(Merkle Tree)の説明も入る。


mam.client.js/blob/master/src/node.js

/*

    SEED: シード
    MESSAGE: 送りたいメッセージ
    SIDE_KEY: sideKey
    CHANNEL: オブジェクト{security,start,count,index}
         *indexはbranch_index(後述)
*/

function createMessage(SEED, MESSAGE, SIDE_KEY, CHANNEL) {
if (!SIDE_KEY)
SIDE_KEY = `999999999999999999999999999999999999999999999999999999999999999999999999999999999`
// MAM settings
let SEED_trits = string_to_ctrits_trits(SEED)
let MESSAGE_trits = string_to_ctrits_trits(MESSAGE)
let SIDE_KEY_trits = string_to_ctrits_trits(SIDE_KEY)

const SECURITY = CHANNEL.security
const START = CHANNEL.start
const COUNT = CHANNEL.count
const NEXT_START = START + COUNT
const NEXT_COUNT = CHANNEL.next_count
const INDEX = CHANNEL.index

const HASH_LENGTH = 81

// set up merkle tree
let root_merkle = iota_merkle_create(SEED_trits, START, COUNT, SECURITY)
...



rootの取得

 rootとはマークル木のルート(根)を意味する。そのためroot取得にはまずMerkle Treeを生成する必要がある。

 MAMのMerkle TreeはSeedから生成する。(種が木へ!)Merkle Treeはstartsizeという引数をとる。これはSeedから生成されうるaddress連鎖のどの部分をMerkle Treeの葉として使うかに用いられる。SeedからPrivate Keyを生成し、addressを生成するために、indexという引数をとったことを覚えているだろうか?(忘れてしまった人のための記事はこちら。)下の図のA、B、C、Dはそれぞれindexが0~3で生成されるPrivate Keyだ。A'、B'、C'、D'は該当Private Keyから生成されるaddressである。

mam_merkle_1-2.png

 Merkle Treeの葉A"、B"、C"、D"にはaddressにハッシュ関数を通したものを用いる。葉からrootへ枝が根に集まるようにハッシュ関数に通していく。図で言うと下のノードから葉の方向へ逆生成させることはできない。

 こうして取得できるrootをアドレスとして(限定公開モードならrootにハッシュ関数をかける)使う。


投稿 - MAM部

 まず、MAMとして送りたいメッセージ(messsage)はこのMAM部に含まれる。messageとなりうるものは、気温データなどのセンサー情報や文字データである。現在のところ日本語などの全角文字には対応しておらず生データはasciiコードで用意する。ライブラリのasciiToTrytes.jstoTrytes(ascii)で変換してmessageに格納する。


nextRootの取得

 一世代のMAMメッセージにつきMerkle Treeは実は二つ生成される。その世代と次の世代の分だ。どちらとも生成方法は同じである。ソースコードでいうとパラメーターはSTARTCOUNTが渡される。

const START = CHANNEL.start           // 一つ目のMerkle Treeの開始index。

const COUNT = CHANNEL.count // 一つ目のMerkle Treeの終了index(の一個手前)。
const NEXT_START = START + COUNT // 二つ目のMerkle Treeの開始index。
const NEXT_COUNT = CHANNEL.next_count // 二つ目のMerkle Treeの終了index(の一個手前)。
const INDEX = CHANNEL.index // 枝番号(後述)

 第0世代のtree0start0 = STARTend0 = COUNT。第1世代のtree1start1 = START+COUNTend1 = CHANNEL.next_count。言い換えると生成されるPrivate Keyの連鎖で1つ目のMerkle Treeが使っていない次の部分を使う。下図参考。

two_merkle.png

 ちなみに全ての世代のMerkle Treeが同じサイズである必要はない。


枝番号 branch_index

(旧MAMの記事では葉番号と仮に名付けたが後ほど説明するチェーンフォークの枝分かれを考えると枝番号(branch_indexという名前に早くも変更させていただいた。)

 枝番号はroot取得の際に使ったMerkle Treeの葉から任意にindexを選んで決める。

 先ほどの例で言うと、Merkle Treeはstart=0count=4で生成されたのでbranch_index0,1,2,3のどれかの任意の値だ。

branch_index.png

 ちなみにbranch_indexは整数である。上の図で見るとPrivate Keyを囲んでいるが枝”番号”なので値はあくまでもデータ型はintの0,1,2,3,...となる。


Siblings

 Siblings(兄弟)という概念について説明する。

 まず、上のMerkle Tree上の葉A'が与えられたとする。rootを得るためには全ての葉B'C'D'が必要になるが全ての葉が分からない場合どうやってrootを導き出せるか。

 Siblingsとは葉A'とともにrootを得るために使われるMerkle Tree上の途中の枝の部分である。図を見る方が早いかも知れない。

mam2_siblings.png

 上の図はbranch_index=0の例である。枝番号に対応する葉A'だけが与えられた。その場合はB"Hash(C"D")があれば全ての葉を知らなくともrootを求めることができる。このような働きをする、B"Hash(C"D")A'Siblingsと呼ぶ。

 また、この際はbranch_index=0Siblingsと呼ぶ。branch_indexが変わるとSiblingsの中身も変わる。


MAM部の生成

 MAM部にはこれまで説明したmessagenextRootbranch_indexSiblingsを公開モードならrootで、限定公開モードならsideKeyで暗号化する。下の図は限定公開モードの例。sideKeyで暗号化をする。

mam2_mam_section.png


投稿 - 署名部

 発行者はMAM部の内容の正当性を証明するために署名をBundleに載せる必要がある。その署名をsignatureFragmentに保管するのがこの署名部の役割だ。


署名の生成

 署名は、key(seed,branch_index,security)で生成されるPrivate KeyをMAM部のmessageTrytesで署名して生成される。署名生成の仕組みはこちらの記事を参照。

mam2_sig.png


閲覧

 閲覧するのに必要なのは、rootと、限定公開ならsideKeyだ。

 まずはrootからアドレスを計算し、Tangleにそのアドレスで検索をかける。見つかったBundleのMAM部のmessageTryteを公開モードならrootで、限定公開モードならsideKeyで復号する。この時点でmessagenextRootbranch_indexSiblingsが手に入る。次にこのメッセージの正当性を確認する必要がある。署名部の出番だ。

 署名部にある署名を、messageTryteを署名されるデータとして承認作業をする。承認作業の仕組みはこちらの記事を参照。承認作業で得られるのはアドレスだ。このアドレスをMerkle Treeのbranch_indexに位置する葉として、Siblingsと組み合わせることでRootを計算する(temp_rootと呼ぶ)。temp_rootがチャンネル発行者から教えてもらったrootと一致すれば今閲覧しているBundleは正当な公式MAMメッセージということになる。

 もし、一致しなければ今閲覧しようとしているBundleは悪意を持った発行者以外の人間にアタッチされたMAMメッセージである。


チェーンフォーク

 MAMのメッセージチェーンは分裂できる。方法も簡単で、枝番号=branch_indexが違うメッセージを投稿するだけだ。ただし、一つの世代につき、その世代のMerkle Treeのサイズ分までしか分裂できない。というのもbranch_indexが次の世代の方へはみ出てしまうからだ。

 また、分裂する際にそれぞれの投稿モード(公開や限定公開)は同じである必要はなく、sideKeyも統一する必要もない。こうすることでサブチェーンごとに役割を住み分けることもできる。

fork.png


何故こんなにも面倒な署名方法なのか

 Merkle Treeの署名方法など使わずに、1つの秘密鍵から生成した1つのアドレスに送りたいだけの数のメッセージを送信することもできる。本人確認はそのメッセージごとに署名をBundleに含めればいいように思える。しかし、IOTAでは1つの秘密鍵ごとに署名は一回しか使えない。

 Merkle Treeをわざわざ使ったのは、異なる秘密鍵でそれぞれのメッセージを署名しつつ、送信先のアドレスを一つのアドレス(root)に集約させるためである。


スナップショット

 さてMAMはトランザクションのsignatureFragmentに保管されている。つまり、スナップショット(=残高プラスのアドレス以外削除)が行われると全てのTangle上のデータは削除される。そのため、MAMはスナップショットが行われないPermanode(パーマノード)という特殊なフルノードに依存する形となる。

 巨大な容量と高速な処理スペックが要求されるPermanodeだが、現在のところ金銭的インセンティブはない。MAMはIOTAのIoT性を左右する要となる機能なだけに今後どうなっていくのか注目される。


参考文献


MAM JS 公式ライブラリ https://github.com/iotaledger/mam.client.js

IOTA github https://github.com/iotaledger/iota.lib.js

Introducing MAM https://blog.iota.org/introducing-masked-authenticated-messaging-e55c1822d50e