@proyecto-didi/didi-blockchain-manager
Version:
Project to abstract the use of multiblockains in DIDI project
531 lines • 42.5 kB
JavaScript
;
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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