UNPKG

safe-coins-wallet

Version:
616 lines (541 loc) 21 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } /* Module which takes care of the SAFE Network altcoins wallet functioning */ var crypto = require('crypto'); var ACTION_INSERT = 'Insert'; var TYPE_TAG_WALLET = 1012017; var TYPE_TAG_WALLET_TX_INBOX = 20082018; var TYPE_TAG_THANKS_COIN = 21082018; var COIN_ENTRY_KEY_DATA = 'coin-data'; var TX_INBOX_ENTRY_KEY_ENC_PK = '__tx_enc_pk'; var TX_INBOX_ENTRY_KEY_WALLET_PK = '__wallet_pk'; var TX_INBOX_METADATA_NAME = 'ThanksCoins TX Inbox'; var TX_INBOX_METADATA_DESC = 'Container to receive notifications of ThanksCoins transactions'; var WALLET_ENTRY_KEY_COINS = '__coins'; var WALLET_METADATA_NAME = 'ThanksCoins Wallet'; var WALLET_METADATA_DESC = 'Container to store the list of ThanksCoins addresses owned'; var ENTRY_KEY_MD_METADATA = '_metadata'; // Generic Helper functions var _fromArrayBuffer = function _fromArrayBuffer(buf) { return String.fromCharCode.apply(null, new Uint8Array(buf)); }; var _genXorName = function _genXorName(safeApp, str) { return safeApp.crypto.sha3Hash(str || ''); }; // Altcoin Wallet management functions var _readEncryptedEntry = function _readEncryptedEntry(md, key) { var encKey, encValue; return Promise.resolve().then(function () { return md.encryptKey(key); }).then(function (_resp) { encKey = _resp; return md.get(encKey); }).then(function (_resp) { encValue = _resp; return md.decrypt(encValue.buf); }); }; var _insertEntriesEncrypted = function _insertEntriesEncrypted(safeApp, md, data) { function _recursive() { if (i < keys.length) { return Promise.resolve().then(function () { key = keys[i]; return md.encryptKey(key); }).then(function (_resp) { encKey = _resp; return md.encryptValue(data[key]); }).then(function (_resp) { encValue = _resp; return mutations.insert(encKey, encValue); }).then(function () { i++; return _recursive(); }); } } var mutations, keys, i, key, encKey, encValue; return Promise.resolve().then(function () { return safeApp.mutableData.newMutation(); }).then(function (_resp) { mutations = _resp; keys = Object.keys(data); i = 0; return _recursive(); }).then(function () { return md.applyEntriesMutation(mutations); }).then(function () {}); }; var createWallet = function createWallet(safeApp, pk) { var emptyCoins, encKeyPair, secEncKey, nonce, xorName, walletMd, serialisedWallet, walletArr; return Promise.resolve().then(function () { console.log("Creating the coin wallet..."); emptyCoins = _defineProperty({}, WALLET_ENTRY_KEY_COINS, JSON.stringify([])); return safeApp.crypto.generateEncKeyPair(); }).then(function (_resp) { encKeyPair = _resp; return encKeyPair.secEncKey.getRaw(); }).then(function (_resp) { secEncKey = _resp; return safeApp.crypto.generateNonce(); }).then(function (_resp) { nonce = _resp; return _genXorName(safeApp, pk); }).then(function (_resp) { xorName = _resp; return safeApp.mutableData.newPrivate(xorName, TYPE_TAG_WALLET, secEncKey, nonce); }).then(function (_resp) { walletMd = _resp; // TODO: support the case that it exists already return walletMd.quickSetup({}, WALLET_METADATA_NAME, WALLET_METADATA_DESC); }).then(function () { return _insertEntriesEncrypted(safeApp, walletMd, emptyCoins); }).then(function () { return walletMd.serialise(); }).then(function (_resp) { serialisedWallet = _resp; walletArr = new Uint8Array(serialisedWallet); return walletArr.toString(); }); }; var _deserialiseArray = function _deserialiseArray(strOrBuffer) { var arrItems = strOrBuffer.split(','); return Uint8Array.from(arrItems); }; var loadWalletData = function loadWalletData(safeApp, serialisedWallet) { var deserialisedWallet, walletMd, coins; return Promise.resolve().then(function () { console.log("Reading the coin wallet info..."); deserialisedWallet = _deserialiseArray(serialisedWallet); return safeApp.mutableData.fromSerial(deserialisedWallet); }).then(function (_resp) { walletMd = _resp; return _readEncryptedEntry(walletMd, WALLET_ENTRY_KEY_COINS); }).then(function (_resp) { coins = _resp; return JSON.parse(_fromArrayBuffer(coins)); }); }; var storeCoinsToWallet = function storeCoinsToWallet(safeApp, serialisedWallet, coins) { var walletMd, encKey, currentCoins, mutations, encValue; return Promise.resolve().then(function () { console.log("Saving coins in the wallet on the network..."); return safeApp.mutableData.fromSerial(_deserialiseArray(serialisedWallet)); }).then(function (_resp) { walletMd = _resp; return walletMd.encryptKey(WALLET_ENTRY_KEY_COINS); }).then(function (_resp) { encKey = _resp; return walletMd.get(encKey); }).then(function (_resp) { currentCoins = _resp; return safeApp.mutableData.newMutation(); }).then(function (_resp) { mutations = _resp; return walletMd.encryptValue(JSON.stringify(coins)); }).then(function (_resp) { encValue = _resp; return mutations.update(encKey, encValue, currentCoins.version + 1); }).then(function () { return walletMd.applyEntriesMutation(mutations); }).then(function () {}); }; // TX Inbox management functions var _genKeyPair = function _genKeyPair(safeApp) { var encKeyPair, rawPubEncKey, rawSecEncKey, rawKeyPair; return Promise.resolve().then(function () { return safeApp.crypto.generateEncKeyPair(); }).then(function (_resp) { encKeyPair = _resp; return encKeyPair.pubEncKey.getRaw(); }).then(function (_resp) { rawPubEncKey = _resp; return encKeyPair.secEncKey.getRaw(); }).then(function (_resp) { rawSecEncKey = _resp; rawKeyPair = { pk: rawPubEncKey.buffer.toString('hex'), sk: rawSecEncKey.buffer.toString('hex') }; return rawKeyPair; }); }; var createTxInbox = function createTxInbox(safeApp, pk) { var _baseInbox, encKeys, baseInbox, xorName, inboxMd, permSet; return Promise.resolve().then(function () { console.log("Creating TX inbox..."); return _genKeyPair(safeApp); }).then(function (_resp) { encKeys = _resp; baseInbox = (_baseInbox = {}, _defineProperty(_baseInbox, TX_INBOX_ENTRY_KEY_WALLET_PK, pk), _defineProperty(_baseInbox, TX_INBOX_ENTRY_KEY_ENC_PK, encKeys.pk), _baseInbox); return _genXorName(safeApp, pk); }).then(function (_resp) { xorName = _resp; return safeApp.mutableData.newPublic(xorName, TYPE_TAG_WALLET_TX_INBOX); }).then(function (_resp) { inboxMd = _resp; return inboxMd.quickSetup(baseInbox, TX_INBOX_METADATA_NAME, TX_INBOX_METADATA_DESC); }).then(function () { permSet = [ACTION_INSERT]; return inboxMd.setUserPermissions(window.safe.CONSTANTS.USER_ANYONE, permSet, 1); }).then(function () { return encKeys; }); }; var _encrypt = function _encrypt(safeApp, input, pk) { var pubEncKey, encrypted; return Promise.resolve().then(function () { if (Array.isArray(input)) { input = input.toString(); } return safeApp.crypto.pubEncKeyFromRaw(Buffer.from(pk, 'hex')); }).then(function (_resp) { pubEncKey = _resp; return pubEncKey.encryptSealed(input); }).then(function (_resp) { encrypted = _resp; return encrypted; }); }; var _decryptTxs = function _decryptTxs(safeApp, encryptedTxs, encPk, encSk) { function _recursive2() { if (i < encryptedTxs.length) { return Promise.resolve().then(function () { encTx = encryptedTxs[i]; rawPk = Buffer.from(encPk, 'hex'); rawSk = Buffer.from(encSk, 'hex'); return safeApp.crypto.generateEncKeyPairFromRaw(rawPk, rawSk); }).then(function (_resp) { encKeyPair = _resp; return encKeyPair.decryptSealed(encTx.txInfo); }).then(function (_resp) { decrypted = _resp; parsedTxInfo = JSON.parse(_fromArrayBuffer(decrypted)); decryptedTxs.push(_extends({ id: encTx.id, version: encTx.version }, parsedTxInfo)); i++; return _recursive2(); }); } } var decryptedTxs, i, encTx, rawPk, rawSk, encKeyPair, decrypted, parsedTxInfo; return Promise.resolve().then(function () { decryptedTxs = []; i = 0; return _recursive2(); }).then(function () { return decryptedTxs; }); }; var readTxInboxData = function readTxInboxData(safeApp, pk, encPk, encSk) { var encryptedTxs, xorName, inboxMd, entries, entriesList, decryptedTxs; return Promise.resolve().then(function () { encryptedTxs = []; return _genXorName(safeApp, pk); }).then(function (_resp) { xorName = _resp; return safeApp.mutableData.newPublic(xorName, TYPE_TAG_WALLET_TX_INBOX); }).then(function (_resp) { inboxMd = _resp; return inboxMd.getEntries(); }).then(function (_resp) { entries = _resp; return entries.listEntries(); }).then(function (_resp) { entriesList = _resp; entriesList.forEach(function (entry) { var id = _fromArrayBuffer(entry.key); var txInfo = entry.value.buf; // Ignore the Public encryption key entry, the metadata entry, and soft-deleted entries. if (id !== TX_INBOX_ENTRY_KEY_WALLET_PK && id !== TX_INBOX_ENTRY_KEY_ENC_PK && id !== ENTRY_KEY_MD_METADATA && txInfo.length > 0) { encryptedTxs.push({ id: id, txInfo: txInfo, version: entry.value.version }); } }); return _decryptTxs(safeApp, encryptedTxs, encPk, encSk); }).then(function (_resp) { decryptedTxs = _resp; return decryptedTxs; }); }; var removeTxInboxData = function removeTxInboxData(safeApp, pk, txs) { function _recursive3() { if (i < txs.length) { return Promise.resolve().then(function () { tx = txs[i]; return mutations.delete(tx.id, tx.version + 1); }).then(function () { i++; return _recursive3(); }); } } var mutations, i, tx, xorName, txInboxMd; return Promise.resolve().then(function () { return safeApp.mutableData.newMutation(); }).then(function (_resp) { mutations = _resp; i = 0; return _recursive3(); }).then(function () { return _genXorName(safeApp, pk); }).then(function (_resp) { xorName = _resp; return safeApp.mutableData.newPublic(xorName, TYPE_TAG_WALLET_TX_INBOX); }).then(function (_resp) { txInboxMd = _resp; return txInboxMd.applyEntriesMutation(mutations); }).then(function () {}); }; var _fetchTxInboxFromWebId = function _fetchTxInboxFromWebId(safeApp, webId) { var _ref, webIdMd, resourceType, webIdRdf, baseUri, walletGraph, SAFETERMS, xornameMatch, typetagMatch, txInboxMd, xorName, typeTag, WALLETTERMS, hasMeAlready, webIdWithHashTag, walletInboxMatch, txInboxXorUrl, _ref2, content; return Promise.resolve().then(function () { return safeApp.fetch(webId); }).then(function (_resp) { _ref = _resp; webIdMd = _ref.content; resourceType = _ref.resourceType; if (resourceType !== 'RDF') { throw 'Service is not mapped to a WebID RDF'; }webIdRdf = webIdMd.emulateAs('rdf'); return webIdRdf.nowOrWhenFetched(); }).then(function () { baseUri = webId.split('#')[0]; // first try to find old format 'walletInbox' graph walletGraph = webIdRdf.sym(baseUri + '/walletInbox'); SAFETERMS = webIdRdf.namespace('http://safenetwork.org/safevocab/'); xornameMatch = webIdRdf.statementsMatching(walletGraph, SAFETERMS('xorName'), undefined); typetagMatch = webIdRdf.statementsMatching(walletGraph, SAFETERMS('typeTag'), undefined); txInboxMd = void 0; if (xornameMatch[0] && typetagMatch[0]) { return Promise.resolve().then(function () { xorName = xornameMatch[0].object.value.split(','); typeTag = parseInt(typetagMatch[0].object.value); return safeApp.mutableData.newPublic(xorName, typeTag); }).then(function (_resp) { txInboxMd = _resp; }); } else { return Promise.resolve().then(function () { // let's try to find the new format for wallet inbox XOR-URL WALLETTERMS = webIdRdf.namespace('https://w3id.org/cc#'); hasMeAlready = webId.includes('#me'); // TODO: we should actually be checking which is the default agent in the WebID webIdWithHashTag = hasMeAlready ? webIdRdf.sym(webId) : webIdRdf.sym(webId + '#me'); walletInboxMatch = webIdRdf.statementsMatching(webIdWithHashTag, WALLETTERMS('inbox'), undefined); if (!walletInboxMatch[0]) { throw Error('No wallet TX inbox link found in WebID or it lacks information'); } txInboxXorUrl = walletInboxMatch[0].object.value; return safeApp.fetch(txInboxXorUrl); }).then(function (_resp) { _ref2 = _resp; content = _ref2.content; txInboxMd = content; }); } }).then(function () { return txInboxMd; }); }; var _fetchTxInbox = function _fetchTxInbox(safeApp, recipient) { var txInboxMd, xorName, typeTag; return Promise.resolve().then(function () { txInboxMd = void 0; if (recipient.toLowerCase().startsWith('safe://')) { return Promise.resolve().then(function () { // the recipient is a WebID, let's resolve the linked wallet TX inbox console.log('Fetching WebID:', recipient); return _fetchTxInboxFromWebId(safeApp, recipient); }).then(function (_resp) { txInboxMd = _resp; }); } else { return Promise.resolve().then(function () { return _genXorName(safeApp, recipient); }).then(function (_resp) { // recipient is just a PK xorName = _resp; typeTag = TYPE_TAG_WALLET_TX_INBOX; return safeApp.mutableData.newPublic(xorName, typeTag); }).then(function (_resp) { txInboxMd = _resp; }); } }).then(function () { return txInboxMd; }); }; var sendTxNotif = function sendTxNotif(safeApp, recipient, coinIds, msg) { var txId, tx, txNotif, txInboxMd, encPk, encryptedTx, mutations; return Promise.resolve().then(function () { txId = crypto.randomBytes(16).toString('hex'); tx = { coinIds: coinIds, msg: msg, date: new Date().toUTCString() }; txNotif = JSON.stringify(tx); // we expect the recipient to be a pk but it will also work if it's a WebID console.log("Sending TX notification to recipient. TX id: ", txId); return _fetchTxInbox(safeApp, recipient); }).then(function (_resp) { txInboxMd = _resp; return txInboxMd.get(TX_INBOX_ENTRY_KEY_ENC_PK); }).then(function (_resp) { encPk = _resp; return _encrypt(safeApp, txNotif, encPk.buf.toString()); }).then(function (_resp) { encryptedTx = _resp; return safeApp.mutableData.newMutation(); }).then(function (_resp) { mutations = _resp; return mutations.insert(txId, encryptedTx); }).then(function () { return txInboxMd.applyEntriesMutation(mutations); }).then(function () {}); }; var _checkOwnership = function _checkOwnership(coin, pk) { var coinData = JSON.parse(coin); console.log("Coin data: ", coinData); if (coinData.owner !== pk) { throw Error("Ownership doesn't match", pk, coinData); } return coinData; }; var _fetchCoin = function _fetchCoin(safeApp, coinId) { var coinMd, coin; return Promise.resolve().then(function () { return safeApp.mutableData.newPublic(Buffer.from(coinId, 'hex'), TYPE_TAG_THANKS_COIN); }).then(function (_resp) { coinMd = _resp; return coinMd.get(COIN_ENTRY_KEY_DATA); }).then(function (_resp) { coin = _resp; return { coin: coin, coinMd: coinMd }; }); }; var checkOwnership = function checkOwnership(safeApp, coinId, pk) { var _ref3, coin, coinMd; return Promise.resolve().then(function () { console.log("Reading coin data...", pk, coinId); return _fetchCoin(safeApp, coinId); }).then(function (_resp) { _ref3 = _resp; coin = _ref3.coin; coinMd = _ref3.coinMd; return _checkOwnership(coin.buf.toString(), pk); }); }; var transferCoin = function transferCoin(safeApp, coinId, pk, sk, recipient) { var _ref4, coin, coinMd, coinData, txInboxMd, recipientPk, recipientPkStr, mutations; return Promise.resolve().then(function () { console.log("Transfering coin's ownership in the network...", coinId, recipient); return _fetchCoin(safeApp, coinId); }).then(function (_resp) { _ref4 = _resp; coin = _ref4.coin; coinMd = _ref4.coinMd; coinData = _checkOwnership(coin.buf.toString(), pk); return _fetchTxInbox(safeApp, recipient); }).then(function (_resp) { txInboxMd = _resp; return txInboxMd.get(TX_INBOX_ENTRY_KEY_WALLET_PK); }).then(function (_resp) { recipientPk = _resp; recipientPkStr = recipientPk.buf.toString(); coinData.owner = recipientPkStr; coinData.prev_owner = pk; console.log("Coin's new ownership: ", coinData); return safeApp.mutableData.newMutation(); }).then(function (_resp) { mutations = _resp; return mutations.update(COIN_ENTRY_KEY_DATA, JSON.stringify(coinData), coin.version + 1); }).then(function () { return coinMd.applyEntriesMutation(mutations); }).then(function () { return recipientPkStr; }); }; var updateLinkInWebId = exports.updateLinkInWebId = function updateLinkInWebId(safeApp, webIdUrl, txInboxPk) { var _ref5, webIdMd, resourceType, webIdRdf, baseUri, WALLETTERMS, hasMeAlready, webIdWithHashTag, xorName, inboxMd, _ref6, xorUrl, nameAndTag, authReqUri, authUri; return Promise.resolve().then(function () { console.log("Updating link in WebID:", webIdUrl, txInboxPk); return safeApp.fetch(webIdUrl); }).then(function (_resp) { _ref5 = _resp; webIdMd = _ref5.content; resourceType = _ref5.resourceType; if (resourceType !== 'RDF') { throw 'Service is not mapped to a WebID RDF'; }webIdRdf = webIdMd.emulateAs('rdf'); return webIdRdf.nowOrWhenFetched(); }).then(function () { baseUri = webIdUrl.split('#')[0]; // remove old format 'walletInbox' graph webIdRdf.removeMany(webIdRdf.sym(baseUri + '/walletInbox'), undefined, undefined); WALLETTERMS = webIdRdf.namespace('https://w3id.org/cc#'); hasMeAlready = webIdUrl.includes('#me'); // TODO: we should actually be checking which is the default agent in the WebID webIdWithHashTag = hasMeAlready ? webIdRdf.sym(webIdUrl) : webIdRdf.sym(webIdUrl + '#me'); webIdRdf.removeMany(webIdWithHashTag, WALLETTERMS('inbox'), undefined); if (txInboxPk) { return Promise.resolve().then(function () { return _genXorName(safeApp, txInboxPk); }).then(function (_resp) { xorName = _resp; return safeApp.mutableData.newPublic(xorName, TYPE_TAG_WALLET_TX_INBOX); }).then(function (_resp) { inboxMd = _resp; return inboxMd.getNameAndTag(); }).then(function (_resp) { _ref6 = _resp; xorUrl = _ref6.xorUrl; webIdRdf.add(webIdWithHashTag, WALLETTERMS('inbox'), webIdRdf.sym(xorUrl)); }); } }).then(function () { return Promise.resolve().then(function () { return webIdRdf.commit(); }).catch(function (error) { return Promise.resolve().then(function () { return webIdMd.getNameAndTag(); }).then(function (_resp) { nameAndTag = _resp; console.log("Authorising application to update WebID..."); return safeApp.auth.genShareMDataUri([{ typeTag: nameAndTag.typeTag, name: nameAndTag.name, perms: ['Insert', 'Update', 'Delete'] }]); }).then(function (_resp) { authReqUri = _resp; return window.safe.authorise(authReqUri); }).then(function (_resp) { authUri = _resp; return safeApp.auth.loginFromUri(authUri); }).then(function () { return safeApp.auth.refreshContainersPermissions(); }).then(function () { console.log("Committing update in WebID now..."); return webIdRdf.commit(); }); }); }).then(function () { console.log("WebID updated with new link to TX inbox"); }); }; module.exports = { createWallet: createWallet, loadWalletData: loadWalletData, storeCoinsToWallet: storeCoinsToWallet, createTxInbox: createTxInbox, readTxInboxData: readTxInboxData, removeTxInboxData: removeTxInboxData, sendTxNotif: sendTxNotif, checkOwnership: checkOwnership, transferCoin: transferCoin, updateLinkInWebId: updateLinkInWebId };