Deep Dive into MAM: the IoT-ic feature of IOTA

Preface

MAM(Masked Authenticated Message) is one of the Beta services but must call more attentions from developers. Currently, IOTA officially is developing the newest version of MAM at here in Rust. However, this article explains the older and deprecated version of MAM written in Javascript. Since the big picture and algorithm used in both version are quite similar and considerring there are few articles available online that disclose the blackbox behind what MAM does, I think the deprecated explanation of MAM is still useful for many potential developers to understand MAM and widen the prospective frontier.

MAM

IOTA boasts of its zero transaction fee. Also, each transaction object could contain a signatureFragment of 2187 trytes length. MAM’s first basic idea is that by encrypting the signatureFragment, one could easily claim cloud storage on Tangle, which in MAM is called Channel. And key that encrypts and decrypts it is called Channel Key. If you want to publish your channel available to public, you could just make your Channel Key public. If not, let it hide and never tell anybodies. The Channel Key can be chosen by channel owner arbitrarly.

Publicized message on channel is stored at the address on the Tangle. Like you search your address balance in Tangle Explorer, you can read your encrypted message there. With corresponding Channel Key, you can understand it.

MAM summary

MAM is often said to be like a radio, where Only who knows the radio frequency may reach the message. In MAM, only who knows the Channel Key can view the message.

Major points

  1. Encrypting raw message with Channel Key.
  2. Only who knows Channel Key can decrypt message.
  3. Knowing the Channel Key is not enough to be a Channel owner. To publicize message on channel, you need channel owner’s seed.
  4. From older to newer, the message is stored in the form of the chain generated chronologically. And the message chain may fork.
  5. If you join the channel message chain at some point, you cannot look back to see the past message.

Detail will be explained later.

In Practice

In practice, MAM would be used in:

  1. IoT data flow management:You could collect data from small sensors and devices in the factory. And those collected data can be encrypted and posted to DLT, Tangle. You can hold public availability and security at the same time.
  2. Group chat:Knowing channel keys of each other, they can chat.(Alpha available called iota1k is being developed.)

Older MAM and Newer MAM

As I notified above. This article explains the older MAM. But, for those who are interested in newer, jump to this link.

MAM Implimentation

Take a quick look at the example codes from github.

Publish MAM – post.js

source here.

const IOTA = require('iota.lib.js');
const MAM = require('../lib/mam');
const MerkleTree = require('../lib/merkle');
const Encryption = require('../lib/encryption');
var Crypto = require('iota.crypto.js');

const iota = new IOTA({
  provider: 'http://localhost:14600'
});

// your seed as a publisher
const seed = 'PAUL9NOZTUVHPBKLTFVRJZTOPODGTYHRUIACDYDKRNAQMCUZGNWMDSDZMPWHKQINYFPYTIEDSZ9EJZYOD';

// raw message you want to post 
const message = "\"'I'm still here for IOTA in the same way that you're here for me, each person is an intricate piece of infinity. -Eyedea\" - Dukakis";

// generate your Channel Key
// in this example is generated by hashing the seed.
// But, you could choose any arbitrary channel key. 
const channelKeyIndex = 3;
const channelKey = Crypto.converter.trytes(Encryption.hash(Encryption.increment(Crypto.converter.trits(seed.slice()))));

//  Merkle Tree attributes (explained later)
const start = 3;
const count = 4;    // Merkle Treeのsize
const security = 1;

//  Merkle Tree generation
const tree0 = new MerkleTree(seed, start, count, security);
const tree1 = new MerkleTree(seed, start + count, count, security);
let index = 0;

//  create bundle necessary for publishing your message to the Tangle.
const mam = new MAM.create({
    message: iota.utils.toTrytes(message),  // convert you raw message to trytes here.
    merkleTree: tree0,
    index: index,
    nextRoot: tree1.root.hash.toString(),
    channelKey: channelKey,
});

// Depth
const depth = 4;

// minWeighMagnitude
const minWeightMagnitude = 13;

console.log("Next Key: " + mam.nextKey);

// Send trytes(attach your Bundle to Tangle)
iota.api.sendTrytes(mam.trytes, depth, minWeightMagnitude, (err, tx) => {
  if (err)
    console.log(err);
  else
    console.log(tx);
});

View message – getMessage.js

source here.

