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