// for nodejs if (typeof sha256 == 'undefined' && typeof require != 'undefined') { try { sha256 = require('./sha256.js').sha256; } catch (e) {console.log(e);} } var micro_ipfs = (function() { var hexVarintToInteger = function(str) { var s = String(str); var total = 0; var offset = 1; for (var i = 0; i < s.length; i += 2) { var byte = parseInt(s.substring(i, i+2), 16); var isLast = null; if (byte >= 128) { byte -= 128; isLast = false; } else { isLast = true; } total += byte * offset; offset *= Math.pow(2,7); } return total; }; var hexStringToIntegerList = function(str) { var s = String(str); var result = []; for (var i = 0; i < s.length; i+=2) { result[i/2] = parseInt(s.substring(i, i+2), 16); } return result; }; var hexStringToString = function(str) { var s = String(str); var result = ''; for (var i = 0; i < s.length; i+=2) { result += String.fromCharCode(parseInt(s.substring(i, i+2), 16)); } return result; }; var sha256IntegerListToMultihash = function(base, lst) { // 0x20 is the length of the hash. var i = 0; var result = []; if (base == 32) { // For some reason these are present in the base32 CIDs but not in the base16 CIDs result[i++] = parseInt('01', 16); result[i++] = parseInt('70', 16); } result[i++] = parseInt('12', 16); result[i++] = parseInt('20', 16); for (var j = 0; j < lst.length; j++) { result[j+i] = lst[j]; } return result; }; var integerListToLowercaseBase16Multibase = function(lst) { var result = ''; for (var i = 0; i < lst.length; i++) { var hex = lst[i].toString(16); if (hex.length < 2) { hex = '0' + hex; } result += hex; } return 'f' + result; }; var int8ListToBitList = function(lst) { var result = []; for (var i = 0; i < lst.length; i++) { result[i*8+0] = (lst[i] & 128) ? 1 : 0; result[i*8+1] = (lst[i] & 64) ? 1 : 0; result[i*8+2] = (lst[i] & 32) ? 1 : 0; result[i*8+3] = (lst[i] & 16) ? 1 : 0; result[i*8+4] = (lst[i] & 8) ? 1 : 0; result[i*8+5] = (lst[i] & 4) ? 1 : 0; result[i*8+6] = (lst[i] & 2) ? 1 : 0; result[i*8+7] = (lst[i] & 1) ? 1 : 0; } return result; }; var base32StringToBitList = function(str) { var baseChars = 'abcdefghijklmnopqrstuvwxyz234567'; var s = String(str); var result = []; for (var i = 0; i < s.length; i++) { var part = baseChars.indexOf(s[i]); //for (var j = 0; j < 6; j++) { // result[i*6+j] = (part & Math.pow(2, 6-1-j)) ? 1 : 0; //} result[i*5+0] = (part & 16) ? 1 : 0; result[i*5+1] = (part & 8) ? 1 : 0; result[i*5+2] = (part & 4) ? 1 : 0; result[i*5+3] = (part & 2) ? 1 : 0; result[i*5+4] = (part & 1) ? 1 : 0; } return result; }; // https://gist.github.com/diafygi/90a3e80ca1c2793220e5/, license: wtfpl var from_b58 = function(S,A){var d=[],b=[],i,j,c,n;for(i in S){j=0,c=A.indexOf(S[i]);if(c<0)return undefined;c||b.length^i?i:b.push(0);while(j in d||c){n=d[j];n=n?n*58+c:c;c=n>>8;d[j]=n%256;j++}}while(j--)b.push(d[j]);return new Uint8Array(b)}; var base58StringToHexString = function(str) { var baseChars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; var ints = from_b58(String(str), baseChars); var result = ''; for (var i = 0; i < ints.length; i++) { var hex = ints[i].toString(16); if (hex.length < 2) { hex = '0' + hex; } result += hex; } return result; }; var integerListToLowercaseBase32Multibase = function(lst) { var baseChars = 'abcdefghijklmnopqrstuvwxyz234567'; var result = ''; var l = int8ListToBitList(lst); for (var i = 0; i < l.length; i+= 5) { var get = function(j) { return ((i+j) < l.length) ? l[i+j] : 0; }; var part = get(0) * 16 + get(1) * 8 + get(2) * 4 + get(3) * 2 + get(4) * 1; result += baseChars[part]; } return 'b' + result; }; var base32StringToBase16LowercaseMultibase = function(str) { var baseChars = '0123456789abcdef'; var result = ''; var l = base32StringToBitList(str); for (var i = 0; i < l.length; i+= 4) { var get = function(j) { return ((i+j) < l.length) ? l[i+j] : 0; }; var part = get(0) * 8 + get(1) * 4 + get(2) * 2 + get(3) * 1; result += baseChars[part]; } return 'f' + result; }; var integerToHexVarint = function(i) { // This function takes a JavaScript integer and returns a hexadecimal string representing that integer encoded as a protobuf varint according to the rules explained at // https://developers.google.com/protocol-buffers/docs/encoding var result = ''; if (i < 0) { throw "Negative integers are supported by Varints but not by this implementation."; } else if (i == 0) { return '00'; } else { while (i > 0) { // Get the least significant 7 bits (0..127) of the integer and shift the rest var leastSignificantBits = i & 127; i = i >> 7; // if this is not the last chunk, set the most significant bit to indicate that the value will be continued in the next byte(s). if (i > 0) { leastSignificantBits |= 128; } // Convert to hexadecimal and pad with 0 to get two digits if needed var hex = leastSignificantBits.toString(16); if (hex.length < 2) { hex = '0' + hex; } result += hex; } return result; } } var utf8StringToHex = function(str) { // The input must already be a string for which .charCodeAt() always returns a value <256 (i.e. a string encoded into utf-8 and those values re-encoded into javascript's internal utf-16) var s = String(str); var result = ''; for (var i = 0; i < s.length; i++) { var hex = s.charCodeAt(i).toString(16); if (hex.length < 2) { hex = '0' + hex; } result += hex; } return result; }; var ipfsBlockWithLinks = function(object) { // object should be an { "Links": links, "Data": hex-encoded string } object // Aside from encoding differences, it should match the contents of the "ipfs object get --data-encoding=base64 Qm…hash" command // // "Links" should be an array of { 'Hash': cidv1Base16Lowercase, 'Size': Integer } objects. // This functions returns a hexadecimal string which encodes the ipfs block with the given links. // This is a partial implementation which is barely sufficient for re-hashing a file, many of the configurable values are hardcoded. // A Qm…hash can be converted to a "CIDv1 base16 lowercase" hash on the command-line using the following code: // ipfs cid format -v 1 -b base16 -f='%m' Qm…hash // // "File" should be the hex-encoded (base 16, lowercase, no prefix) data, or "false" when the entry is not a DAG leaf // // The "Data" field as given by the following command // ipfs object get --data-encoding=base64 Qm…hash | jq -r '.Data' | base64 -d | xxd -ps // is automatically generated using the "File" field if present and the various sizes etc. var links = object.Links; var fileHex = object.File; var result = ''; for (var i = 0; i < links.length; i++) { var cid = links[i].Hash; var size = links[i].Size; var name = links[i].Name; var fileHex = object.File; result += '12'; var encodedLink = '' // Some sort of separator or terminator encodedLink += '0a'; // size of the CID (32 bytes + 2 bytes header = 34 bytes = 22 in hex) encodedLink += '22'; if (cid[0] != 'f' || cid.length != 69) { if (cid[0] == 'Q' && cid[1] == 'm' && cid.length == 46) { cid = 'f' + base58StringToHexString(cid); if (cid[0] != 'f' || cid.length != 69) { throw "Internal error"; } } else { throw "Expected a lowercase base16 CIDv1 or a Qm…hash in base58 (length 46). The base16 encoding should start with 'f'" + /*+*/ " and have a length of 32 bytes (64 hexadecimal characters) plus the leading prefix 'f1220' (length of 69 characters in total)" + /*+*/ " as described in https://github.com/multiformats/multibase. The given hash started with " + cid[0] + " and had a length of " + cid.length; } } // Add the CID. encodedLink += cid.substring(1); // Add a second hardcoded part of the encoding. encodedLink += '12'; // length of filename encodedLink += integerToHexVarint(name.length); encodedLink += utf8StringToHex(name); encodedLink += '18'; // Add the size. encodedLink += integerToHexVarint(size); var encodedLinkSize = encodedLink.length/2 result += integerToHexVarint(encodedLinkSize); result += encodedLink; } // Generate the "Data" field var totalSize = (fileHex || '').length / 2; for (var i = 0; i < object.Links.length; i++) { totalSize += object.Links[i].ContentSize; } var encodedData = ''; if (object.isFile) { // '08' '02' encodedData += '08' + '02'; // field 12 seems to be optional (for DAG nodes with links (groups of blocks and directories)) if (fileHex !== false) { encodedData += '12'; encodedData += integerToHexVarint(totalSize); encodedData += fileHex; } // '18' [8f b0 15 = total size of contents of the file = 35022300] encodedData += '18' + integerToHexVarint(totalSize); for (var j = 0; j < object.Links.length; j++) { // 20 [80 80 10 = size of contents of block 1 = 262144] // 20 [8f b0 05 = size of contents of block 2 = 88079] encodedData += '20'; encodedData += integerToHexVarint(object.Links[j].ContentSize); } } else { // directory encodedData += '08' + '01'; } // Some sort of separator or terminator result += '0a'; var encodedDataSize = encodedData.length / 2; result += integerToHexVarint(encodedDataSize); result += encodedData; return result; }; var ipfsHashWithLinks = function(base, object) { var block = hexStringToIntegerList(ipfsBlockWithLinks(object)); //console.time('sha256'); var sha = sha256(block); //console.timeEnd('sha256'); var hash = sha256IntegerListToMultihash(base, sha); if (base == 16) { return { "hash" : integerListToLowercaseBase16Multibase(hash), "block" : block }; } else { return { "hash" : integerListToLowercaseBase32Multibase(hash), "block" : block }; } }; return { utf8StringToHex: utf8StringToHex, hashWithLinks: ipfsHashWithLinks }; })(); var ipfs_self_hash = (function() { var ipfs = micro_ipfs; var get_root_with_vanity = function(vanity_attempt, ipfs_directory_hashes) { var find_link_entry = function() { for (var i = 0; i < ipfs_directory_hashes.tree.Links.length; i++) { if (ipfs_directory_hashes.tree.Links[i].Name == 'directory_hashes.js') { return i; } } console.error(ipfs_directory_hashes); console.error(ipfs_directory_hashes.tree.Links); throw "Could not find entry for directory_hashes.js"; } var foo_link_entry = find_link_entry(); ipfs_directory_hashes.tree.Links[foo_link_entry].Hash = ""; ipfs_directory_hashes.tree.Links[foo_link_entry].Size = 0; ipfs_directory_hashes.vanity_number = vanity_attempt; // TODO: using JSON.stringify to recreate the file is more brittle, better store the stringified version as a hex string, and then decode it? var file_directory_hashes = 'jsonp_ipfs_directory_hashes(' + JSON.stringify(ipfs_directory_hashes) + ');\n'; var foo = ipfs.hashWithLinks(16, { "Links": [], "isFile": true, "File": ipfs.utf8StringToHex(file_directory_hashes) }); ipfs_directory_hashes.tree.Links[foo_link_entry].Hash = foo.hash; ipfs_directory_hashes.tree.Links[foo_link_entry].Size = foo.block.length; root = ipfs.hashWithLinks(32, ipfs_directory_hashes.tree); return root; }; var expected_vanity_attempt = 32*32*32; var max_vanity_attempt = expected_vanity_attempt*10; function find_vanity_node(vanity_text, vanity_attempt, ipfs_directory_hashes) { if ((! (typeof vanity_text == 'string' || vanity_text instanceof String)) || vanity_text.length != 3) { throw 'find_vanity_node(vanity_text, ...) : expected a string of length 3'; } var offset = 1; switch (vanity_text[2]) { case '4': case 'a': case 'e': case 'i': case 'm': case 'q': case 'u': case 'y': offset = 0; } while (true) { if (vanity_attempt > max_vanity_attempt) { // give up: throw 'Failed to brute-force a number that generates the desired vanity text.'; } else { var root = get_root_with_vanity(vanity_attempt, ipfs_directory_hashes); if (root.hash[root.hash.length-1-offset] == vanity_text[2] && root.hash[root.hash.length-2-offset] == vanity_text[1]) { console.error(vanity_attempt + ' (' + Math.floor(100*vanity_attempt/expected_vanity_attempt) + '%)'); if (root.hash[root.hash.length-3-offset] == vanity_text[0]) { return vanity_attempt; } } vanity_attempt++; } } }; function find_vanity_browser_(offset, old_root, vanity_text, vanity_attempt, callback, ipfs_directory_hashes) { var root = get_root_with_vanity(vanity_attempt, ipfs_directory_hashes); if (vanity_attempt > max_vanity_attempt) { // give up: root = get_root_with_vanity(ipfs_directory_hashes.vanity_number, ipfs_directory_hashes) callback(root, 'timeout', false); } else { if (root.hash[root.hash.length-1-offset] == vanity_text[2]) { callback(old_root, '… ' + vanity_attempt + ' (' + Math.floor(100*vanity_attempt/expected_vanity_attempt) + '%)', false); if (root.hash[root.hash.length-2-offset] == vanity_text[1] && root.hash[root.hash.length-3-offset] == vanity_text[0]) { callback(root, vanity_attempt, true); } else { window.setTimeout(function() { find_vanity_browser_(offset, old_root, vanity_text, vanity_attempt + 1, callback, ipfs_directory_hashes); }, 0); } } else { window.setTimeout(function() { find_vanity_browser_(offset, old_root, vanity_text, vanity_attempt + 1, callback, ipfs_directory_hashes); }, 0); } } }; var find_vanity_browser = function(old_root, vanity_text, vanity_attempt, callback, ipfs_directory_hashes) { if ((! (typeof vanity_text == 'string' || vanity_text instanceof String)) || vanity_text.length != 3) { throw 'find_vanity_node(vanity_text, ...) : expected a string of length 3'; } var offset = 1; switch (vanity_text[2]) { case '4': case 'a': case 'e': case 'i': case 'm': case 'q': case 'u': case 'y': offset = 0; } return find_vanity_browser(offset, old_root, vanity_text, vanity_attempt, callback, ipfs_directory_hashes) }; var debug = function(show_link) { var root = get_root_with_vanity(ipfs_directory_hashes.vanity_number, ipfs_directory_hashes); var vanity_text = ipfs_directory_hashes.vanity_text; if (root.hash[root.hash.length-1] == vanity_text[2] && root.hash[root.hash.length-2] == vanity_text[1] && root.hash[root.hash.length-3] == vanity_text[0]) { // vanity check is ok show_link(root, ipfs_directory_hashes.vanity_number, true); } else { // Brute-force to try to find a number that gives the desired last 3 characters show_link(root, '…', false); find_vanity_browser(root, vanity_text, 0, show_link, ipfs_directory_hashes); } }; var get_link = function get_link() { var root = get_root_with_vanity(ipfs_directory_hashes.vanity_number, ipfs_directory_hashes); return root.hash; }; return { get_link: get_link, find_vanity_browser: find_vanity_browser, find_vanity_node: find_vanity_node }; })(); function jsonp_ipfs_directory_hashes(arg) { ipfs_directory_hashes = arg; } if (typeof module != 'undefined') { module.exports = { micro_ipfs : micro_ipfs, ipfs_self_hash : ipfs_self_hash }; }