@browser-network/database
Version:
A type of distributed database built on top of the distributed browser-network
1,389 lines (1,244 loc) • 772 kB
JavaScript
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Db = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
exports.__esModule = true;
exports.LocalDB = void 0;
var buildStorageShim = function () {
// @ts-ignore
var localStorageShim = {};
localStorageShim.setItem = function (key, val) {
localStorageShim[key] = val;
};
localStorageShim.getItem = function (key) {
return localStorageShim[key];
};
localStorageShim.removeItem = function (key) {
delete localStorageShim[key];
};
return localStorageShim;
};
var LocalDB = /** @class */ (function () {
function LocalDB(appId) {
var _this = this;
this.keyPrefix = 'db';
this.getByKey = function (key) {
var gotten = _this.localStorage.getItem(key);
if (!gotten)
return null;
return JSON.parse(gotten);
};
this.appId = appId;
this.localStorage = globalThis.localStorage || buildStorageShim();
}
LocalDB.prototype.set = function (val, address) {
var key = this.buildKey(address);
this.localStorage.setItem(key, JSON.stringify(val));
};
LocalDB.prototype.get = function (address) {
var key = this.buildKey(address);
return this.getByKey(key);
};
LocalDB.prototype.getAll = function () {
var _this = this;
var lsKeys = Object.keys(this.localStorage);
var ourKeys = lsKeys.filter(function (key) { return key.indexOf(_this.buildKey('')) > -1; });
return ourKeys.map(this.getByKey);
};
// Remove a single item
LocalDB.prototype.remove = function (address) {
var key = this.buildKey(address);
this.localStorage.removeItem(key);
};
// Remove all items
LocalDB.prototype.clear = function () {
var _this = this;
this.getAll().forEach(function (wrapped) {
_this.remove(wrapped.id);
});
};
LocalDB.prototype.buildKey = function (address) {
return "".concat(this.keyPrefix, "-").concat(this.appId, "-").concat(address);
};
return LocalDB;
}());
exports.LocalDB = LocalDB;
});
},{}],2:[function(require,module,exports){
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "@browser-network/crypto", "./LocalDB", "./util"], factory);
}
})(function (require, exports) {
"use strict";
exports.__esModule = true;
var bnc = __importStar(require("@browser-network/crypto"));
var LocalDB_1 = require("./LocalDB");
var util_1 = require("./util");
var debug = (0, util_1.debugFactory)('Db');
// convenience
var wrapState = function (state, address, pub, priv) { return __awaiter(void 0, void 0, void 0, function () {
var wrapp, signature, wrapped;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
wrapp = {
id: address,
timestamp: Date.now(),
state: state,
publicKey: pub
};
return [4 /*yield*/, bnc.sign(priv, wrapp)];
case 1:
signature = _a.sent();
wrapped = Object.assign(wrapp, { signature: signature });
return [2 /*return*/, wrapped];
}
});
}); };
// convenience
var verifySignature = function (update) {
var signature = update.signature, wrapp = __rest(update, ["signature"]);
return bnc.verifySignature(wrapp, signature, update.publicKey);
};
// TODO I've seen a loop before, where state-offering messages are being spammed.
// I don't know how this would have happened as it seems to be hard coded to send only
// once per 5 seconds.
var Db = /** @class */ (function () {
function Db(_a) {
var secret = _a.secret, appId = _a.appId, network = _a.network;
var _this = this;
this._denyList = {};
this._allowList = {};
this._onChangeHandlers = [];
/**
* @description Get all entries from our local DB, wrapped in the DB's
* WrappedState type.
*
* @TODO: Does it need to be wrapped? Does the user ever care about this
* wrapping or should they just be able to go straight to their state?
*/
this.getAll = function () { return _this.localDB.getAll(); };
/**
* @description Clear the local storage of everyone's items. Essentially resets the machine
* to as if it's never seen the network before. If it is connected still, it will
* rapidly start to repopulate.
*/
this.clear = function () {
_this.localDB.clear();
_this.runChangeHandlers();
};
/**
* @description Effectively blocks a user. Adds them to our deny list, which means we'll no longer
* accept updates from them, which means we will no longer forward their updates as well. Also
* removes their state from our storage.
*
* It's up to the developer to keep track of these (probably
* within the state object that they store in this db), and repopulate this list on startup.
* Calling deny with an address that's already blocked is a noop and O(1) time so don't worry about
* spamming this call.
*/
this.deny = function (address) {
if (_this._denyList[address]) {
return;
}
_this._denyList[address] = true;
_this.localDB.remove(address);
};
/**
* @description Unblock a user. Removes them from our deny list, at which point the DB will naturally
* start to repopulate that user's state.
*/
this.undeny = function (address) {
delete _this._denyList[address];
};
/**
* @description Add a user to our allow list. Once a single user is on this list, _only users on the
* allow list will be recorded in the database_. All other users will automatically be ignored.
*
* It's up to the developer to keep track of these (probably
* within the state object that they store in this db), and repopulate this list on startup.
* Calling allow with an address that's already on the list is a noop and O(1) time so don't worry about
* spamming this call.
*/
this.allow = function (address) {
if (_this._allowList[address]) {
return;
}
_this._allowList[address] = true;
};
/**
* @description Remove a user from the allow list. Calling this will remove the user's state from
* our storage, and that user's state will no longer be forwarded either.
*/
this.unallow = function (address) {
delete _this._allowList[address];
_this.localDB.remove(address);
};
this.onMessage = function (message) {
switch (message.type) {
case 'state-offering': {
debug(5, 'received state-offering:', message);
return _this.onStateOffering(message.data, message.address);
}
case 'state-request': {
debug(5, 'received state-request:', message);
return _this.broadcastStateUpdateByStateId(message.data);
}
case 'state-update': {
debug(5, 'received state-update:', message);
return _this.onStateUpdate(message.data);
}
}
};
/**
* We will periodically inform the network of what states we have
* and how old they are. If someone else hears that we have a state
* newer than what they have on record, they can send us a request for
* what we have.
*/
this.broadcastStateOfferings = function () {
var offerings = {};
for (var _i = 0, _a = _this.localDB.getAll(); _i < _a.length; _i++) {
var state = _a[_i];
offerings[state.id] = state.timestamp;
}
_this.network.broadcast({
type: 'state-offering',
appId: _this.appId,
data: offerings
});
};
this.runChangeHandlers = function () {
_this._onChangeHandlers.forEach(function (handler) { return handler(); });
};
this.networkId = network.networkId;
this.appId = appId;
this.localDB = new LocalDB_1.LocalDB(appId);
this.address = network.address;
this.secret = secret;
this.network = network;
this.network.on('message', function (message) {
if (message.appId !== _this.appId)
return;
_this.onMessage(message);
});
// Here we derive the pub key from the private
this.publicKey = bnc.derivePubKey(secret);
setInterval(this.broadcastStateOfferings, 5000);
}
/**
* @description This is how you write data to the network. This will put whatever
* state you give it into a DB specific wrapper with your state in the `state` key.
*/
Db.prototype.set = function (state) {
return __awaiter(this, void 0, void 0, function () {
var data;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, wrapState(state, this.address, this.publicKey, this.secret)];
case 1:
data = _a.sent();
this.setLocal(data);
// send update into the network
this.broadcastStateUpdate(data);
return [2 /*return*/];
}
});
});
};
/**
* @description Get the state of the user whose address is passed in.
*/
Db.prototype.get = function (address) {
return this.localDB.get(address);
};
/**
* @description This will fire every time we update our state. This way reactive
* UIs can listen for changes and update based on the new state of the world
*/
Db.prototype.onChange = function (handler) {
this._onChangeHandlers.push(handler);
};
/**
* @description clear the DB of all listeners.
*/
Db.prototype.removeChangeHandlers = function () {
this._onChangeHandlers = [];
};
/**
* @description clear the DB of a specific listener.
*/
Db.prototype.removeChangeHandler = function (func) {
var _this = this;
var handlers = Array.from(this._onChangeHandlers);
handlers.forEach(function (handler, i) {
if (handler === func) {
_this._onChangeHandlers.splice(i, 1);
}
});
};
Db.prototype.onStateOffering = function (offerings, sender) {
var _this = this;
var requestState = function (address) {
_this.network.broadcast({
type: 'state-request',
appId: _this.appId,
destination: sender,
data: address
});
};
for (var remoteId in offerings) {
var remoteStateTimestamp = offerings[remoteId];
var localState = this.localDB.get(remoteId);
if (this.isForbidden(remoteId)) {
continue;
}
if (!localState) {
// we don't even have this state yet
requestState(remoteId);
}
else if (localState.timestamp < remoteStateTimestamp) {
// This means they have a newer offering, for which we will now ask
requestState(remoteId);
} // Otherwise we have a newer or equal version
}
};
Db.prototype.onStateUpdate = function (update) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (this.isForbidden(update.publicKey)) {
return [2 /*return*/, debug(5, 'state update from pubKey not allowed:', update.publicKey)];
}
debug(5, 'received update from another node:', update);
return [4 /*yield*/, this.verify(update)];
case 1:
if (!(_a.sent())) {
return [2 /*return*/];
}
this.setLocal(update);
return [2 /*return*/];
}
});
});
};
// We won't accept state if:
// * The sender is on our deny list, or
// * We have an allow list going and the sender is not on it
Db.prototype.isForbidden = function (address) {
var isForbidden = this._denyList[address] ||
(Object.keys(this._allowList).length > 0 && !this._allowList[address]);
return isForbidden;
};
// Broadcast an update for a specific state id
Db.prototype.broadcastStateUpdate = function (data) {
this.network.broadcast({
type: 'state-update',
appId: this.appId,
data: data
});
};
Db.prototype.broadcastStateUpdateByStateId = function (stateId) {
var data = this.localDB.get(stateId);
if (!data) {
return;
}
this.broadcastStateUpdate(data);
};
Db.prototype.setLocal = function (wrapped) {
this.localDB.set(wrapped, wrapped.id);
// Every time we set local, we've updated our storage, and we
// want to inform the user as such
this.runChangeHandlers();
};
Db.prototype.verify = function (update) {
return __awaiter(this, void 0, void 0, function () {
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// 1. verify timestamp is newer than last
if (!this.isNew(update)) {
return [2 /*return*/, false];
}
return [4 /*yield*/, verifySignature(update)];
case 1:
// 2. check veracity of signature
if (!(_a.sent())) {
debug(1, 'update does not pass verification! update, local version:', update, this.localDB.get(update.id));
// TODO add motrucka to rude list
return [2 /*return*/, false];
}
return [2 /*return*/, true];
}
});
});
};
Db.prototype.isNew = function (update) {
var local = this.get(update.id);
if (!local)
return true;
return update.timestamp > local.timestamp;
};
return Db;
}());
exports["default"] = Db;
});
},{"./LocalDB":1,"./util":3,"@browser-network/crypto":4}],3:[function(require,module,exports){
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
exports.__esModule = true;
exports.exhaustive = exports.isPromise = exports.debugFactory = void 0;
var debugFactory = function (appName) {
return function (logLevel) {
var args = [];
for (var _i = 1; _i < arguments.length; _i++) {
args[_i - 1] = arguments[_i];
}
if (globalThis['DEBUG'] >= logLevel) {
console.log.apply(console, __spreadArray(["[".concat(logLevel, "] ").concat(appName, ": ")], args, false));
}
};
};
exports.debugFactory = debugFactory;
var isPromise = function (promise) {
return promise.hasOwnProperty('then');
};
exports.isPromise = isPromise;
var exhaustive = function (arg) {
throw new Error('can not have gotten here');
};
exports.exhaustive = exhaustive;
});
},{}],4:[function(require,module,exports){
(function (Buffer){(function (){
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "crypto", "eccrypto"], factory);
}
})(function (require, exports) {
"use strict";
exports.__esModule = true;
exports.generateSecret = exports.derivePubKey = exports.verifySignature = exports.sign = exports.hash = exports.stob = exports.btos = void 0;
var crypto_1 = require("crypto");
var eccrypto = __importStar(require("eccrypto"));
// Buffer to string. eccrypto uses buffers for everything but we want to use strings.
// This is to convert.
var btos = function (buffer) { return buffer.toString('hex'); };
exports.btos = btos;
// String to buffer. Takes a string, like a key, and turns it into a buffer that
// eccrypto can use.
var stob = function (str) { return Buffer.from(str, 'hex'); };
exports.stob = stob;
// Generate a hash of any arbitrary data, so long as it's JSON stringifiable.
var hash = function (data) { return (0, crypto_1.createHash)('sha256').update(JSON.stringify(data)).digest(); };
exports.hash = hash;
// Take an object and create a signature for it based on a given private key.
var sign = function (secret, obj) { return __awaiter(void 0, void 0, void 0, function () {
var stringState, hashBuff, keyBuff, sigBuff, signature;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
stringState = JSON.stringify(obj);
hashBuff = (0, exports.hash)(stringState);
keyBuff = (0, exports.stob)(secret);
return [4 /*yield*/, eccrypto.sign(keyBuff, hashBuff)];
case 1:
sigBuff = _a.sent();
signature = (0, exports.btos)(sigBuff);
return [2 /*return*/, signature];
}
});
}); };
exports.sign = sign;
// Take an object, signature and pub key, and ensure that the signature matches the object
// given the public key.
var verifySignature = function (object, signature, publicKey) { return __awaiter(void 0, void 0, void 0, function () {
var stringObj, hashBuf, pubBuf, sigBuf, e_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
stringObj = JSON.stringify(object);
hashBuf = (0, exports.hash)(stringObj);
pubBuf = (0, exports.stob)(publicKey);
sigBuf = (0, exports.stob)(signature);
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, eccrypto.verify(pubBuf, hashBuf, sigBuf)];
case 2:
_a.sent();
return [2 /*return*/, true];
case 3:
e_1 = _a.sent();
return [2 /*return*/, false];
case 4: return [2 /*return*/];
}
});
}); };
exports.verifySignature = verifySignature;
// Derive an EC public key from a given private key.
var derivePubKey = function (secret) {
return (0, exports.btos)(eccrypto.getPublicCompressed((0, exports.stob)(secret)));
};
exports.derivePubKey = derivePubKey;
// Generate the type of private key that network uses for its
// cryptography. Generating one of these is as good as creating a new identity
// on the network.
var generateSecret = function () {
var privBuf = eccrypto.generatePrivate();
return (0, exports.btos)(privBuf);
};
exports.generateSecret = generateSecret;
});
// TODO encrypt, decrypt, diffie helman
}).call(this)}).call(this,require("buffer").Buffer)
},{"buffer":67,"crypto":75,"eccrypto":87}],5:[function(require,module,exports){
'use strict';
const asn1 = exports;
asn1.bignum = require('bn.js');
asn1.define = require('./asn1/api').define;
asn1.base = require('./asn1/base');
asn1.constants = require('./asn1/constants');
asn1.decoders = require('./asn1/decoders');
asn1.encoders = require('./asn1/encoders');
},{"./asn1/api":6,"./asn1/base":8,"./asn1/constants":12,"./asn1/decoders":14,"./asn1/encoders":17,"bn.js":19}],6:[function(require,module,exports){
'use strict';
const encoders = require('./encoders');
const decoders = require('./decoders');
const inherits = require('inherits');
const api = exports;
api.define = function define(name, body) {
return new Entity(name, body);
};
function Entity(name, body) {
this.name = name;
this.body = body;
this.decoders = {};
this.encoders = {};
}
Entity.prototype._createNamed = function createNamed(Base) {
const name = this.name;
function Generated(entity) {
this._initNamed(entity, name);
}
inherits(Generated, Base);
Generated.prototype._initNamed = function _initNamed(entity, name) {
Base.call(this, entity, name);
};
return new Generated(this);
};
Entity.prototype._getDecoder = function _getDecoder(enc) {
enc = enc || 'der';
// Lazily create decoder
if (!this.decoders.hasOwnProperty(enc))
this.decoders[enc] = this._createNamed(decoders[enc]);
return this.decoders[enc];
};
Entity.prototype.decode = function decode(data, enc, options) {
return this._getDecoder(enc).decode(data, options);
};
Entity.prototype._getEncoder = function _getEncoder(enc) {
enc = enc || 'der';
// Lazily create encoder
if (!this.encoders.hasOwnProperty(enc))
this.encoders[enc] = this._createNamed(encoders[enc]);
return this.encoders[enc];
};
Entity.prototype.encode = function encode(data, enc, /* internal */ reporter) {
return this._getEncoder(enc).encode(data, reporter);
};
},{"./decoders":14,"./encoders":17,"inherits":137}],7:[function(require,module,exports){
'use strict';
const inherits = require('inherits');
const Reporter = require('../base/reporter').Reporter;
const Buffer = require('safer-buffer').Buffer;
function DecoderBuffer(base, options) {
Reporter.call(this, options);
if (!Buffer.isBuffer(base)) {
this.error('Input not Buffer');
return;
}
this.base = base;
this.offset = 0;
this.length = base.length;
}
inherits(DecoderBuffer, Reporter);
exports.DecoderBuffer = DecoderBuffer;
DecoderBuffer.isDecoderBuffer = function isDecoderBuffer(data) {
if (data instanceof DecoderBuffer) {
return true;
}
// Or accept compatible API
const isCompatible = typeof data === 'object' &&
Buffer.isBuffer(data.base) &&
data.constructor.name === 'DecoderBuffer' &&
typeof data.offset === 'number' &&
typeof data.length === 'number' &&
typeof data.save === 'function' &&
typeof data.restore === 'function' &&
typeof data.isEmpty === 'function' &&
typeof data.readUInt8 === 'function' &&
typeof data.skip === 'function' &&
typeof data.raw === 'function';
return isCompatible;
};
DecoderBuffer.prototype.save = function save() {
return { offset: this.offset, reporter: Reporter.prototype.save.call(this) };
};
DecoderBuffer.prototype.restore = function restore(save) {
// Return skipped data
const res = new DecoderBuffer(this.base);
res.offset = save.offset;
res.length = this.offset;
this.offset = save.offset;
Reporter.prototype.restore.call(this, save.reporter);
return res;
};
DecoderBuffer.prototype.isEmpty = function isEmpty() {
return this.offset === this.length;
};
DecoderBuffer.prototype.readUInt8 = function readUInt8(fail) {
if (this.offset + 1 <= this.length)
return this.base.readUInt8(this.offset++, true);
else
return this.error(fail || 'DecoderBuffer overrun');
};
DecoderBuffer.prototype.skip = function skip(bytes, fail) {
if (!(this.offset + bytes <= this.length))
return this.error(fail || 'DecoderBuffer overrun');
const res = new DecoderBuffer(this.base);
// Share reporter state
res._reporterState = this._reporterState;
res.offset = this.offset;
res.length = this.offset + bytes;
this.offset += bytes;
return res;
};
DecoderBuffer.prototype.raw = function raw(save) {
return this.base.slice(save ? save.offset : this.offset, this.length);
};
function EncoderBuffer(value, reporter) {
if (Array.isArray(value)) {
this.length = 0;
this.value = value.map(function(item) {
if (!EncoderBuffer.isEncoderBuffer(item))
item = new EncoderBuffer(item, reporter);
this.length += item.length;
return item;
}, this);
} else if (typeof value === 'number') {
if (!(0 <= value && value <= 0xff))
return reporter.error('non-byte EncoderBuffer value');
this.value = value;
this.length = 1;
} else if (typeof value === 'string') {
this.value = value;
this.length = Buffer.byteLength(value);
} else if (Buffer.isBuffer(value)) {
this.value = value;
this.length = value.length;
} else {
return reporter.error('Unsupported type: ' + typeof value);
}
}
exports.EncoderBuffer = EncoderBuffer;
EncoderBuffer.isEncoderBuffer = function isEncoderBuffer(data) {
if (data instanceof EncoderBuffer) {
return true;
}
// Or accept compatible API
const isCompatible = typeof data === 'object' &&
data.constructor.name === 'EncoderBuffer' &&
typeof data.length === 'number' &&
typeof data.join === 'function';
return isCompatible;
};
EncoderBuffer.prototype.join = function join(out, offset) {
if (!out)
out = Buffer.alloc(this.length);
if (!offset)
offset = 0;
if (this.length === 0)
return out;
if (Array.isArray(this.value)) {
this.value.forEach(function(item) {
item.join(out, offset);
offset += item.length;
});
} else {
if (typeof this.value === 'number')
out[offset] = this.value;
else if (typeof this.value === 'string')
out.write(this.value, offset);
else if (Buffer.isBuffer(this.value))
this.value.copy(out, offset);
offset += this.length;
}
return out;
};
},{"../base/reporter":10,"inherits":137,"safer-buffer":166}],8:[function(require,module,exports){
'use strict';
const base = exports;
base.Reporter = require('./reporter').Reporter;
base.DecoderBuffer = require('./buffer').DecoderBuffer;
base.EncoderBuffer = require('./buffer').EncoderBuffer;
base.Node = require('./node');
},{"./buffer":7,"./node":9,"./reporter":10}],9:[function(require,module,exports){
'use strict';
const Reporter = require('../base/reporter').Reporter;
const EncoderBuffer = require('../base/buffer').EncoderBuffer;
const DecoderBuffer = require('../base/buffer').DecoderBuffer;
const assert = require('minimalistic-assert');
// Supported tags
const tags = [
'seq', 'seqof', 'set', 'setof', 'objid', 'bool',
'gentime', 'utctime', 'null_', 'enum', 'int', 'objDesc',
'bitstr', 'bmpstr', 'charstr', 'genstr', 'graphstr', 'ia5str', 'iso646str',
'numstr', 'octstr', 'printstr', 't61str', 'unistr', 'utf8str', 'videostr'
];
// Public methods list
const methods = [
'key', 'obj', 'use', 'optional', 'explicit', 'implicit', 'def', 'choice',
'any', 'contains'
].concat(tags);
// Overrided methods list
const overrided = [
'_peekTag', '_decodeTag', '_use',
'_decodeStr', '_decodeObjid', '_decodeTime',
'_decodeNull', '_decodeInt', '_decodeBool', '_decodeList',
'_encodeComposite', '_encodeStr', '_encodeObjid', '_encodeTime',
'_encodeNull', '_encodeInt', '_encodeBool'
];
function Node(enc, parent, name) {
const state = {};
this._baseState = state;
state.name = name;
state.enc = enc;
state.parent = parent || null;
state.children = null;
// State
state.tag = null;
state.args = null;
state.reverseArgs = null;
state.choice = null;
state.optional = false;
state.any = false;
state.obj = false;
state.use = null;
state.useDecoder = null;
state.key = null;
state['default'] = null;
state.explicit = null;
state.implicit = null;
state.contains = null;
// Should create new instance on each method
if (!state.parent) {
state.children = [];
this._wrap();
}
}
module.exports = Node;
const stateProps = [
'enc', 'parent', 'children', 'tag', 'args', 'reverseArgs', 'choice',
'optional', 'any', 'obj', 'use', 'alteredUse', 'key', 'default', 'explicit',
'implicit', 'contains'
];
Node.prototype.clone = function clone() {
const state = this._baseState;
const cstate = {};
stateProps.forEach(function(prop) {
cstate[prop] = state[prop];
});
const res = new this.constructor(cstate.parent);
res._baseState = cstate;
return res;
};
Node.prototype._wrap = function wrap() {
const state = this._baseState;
methods.forEach(function(method) {
this[method] = function _wrappedMethod() {
const clone = new this.constructor(this);
state.children.push(clone);
return clone[method].apply(clone, arguments);
};
}, this);
};
Node.prototype._init = function init(body) {
const state = this._baseState;
assert(state.parent === null);
body.call(this);
// Filter children
state.children = state.children.filter(function(child) {
return child._baseState.parent === this;
}, this);
assert.equal(state.children.length, 1, 'Root node can have only one child');
};
Node.prototype._useArgs = function useArgs(args) {
const state = this._baseState;
// Filter children and args
const children = args.filter(function(arg) {
return arg instanceof this.constructor;
}, this);
args = args.filter(function(arg) {
return !(arg instanceof this.constructor);
}, this);
if (children.length !== 0) {
assert(state.children === null);
state.children = children;
// Replace parent to maintain backward link
children.forEach(function(child) {
child._baseState.parent = this;
}, this);
}
if (args.length !== 0) {
assert(state.args === null);
state.args = args;
state.reverseArgs = args.map(function(arg) {
if (typeof arg !== 'object' || arg.constructor !== Object)
return arg;
const res = {};
Object.keys(arg).forEach(function(key) {
if (key == (key | 0))
key |= 0;
const value = arg[key];
res[value] = key;
});
return res;
});
}
};
//
// Overrided methods
//
overrided.forEach(function(method) {
Node.prototype[method] = function _overrided() {
const state = this._baseState;
throw new Error(method + ' not implemented for encoding: ' + state.enc);
};
});
//
// Public methods
//
tags.forEach(function(tag) {
Node.prototype[tag] = function _tagMethod() {
const state = this._baseState;
const args = Array.prototype.slice.call(arguments);
assert(state.tag === null);
state.tag = tag;
this._useArgs(args);
return this;
};
});
Node.prototype.use = function use(item) {
assert(item);
const state = this._baseState;
assert(state.use === null);
state.use = item;
return this;
};
Node.prototype.optional = function optional() {
const state = this._baseState;
state.optional = true;
return this;
};
Node.prototype.def = function def(val) {
const state = this._baseState;
assert(state['default'] === null);
state['default'] = val;
state.optional = true;
return this;
};
Node.prototype.explicit = function explicit(num) {
const state = this._baseState;
assert(state.explicit === null && state.implicit === null);
state.explicit = num;
return this;
};
Node.prototype.implicit = function implicit(num) {
const state = this._baseState;
assert(state.explicit === null && state.implicit === null);
state.implicit = num;
return this;
};
Node.prototype.obj = function obj() {
const state = this._baseState;
const args = Array.prototype.slice.call(arguments);
state.obj = true;
if (args.length !== 0)
this._useArgs(args);
return this;
};
Node.prototype.key = function key(newKey) {
const state = this._baseState;
assert(state.key === null);
state.key = newKey;
return this;
};
Node.prototype.any = function any() {
const state = this._baseState;
state.any = true;
return this;
};
Node.prototype.choice = function choice(obj) {
const state = this._baseState;
assert(state.choice === null);
state.choice = obj;
this._useArgs(Object.keys(obj).map(function(key) {
return obj[key];
}));
return this;
};
Node.prototype.contains = function contains(item) {
const state = this._baseState;
assert(state.use === null);
state.contains = item;
return this;
};
//
// Decoding
//
Node.prototype._decode = function decode(input, options) {
const state = this._baseState;
// Decode root node
if (state.parent === null)
return input.wrapResult(state.children[0]._decode(input, options));
let result = state['default'];
let present = true;
let prevKey = null;
if (state.key !== null)
prevKey = input.enterKey(state.key);
// Check if tag is there
if (state.optional) {
let tag = null;
if (state.explicit !== null)
tag = state.explicit;
else if (state.implicit !== null)
tag = state.implicit;
else if (state.tag !== null)
tag = state.tag;
if (tag === null && !state.any) {
// Trial and Error
const save = input.save();
try {
if (state.choice === null)
this._decodeGeneric(state.tag, input, options);
else
this._decodeChoice(input, options);
present = true;
} catch (e) {
present = false;
}
input.restore(save);
} else {
present = this._peekTag(input, tag, state.any);
if (input.isError(present))
return present;
}
}
// Push object on stack
let prevObj;
if (state.obj && present)
prevObj = input.enterObject();
if (present) {
// Unwrap explicit values
if (state.explicit !== null) {
const explicit = this._decodeTag(input, state.explicit);
if (input.isError(explicit))
return explicit;
input = explicit;
}
const start = input.offset;
// Unwrap implicit and normal values
if (state.use === null && state.choice === null) {
let save;
if (state.any)
save = input.save();
const body = this._decodeTag(
input,
state.implicit !== null ? state.implicit : state.tag,
state.any
);
if (input.isError(body))
return body;
if (state.any)
result = input.raw(save);
else
input = body;
}
if (options && options.track && state.tag !== null)
options.track(input.path(), start, input.length, 'tagged');
if (options && options.track && state.tag !== null)
options.track(input.path(), input.offset, input.length, 'content');
// Select proper method for tag
if (state.any) {
// no-op
} else if (state.choice === null) {
result = this._decodeGeneric(state.tag, input, options);
} else {
result = this._decodeChoice(input, options);
}
if (input.isError(result))
return result;
// Decode children
if (!state.any && state.choice === null && state.children !== null) {
state.children.forEach(function decodeChildren(child) {
// NOTE: We are ignoring errors here, to let parser continue with other
// parts of encoded data
child._decode(input, options);
});
}
// Decode contained/encoded by schema, only in bit or octet strings
if (state.contains && (state.tag === 'octstr' || state.tag === 'bitstr')) {
const data = new DecoderBuffer(result);
result = this._getUse(state.contains, input._reporterState.obj)
._decode(data, options);
}
}
// Pop object
if (state.obj && present)
result = input.leaveObject(prevObj);
// Set key
if (state.key !== null && (result !== null || present === true))
input.leaveKey(prevKey, state.key, result);
else if (prevKey !== null)
input.exitKey(prevKey);
return result;
};
Node.prototype._decodeGeneric = function decodeGeneric(tag, input, options) {
const state = this._baseState;
if (tag === 'seq' || tag === 'set')
return null;
if (tag === 'seqof' || tag === 'setof')
return this._decodeList(input, tag, state.args[0], options);
else if (/str$/.test(tag))
return this._decodeStr(input, tag, options);
else if (tag === 'objid' && state.args)
return this._decodeObjid(input, state.args[0], state.args[1], options);
else if (tag === 'objid')
return this._decodeObjid(input, null, null, options);
else if (tag === 'gentime' || tag === 'utctime')
return this._decodeTime(input, tag, options);
else if (tag === 'null_')
return this._decodeNull(input, options);
else if (tag === 'bool')
return this._decodeBool(input, options);
else if (tag === 'objDesc')
return this._decodeStr(input, tag, options);
else if (tag === 'int' || tag === 'enum')
return this._decodeInt(input, state.args && state.args[0], options);
if (state.use !== null) {
return this._getUse(state.use, input._reporterState.obj)
._decode(input, options);
} else {
return input.error('unknown tag: ' + tag);
}
};
Node.prototype._getUse = function _getUse(entity, obj) {
const state = this._baseState;
// Create altered use decoder if implicit is set
state.useDecoder = this._use(entity, obj);
assert(state.useDecoder._baseState.parent === null);
state.useDecoder = state.useDecoder._baseState.children[0];
if (state.implicit !== state.useDecoder._baseState.implicit) {
state.useDecoder = state.useDecoder.clone();
state.useDecoder._baseState.implicit = state.implicit;
}
return state.useDecoder;
};
Node.prototype._decodeChoice = function decodeChoice(input, options) {
const state = this._baseState;
let result = null;
let match = false;
Object.keys(state.choice).some(function(key) {
const save = input.save();
const node = state.choice[key];
try {
const value = node._decode(input, options);
if (input.isError(value))
return false;
result = { type: key, value: value };
match = true;
} catch (e) {
input.restore(save);
return false;
}
return true;
}, this);
if (!match)
return input.error('Choice not matched');
return result;
};
//
// Encoding
//
Node.prototype._createEncoderBuffer = function createEncoderBuffer(data) {
return new EncoderBuffer(data, this.reporter);
};
Node.prototype._encode = function encode(data, reporter, parent) {
const state = this._baseState;
if (state['default'] !== null && state['default'] === data)
return;
const result = this._encodeValue(data, reporter, p