Verify IPFS Multihash

Verify IPFS Multihash

IPFS has a unique way of identifying the data you store with multihash. For example, using ipfs add an_image.png will actually return a mutlihash which you can use to retrieve the file. However, it seems plausible for the front-end (JavaScript) to have the ability to verify it after the content is downloaded. Don’t worry, you are about to grasp it!

Decode Multihash

Multihash actually contains the hash method we use and the hash digest output of the content, encoded by base58. Using multihashes library will make you understand it. In IPFS, the hash method is always sha2-256 right now. The following class CID will help you convert between sha256 hash and multihash.

const mh = require('multihashes');

class CID {
/**
* convert IPFS multihash to sha2-256 hash string
* @param {string} multihash
* @returns {string} sha2-256 hash string starting with 0x
*/
static toHash(multihash) {
return '0x' + mh.decode(mh.fromB58String(multihash)).digest.toString('hex')
}

/**
* convert sha2-256 hash string to IPFS multihash
* @param {string} str
* @returns {string} IPFS multihash starting with Qm
*/
static fromHash(str) {
str = str.startsWith('0x') ? str.slice(2) : str;
return mh.toB58String(mh.encode(Buffer.from(str, 'hex'), 'sha2-256'))
}
}

The input of SHA256

However, the input of SHA256 is the content alone, it’s actually wrapped by protobuf. To get a deep understanding of it, we can do a simple test.

  • create a file containes ipfs-multihash and add it to IPFS

    echo ipfs-multihash > test.txt | ipfs add test.txt

    Its multihash CID will be QmfQj4DUWEudeFdWKVzPaTbYimdYzsp14DZX1VLV1BbtdN.

  • Get the SHA256 hash from the multihash.

    console.log(CID.toHash("QmfQj4DUWEudeFdWKVzPaTbYimdYzsp14DZX1VLV1BbtdN"));

    The result will be 0xfda1ea739791f6784b13590d4be03f6a6dad136eb3ac7615522ad0d910f66cd9

  • hash the file

    ipfs get QmfQj4DUWEudeFdWKVzPaTbYimdYzsp14DZX1VLV1BbtdN \
    && shasum -a 256 < QmfQj4DUWEudeFdWKVzPaTbYimdYzsp14DZX1VLV1BbtdN

    The SHA256 result is b41a3a2a90e84bf773e03a8bd0d0090f5beb42332a6faaa8718db083ebb130dd,

    which clearly is not fda1ea739791f6784b13590d4be03f6a6dad136eb3ac7615522ad0d910f66cd9

  • Using block get instead

    ipfs block get QmfQj4DUWEudeFdWKVzPaTbYimdYzsp14DZX1VLV1BbtdN > block.tmp \
    && shasum -a 256 < block.tmp

    Finally, the SHA256 result is fda1ea739791f6784b13590d4be03f6a6dad136eb3ac7615522ad0d910f66cd9.

    So what is the content of block get anyway?

  • Using protoc to see what actually happens

    protoc --decode_raw < block.tmp

    The result is actually great

    1 {
    1: 2
    2: "ipfs-multihash\n"
    3: 15
    }

    According to how-recreate-a-hash-digest-of-a-multihash-in-ipfs, we know it contains Type, Data and fileSize. A simplified and working example is here:

    syntax = "proto3";

    message PBNode {
    bytes Data = 1;
    }

    message PBLink {
    bytes Hash = 1;
    string Name = 2;
    uint64 Tsize = 3;
    }

    message Data {
    enum DataType {
    Raw = 0;
    Directory = 1;
    File = 2;
    Metadata = 3;
    Symlink = 4;
    HAMTShard = 5;
    }
    DataType Type = 1;
    bytes Data = 2;
    }

Full example

const mh = require('multihashes');
const axios = require('axios');
const crypto = require('crypto');
const protobuf = require("protobufjs");
const IPFS = protobuf.loadSync('./ipfs.proto').lookupType('PBNode');

class CID {
/**
* convert IPFS multihash to sha2-256 hash string
* @param {string} multihash
* @param {boolean} prefix
* @returns {string} sha2-256 hash string starting with 0x
*/
static toHash(multihash, prefix = false) {
return prefix ? '0x' : ''
+ mh.decode(mh.fromB58String(multihash)).digest.toString('hex')
}

/**
* convert sha2-256 hash string to IPFS multihash
* @param {string} str
* @returns {string} IPFS multihash starting with Qm
*/
static fromHash(str) {
str = str.startsWith('0x') ? str.slice(2) : str;
return mh.toB58String(mh.encode(Buffer.from(str, 'hex'), 'sha2-256'))
}

/**
* hash the buffer and get the SHA256 result compatible with IPFS multihash
* @param {Buffer} buf
* @returns {string}
*/
static hash(buf) {
const r = IPFS.encode({
Data: {
Type: 2,
Data: buf,
filesize: buf.length
}

}).finish();
return crypto.createHash('sha256').update(r).digest('hex');
}
}

async function ipfsGet(cid) {
const x = await axios.get(`http://your.address.xxx/ipfs/${cid}`, {
responseType: 'arraybuffer'
});
return Buffer.from(x.data);
}

const r = "QmfQj4DUWEudeFdWKVzPaTbYimdYzsp14DZX1VLV1BbtdN";
const hashFromCID = CID.toHash(r);
console.log(hashFromCID);
ipfsGet(r).then(buf => {
const hashCalculated = CID.hash(buf);
console.log(hashCalculated);
console.log(hashCalculated === hashFromCID);
console.log(CID.fromHash(hashCalculated) === r)
});

module.exports = CID;
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×