const IOTA = require('iota.lib.js');
const MAM = require('../lib/mam');
const MerkleTree = require('../lib/merkle');
const Encryption = require('../lib/encryption');
var Crypto = require('iota.crypto.js');

const iota = new IOTA({
  provider: 'http://localhost:14600'
});

//  seed
const seed = 'PAUL9NOZTUVHPBKLTFVRJZTOPODGTYHRUIACDYDKRNAQMCUZGNWMDSDZMPWHKQINYFPYTIEDSZ9EJZYOD';
const channelKeyIndex = 3;

//  generating Channel Key of the channel you want to view.
//  When viewing someone else's channel, substitute given Channel Key here.
const channelKey = Crypto.converter.trytes(Encryption.hash(Encryption.increment(Crypto.converter.trits(seed.slice()))));

//  Merkle Tree attributes.
//  When viewing someone else's channel, substituting given root here.
//  explained later.
const start = 3;
const count = 4;    // size of Merkle Tree
const security = 1;

//  Merkle Tree generation
const tree0 = new MerkleTree(seed, start, count, security);

//  get the root
const root = tree0.root.hash.toString();



iota.api.sendCommand({
    command: "MAM.getMessage",
    channel: MAM.messageID(channelKey)
}, function(e, result) {
    if(e == undefined) {
        result.ixi.map(mam => {
            const output = MAM.parse({key: channelKey, message: mam.message, tag: mam.tag});
            const asciiMessage = iota.utils.fromTrytes(output.message);
            if (root === output.root) {
                console.log("Public key match for " + root);
            }
            console.log("received: " + asciiMessage);
        });

    }
});

Behind MAM – Overview

Channel Key – channelKey

chennelKey is an arbitrary 81 trytes, like seed. First of all, publisher publish first message. The message is encrypted with the channelKey. In this way, no one other than this publisher can understand the message.

However, in this way, you can only publish message once on your channel. In other words, one ‘channelKey’ can reach only one message. If you post new message on the channel, whenever you do so, you have to tell your viewer the new Channel Key. And you have to store all channel key safely and keep track of them which to which. It can be a mess.

Image of Solution

Let me explain to you with easy image for now. That might ignore small negligible details. (Don’t worry for those with interests in minute detail, I will eventually explain.)

Next Channel Key – nextChannelKey

Before encrypting your raw message, MAM includes the next channel key(nextChannelKey) in your message to be encrypted. If to say in RPG, in the first treasure chest you opened, it’s like you would find the key that opens next treasure chest. By repeating this can viewers sequently decrypt next message with nextChannelKey you obtained from current message you have just decrypted. And publisher can give nextChannelKey of any points on the channel. That way, you can restrict viewers from viewing the past messages. *nextChannelKey can be arbitrary 81 trytes.

Address of the message – messageID

You might have ever noticed, you have your key but don’t have the map that guides you to the treasure chest. In technical term, having channelKey or nextChannelKey is no use to find where the message is stored. In MAM, the address where the message is stored is called messageID. And it is simply generated by hashing channelKey or nextChannelKey twice. The treasure map is drawn on the key. Just having your key is sufficient for both finding and decrpting message.

* The way to generate messageID is arbitrary as long as you and potential viewers share how to. But, at least you should hash it to protect your key.

Message Chain

The genesis message that contains raw message and nextChannelKey of the channel encrypted with channelKey will be a part of the bundle. And the bundle that contain next message is pointed by the previous bundle. By repeating this, you can now see a chain on the Tangle, Message Chain. You can join message chain at any points with corresponding key to decrypt it, but never can go back to see past posts.

Implimentaion in Practice

Now the time to see what’s in practice. At first, it is costly to encryt message to get nextChannelKey. If you are interested in the newest post of the channel, but only to realize it has hundreds of the previous posts. Decrypting all of them just to view one last post is not realistically feasible. So, in practice, instead of having nextChannelKey encrypted IN the message with raw message, nextChannelKey is generated by the formula nextChannelKey = hash(channelKey + salt) ,where salt is an arbitrary tag of the Transaction object chosen when posting the message. Since the tag is a part of the Transaction object, to know tag you need messageID(address) the transaction is stored. And the transaction can be found only with channelKey. Note that this way still holds MAM’s restriction, only who knows channelKey can know nextChannelKey.

If to say in the treasure chest analogy, next key is not in the chest, but attached to the surface of the chest. But, only who brought the key can see the next key.

