1 | 'use strict';
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 | exports.hashDataPayload = hashDataPayload;
|
7 | exports.hashRawData = hashRawData;
|
8 | exports.decodePrivateKey = decodePrivateKey;
|
9 | exports.signRawData = signRawData;
|
10 | exports.signDataPayload = signDataPayload;
|
11 | exports.makeFullyQualifiedDataId = makeFullyQualifiedDataId;
|
12 | exports.makeMutableDataInfo = makeMutableDataInfo;
|
13 | exports.makeDataTombstone = makeDataTombstone;
|
14 | exports.makeMutableDataTombstones = makeMutableDataTombstones;
|
15 | exports.makeInodeTombstones = makeInodeTombstones;
|
16 | exports.signDataTombstone = signDataTombstone;
|
17 | exports.signMutableDataTombstones = signMutableDataTombstones;
|
18 | exports.makeInodeHeaderBlob = makeInodeHeaderBlob;
|
19 | exports.makeDirInodeBlob = makeDirInodeBlob;
|
20 | exports.makeFileInodeBlob = makeFileInodeBlob;
|
21 | exports.getChildVersion = getChildVersion;
|
22 | exports.inodeDirLink = inodeDirLink;
|
23 | exports.inodeDirUnlink = inodeDirUnlink;
|
24 |
|
25 | var _util = require('./util');
|
26 |
|
27 | var _schemas = require('./schemas');
|
28 |
|
29 | var assert = require('assert');
|
30 | var crypto = require('crypto');
|
31 | var EC = require('elliptic').ec;
|
32 | var ec = EC('secp256k1');
|
33 | var Ajv = require('ajv');
|
34 |
|
35 | var BLOCKSTACK_STORAGE_PROTO_VERSION = 1;
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 | function hashDataPayload(payload_buffer) {
|
46 | var hash = crypto.createHash('sha256');
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | var payload_str = Buffer.from(payload_buffer);
|
52 |
|
53 | hash.update(payload_str.length + ':');
|
54 | hash.update(payload_str);
|
55 | hash.update(',');
|
56 |
|
57 | return hash.digest('hex');
|
58 | }
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 |
|
66 | function hashRawData(payload_buffer) {
|
67 | var hash = crypto.createHash('sha256');
|
68 |
|
69 | hash.update(payload_buffer);
|
70 |
|
71 | return hash.digest('hex');
|
72 | }
|
73 |
|
74 |
|
75 |
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 | function decodeHexString(hex) {
|
82 | var bytes = [];
|
83 | for (var i = 0; i < hex.length - 1; i += 2) {
|
84 | bytes.push(parseInt(hex.substr(i, 2), 16));
|
85 | }
|
86 | return Buffer.from(bytes);
|
87 | }
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 | function decodePrivateKey(privatekey_hex) {
|
98 | if (privatekey_hex.length === 66 && privatekey_hex.slice(64, 66) === '01') {
|
99 |
|
100 | privatekey_hex = privatekey_hex.slice(0, 64);
|
101 | }
|
102 | return decodeHexString(privatekey_hex);
|
103 | }
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 |
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | function signRawData(payload_buffer, privkey_hex, hash) {
|
115 |
|
116 | var privkey = decodePrivateKey(privkey_hex);
|
117 |
|
118 | if (!hash) {
|
119 | hash = hashRawData(payload_buffer);
|
120 | }
|
121 |
|
122 | var sig = ec.sign(hash, privkey, { canonical: true });
|
123 |
|
124 |
|
125 | var r_array = sig.r.toArray();
|
126 | var s_array = sig.s.toArray();
|
127 | var r_buf = Buffer.from(r_array).toString('hex');
|
128 | var s_buf = Buffer.from(s_array).toString('hex');
|
129 |
|
130 | if (r_buf.length < 64) {
|
131 | while (r_buf.length < 64) {
|
132 | r_buf = "0" + r_buf;
|
133 | }
|
134 | }
|
135 |
|
136 | if (s_buf.length < 64) {
|
137 | while (s_buf.length < 64) {
|
138 | s_buf = "0" + s_buf;
|
139 | }
|
140 | }
|
141 |
|
142 | var sig_buf_hex = r_buf + s_buf;
|
143 |
|
144 | assert(sig_buf_hex.length == 128);
|
145 |
|
146 | var sigb64 = Buffer.from(sig_buf_hex, 'hex').toString('base64');
|
147 | return sigb64;
|
148 | }
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 | function signDataPayload(payload_string, privkey_hex) {
|
160 | return signRawData(Buffer.concat([Buffer.from(payload_string.length + ':'), Buffer.from(payload_string), Buffer.from(',')]), privkey_hex);
|
161 | }
|
162 |
|
163 |
|
164 |
|
165 |
|
166 |
|
167 |
|
168 |
|
169 |
|
170 |
|
171 |
|
172 | function makeFullyQualifiedDataId(device_id, data_id) {
|
173 | return escape((device_id + ':' + data_id).replace('/', '\\x2f'));
|
174 | }
|
175 |
|
176 |
|
177 |
|
178 |
|
179 |
|
180 |
|
181 |
|
182 |
|
183 |
|
184 |
|
185 |
|
186 | function makeMutableDataInfo(data_id, data_payload, device_id, version) {
|
187 | var fq_data_id = makeFullyQualifiedDataId(device_id, data_id);
|
188 | var timestamp = new Date().getTime();
|
189 |
|
190 | var ret = {
|
191 | 'fq_data_id': fq_data_id,
|
192 | 'data': data_payload,
|
193 | 'version': version,
|
194 | 'timestamp': timestamp
|
195 | };
|
196 |
|
197 | return ret;
|
198 | }
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 |
|
207 | function makeDataTombstone(tombstone_payload) {
|
208 | var now = parseInt(new Date().getTime() / 1000);
|
209 | return 'delete-' + now + ':' + tombstone_payload;
|
210 | }
|
211 |
|
212 |
|
213 |
|
214 |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 | function makeMutableDataTombstones(device_ids, data_id) {
|
221 | var ts = [];
|
222 | var _iteratorNormalCompletion = true;
|
223 | var _didIteratorError = false;
|
224 | var _iteratorError = undefined;
|
225 |
|
226 | try {
|
227 | for (var _iterator = device_ids[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
|
228 | var device_id = _step.value;
|
229 |
|
230 | ts.push(makeDataTombstone(makeFullyQualifiedDataId(device_id, data_id)));
|
231 | }
|
232 | } catch (err) {
|
233 | _didIteratorError = true;
|
234 | _iteratorError = err;
|
235 | } finally {
|
236 | try {
|
237 | if (!_iteratorNormalCompletion && _iterator.return) {
|
238 | _iterator.return();
|
239 | }
|
240 | } finally {
|
241 | if (_didIteratorError) {
|
242 | throw _iteratorError;
|
243 | }
|
244 | }
|
245 | }
|
246 |
|
247 | return ts;
|
248 | }
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 | function makeInodeTombstones(datastore_id, inode_uuid, device_ids) {
|
260 | assert(device_ids.length > 0);
|
261 |
|
262 | var header_id = datastore_id + '.' + inode_uuid + '.hdr';
|
263 | var header_tombstones = makeMutableDataTombstones(device_ids, header_id);
|
264 |
|
265 | var idata_id = datastore_id + '.' + inode_uuid;
|
266 | var idata_tombstones = makeMutableDataTombstones(device_ids, idata_id);
|
267 |
|
268 | return header_tombstones.concat(idata_tombstones);
|
269 | }
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 | function signDataTombstone(tombstone, privkey) {
|
280 | var sigb64 = signRawData(tombstone, privkey);
|
281 | return tombstone + ':' + sigb64;
|
282 | }
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 | function signMutableDataTombstones(tombstones, privkey) {
|
293 | var sts = [];
|
294 | var _iteratorNormalCompletion2 = true;
|
295 | var _didIteratorError2 = false;
|
296 | var _iteratorError2 = undefined;
|
297 |
|
298 | try {
|
299 | for (var _iterator2 = tombstones[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
|
300 | var ts = _step2.value;
|
301 |
|
302 | sts.push(signDataTombstone(ts, privkey));
|
303 | }
|
304 | } catch (err) {
|
305 | _didIteratorError2 = true;
|
306 | _iteratorError2 = err;
|
307 | } finally {
|
308 | try {
|
309 | if (!_iteratorNormalCompletion2 && _iterator2.return) {
|
310 | _iterator2.return();
|
311 | }
|
312 | } finally {
|
313 | if (_didIteratorError2) {
|
314 | throw _iteratorError2;
|
315 | }
|
316 | }
|
317 | }
|
318 |
|
319 | ;
|
320 | return sts;
|
321 | }
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 | function makeInodeHeaderBlob(datastore_id, inode_type, owner_id, inode_uuid, data_hash, device_id, version) {
|
337 |
|
338 | var header = {
|
339 | 'type': inode_type,
|
340 | 'owner': owner_id,
|
341 | 'uuid': inode_uuid,
|
342 | 'readers': [],
|
343 | 'data_hash': data_hash,
|
344 | 'version': version,
|
345 | 'proto_version': BLOCKSTACK_STORAGE_PROTO_VERSION
|
346 | };
|
347 |
|
348 | var valid = null;
|
349 | var ajv = new Ajv();
|
350 | try {
|
351 | valid = ajv.validate(_schemas.MUTABLE_DATUM_INODE_HEADER_SCHEMA, header);
|
352 | assert(valid);
|
353 | } catch (e) {
|
354 | console.log('header: ' + JSON.stringify(header));
|
355 | console.log('schema: ' + JSON.stringify(_schemas.MUTABLE_DATUM_INODE_HEADER_SCHEMA));
|
356 | console.log(e.stack);
|
357 | throw e;
|
358 | }
|
359 |
|
360 | var inode_data_id = datastore_id + '.' + inode_uuid + '.hdr';
|
361 | var inode_data_payload = (0, _util.jsonStableSerialize)(header);
|
362 | var inode_header_blob = makeMutableDataInfo(inode_data_id, inode_data_payload, device_id, version);
|
363 | return (0, _util.jsonStableSerialize)(inode_header_blob);
|
364 | }
|
365 |
|
366 |
|
367 |
|
368 |
|
369 |
|
370 |
|
371 |
|
372 |
|
373 |
|
374 |
|
375 |
|
376 |
|
377 | function makeDirInodeBlob(datastore_id, owner_id, inode_uuid, dir_listing, device_id, version) {
|
378 |
|
379 | var ajv = new Ajv();
|
380 | var valid = null;
|
381 | try {
|
382 | valid = ajv.validate(_schemas.MUTABLE_DATUM_DIR_IDATA_SCHEMA.properties.children, dir_listing);
|
383 | assert(valid);
|
384 | } catch (e) {
|
385 | console.log('dir listing: ' + JSON.stringify(dir_listing));
|
386 | console.log('schema: ' + JSON.stringify(_schemas.MUTABLE_DATUM_DIR_IDATA_SCHEMA));
|
387 | throw e;
|
388 | }
|
389 |
|
390 | if (!version) {
|
391 | version = 1;
|
392 | }
|
393 |
|
394 | var empty_hash = '0000000000000000000000000000000000000000000000000000000000000000';
|
395 | var internal_header_blob = makeInodeHeaderBlob(datastore_id, _schemas.MUTABLE_DATUM_DIR_TYPE, owner_id, inode_uuid, empty_hash, device_id, version);
|
396 |
|
397 |
|
398 | var internal_header = JSON.parse(JSON.parse(internal_header_blob).data);
|
399 | var idata_payload = {
|
400 | children: dir_listing,
|
401 | header: internal_header
|
402 | };
|
403 |
|
404 | var idata_payload_str = (0, _util.jsonStableSerialize)(idata_payload);
|
405 | var idata_hash = hashDataPayload(idata_payload_str);
|
406 |
|
407 | var header_blob = makeInodeHeaderBlob(datastore_id, _schemas.MUTABLE_DATUM_DIR_TYPE, owner_id, inode_uuid, idata_hash, device_id, version);
|
408 | return { 'header': header_blob, 'idata': idata_payload_str };
|
409 | }
|
410 |
|
411 |
|
412 |
|
413 |
|
414 |
|
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 | function makeFileInodeBlob(datastore_id, owner_id, inode_uuid, data_hash, device_id, version) {
|
423 |
|
424 | var header_blob = makeInodeHeaderBlob(datastore_id, _schemas.MUTABLE_DATUM_FILE_TYPE, owner_id, inode_uuid, data_hash, device_id, version);
|
425 | return { 'header': header_blob };
|
426 | }
|
427 |
|
428 |
|
429 |
|
430 |
|
431 |
|
432 |
|
433 |
|
434 |
|
435 | function getChildVersion(parent_dir, child_name) {
|
436 | assert(parent_dir['idata']['children'][child_name]);
|
437 | return parent_dir['idata']['children'][child_name].version;
|
438 | }
|
439 |
|
440 |
|
441 |
|
442 |
|
443 |
|
444 |
|
445 |
|
446 |
|
447 |
|
448 |
|
449 |
|
450 |
|
451 | function inodeDirLink(parent_dir, child_type, child_name, child_uuid, exists) {
|
452 |
|
453 | assert(parent_dir['type'] === _schemas.MUTABLE_DATUM_DIR_TYPE);
|
454 | assert(parent_dir['idata']);
|
455 | assert(parent_dir['idata']['children']);
|
456 |
|
457 | if (!exists) {
|
458 | assert(!Object.keys(parent_dir['idata']['children']).includes(child_name));
|
459 | }
|
460 |
|
461 | var new_dirent = {
|
462 | uuid: child_uuid,
|
463 | type: child_type,
|
464 | version: 1
|
465 | };
|
466 |
|
467 | if (parent_dir['idata']['children']['version']) {
|
468 | new_dirent.version = parent_dir['idata']['children']['version'] + 1;
|
469 | }
|
470 |
|
471 | parent_dir['idata']['children'][child_name] = new_dirent;
|
472 | parent_dir['version'] += 1;
|
473 | return parent_dir;
|
474 | }
|
475 |
|
476 |
|
477 |
|
478 |
|
479 |
|
480 |
|
481 |
|
482 |
|
483 |
|
484 | function inodeDirUnlink(parent_dir, child_name) {
|
485 |
|
486 | assert(parent_dir['type'] === _schemas.MUTABLE_DATUM_DIR_TYPE);
|
487 | assert(parent_dir['idata']);
|
488 | assert(parent_dir['idata']['children']);
|
489 |
|
490 | assert(Object.keys(parent_dir['idata']['children']).includes(child_name));
|
491 |
|
492 | delete parent_dir['idata']['children'][child_name];
|
493 | parent_dir['version'] += 1;
|
494 | return parent_dir;
|
495 | } |
\ | No newline at end of file |