UNPKG

blockstack-storage

Version:

The Blockstack Javascript library for storage.

283 lines (244 loc) 7.58 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.hashDataPayload = hashDataPayload; exports.signRawData = signRawData; exports.signDataPayload = signDataPayload; exports.makeDataInfo = makeDataInfo; exports.makeDataTombstone = makeDataTombstone; exports.makeDataTombstones = makeDataTombstones; exports.signDataTombstone = signDataTombstone; exports.signDataTombstones = signDataTombstones; exports.parseDataTombstone = parseDataTombstone; exports.parseFullyQualifiedDataId = parseFullyQualifiedDataId; var _util = require('./util'); var _blockstack = require('blockstack'); var assert = require('assert'); var crypto = require('crypto'); var EC = require('elliptic').ec; var ec = EC('secp256k1'); var Ajv = require('ajv'); var BigInteger = require('bigi'); var bitcoinjs = require('bitcoinjs-lib'); /* * Hash an inode payload and its length. * Specifically hash `${payload.length}:${payload},` * * @param payload_buffer (String) the payload to hash * * Return the sha256 */ function hashDataPayload(payload_buffer) { var hash = crypto.createHash('sha256'); // this forces the hash to be computed over the bytestring // (important when computing length!) which will make it // match with the Python hash verifier var payload_str = Buffer.from(payload_buffer); hash.update(payload_str.length + ':'); hash.update(payload_str); hash.update(','); return hash.digest('hex'); } /* * Sign a string of data. * * @param payload_buffer (Buffer) the buffer to sign * @param privkey_hex (String) the hex-encoded ECDSA private key * @param hash (String) optional; the hash of the payload. payload_buffer can be null if hash is given. * * Return the base64-encoded signature */ function signRawData(payload_buffer, privkey_hex, hash) { var privkey = (0, _blockstack.decodePrivateKey)(privkey_hex); if (!hash) { hash = (0, _util.hashRawData)(payload_buffer); } var sig = ec.sign(hash, privkey, { canonical: true }); // use signature encoding compatible with Blockstack var r_array = sig.r.toArray(); var s_array = sig.s.toArray(); var r_buf = Buffer.from(r_array).toString('hex'); var s_buf = Buffer.from(s_array).toString('hex'); if (r_buf.length < 64) { while (r_buf.length < 64) { r_buf = "0" + r_buf; } } if (s_buf.length < 64) { while (s_buf.length < 64) { s_buf = "0" + s_buf; } } var sig_buf_hex = r_buf + s_buf; assert(sig_buf_hex.length == 128); var sigb64 = Buffer.from(sig_buf_hex, 'hex').toString('base64'); return sigb64; } /* * Sign a data payload and its length. * Specifically sign `${payload.length}:${payload},` * * @payload_string (String) the string to sign * @privkey_hex (String) the hex-encoded private key * * Return the base64-encoded signature */ function signDataPayload(payload_string, privkey_hex) { return signRawData(Buffer.concat([Buffer.from(payload_string.length + ':'), Buffer.from(payload_string), Buffer.from(',')]), privkey_hex); } /* * Make a mutable data payload * * @param data_id (String) the data identifier (not fully qualified) * @param data_payload (String) the data payload to store * @param device_id (String) the ID of the device creating this data * * Returns an mutable data payload object. */ function makeDataInfo(data_id, data_payload, device_id) { var fq_data_id = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; if (!fq_data_id) { fq_data_id = (0, _blockstack.makeFullyQualifiedDataId)(device_id, data_id); } var timestamp = new Date().getTime(); var ret = { 'fq_data_id': fq_data_id, 'data': data_payload, 'version': 1, 'timestamp': timestamp }; return ret; } /* * Make a single datum tombstone. * * @param tombstone_payload (String) the string that encodes the tombstone * * Returns the tombstone (to be fed into the storage driver) */ function makeDataTombstone(tombstone_payload) { var now = parseInt(new Date().getTime()); return 'delete-' + now + ':' + tombstone_payload; } /* * Make a list of data tombstones. * * @param device_ids (Array) the list of device IDs * @param data_id (String) the datum ID * * Returns a list of tombstones. */ function makeDataTombstones(device_ids, data_id) { var ts = []; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = device_ids[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var device_id = _step.value; ts.push(makeDataTombstone((0, _blockstack.makeFullyQualifiedDataId)(device_id, data_id))); } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } return ts; } /* * Sign a datum tombstone * * @param tombstone (String) the tombstone string * @param privkey (String) the hex-encoded private key * * Returns the signed tombstone as a String */ function signDataTombstone(tombstone, privkey) { var sigb64 = signRawData(tombstone, privkey); return tombstone + ':' + sigb64; } /* * Sign a list of mutable data tombstones * * @param tobmstones (Array) the list of per-device tombstones * @param privkey (String) the hex-encoded private key * * Returns the list of signed tombstones as an Array. */ function signDataTombstones(tombstones, privkey) { var sts = []; var _iteratorNormalCompletion2 = true; var _didIteratorError2 = false; var _iteratorError2 = undefined; try { for (var _iterator2 = tombstones[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { var ts = _step2.value; sts.push(signDataTombstone(ts, privkey)); } } catch (err) { _didIteratorError2 = true; _iteratorError2 = err; } finally { try { if (!_iteratorNormalCompletion2 && _iterator2.return) { _iterator2.return(); } } finally { if (_didIteratorError2) { throw _iteratorError2; } } } ; return sts; } /* * Parse a (unsigned) data tombstone * * @param tombstone (string) the tombstone payload * * Returns an object with: * .timestamp (int) the timestamp of the tombstone (in milliseconds) * .id (string) the data ID of the tombstone * * Returns null on failure */ function parseDataTombstone(tombstone) { var re = new RegExp(OP_TOMBSTONE_PATTERN); var groups = re.exec(tombstone); if (!groups) { return null; } var ts = parseInt(groups[1]); var data_id = groups[2]; return { 'timestamp': ts, 'id': data_id }; } /* * Parse a fully-qualified data ID * * @param fq_data_id (string) the fully-qualified data ID * * Returns an object with: * .device_id: the device identifier * .data_id: the device-specific data ID * * Returns null on failure to parse */ function parseFullyQualifiedDataId(fq_data_id) { fq_data_id = unescape(fq_data_id).replace("\\x2f", "/"); var parts = fq_data_id.split(":", 2); if (parts.length != 2) { return null; } return { 'device_id': parts[0], 'data_id': parts[1] }; }