Moreover, if the message exceeds the capacity(2187 trytes) of the signatureFragment of the Transaction, you don’t need to make two different MAM messages and post twice as two messages on the message chain. You can just increase the number of transactions stored in the bundle and attach the bundle with long message as one MAM message. But, too long bundle may require too heavy PoW to attach it to the Tangle and it’s also costly when retrieve it from the Tangle.

Behind MAM – Publishing

Now I’m going to talk about the forgotten aspect of the MAM, Channel’s ownership. Let’s take a look at the case below.

Alice has just published her first message on her channel and want Bob to read her post. So, Alice gave him her channelKey. Bob visited her message by generating address(=messageID) and decrypt it with channelKey. Bob enjoyed her post and thought “wanna read next!”. And he has nextChannelKey in the decrypted current message, and can generate next address out of it. But, he realized that Alice hasn’t published next message yet, at next messageID, there’re no transactions made. Bob suddenly became evil. He has nextChannelKey and next messageID. He can post his message encrypted with nextChannelKey to the address messageID. And he could hijack Alice’s channel. Do you think it would work? From information explained so far, there’s no contradiction. …However, that’s MAM’s failure. Don’t worry, MAM is designed so people like Bob cannot jumble up the channel. Next topic to cover is how Alice keeps her message chain from being edited by anyone else.

Channel’s Ownership

The problems mentioned above could simply be summarized as:Chennel Key implimentation cannot identify who of Alice and Bob owns messages.  Now recall how the address of IOTA is signed by its correct owner.  MAM uses technic that utilizes both Private Key and Merkle Tree scheme to prove ownership of the channel.

Merkle tree based signature scheme

Merkle tree based signature scheme is the technic used in MAM.  But, I don’t explain what the computer science term “Merkle Tree” is. Because since interests in Cryptos skyrocket, the idea of the merkle tree, which is the core module often seen in plenty of cryoto projects, is explained countless times at various places. You could just google it.

Merkle Tree

Source here.

Merkle Tree of MAM is generated from seed. (What a surprise that seed grows up to a tree!) Merkle Tree has two parameters, start and size. start is used as a parameter index of creating private key. And size is used to specify the range in the sequence of the private key generation. To illustrate, look at the figure below. A,B,C,D are the private keys generated with index parameters as 0,1,2,3(where start = 0). And A’,B’,C’,D’ are the address generated out of each private key.

Leaf of the merkle tree, A”,B”,C”,D” are =hash(address). From leaf to root, hash of children nodes are merged into their parent node. Obviously, you cannot generate children nodes from parent.

Siblings

Let’s say, leaf A’ is given, to obtain root, you need to have all other leaves B’C’D’. But, if you cannot access to those, how do you get root? Siblings are the complimentary hashes that are necessary to generate root with given leaf. Look at the figure below.

For the case leaf is A’, siblings are B”and Hash(C”D”). Without knowing all leaves, with siblings, you can calculate root.

Secret Key and Public Key

Publisher(Seed owner) has to make Private Keys, A,B,C,D confidential. But, root and siblings are public keys, because you can still achieve your private key’s safety. Because, only the seed owner, or to say Merkle Tree owner, can calculate root from public information. Other cannot generate root. Yes, they know what the root is, but they don’t know how to generate. To illustrate, using the example above, other people must have a leaf A’ to generate root by combining it with also public siblings. This way, you can distinguish correct owner of the merkle tree from other folks.

This technic is used in MAM magically to prove ownership of the message chain.

Behind MAM – Signature and Validation

In this chapter, I’m going to merge two major topics discussed so far:

  1. Message Chain connected via nextChannelKey
  2. Merkle Tree

MAM.create

source here.

Alice opens Channel. Alice then has to:

  1. generate channelKey.
  2. generate two Merkle Trees of same size. The first one starts with the index start.

The first tree namely tree0, has start0 = start,size0 = size. Second tree tree1 starts with start1 = srart0 + size0, size1 – size0. Second tree uses next sequence of the private keys. Figure below.

When creating MAM,tree0 itself and the root of tree1, namely root_1(nextRoot) is one of the parameters. And leaf index is chosen from 0 < i <size. Let’s say in this case, leaf_index = 0. Leaf index of the merkle tree is the distance from the most left leaf. leaf_index ‘0’ of tree0 is A’, leaf_index 2 of tree1 is G’.

