-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmicro_ipfs.js
437 lines (390 loc) · 16.3 KB
/
micro_ipfs.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
// 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 }; }