UNPKG

@proyecto-didi/didi-blockchain-manager

Version:
531 lines 42.5 kB
"use strict"; 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BlockchainManager = exports.addPrefix = void 0; const did_resolver_1 = require("did-resolver"); const web3_1 = __importDefault(require("web3")); const { Credentials } = require("uport-credentials"); const { createVerifiableCredentialJwt, verifyCredential, } = require("did-jwt-vc"); const DidRegistryContract = require("ethr-did-registry"); const didJWT = require("did-jwt"); const { delegateTypes, getResolver } = require("ethr-did-resolver"); const EthrDID = require("ethr-did"); const blockChainSelector = (networkConfig, did) => { let routerCharPos = -1; let index = -1; let i = 1; let searchArray = true; const noUportPrefixDid = did.slice(9, did.length); routerCharPos = noUportPrefixDid.search(":"); // if routerCharPos > 0 there's another prefix, should search the provider array if (routerCharPos === -1) { // if not, connect directly to mainnet searchArray = false; index = 0; } while (i < networkConfig.length && searchArray) { routerCharPos = did.search(networkConfig[i].name); if (routerCharPos > 0) { index = i; // saves index for connection later searchArray = false; } else { i += 1; // provider not found, keep going through array } } const blockchainToConnect = { provider: null, address: null, name: null, }; if (index >= 0) { blockchainToConnect.provider = networkConfig[index].rpcUrl; blockchainToConnect.address = networkConfig[index].registry; blockchainToConnect.name = networkConfig[index].name; return blockchainToConnect; } throw new Error("Invalid Provider Prefix"); }; function addPrefix(prefixToAdd, did) { const prefixedDid = did.slice(0, 9) + prefixToAdd + did.slice(9, did.length); return prefixedDid; } exports.addPrefix = addPrefix; const checkPrefix = (prefix, networkArray) => { let i = 0; let notFounded = true; while (i < networkArray.length && notFounded) { if (prefix === networkArray[i].name) { notFounded = false; } else { i += 1; } } return !notFounded; }; class BlockchainManager { constructor(config, gasSafetyValue = 1.2, gasPriceSafetyValue = 1.1) { this.config = config; this.didResolver = new did_resolver_1.Resolver(getResolver(config.providerConfig)); this.gasSafetyValue = gasSafetyValue; this.gasPriceSafetyValue = gasPriceSafetyValue; } /** * Get the minimum gas price for the given method and options * @returns {number} */ getGasPrice(web3) { return __awaiter(this, void 0, void 0, function* () { const gasPrice = yield web3.eth.getGasPrice(); const retGasPrice = Math.round(parseInt(gasPrice, 10) * this.gasPriceSafetyValue); return retGasPrice; }); } /** * Get gas limit given the method and options * @returns {number} */ getGasLimit(method, options) { return __awaiter(this, void 0, void 0, function* () { // 21000 is a recommended number const gasQty = Math.round(Math.max(yield method.estimateGas(options), 21000) * this.gasSafetyValue); return gasQty; }); } /** * Obtains the ethr-did-registry contract * @param options * @returns {Contract} */ static getDidContract(options, contractAddress, web3) { return new web3.eth.Contract(DidRegistryContract.abi, contractAddress, { from: options.from, }); } /** * Returns the address of the DID * @param {string} did Did to get the address from * @returns {string} */ static getDidAddress(did) { const cleanDid = did.split(":"); return cleanDid[cleanDid.length - 1]; } /** * * @param {string} did Did to get the blockchain name from */ static getDidBlockchain(did) { const didAsArray = did.split(":"); return didAsArray.length === 4 ? didAsArray[2] : null; } /** * Add a blockchain beofre the ethereum address. Throws if already contains * a blockchain. did:ethr:0x123 => did:ethr:rinkeby:0x123 * @param {string} did Did to add the blockchain * @param {string} blockchain Blockchain to add */ static addBlockchainToDid(did, blockchain) { const didAsArray = did.split(":"); if (didAsArray.length === 4) throw new Error("#blockchainManager-didWithNetwork"); didAsArray.splice(2, 0, blockchain); return didAsArray.join(":"); } /** * Remove netowrk from did. If the did doesn't contain a network, it returns same did * did:ethr:net:0x123 => did:ethr:0x123 * did:ethr:0x123 => did:ethr:0x123 * * @param {string} did DID to remove the network */ static removeBlockchainFromDid(did) { const didAsArray = did.split(":"); if (didAsArray.length === 3) return did; didAsArray.splice(2, 1); return didAsArray.join(":"); } /** * Compare two dids DIDs. A DIDI without netowors is equal to a DID with network and * same address. Two DIDs with same netork and different address are different. Ex: * did:ethr:net:0x123 == did:ethr:0x123 * did:ethr:net1:0x123 != did:ethr:net2:0x123 * did:ethr:net:0x123 != did:ethr:net:0x124 * did:ethr:0x123 != did:ethr:0x124 * did:ethr:net1:0x123 != did:ethr:net2:0x124 * @param {string} did1 * @param {string} did2 */ static compareDid(did1, did2) { const didAddress1 = BlockchainManager.getDidAddress(did1); const didAddress2 = BlockchainManager.getDidAddress(did2); const didBlockchain1 = BlockchainManager.getDidBlockchain(did1); const didBlockchain2 = BlockchainManager.getDidBlockchain(did2); if (didBlockchain1 == null || didBlockchain2 == null) { return didAddress1 === didAddress2; } return didAddress1 === didAddress2 && didBlockchain1 === didBlockchain2; } /** * If syncing throws #blockchainManager-nodeIsSyncing * @param web3 web3 instance */ static onlySynced({ eth }) { return __awaiter(this, void 0, void 0, function* () { try { const isSyncingResponse = yield eth.isSyncing(); if (isSyncingResponse) throw new Error("#blockchainManager-nodeIsSyncing"); } catch (e) { // RSK public node don't allow eth_syncing. We assume that is always in sync if (e.message.includes("403 Method Not Allowed")) { return; } throw e; } }); } /** * Given a network add delegateDID as a delegate of identity * @param {NetworkConfig} blockchainToConnect * @param {Identity} identity * @param {string} delegateDID * @param {string} validity */ delegateOnBlockchain(blockchainToConnect, identity, delegateDID, validity) { return __awaiter(this, void 0, void 0, function* () { const provider = new web3_1.default.providers.HttpProvider(blockchainToConnect.provider); const web3 = new web3_1.default(provider); yield BlockchainManager.onlySynced(web3); const identityAddr = BlockchainManager.getDidAddress(identity.did); const delegateAddr = BlockchainManager.getDidAddress(delegateDID); const options = { from: identityAddr, }; const contract = BlockchainManager.getDidContract(options, blockchainToConnect.address, web3); const account = web3.eth.accounts.privateKeyToAccount(identity.privateKey); web3.eth.accounts.wallet.add(account); const addDelegateMethod = contract.methods.addDelegate(identityAddr, BlockchainManager.delegateType, delegateAddr, validity); options.gas = yield this.getGasLimit(addDelegateMethod, options); options.gasPrice = yield this.getGasPrice(web3); options.nonce = blockchainToConnect.name !== "lacchain" ? yield web3.eth.getTransactionCount(identityAddr, "pending") : undefined; let delegateMethodSent; try { delegateMethodSent = yield addDelegateMethod.send(options); } catch (e) { if (BlockchainManager.isUnknownError(e)) { throw e; } delegateMethodSent = yield this.delegateOnBlockchain(blockchainToConnect, identity, delegateDID, validity); } web3.eth.accounts.wallet.remove(account.address); return delegateMethodSent; }); } /** * Add delegateDID as a delegate of identity on one or more networks * @param {Identity} identity * @param {string} delegateDID * @param {string} validity */ addDelegate(identity, delegateDID, validity) { return __awaiter(this, void 0, void 0, function* () { const blockchain = BlockchainManager.getDidBlockchain(delegateDID); if (blockchain) { const blockchainToConnect = blockChainSelector(this.config.providerConfig.networks, delegateDID); const [delegation] = yield Promise.allSettled([ this.delegateOnBlockchain(blockchainToConnect, identity, delegateDID, validity), ]); return [ { network: blockchainToConnect.name, status: delegation.status, value: delegation.reason || delegation.value, }, ]; } const validNetworks = this.config.providerConfig.networks.filter(({ name }) => !!name); const delegations = validNetworks .map(({ rpcUrl, registry, name }) => ({ provider: rpcUrl, address: registry, name, })) .map((network) => this.delegateOnBlockchain(network, identity, delegateDID, validity)); // PromiseConstructor.allSettled<any>(values: any) should be an array ,but is a single value const settledDelegations = yield Promise.allSettled(delegations); return settledDelegations.map((result, index) => (Object.assign({ network: validNetworks[index].name }, result))); }); } /** * Given a blockchain and an issuer, validate the delegate * @param {NetworkConfig} blockchainToConnect * @param {String} identityAddr * @param {String} delegateAddr */ static validateOnBlockchain(blockchainToConnect, identityAddr, delegateAddr) { return __awaiter(this, void 0, void 0, function* () { const provider = new web3_1.default.providers.HttpProvider(blockchainToConnect.provider); const web3 = new web3_1.default(provider); yield BlockchainManager.onlySynced(web3); const options = { from: identityAddr, }; const contract = BlockchainManager.getDidContract(options, blockchainToConnect.address, web3); const validDelegateMethod = contract.methods.validDelegate(identityAddr, BlockchainManager.delegateType, delegateAddr); return validDelegateMethod.call(options); }); } /** * validate if delegateDID is delegate of identityDID * @param {Identity} identityDID * @param {string} delegateDID */ validDelegate(identityDID, delegateDID) { return __awaiter(this, void 0, void 0, function* () { const identityAddr = BlockchainManager.getDidAddress(identityDID); const delegateAddr = BlockchainManager.getDidAddress(delegateDID); const blockchain = BlockchainManager.getDidBlockchain(delegateDID); if (blockchain) { const blockchainToConnect = blockChainSelector(this.config.providerConfig.networks, delegateDID); return BlockchainManager.validateOnBlockchain(blockchainToConnect, identityAddr, delegateAddr); } const validations = this.config.providerConfig.networks.map((network) => { const blockchainToConnect = { provider: network.rpcUrl, address: network.registry, name: network.name, }; return BlockchainManager.validateOnBlockchain(blockchainToConnect, identityAddr, delegateAddr); }); const results = yield Promise.allSettled(validations); return results.some((result) => result.value === true); }); } /** * Resolve a DID document * @param {string} did DID to resolve its document */ resolveDidDocument(did) { return __awaiter(this, void 0, void 0, function* () { const resolvedDid = yield this.didResolver.resolve(did); return resolvedDid; }); } /** * Creates a JWT from a base payload with the information to encode * @param {string} issuerDid Issuer DID * @param {object} payload Information of the JWT * @param {string} pkey Information of the PK * @param {number} expiration Expiration of the JWT in [NumericDate]{@link https://tools.ietf.org/html/rfc7519#section-2} * @param {string} audienceDID DID of the audience of the JWT * @returns {string} JWT's string */ static createJWT(issuerDid, pkey, payload, expiration = undefined, audienceDID = undefined) { return __awaiter(this, void 0, void 0, function* () { const signer = didJWT.SimpleSigner(pkey); const response = yield didJWT.createJWT(Object.assign(Object.assign({}, payload), { exp: expiration, aud: audienceDID }), { issuer: issuerDid, signer }, { alg: "ES256K-R" }); return response; }); } /** * Creates a valid signer * @param {string} privateKey A hex encoded private key * @returns {Signer} A configured signer function */ static getSigner(privateKey) { return didJWT.SimpleSigner(privateKey); } /** * Verify a JWT string with the given audience * @param {string} jwt JWT to be verified * @param {string} audienceDID DID of the audience if needed */ verifyJWT(jwt, audienceDID = undefined) { return __awaiter(this, void 0, void 0, function* () { const response = yield didJWT.verifyJWT(jwt, { resolver: this.didResolver, audience: audienceDID, }); response.doc = response.didResolutionResult.didDocument; return response; }); } /** * Waring: Use verifyJWT. Decodes a token and returns the contet. * @param {string} jwt */ static decodeJWT(jwt) { return __awaiter(this, void 0, void 0, function* () { return didJWT.decodeJWT(jwt); }); } /** * genera un certificado asociando la informacion recibida en "subject" con el did * @param {string} subjectDid This did has this prefix always (did:ethr:) it doesn't change * @param {string} subjectPayload * @param {Date} expirationDate * @param {string} issuerDid The issuer might change and has different prefixes * @param {string} issuerPkey */ static createCredential(subjectDid, subjectPayload, expirationDate, issuerDid, issuerPkey) { return __awaiter(this, void 0, void 0, function* () { const cleanDid = issuerDid.split(":"); const prefixedDid = cleanDid.slice(2).join(":"); const vcIssuer = new EthrDID({ address: prefixedDid, privateKey: issuerPkey, }); const date = expirationDate ? Math.floor(new Date(expirationDate).getTime() / 1000 || 0) : undefined; const vcPayload = { sub: subjectDid, vc: { "@context": ["https://www.w3.org/2018/credentials/v1"], type: ["VerifiableCredential"], credentialSubject: subjectPayload, }, exp: date, }; const result = yield createVerifiableCredentialJwt(vcPayload, vcIssuer); return result; }); } /** * Verifies a credential using the universal resolver and verifies issuer * @param {string} jwt Credential encoded as jwt * @param {string} IdentityDid Central entity DID, usually DIDI */ verifyCredential(jwt, IdentityDid) { return __awaiter(this, void 0, void 0, function* () { const credentialVerification = verifyCredential(jwt, this.didResolver); if (!IdentityDid) return credentialVerification; const isIssuerValid = this.validDelegate(IdentityDid, credentialVerification.issuer); return Object.assign({ isIssuerValid }, credentialVerification); }); } /** * Given an prefix, genereates new privte and public keys. * @param {string} prefixToAdd */ createIdentity(prefixToAdd = "") { let prefixChecked = false; let prefixedDid = null; if (prefixToAdd) { prefixChecked = checkPrefix(prefixToAdd, this.config.providerConfig.networks); if (!prefixChecked) { throw new Error("Invalid Prefix - Check Provider Network Configuration"); } } const credential = Credentials.createIdentity(); if (prefixToAdd) { prefixedDid = addPrefix(`${prefixToAdd}:`, credential.did); credential.did = prefixedDid; } return credential; } /** * Given a blockchain revoke a delegate * @param {NetworkConfig} blockchainToConnect * @param {string} delegatedDID * @param {Identity} issuerCredentials */ revokeOnBlockchain(blockchainToConnect, delegatedDID, issuerCredentials) { return __awaiter(this, void 0, void 0, function* () { const sourceAddress = BlockchainManager.getDidAddress(issuerCredentials.did); const targetAddress = BlockchainManager.getDidAddress(delegatedDID); const provider = new web3_1.default.providers.HttpProvider(blockchainToConnect.provider); const web3 = new web3_1.default(provider); const options = { from: sourceAddress }; const contract = BlockchainManager.getDidContract(options, blockchainToConnect.address, web3); const account = web3.eth.accounts.privateKeyToAccount(issuerCredentials.privateKey); web3.eth.accounts.wallet.add(account); const revokeDelegateMethod = contract.methods.revokeDelegate(sourceAddress, BlockchainManager.delegateType, targetAddress); options.gas = yield this.getGasLimit(revokeDelegateMethod, options); options.gasPrice = yield this.getGasPrice(web3); options.nonce = blockchainToConnect.name !== "lacchain" ? yield web3.eth.getTransactionCount(sourceAddress, "pending") : undefined; let revokeMethodSent; try { revokeMethodSent = yield revokeDelegateMethod.send(options); } catch (e) { if (BlockchainManager.isUnknownError(e)) { throw e; } revokeMethodSent = yield this.revokeDelegate(issuerCredentials, delegatedDID); } web3.eth.accounts.wallet.remove(account.address); return revokeMethodSent; }); } /** * Revoke delegation * @param {Identity} issuerCredentials * @param {string} delegatedDID */ revokeDelegate(issuerCredentials, delegatedDID) { return __awaiter(this, void 0, void 0, function* () { const blockchain = BlockchainManager.getDidBlockchain(delegatedDID); if (blockchain) { const blockchainToConnect = blockChainSelector(this.config.providerConfig.networks, delegatedDID); const revoke = yield Promise.allSettled([ this.revokeOnBlockchain(blockchainToConnect, delegatedDID, issuerCredentials), ]); return [ { network: blockchainToConnect.name, status: revoke[0].status, value: revoke[0].reason || revoke[0].value, }, ]; } const validNetworks = this.config.providerConfig.networks.filter(({ name }) => !!name); const delegations = validNetworks .map(({ rpcUrl, registry, name }) => ({ provider: rpcUrl, address: registry, name, })) .map((network) => this.revokeOnBlockchain(network, delegatedDID, issuerCredentials)); // PromiseConstructor.allSettled<any>(values: any) should be an array ,but is a single value const settledDelegations = yield Promise.allSettled(delegations); return settledDelegations.map((result, index) => (Object.assign({ network: validNetworks[index].name }, result))); }); } /** * We dont want to bump txs. This only happen if simultaneous tx are sent, this resend recursively * the tx increasing nonce by one * @param error */ static isUnknownError(error) { return !(error.message.includes("gas price not enough to bump transaction") || error.message.includes("transaction underpriced") || error.message.includes("too low") || error.message.includes("too high")); } } exports.BlockchainManager = BlockchainManager; BlockchainManager.delegateType = delegateTypes.Secp256k1SignatureAuthentication2018; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQmxvY2tjaGFpbk1hbmFnZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvQmxvY2tjaGFpbk1hbmFnZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsK0NBQXdDO0FBQ3hDLGdEQUF3QjtBQUV4QixNQUFNLEVBQUUsV0FBVyxFQUFFLEdBQUcsT0FBTyxDQUFDLG1CQUFtQixDQUFDLENBQUM7QUFDckQsTUFBTSxFQUNKLDZCQUE2QixFQUM3QixnQkFBZ0IsR0FDakIsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUM7QUFDMUIsTUFBTSxtQkFBbUIsR0FBRyxPQUFPLENBQUMsbUJBQW1CLENBQUMsQ0FBQztBQUN6RCxNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLENBQUM7QUFDbEMsTUFBTSxFQUFFLGFBQWEsRUFBRSxXQUFXLEVBQUUsR0FBRyxPQUFPLENBQUMsbUJBQW1CLENBQUMsQ0FBQztBQUNwRSxNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsVUFBVSxDQUFDLENBQUM7QUF3Q3BDLE1BQU0sa0JBQWtCLEdBQUcsQ0FDekIsYUFBbUUsRUFDbkUsR0FBVyxFQUNJLEVBQUU7SUFDakIsSUFBSSxhQUFhLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDdkIsSUFBSSxLQUFLLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDZixJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDVixJQUFJLFdBQVcsR0FBRyxJQUFJLENBQUM7SUFDdkIsTUFBTSxnQkFBZ0IsR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxHQUFHLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDbEQsYUFBYSxHQUFHLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLGdGQUFnRjtJQUM5SCxJQUFJLGFBQWEsS0FBSyxDQUFDLENBQUMsRUFBRTtRQUN4QixzQ0FBc0M7UUFDdEMsV0FBVyxHQUFH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