@investorid/identity-sdk
Version:
Interact with BlockChain Identities.
479 lines • 19.9 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const ethers_1 = require("ethers");
const utils_1 = require("ethers/utils");
const Config_1 = require("../core/Config");
const ERC735_interface_1 = require("./ERC735.interface");
const ERC725_interface_1 = require("./ERC725.interface");
const Key_interface_1 = require("./Key.interface");
const ClaimHolder_interface_1 = require("./ClaimHolder.interface");
const ENS_1 = require("../core/utils/ENS");
const Claim_1 = require("../claims/Claim");
class Identity {
/**
* Instantiate a new Identity with the provided address or ENS string that will be resolved.
* @param addressOrENS Must be a valid Ethereum address, checksumed, all lower-case or all uppercase.
* @param [provider] If provided, the identity will use this provider for all blockchain operation (unless override) instead of the SDK default provider.
*/
static at(addressOrENS, provider) {
return __awaiter(this, void 0, void 0, function* () {
let address;
if (!addressOrENS.includes('.')) {
address = ENS_1.normalizeAddress(addressOrENS);
}
else {
address = yield ENS_1.resolveENS(addressOrENS, provider);
}
return new Identity(address, provider);
});
}
/**
* Deploy a new Identity, and return the Identity object.
* The signer will pay for the deployment, and will be added in the MANAGEMENT keys.
* If not given, the Signer will use the default provider from the SDK if it is defined and is a Signer.
* Note that the identity will be returned with the provided Signer, thus management operation can be chained.
* @param [signer]
*/
static deployNew(signer) {
return __awaiter(this, void 0, void 0, function* () {
const _signer = signer || Config_1.getProvider();
if (!ethers_1.Signer.isSigner(_signer)) {
throw new Config_1.InvalidProviderError('Contract deployment requires a Signer.');
}
const contract = yield new ethers_1.ContractFactory(ClaimHolder_interface_1.ClaimHolderABI.abi, ClaimHolder_interface_1.ClaimHolderABI.bytecode, _signer).deploy();
const identity = new Identity(contract.address, _signer);
identity.claimHolderInstance = contract;
return identity;
});
}
/**
* Instantiate an Identity.
* @param address A valid Ethereum address (not an ENS, use `Identity#at(ens)`.).
* @param provider Override the default provider of SDK, and use for all operation of this Identity.
*/
constructor(address, provider) {
this.address = ENS_1.normalizeAddress(address);
this.claimHolderInstance = undefined;
this.keyHolderInstance = undefined;
this.provider = provider;
}
/**
* Add a claim to an Identity.
* The signature must have been signed with a keypair having the public key in the CLAIM keys of Identity.
* @param claimType
* @param scheme
* @param issuer
* @param signature
* @param data
* @param uri
* @param [signer]
*/
addClaim(claimType, scheme, issuer, signature, data, uri, signer) {
return __awaiter(this, void 0, void 0, function* () {
const _signer = signer || this.provider || Config_1.getProvider();
if (!ethers_1.Signer.isSigner(_signer)) {
throw new Config_1.InvalidProviderError('Contract operations require a Signer.');
}
let instance = this.claimHolderInstance;
if (!instance) {
instance = yield this.instantiateClaimHolder(_signer);
}
const claimKey = yield this.getKey(utils_1.keccak256(issuer), _signer);
if (!claimKey || claimKey.purpose > Key_interface_1.KeyPurpose.CLAIM) {
throw new Error('ClaimData Issuer key is not in CLAIM keys of Identity.');
}
if (!utils_1.isHexString(signature)) {
throw new Error('signature must be a valid hex string.');
}
const signatureBytes = utils_1.arrayify(signature);
if (!utils_1.isHexString(data)) {
throw new Error('data must be a valid hex string.');
}
const dataBytes = utils_1.arrayify(data);
return instance.addClaim(claimType, scheme, issuer, signatureBytes, dataBytes, uri);
});
}
/**
* Add a Key to an Identity.
* The Signer must have a MANAGEMENT key in the Identity.
* @param key Must be a valid byte32 hex string (pass the keccak256 hash of the key string).
* @param purpose Must be an integer. It is recommended to use the standard KeyPurpose enum.
* @param type Must be a an integer. It is recommended to use the standard KeyType enum.
* @param [signer] Blockchain signer.
*/
addKey(key, purpose, type, signer) {
return __awaiter(this, void 0, void 0, function* () {
const _signer = signer || this.provider || Config_1.getProvider();
if (!ethers_1.Signer.isSigner(_signer)) {
throw new Config_1.InvalidProviderError('Contract operations require a Signer.');
}
let instance = this.keyHolderInstance;
if (!instance) {
instance = yield this.instantiateKeyHolder(_signer);
}
const claimKey = yield this.getKey(utils_1.keccak256(yield _signer.getAddress()));
if (!claimKey || claimKey.type < Key_interface_1.KeyPurpose.MANAGEMENT) {
throw new Error('Signer key is not in MANAGEMENT keys of Identity.');
}
return instance.addKey(key, purpose, type);
});
}
/**
* Get ClaimData details for an Identity.
* @param claimId
* @param [provider]
*/
getClaim(claimId, provider) {
return __awaiter(this, void 0, void 0, function* () {
const _provider = provider || this.provider || Config_1.getProvider();
let instance = this.claimHolderInstance;
if (!instance) {
instance = yield this.instantiateClaimHolder(_provider);
}
return instance.getClaim(claimId).then((claim) => {
return {
id: claimId,
type: claim[0].toNumber(),
scheme: claim[1].toNumber(),
issuer: claim[2],
signature: claim[3],
data: claim[4],
uri: claim[5],
};
});
});
}
/**
* Get claims details for an Identity.
* @deprecated
* @param claimId
* @param [provider]
*/
getClaims(claimId, provider) {
return __awaiter(this, void 0, void 0, function* () {
throw new Error('Claim retrieval must be performed by exploring ClaimAdded and ClaimRemoved events.');
});
}
/**
* Get Claims details by type for an Identity.
* @param claimType
* @param [provider]
*/
getClaimsByType(claimType, provider) {
return __awaiter(this, void 0, void 0, function* () {
const _provider = provider || this.provider || Config_1.getProvider();
const promises = yield this.getClaimIdsByType(claimType, _provider)
.then((claimIds) => claimIds.map((claimId) => __awaiter(this, void 0, void 0, function* () { return this.getClaim(claimId); })));
return Promise.all(promises);
});
}
/**
* Get ClaimData IDs by type for an Identity.
* @param claimType
* @param [provider]
*/
getClaimIdsByType(claimType, provider) {
return __awaiter(this, void 0, void 0, function* () {
const _provider = provider || this.provider || Config_1.getProvider();
let instance = this.claimHolderInstance;
if (!instance) {
instance = yield this.instantiateClaimHolder(_provider);
}
return instance.getClaimIdsByType(claimType);
});
}
/**
* Get the details of a key in an Identity.
* @param key
* @param [provider]
*/
getKey(key, provider) {
return __awaiter(this, void 0, void 0, function* () {
const _provider = provider || this.provider || Config_1.getProvider();
let instance = this.keyHolderInstance;
if (!instance) {
instance = yield this.instantiateKeyHolder(_provider);
}
return instance.getKey(key).then((key) => {
return {
purpose: key[0].toNumber(),
type: key[1].toNumber(),
key: key[2],
};
});
});
}
/**
* Get the purpose of a key in an identity.
* @param key
* @param [provider]
*/
getKeyPurpose(key, provider) {
return __awaiter(this, void 0, void 0, function* () {
const _provider = provider || this.provider || Config_1.getProvider();
let instance = this.keyHolderInstance;
if (!instance) {
instance = yield this.instantiateKeyHolder(_provider);
}
return instance.getKeyPurpose(key).then((key) => {
return key.toNumber();
});
});
}
/**
* Get the details of the keys contained in an Identity by purpose.
* @param purpose
* @param [provider]
*/
getKeysByPurpose(purpose, provider) {
return __awaiter(this, void 0, void 0, function* () {
const _provider = provider || this.provider || Config_1.getProvider();
let instance = this.keyHolderInstance;
if (!instance) {
instance = yield this.instantiateKeyHolder(_provider);
}
const promises = yield instance.getKeysByPurpose(purpose)
.then((keys) => {
return keys.map((key) => __awaiter(this, void 0, void 0, function* () {
return this.getKey(key);
}));
});
return Promise.all(promises);
});
}
/**
* Instantiate the Identity ClaimHolder Contract with the Identity's address.
* @param [providerOrSigner]
*/
instantiateClaimHolder(providerOrSigner) {
return __awaiter(this, void 0, void 0, function* () {
const _provider = providerOrSigner || this.provider || Config_1.getProvider();
if (this.claimHolderInstance) {
return this.claimHolderInstance;
}
this.claimHolderInstance = yield this.instantiate(ERC735_interface_1.ERC735ABI.abi, _provider);
return this.claimHolderInstance;
});
}
/**
* Instantiate the Identity KeyHolder Contract with the Identity's address.
* @param [providerOrSigner]
*/
instantiateKeyHolder(providerOrSigner) {
return __awaiter(this, void 0, void 0, function* () {
const _provider = providerOrSigner || this.provider || Config_1.getProvider();
if (this.keyHolderInstance) {
return this.keyHolderInstance;
}
this.keyHolderInstance = yield this.instantiate(ERC725_interface_1.ERC725ABI.abi, _provider);
return this.keyHolderInstance;
});
}
/**
* Instantiate an Identity with the given abi using the object's address.
* @param abi
* @param [providerOrSigner]
*/
instantiate(abi, providerOrSigner) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.address) {
throw new Error('Identity has no address defined. Use .instantiateAtAddress() or set .address first.');
}
const _provider = providerOrSigner || this.provider || Config_1.getProvider();
return this.instantiateAtAddress(this.address, abi, _provider);
});
}
/**
* Instantiate an Identity with the given abi at a given address.
* @param address
* @param abi
* @param [providerOrSigner]
*/
instantiateAtAddress(address, abi, providerOrSigner) {
return __awaiter(this, void 0, void 0, function* () {
const _provider = providerOrSigner || this.provider || Config_1.getProvider();
return new ethers_1.Contract(address, abi, _provider);
});
}
/**
* Check if a key has at least the given purpose.
* @param key
* @param purpose
* @param [provider]
*/
keyHasPurpose(key, purpose, provider) {
return __awaiter(this, void 0, void 0, function* () {
const keyPurpose = yield this.getKeyPurpose(key, provider);
if (keyPurpose === 0) {
return false;
}
return keyPurpose <= purpose;
});
}
/**
* Remove a claim, provided the signer has the right to do so.
* @param claimId
* @param [signer]
*/
removeClaim(claimId, signer) {
return __awaiter(this, void 0, void 0, function* () {
const _signer = signer || this.provider || Config_1.getProvider();
if (!ethers_1.Signer.isSigner(_signer)) {
throw new Config_1.InvalidProviderError('Contract operations require a Signer.');
}
const claim = yield this.getClaim(claimId);
if (!claim || claim.type === 0) {
throw new Error('There is no such claim.');
}
const managementKey = yield this.getKey(utils_1.keccak256(yield _signer.getAddress()));
if (!managementKey || managementKey.type < Key_interface_1.KeyPurpose.MANAGEMENT) {
throw new Error('Signer key is not in MANAGEMENT keys of Identity.');
}
let instance = this.claimHolderInstance;
if (!instance) {
instance = yield this.instantiateClaimHolder(_signer);
}
return instance.removeClaim(claimId);
});
}
/**
* Remove a Key from an Identity.
* The Signer must have a MANAGEMENT key in the Identity.
* @param key Key must be a valid byte32 hex string.
* @param [signer] Override the identity or default SDK signer.
*/
removeKey(key, signer) {
return __awaiter(this, void 0, void 0, function* () {
const _signer = signer || this.provider || Config_1.getProvider();
if (!ethers_1.Signer.isSigner(_signer)) {
throw new Config_1.InvalidProviderError('Contract operations require a Signer.');
}
let instance = this.keyHolderInstance;
if (!instance) {
instance = yield this.instantiateKeyHolder(_signer);
}
const claimKey = yield this.getKey(utils_1.keccak256(yield _signer.getAddress()));
if (!claimKey || claimKey.type < Key_interface_1.KeyPurpose.MANAGEMENT) {
throw new Error('Signer key is not in MANAGEMENT keys of Identity.');
}
return instance.removeKey(key);
});
}
/**
* Use another provider or signer to interact with the Identity.
* This will reset all contract instances of the identity that will need to be instantiated once again with the new provider.
* @param providerOrSigner
*/
useProvider(providerOrSigner) {
this.provider = providerOrSigner;
// Reset contract instances.
this.claimHolderInstance = undefined;
this.keyHolderInstance = undefined;
}
/**
* Verify if the message was signed with a key that is authorized to perform action for this Identity.
* @param message
* @param signature
* @param providerOrSigner
*/
validateSignature(message, signature, providerOrSigner) {
return __awaiter(this, void 0, void 0, function* () {
let signingKey;
try {
signingKey = utils_1.verifyMessage(message, signature);
}
catch (err) {
return false;
}
return yield this.keyHasPurpose(utils_1.keccak256(signingKey), Key_interface_1.KeyPurpose.ACTION, providerOrSigner);
});
}
/**
* Verify a specific claim given with full data or by ID.
* @param claim Claim object or ID.
* @param [provider]
*/
verifyClaim(claim, provider) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.address) {
return {
valid: false,
reason: 'Identity is not deployed.',
};
}
let _claim;
if (typeof claim === 'string') {
_claim = yield this.getClaim(claim);
}
else {
_claim = claim;
}
let claimHash;
try {
claimHash = Claim_1.Claim.generateBlockchainHash(this.address, claim.type, claim.data);
}
catch (err) {
return {
valid: false,
reason: 'Claim hash could not be computed properly.',
};
}
let signingKey;
try {
signingKey = yield utils_1.verifyMessage(utils_1.arrayify(claimHash), _claim.signature);
}
catch (err) {
return {
valid: false,
reason: 'Claim signature could not be verified.',
details: err.message,
};
}
let signerKeyPurpose;
try {
const issuerIdentity = new Identity(_claim.issuer);
signerKeyPurpose = yield issuerIdentity.getKeyPurpose(utils_1.keccak256(signingKey), provider);
}
catch (err) {
return {
valid: false,
reason: 'The key used to sign the claim could not be found, is the issuer still a valid contract address?',
signingKey,
signerKeyPurpose,
details: err.message,
};
}
if (signerKeyPurpose === 0) {
return {
valid: false,
reason: 'The key used to sign the claim does not exist in the Claim Issuer contract that emitted the claim, maybe it was removed.',
signingKey,
signerKeyPurpose,
};
}
if (signerKeyPurpose > 3) {
return {
valid: false,
reason: 'The key used to sign the claim does not have enough rights to sign claims. The Claim Issuer must define it as a CLAIM key.',
signingKey,
signerKeyPurpose,
};
}
return {
valid: true,
signingKey,
signerKeyPurpose,
};
});
}
}
exports.default = Identity;
//# sourceMappingURL=Identity.js.map