leaf_index = 0    // leaf index

const mam = new MAM.create({
    message: iota.utils.toTrytes(message),  // convert raw message to tryte
    merkleTree: tree0,                      // the first merkle tree
    index: leaf_index,                      // leaf index
    nextRoot: tree1.root.hash.toString(),   // next root. root_1 above.
    channelKey: channelKey,                 // ChannelKey, used to encrypt this whole message.
});

So, what’s going to happen in MAM.create then?

Firstly, create siblings of tree0 with index = leaf_index. In this case, leaf_index = 0, so siblings are B” and Hash(C”D”).

Nextly, make a signature, where messageTrytes that are composite of nextRoot and raw_message as signed data, and private key of leaf_index as signing key. Forgot how to sign? Explained here.

Now with these given attributes, one MAM message is created. This is visulaized below.

MAM.parse

source here.

So, how we parse this message?

Tell both channelKey and root to viewers
  • chennelKey is used to decrypt message, and for generating address.
  • root is used as ownership verification checker to check decrypted message is Alice’s or not.

In this example, root_0 of tree0 and channelKey is given to viewers so that they can understand Alice’s first published message. Now, assume that Bob has been given both root_0 and chennelKey.  Bob calculates messageID from given channelKey to find where Alice’s message is being stored. Then, decrypt found message with channelKey.  Next, he validates signature of the message with messageTrytes as signed data. Forgot how to validate signature? Explained here.

Ownership Verification

After validation of the signature, Bob gets an address. He uses the address as a leaf at leaf_index of the merkle tree. Then with given siblings in the message, he finds root of the merkle tree. If this root matches the root Alice told him, the decrypted message is proved to be made by Alice.

If not, that means signed data messageTrytes is different than Alice’s. So, the address generated by validating singnature is also going to be different. Then with that address, calculated with siblings, he gets root. This root does not match the root Alice gave him. In this way, malicious attackers who know the channel key at any points on the message chain cannot generate fake message on the chain. Precisely, they can post fake message on the address messageID, but when viewers decrypt the fake message, because they cannot match their given root, fake message is never going to be regarded as Alice’s real message.

nextRoot

As I mentioned, MAM is a message chain. One message decryption has to lead to next message. Finding next message is the role of nextChannelKey, but what about the checking ownership of the next message? Each messageTrytes contain the value nextRoot stored with raw message. This nextRoot is used to check the validity of the next message in the same way as explained in previous chapter. The more you generate nextRoot, the larger index is used as start of the merkle tree. Remember nextChannelKey is to find the address and decrypt next message, nextRoot is used to compare with caluculated root of the next message, which has siblings and leaf_index in it.

Chain fork

MAM’s message chain may fork. By changing leaf_index and coressponding siblings, you could post different message that still holds your ownership. In this way, at the same address messageID, you could have your message as many as the merkle tree’s size. And nextChannelKey can be generated in different way such that forked chain will follow different messageID path.

Use case

I came up with how to use forking message chain. You may see forking chain as the tree structure of the directory, where the first published message as root directory and children are generated by forking chain. And telling users different channel keys at differnt points on the chain, you can manage access controll. The users close to superuser knows the channel key that opens closer message to the first message.

MAM is Protocol

You might have noticed that by changing the way to hash to calculate root, channel key, siblings and etc.. As long as you share how to post and parse messages with your viewers, you can have different format of MAM. The MAM explained in this article is just one of all possible MAMs.

Ask for Revise and Feedback

Please, let me know any mistakes and ambiguity found on this article. Feedback is always welcome.

Reference

About Author

@abmushi on twitter, slack

Translated from my original article written in Japanese here.

Donation is always welcome and appreciated!

BTC: 1ACGpgpAMgaAKbGpPq2sDa467MnRNdW4wX

IOTA: KWIEEQHAJBJTDPE9WEDILKMVQCJPZSF9CXALYQTULCGNPLIIKJLFYHCWSJNXDALKHAOOTELQUIXWIOFVDPQNXMLBZB

1 個のコメント

  • Leave a Reply

    Your email address will not be published. Required fields are marked *

    ABOUTこの記事をかいた人

    ABmushi

    IOTAを理解したい。10年後ビックリしたい。 記事が気に入りいましたら寄付をお願いします!
    BTC: 1ACGpgpAMgaAKbGpPq2sDa467MnRNdW4wX
    IOTA: