- Raw data to IPFS
- Raw data to merkle root
- Publish root and IPFS hash
- Read IPFS data and get merkle proof
- Exercise permissions
$ node bin/merk-tools.js Usage: merk-tools [options] [command]
Options:
-V, --version output the version number
-h, --help output usage information
Commands:
save <file> Save to IPFS
merkle-root <file> Get Merkle Root
merkle-proof <file> <index> Get merkle proof for index
publish <merkle-root> <ipfs-hash> Publish IPFS hash and Merkle Root
verify <index> <address> <permission> <proof> Verify Proof On-Chain
Using Merkle Roots to make large state updates on the public chain encompassed in a single root hash.
Smart Contract development on the public blockchain is unlike other paradigms. Storage is not cheap by any stretch of the imagination and should be used as infrequently as possible.
Data intensive use cases, such as suply chain management or even access management for a large organization are non starters due to storage costs.
We build off of Richard Moore's Merkle AirDrops and introduce two novel additions:
- Pair with IPFS storage for data persistance;
- Demonstrate a new use case.
The specific use case we demonstrate is updating permissions, i.e. access control, for any number of parties, without publishing thier addresses to the public chain. Should permissions change, a single root hash is published to the smart contract along with the IPFS hash of the latest dataset.
The publishing of both hashes means the smart contract is always able to respond to the latest permission structure and users are always able to obtain thier merkle proofs from the IPFS dataset.
We assume raw data is in JSON format structured as shown here:
[
{ "address": "0x5a10c2afdbbddd6374318ff754dc1f1852403e2c", "permission": 1 },
{ "address": "0xcf0aa32424fc4cb0e0b6ce34bc5134f2875c068a", "permission": 0 },
{ "address": "0xe8570478ea019edf1deaf114f9545282075bcdeb", "permission": 0 },
{ "address": "0x04cb2f244c974e8607ce25c80860234add63fcc3", "permission": 1 },
{ "address": "0xdf4271fc55e4eca22831543370b2ffda3a0672fe", "permission": 0 },
{ "address": "0xdeb29b6cd55de7c17d1644db71b693f091481bfc", "permission": 0 },
{ "address": "0x770d5125d8eb0fb9851585d55845b782e6769038", "permission": 1 },
{ "address": "0x2d84b2c1512c45f10cc147978deaba04137bae6b", "permission": 1 },
{ "address": "0x1bb6893e787d02ecc4270d87005f65f3a6390820", "permission": 0 },
{ "address": "0xeb4711fa1c9faa6adee37566965fc8f239b0f00c", "permission": 1 }
]
In our demonstration, 1 is an admin and 0 is view only access. However, any range of permission schemes can be supported.
The save
command saves the raw data to IPFS.
In the project directory run node bin/merk-tools.js save ./testData.json
and the IPFS hash is logged to the console:
$ node bin/merk-tools.js save ./testData.json
Saving ./testData.json to IPFS
added QmUbr6QEAgSxraQEm1YutLGGNKU5sceLjL1SSxDJbMhdnG testData.json
The merkle-root
command computes the merkle root hash from the dataset stored on IPFS.
In the project directory run node bin/merk-tools.js merkle-root QmUbr6QEAgSxraQEm1YutLGGNKU5sceLjL1SSxDJbMhdnG
ensuring the hash is the one returned when you ran the save
command.
$ node bin/merk-tools.js merkle-root QmUbr6QEAgSxraQEm1YutLGGNKU5sceLjL1SSxDJbMhdnG
Getting QmUbr6QEAgSxraQEm1YutLGGNKU5sceLjL1SSxDJbMhdnG from IPFS
Computed Merkle Root:
0x893598180931f55c2e4d0ad638fb258455356f9c4177d8340c41ddf756a26222
The publish
command persists the merkle root hash and the IPFS hash to the relevant smart contract.
$ node bin/merk-tools.js publish 0x893598180931f55c2e4d0ad638fb258455356f9c4177d8340c41ddf756a26222 QmUbr6QEAgSxraQEm1YutLGGNKU5sceLjL1SSxDJbMhdnG
Publishing Merkle Root: 0x893598180931f55c2e4d0ad638fb258455356f9c4177d8340c41ddf756a26222
Publishing IPFS Hash: QmUbr6QEAgSxraQEm1YutLGGNKU5sceLjL1SSxDJbMhdnG
Using network 'development'.
rootHash set to: 0x893598180931f55c2e4d0ad638fb258455356f9c4177d8340c41ddf756a26222
ipfsHash set to: QmUbr6QEAgSxraQEm1YutLGGNKU5sceLjL1SSxDJbMhdnG
The set to:
confirmations that are logged read from the chain confirming that the hashes have been correctly set.
The merkle-proof
command computes the merkle proof from a particular address. As there can be multiple permissions per address, an index is required as input instead of the address.
$ node bin/merk-tools.js merkle-proof QmUbr6QEAgSxraQEm1YutLGGNKU5sceLjL1SSxDJbMhdnG 6
Getting QmUbr6QEAgSxraQEm1YutLGGNKU5sceLjL1SSxDJbMhdnG from IPFS
Computed Merkle Proof:
0xc9f9fe58366731a2330cc5f4069e0f30a77559cc51443804af0a0e483fd85765,0x4e2eecf11ba0a8c4107fc693c609f8ec16eea441313f44d49f080f8e37a3dda2,0xd2631027d84926112b37abf1716adba457523111b869b650c90fe803b02888d3,0xff58eb23a3139593da470709976eed92196bfb94085c3627c82a1287aa2f4175
The verify
command verifies that a particular address is an admin. Verification relies on the merkle proof to determine permission level.
$ node bin/merk-tools.js verify 6 0x770d5125d8eb0fb9851585d55845b782e6769038 1 "[\"0xc9f9fe58366731a2330cc5f4069e0f30a77559cc51443804af0a0e483fd85765\",\"0x4e2eecf11ba0a8c4107fc693c609f8ec16eea441313f44d49f080f8e37a3dda2\",\"0xd2631027d84926112b37abf1716adba457523111b869b650c90fe803b02888d3\",\"0xff58eb23a3139593da470709976eed92196bfb94085c3627c82a1287aa2f4175\"]"
Verifying permission == 1
For address: 0x770d5125d8eb0fb9851585d55845b782e6769038
Using network 'development'.
Index: 6
Address: 0x770d5125d8eb0fb9851585d55845b782e6769038
Permission: 1
Proof: 0xc9f9fe58366731a2330cc5f4069e0f30a77559cc51443804af0a0e483fd85765,0x4e2eecf11ba0a8c4107fc693c609f8ec16eea441313f44d49f080f8e37a3dda2,0xd2631027d84926112b37abf1716adba457523111b869b650c90fe803b02888d3,0xff58eb23a3139593da470709976eed92196bfb94085c3627c82a1287aa2f4175
Is admin: true
And it works! This address coupled with its merkle proof is confirmed to have admin level permissions.
Raw data:
[
{ "address": "0x5a10c2afdbbddd6374318ff754dc1f1852403e2c", "permission": 1 },
{ "address": "0xcf0aa32424fc4cb0e0b6ce34bc5134f2875c068a", "permission": 0 },
{ "address": "0xe8570478ea019edf1deaf114f9545282075bcdeb", "permission": 0 },
{ "address": "0x04cb2f244c974e8607ce25c80860234add63fcc3", "permission": 1 },
{ "address": "0xdf4271fc55e4eca22831543370b2ffda3a0672fe", "permission": 0 },
{ "address": "0xdeb29b6cd55de7c17d1644db71b693f091481bfc", "permission": 0 },
{ "address": "0x770d5125d8eb0fb9851585d55845b782e6769038", "permission": 1 },
{ "address": "0x2d84b2c1512c45f10cc147978deaba04137bae6b", "permission": 1 },
{ "address": "0x1bb6893e787d02ecc4270d87005f65f3a6390820", "permission": 0 },
{ "address": "0xeb4711fa1c9faa6adee37566965fc8f239b0f00c", "permission": 1 }
]
Hashes:
[ '0xcca476cd0be4e945376bd7e3899136574726b9b587dc5801974f9a1aeaa0be3c',
'0xb18996966b9ba8f7b20ef1ddd3869fa011ccdad71e1b39575929deb7a5ff4a4d',
'0x87c774c905cc558fe459d6448dce65940689f0c6e693b62287ddf825945f3184',
'0xc8be72acbf8eba5486fd9261a6056856b432b6cdc97fb625e0cd3ea2bac7f2fd',
'0xe3f9d19da85727b4b6d34bf596ae0e65b5c0e48ddfc75d384d3903e7dd9bc241',
'0x0b93d849aeccd02c809789050ce753171abef866b50436de12b40daa244f2fed',
'0x5936077b0e668efe22c56f5350c2a4801b8ead5059bfded08adcc837e5393014',
'0xc9f9fe58366731a2330cc5f4069e0f30a77559cc51443804af0a0e483fd85765',
'0x29c4ee2fb2040e2a6db3f4de4815228a1fea444611d1db1078f9a8249598e928',
'0x1b955cfcb771cf027a01605cee78b7a6c3582259e87c2353530a9e8740816814' ]
[ '0xd8e9684cd4acd0e28df17a71c0c6b1a2317bc3b6a6530bcf0af108ea80274001',
'0x34c284d67383807092c6add3186328b382b1164975f5387a6e32ca5962ac6f6c',
'0x4e2eecf11ba0a8c4107fc693c609f8ec16eea441313f44d49f080f8e37a3dda2',
'0xe66e5856bdbdb04a5f8925b181ffd5d7f7fdf390926f65fc001c1dcb427034d2',
'0x56af6213ac959c19f794c3df00dc3c9c6ae27a542ea6f66606334037fc90b08c' ]
[ '0xd2631027d84926112b37abf1716adba457523111b869b650c90fe803b02888d3',
'0x871e8e7820620e7a1fea87576d8387e2ca1d20754964dfe4b22d0757544a3ccf',
'0x074516d68e605290ad5494b58848feb36a12fb4cca6a875d9725393b576c441c' ]
[ '0x4d4267e6439f907d4330fb945a7c8ff75b65533d9edc54ebdefb7c56f33820f3',
'0xff58eb23a3139593da470709976eed92196bfb94085c3627c82a1287aa2f4175' ]
Root:
[ '0x893598180931f55c2e4d0ad638fb258455356f9c4177d8340c41ddf756a26222' ]
merk.utils.checkMerkleProof(0, "0xcca476cd0be4e945376bd7e3899136574726b9b587dc5801974f9a1aeaa0be3c", ["0xb18996966b9ba8f7b20ef1ddd3869fa011ccdad71e1b39575929deb7a5ff4a4d","0x34c284d67383807092c6add3186328b382b1164975f5387a6e32ca5962ac6f6c","0x871e8e7820620e7a1fea87576d8387e2ca1d20754964dfe4b22d0757544a3ccf","0xff58eb23a3139593da470709976eed92196bfb94085c3627c82a1287aa2f4175"], "0x893598180931f55c2e4d0ad638fb258455356f9c4177d8340c41ddf756a26222")
var merk = require('./index.js');
var leaves = merk.utils.hash([
{ "address": "0x5a10c2afdbbddd6374318ff754dc1f1852403e2c", "permission": 1 },
{ "address": "0xcf0aa32424fc4cb0e0b6ce34bc5134f2875c068a", "permission": 0 },
{ "address": "0xe8570478ea019edf1deaf114f9545282075bcdeb", "permission": 0 },
{ "address": "0x04cb2f244c974e8607ce25c80860234add63fcc3", "permission": 1 },
{ "address": "0xdf4271fc55e4eca22831543370b2ffda3a0672fe", "permission": 0 },
{ "address": "0xdeb29b6cd55de7c17d1644db71b693f091481bfc", "permission": 0 },
{ "address": "0x770d5125d8eb0fb9851585d55845b782e6769038", "permission": 1 },
{ "address": "0x2d84b2c1512c45f10cc147978deaba04137bae6b", "permission": 1 },
{ "address": "0x1bb6893e787d02ecc4270d87005f65f3a6390820", "permission": 0 },
{ "address": "0xeb4711fa1c9faa6adee37566965fc8f239b0f00c", "permission": 1 }
])
merk.utils.reduceMerkleRoot(leaves)