@0xpolygonid/js-sdk
Version:
SDK to work with Polygon ID
766 lines (765 loc) • 38.4 kB
JavaScript
;
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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.IdentityWallet = void 0;
const kms_1 = require("../kms");
const js_iden3_core_1 = require("@iden3/js-iden3-core");
const js_crypto_1 = require("@iden3/js-crypto");
const js_merkletree_1 = require("@iden3/js-merkletree");
const common_1 = require("./common");
const uuid = __importStar(require("uuid"));
const schema_processor_1 = require("../schema-processor");
const storage_1 = require("../storage");
const verifiable_1 = require("../verifiable");
const credentials_1 = require("../credentials");
const circuits_1 = require("../circuits");
const utils_1 = require("../utils");
const credential_status_publisher_1 = require("../credentials/status/credential-status-publisher");
const proof_1 = require("../proof");
const blockchain_1 = require("../blockchain");
/**
* @public
* Wallet instance to manage the digital identity based on iden3 protocol
* allows to: create identity/profile, sign payloads (bigint / bytes), generate keys,
* generate Merkle tree proofs of inclusion / non-inclusion to Merkle trees, issue credentials with a BJJSignature and Iden3SparseMerkleTree Proofs,
* revoke credentials, add credentials to Merkle trees, push states to reverse hash service
*
*
* @class IdentityWallet - class
* @implements implements IIdentityWallet interface
*/
class IdentityWallet {
/**
* Constructs a new instance of the `IdentityWallet` class
*
* @param {KMS} _kms - Key Management System that allows signing data with BJJ key
* @param {IDataStorage} _storage - data storage to access credential / identity / Merkle tree data
* @param {ICredentialWallet} _credentialWallet - credential wallet instance to quickly access credential CRUD functionality
* @public
*/
constructor(_kms, _storage, _credentialWallet, _opts) {
this._kms = _kms;
this._storage = _storage;
this._credentialWallet = _credentialWallet;
this._opts = _opts;
this._credentialStatusPublisherRegistry = this.getCredentialStatusPublisherRegistry(_opts);
this._inputsGenerator = new proof_1.InputGenerator(this, _credentialWallet, _storage.states);
this._transactionService = new blockchain_1.TransactionService(_storage.states.getRpcProvider());
}
get credentialWallet() {
return this._credentialWallet;
}
getCredentialStatusPublisherRegistry(_opts) {
if (!_opts?.credentialStatusPublisherRegistry) {
const registry = new credential_status_publisher_1.CredentialStatusPublisherRegistry();
const emptyPublisher = { publish: () => Promise.resolve() };
registry.register(verifiable_1.CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, new credential_status_publisher_1.Iden3SmtRhsCredentialStatusPublisher());
registry.register(verifiable_1.CredentialStatusType.SparseMerkleTreeProof, emptyPublisher);
registry.register(verifiable_1.CredentialStatusType.Iden3commRevocationStatusV1, emptyPublisher);
return registry;
}
else {
return this._opts?.credentialStatusPublisherRegistry;
}
}
async createAuthCoreClaim(revNonce, seed) {
const keyId = await this._kms.createKeyFromSeed(kms_1.KmsKeyType.BabyJubJub, seed);
const pubKeyHex = await this._kms.publicKey(keyId);
const pubKey = js_crypto_1.PublicKey.newFromHex(pubKeyHex);
const schemaHash = js_iden3_core_1.SchemaHash.authSchemaHash;
const authClaim = js_iden3_core_1.Claim.newClaim(schemaHash, js_iden3_core_1.ClaimOptions.withIndexDataInts(pubKey.p[0], pubKey.p[1]), js_iden3_core_1.ClaimOptions.withRevocationNonce(BigInt(0)));
authClaim.setRevocationNonce(BigInt(revNonce));
return { authClaim, pubKey };
}
async createAuthBJJCredential(did, pubKey, authClaim, currentState, revocationOpts) {
const authData = authClaim.getExpirationDate();
const expiration = authData ? (0, js_iden3_core_1.getUnixTimestamp)(authData) : 0;
const request = {
credentialSchema: verifiable_1.VerifiableConstants.AUTH.AUTH_BJJ_CREDENTIAL_SCHEMA_JSON_URL,
type: verifiable_1.VerifiableConstants.AUTH.AUTH_BJJ_CREDENTIAL_TYPE,
credentialSubject: {
x: pubKey.p[0].toString(),
y: pubKey.p[1].toString()
},
subjectPosition: (0, common_1.subjectPositionIndex)(authClaim.getIdPosition()),
version: 0,
expiration,
revocationOpts: {
nonce: Number(authClaim.getRevocationNonce()),
id: revocationOpts.id.replace(/\/$/, ''),
type: revocationOpts.type,
issuerState: currentState.hex()
}
};
// Check if has already an auth credential
const authCredentials = await this._credentialWallet.getAllAuthBJJCredentials(did);
let credential = new verifiable_1.W3CCredential();
if (authCredentials.length === 0) {
const schema = JSON.parse(verifiable_1.VerifiableConstants.AUTH.AUTH_BJJ_CREDENTIAL_SCHEMA_JSON);
try {
credential = this._credentialWallet.createCredential(did, request, schema);
}
catch (e) {
throw new Error(`Error create w3c credential ${e.message}`);
}
}
else {
// credential with sigProof signed with previous auth bjj credential
credential = await this.issueCredential(did, request);
}
return credential;
}
/**
* {@inheritDoc IIdentityWallet.createIdentity}
*/
async createIdentity(opts) {
const tmpIdentifier = opts.seed ? uuid.v5(js_crypto_1.Hex.encode((0, js_crypto_1.sha256)(opts.seed)), uuid.NIL) : uuid.v4();
opts.seed = opts.seed ?? (0, js_crypto_1.getRandomBytes)(32);
await this._storage.mt.createIdentityMerkleTrees(tmpIdentifier);
const revNonce = opts.revocationOpts.nonce ?? 0;
const { authClaim, pubKey } = await this.createAuthCoreClaim(revNonce, opts.seed);
const { hi, hv } = authClaim.hiHv();
await this._storage.mt.addToMerkleTree(tmpIdentifier, storage_1.MerkleTreeType.Claims, hi, hv);
const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(tmpIdentifier, storage_1.MerkleTreeType.Claims);
const ctr = await claimsTree.root();
const currentState = (0, js_merkletree_1.hashElems)([ctr.bigInt(), js_merkletree_1.ZERO_HASH.bigInt(), js_merkletree_1.ZERO_HASH.bigInt()]);
const didType = (0, js_iden3_core_1.buildDIDType)(opts.method || js_iden3_core_1.DidMethod.Iden3, opts.blockchain || js_iden3_core_1.Blockchain.Polygon, opts.networkId || js_iden3_core_1.NetworkId.Amoy);
const identifier = js_iden3_core_1.Id.idGenesisFromIdenState(didType, currentState.bigInt());
const did = js_iden3_core_1.DID.parseFromId(identifier);
await this._storage.mt.bindMerkleTreeToNewIdentifier(tmpIdentifier, did.string());
const oldTreeState = {
revocationRoot: js_merkletree_1.ZERO_HASH,
claimsRoot: ctr,
state: currentState,
rootOfRoots: js_merkletree_1.ZERO_HASH
};
const identity = await this._storage.identity.getIdentity(did.string());
if (!identity) {
await this._storage.identity.saveIdentity({
did: did.string(),
state: currentState,
isStatePublished: false,
isStateGenesis: true
});
}
// check whether we have auth credential, if not - create a new one
const credentials = await this._credentialWallet.findByQuery({
credentialSubject: {
x: {
$eq: pubKey.p[0].toString()
},
y: {
$eq: pubKey.p[1].toString()
}
},
allowedIssuers: [did.string()]
});
// if credential exists with the same credential status type we return this credential
if (credentials.length === 1 &&
credentials[0].credentialStatus.type === opts.revocationOpts.type) {
return {
did,
credential: credentials[0]
};
}
// otherwise something is already wrong with storage as it has more than 1 credential in it or credential status type of existing credential is different from what user provides - We should remove everything and create new credential.
// in this way credential status of auth credential can be upgraded
for (let i = 0; i < credentials.length; i++) {
await this._credentialWallet.remove(credentials[i].id);
}
// otherwise we create a new credential
const credential = await this.createAuthBJJCredential(did, pubKey, authClaim, currentState, opts.revocationOpts);
const index = authClaim.hIndex();
const { proof } = await claimsTree.generateProof(index, ctr);
const mtpProof = new verifiable_1.Iden3SparseMerkleTreeProof({
mtp: proof,
issuerData: {
id: did,
state: {
rootOfRoots: oldTreeState.rootOfRoots,
revocationTreeRoot: oldTreeState.revocationRoot,
claimsTreeRoot: ctr,
value: currentState
}
},
coreClaim: authClaim
});
credential.proof = [mtpProof];
// only if user specified that genesis state publishing is not needed we won't do this.
if (!opts.revocationOpts.genesisPublishingDisabled) {
await this.publishRevocationInfoByCredentialStatusType(did, opts.revocationOpts.type, {
rhsUrl: opts.revocationOpts.id,
onChain: opts.revocationOpts.onChain
});
}
await this._credentialWallet.save(credential);
return {
did,
credential
};
}
/**
* {@inheritDoc IIdentityWallet.createEthereumBasedIdentity}
*/
async createEthereumBasedIdentity(opts) {
opts.seed = opts.seed ?? (0, js_crypto_1.getRandomBytes)(32);
opts.createBjjCredential = opts.createBjjCredential ?? true;
let credential;
const ethSigner = opts.ethSigner;
if (opts.createBjjCredential && !ethSigner) {
throw new Error('Ethereum signer is required to create Ethereum identities in order to transit state');
}
const currentState = js_merkletree_1.ZERO_HASH; // In Ethereum identities we don't have an initial state with the auth credential
const didType = (0, js_iden3_core_1.buildDIDType)(opts.method || js_iden3_core_1.DidMethod.Iden3, opts.blockchain || js_iden3_core_1.Blockchain.Polygon, opts.networkId || js_iden3_core_1.NetworkId.Amoy);
const keyIdEth = await this._kms.createKeyFromSeed(kms_1.KmsKeyType.Secp256k1, opts.seed);
const pubKeyHexEth = (await this._kms.publicKey(keyIdEth)).slice(2); // 04 + x + y (uncompressed key)
const did = (0, utils_1.buildDIDFromEthPubKey)(didType, pubKeyHexEth);
await this._storage.mt.createIdentityMerkleTrees(did.string());
await this._storage.identity.saveIdentity({
did: did.string(),
state: currentState,
isStatePublished: false,
isStateGenesis: true
});
if (opts.createBjjCredential && ethSigner) {
// Old tree state genesis state
const oldTreeState = {
revocationRoot: js_merkletree_1.ZERO_HASH,
claimsRoot: js_merkletree_1.ZERO_HASH,
state: currentState,
rootOfRoots: js_merkletree_1.ZERO_HASH
};
credential = await this.addBJJAuthCredential(did, oldTreeState, true, ethSigner, opts);
}
return {
did,
credential
};
}
/** {@inheritDoc IIdentityWallet.getGenesisDIDMetadata} */
async getGenesisDIDMetadata(did) {
// check if it is a genesis identity
const identity = await this._storage.identity.getIdentity(did.string());
if (identity) {
return { nonce: 0, genesisDID: js_iden3_core_1.DID.parse(identity.did) };
}
const profile = await this._storage.identity.getProfileById(did.string());
if (!profile) {
throw new Error('profile or identity not found');
}
return { nonce: profile.nonce, genesisDID: js_iden3_core_1.DID.parse(profile.genesisIdentifier) };
}
/** {@inheritDoc IIdentityWallet.createProfile} */
async createProfile(did, nonce, verifier) {
const profileDID = (0, common_1.generateProfileDID)(did, nonce);
const identityProfiles = await this._storage.identity.getProfilesByGenesisIdentifier(did.string());
const existingProfile = identityProfiles.find((p) => p.nonce == nonce || p.verifier == verifier);
if (existingProfile) {
throw new Error('profile with given nonce or verifier already exists');
}
await this._storage.identity.saveProfile({
id: profileDID.string(),
nonce,
genesisIdentifier: did.string(),
verifier
});
return profileDID;
}
/**
*
* gets profile identity by genesis identifiers
*
* @param {string} genesisIdentifier - genesis identifier from which profile has been derived
* @returns `{Promise<Profile[]>}`
*/
async getProfilesByDID(did) {
return this._storage.identity.getProfilesByGenesisIdentifier(did.string());
}
/** {@inheritDoc IIdentityWallet.generateKey} */
async generateKey(keyType) {
const key = await this._kms.createKeyFromSeed(keyType, (0, js_crypto_1.getRandomBytes)(32));
return key;
}
async getProfileByVerifier(verifier) {
return this._storage.identity.getProfileByVerifier(verifier);
}
/** {@inheritDoc IIdentityWallet.getDIDTreeModel} */
async getDIDTreeModel(did) {
const didStr = did.string();
const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(didStr, storage_1.MerkleTreeType.Claims);
const revocationTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(didStr, storage_1.MerkleTreeType.Revocations);
const rootsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(didStr, storage_1.MerkleTreeType.Roots);
const state = (0, js_merkletree_1.hashElems)([
(await claimsTree.root()).bigInt(),
(await revocationTree.root()).bigInt(),
(await rootsTree.root()).bigInt()
]);
return {
state,
claimsTree,
revocationTree,
rootsTree
};
}
/** {@inheritDoc IIdentityWallet.generateClaimMtp} */
async generateCredentialMtp(did, credential, treeState) {
const coreClaim = await this.getCoreClaimFromCredential(credential);
return this.generateCoreClaimMtp(did, coreClaim, treeState);
}
/** {@inheritDoc IIdentityWallet.generateClaimMtp} */
async generateCoreClaimMtp(did, coreClaim, treeState) {
const treesModel = await this.getDIDTreeModel(did);
const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(did.string(), storage_1.MerkleTreeType.Claims);
const claimsRoot = await treesModel.claimsTree.root();
const rootOfRoots = await treesModel.rootsTree.root();
const revocationRoot = await treesModel.revocationTree.root();
const { proof } = await claimsTree.generateProof(coreClaim.hIndex(), treeState ? treeState.claimsRoot : claimsRoot);
return {
proof,
treeState: treeState ?? {
state: treesModel.state,
claimsRoot,
rootOfRoots,
revocationRoot
}
};
}
/** {@inheritDoc IIdentityWallet.generateNonRevocationMtp} */
async generateNonRevocationMtp(did, credential, treeState) {
const coreClaim = await this.getCoreClaimFromCredential(credential);
const revNonce = coreClaim.getRevocationNonce();
return this.generateNonRevocationMtpWithNonce(did, revNonce, treeState);
}
/** {@inheritDoc IIdentityWallet.generateNonRevocationMtpWithNonce} */
async generateNonRevocationMtpWithNonce(did, revNonce, treeState) {
const treesModel = await this.getDIDTreeModel(did);
const revocationTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(did.string(), storage_1.MerkleTreeType.Revocations);
const claimsRoot = await treesModel.claimsTree.root();
const rootOfRoots = await treesModel.rootsTree.root();
const revocationRoot = await treesModel.revocationTree.root();
const { proof } = await revocationTree.generateProof(revNonce, treeState ? treeState.revocationRoot : revocationRoot);
return {
proof,
treeState: treeState ?? {
state: treesModel.state,
claimsRoot,
rootOfRoots,
revocationRoot
}
};
}
/** {@inheritDoc IIdentityWallet.sign} */
async sign(message, credential) {
const keyKMSId = (0, credentials_1.getKMSIdByAuthCredential)(credential);
const payload = js_crypto_1.poseidon.hashBytes(message);
const signature = await this._kms.sign(keyKMSId, js_iden3_core_1.BytesHelper.intToBytes(payload));
return js_crypto_1.Signature.newFromCompressed(signature);
}
/** {@inheritDoc IIdentityWallet.signChallenge} */
async signChallenge(challenge, credential) {
const keyKMSId = (0, credentials_1.getKMSIdByAuthCredential)(credential);
const signature = await this._kms.sign(keyKMSId, js_iden3_core_1.BytesHelper.intToBytes(challenge));
return js_crypto_1.Signature.newFromCompressed(signature);
}
/** {@inheritDoc IIdentityWallet.issueCredential} */
async issueCredential(issuerDID, req, opts) {
req.revocationOpts.id = req.revocationOpts.id.replace(/\/$/, '');
let schema;
const loader = opts?.documentLoader ?? (0, schema_processor_1.cacheLoader)(opts);
try {
schema = (await loader(req.credentialSchema)).document;
}
catch (e) {
throw new Error(`can't load credential schema ${req.credentialSchema}`);
}
const jsonSchema = schema;
let credential = new verifiable_1.W3CCredential();
const issuerRoots = await this.getDIDTreeModel(issuerDID);
req.revocationOpts.issuerState = issuerRoots.state.hex();
req.revocationOpts.nonce =
typeof req.revocationOpts.nonce === 'number'
? req.revocationOpts.nonce
: new DataView((0, js_crypto_1.getRandomBytes)(16).buffer).getUint32(0, false);
req.subjectPosition = req.subjectPosition ?? verifiable_1.SubjectPosition.Index;
try {
credential = this._credentialWallet.createCredential(issuerDID, req, jsonSchema);
const encodedCred = utils_1.byteEncoder.encode(JSON.stringify(credential));
const encodedSchema = utils_1.byteEncoder.encode(JSON.stringify(schema));
await new schema_processor_1.JsonSchemaValidator().validate(encodedCred, encodedSchema);
}
catch (e) {
throw new Error(`Error create w3c credential ${e.message}`);
}
const { authCredential: issuerAuthBJJCredential } = await this.getActualAuthCredential(issuerDID);
const coreClaimOpts = {
revNonce: req.revocationOpts.nonce,
subjectPosition: req.subjectPosition,
merklizedRootPosition: req.merklizedRootPosition ?? verifiable_1.MerklizedRootPosition.None,
updatable: false,
version: 0,
merklizeOpts: { ...opts, documentLoader: loader }
};
const coreClaim = await credential.toCoreClaim(coreClaimOpts);
const { hi, hv } = coreClaim.hiHv();
const coreClaimHash = js_crypto_1.poseidon.hash([hi, hv]);
const signature = await this.signChallenge(coreClaimHash, issuerAuthBJJCredential);
if (!issuerAuthBJJCredential.proof) {
throw new Error('issuer auth credential must have proof');
}
const mtpAuthBJJProof = issuerAuthBJJCredential.getIden3SparseMerkleTreeProof();
if (!mtpAuthBJJProof) {
throw new Error('mtp is required for auth bjj key to issue new credentials');
}
const sigProof = new verifiable_1.BJJSignatureProof2021({
issuerData: {
id: issuerDID,
state: mtpAuthBJJProof.issuerData.state,
authCoreClaim: mtpAuthBJJProof.coreClaim,
mtp: mtpAuthBJJProof.mtp,
credentialStatus: issuerAuthBJJCredential.credentialStatus
},
coreClaim,
signature
});
credential.proof = [sigProof];
return credential;
}
/** {@inheritDoc IIdentityWallet.getActualAuthCredential} */
async getActualAuthCredential(did, treeStateInfo) {
const authCredentials = await this._credentialWallet.getAllAuthBJJCredentials(did);
for (let i = 0; i < authCredentials.length; i++) {
const incProof = await this.generateCredentialMtp(did, authCredentials[i], treeStateInfo);
if (!incProof.proof.existence) {
continue;
}
const nonRevProof = await this.generateNonRevocationMtp(did, authCredentials[i], treeStateInfo);
if (!nonRevProof.proof.existence) {
return {
authCredential: authCredentials[i],
incProof,
nonRevProof
};
}
}
throw new Error(verifiable_1.VerifiableConstants.ERRORS.NO_AUTH_CRED_FOUND);
}
/** {@inheritDoc IIdentityWallet.revokeCredential} */
async revokeCredential(issuerDID, credential) {
const issuerTree = await this.getDIDTreeModel(issuerDID);
const coreClaim = await this.getCoreClaimFromCredential(credential);
if (!coreClaim) {
throw new Error('credential must have coreClaim representation in proofs');
}
const nonce = coreClaim.getRevocationNonce();
await issuerTree.revocationTree.add(nonce, BigInt(0));
return Number(BigInt.asUintN(64, nonce));
}
/** {@inheritDoc IIdentityWallet.addCredentialsToMerkleTree} */
async addCredentialsToMerkleTree(credentials, issuerDID) {
const oldIssuerTree = await this.getDIDTreeModel(issuerDID);
let claimsRoot = await oldIssuerTree.claimsTree.root();
let rootOfRoots = await oldIssuerTree.rootsTree.root();
let revocationRoot = await oldIssuerTree.revocationTree.root();
const oldTreeState = {
state: oldIssuerTree.state,
claimsRoot,
revocationRoot,
rootOfRoots
};
for (let index = 0; index < credentials.length; index++) {
const credential = credentials[index];
// credential must have a bjj signature proof
const coreClaim = credential.getCoreClaimFromProof(verifiable_1.ProofType.BJJSignature);
if (!coreClaim) {
throw new Error('credential must have coreClaim representation in the signature proof');
}
await this._storage.mt.addToMerkleTree(issuerDID.string(), storage_1.MerkleTreeType.Claims, coreClaim.hIndex(), coreClaim.hValue());
}
const newIssuerTreeState = await this.getDIDTreeModel(issuerDID);
const claimTreeRoot = await newIssuerTreeState.claimsTree.root();
await this._storage.mt.addToMerkleTree(issuerDID.string(), storage_1.MerkleTreeType.Roots, claimTreeRoot.bigInt(), BigInt(0));
const newIssuerTreeStateWithROR = await this.getDIDTreeModel(issuerDID);
claimsRoot = await newIssuerTreeStateWithROR.claimsTree.root();
rootOfRoots = await newIssuerTreeStateWithROR.rootsTree.root();
revocationRoot = await newIssuerTreeStateWithROR.revocationTree.root();
return {
credentials,
newTreeState: {
state: newIssuerTreeStateWithROR.state,
claimsRoot,
rootOfRoots,
revocationRoot
},
oldTreeState: oldTreeState
};
}
/** {@inheritDoc IIdentityWallet.generateIden3SparseMerkleTreeProof} */
// treeState - optional, if it is not passed proof of claim inclusion will be generated on the latest state in the tree.
async generateIden3SparseMerkleTreeProof(issuerDID, credentials, txId, blockNumber, blockTimestamp, treeState, opts) {
for (let index = 0; index < credentials.length; index++) {
const credential = credentials[index];
// TODO: return coreClaim from generateCredentialMtp and use it below
// credential must have a bjj signature proof
const coreClaim = credential.getCoreClaimFromProof(verifiable_1.ProofType.BJJSignature) ||
(await credential.toCoreClaim(opts));
if (!coreClaim) {
throw new Error('credential must have coreClaim representation in the signature proof');
}
const mtpWithProof = await this.generateCoreClaimMtp(issuerDID, coreClaim, treeState);
const mtpProof = new verifiable_1.Iden3SparseMerkleTreeProof({
mtp: mtpWithProof.proof,
issuerData: {
id: issuerDID,
state: {
claimsTreeRoot: mtpWithProof.treeState.claimsRoot,
revocationTreeRoot: mtpWithProof.treeState.revocationRoot,
rootOfRoots: mtpWithProof.treeState.rootOfRoots,
value: mtpWithProof.treeState.state,
txId,
blockNumber,
blockTimestamp
}
},
coreClaim
});
if (Array.isArray(credentials[index].proof)) {
credentials[index].proof.push(mtpProof);
}
else {
credentials[index].proof = credentials[index].proof
? [credentials[index].proof, mtpProof]
: [mtpProof];
}
}
return credentials;
}
/** {@inheritDoc IIdentityWallet.publishSpecificStateToRHS} */
async publishSpecificStateToRHS(treeModel, rhsURL, revokedNonces) {
await (0, credentials_1.pushHashesToRHS)(treeModel.state, treeModel, rhsURL, revokedNonces);
}
/** {@inheritDoc IIdentityWallet.publishStateToRHS} */
async publishStateToRHS(issuerDID, rhsURL, revokedNonces) {
const treeState = await this.getDIDTreeModel(issuerDID);
await (0, credentials_1.pushHashesToRHS)(treeState.state, {
revocationTree: treeState.revocationTree,
claimsTree: treeState.claimsTree,
state: treeState.state,
rootsTree: treeState.rootsTree
}, rhsURL, revokedNonces);
}
/** {@inheritDoc IIdentityWallet.publishRevocationInfoByCredentialStatusType} */
async publishRevocationInfoByCredentialStatusType(issuerDID, credentialStatusType, opts) {
const rhsPublishers = this._credentialStatusPublisherRegistry.get(credentialStatusType);
if (!rhsPublishers) {
throw new Error(`there is no registered publisher to save hash is not registered for ${credentialStatusType} is not registered`);
}
let nodes = [];
const tree = opts?.treeModel ?? (await this.getDIDTreeModel(issuerDID));
nodes = await (0, credentials_1.getNodesRepresentation)(opts?.revokedNonces ?? [], {
revocationTree: tree.revocationTree,
claimsTree: tree.claimsTree,
state: tree.state,
rootsTree: tree.rootsTree
}, tree.state);
if (!nodes.length) {
return;
}
const rhsPublishersTask = rhsPublishers.map((publisher) => publisher.publish({ nodes, ...opts, credentialStatusType, issuerDID }));
await Promise.all(rhsPublishersTask);
}
async getCoreClaimFromCredential(credential) {
const coreClaimFromSigProof = credential.getCoreClaimFromProof(verifiable_1.ProofType.BJJSignature);
const coreClaimFromMtpProof = credential.getCoreClaimFromProof(verifiable_1.ProofType.Iden3SparseMerkleTreeProof);
if (coreClaimFromMtpProof &&
coreClaimFromSigProof &&
coreClaimFromMtpProof.hex() !== coreClaimFromSigProof.hex()) {
throw new Error('core claim representations is set in both proofs but they are not equal');
}
if (!coreClaimFromMtpProof && !coreClaimFromSigProof) {
throw new Error('core claim is not set in credential proofs');
}
//eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const coreClaim = coreClaimFromMtpProof ?? coreClaimFromSigProof;
return coreClaim;
}
async findOwnedCredentialsByDID(did, query) {
const credentials = await this._credentialWallet.findByQuery(query);
if (!credentials.length) {
throw new Error(`no credential satisfied query`);
}
const { genesisDID } = await this.getGenesisDIDMetadata(did);
const profiles = await this.getProfilesByDID(genesisDID);
return credentials.filter((cred) => {
const credentialSubjectId = cred.credentialSubject['id']; // credential subject
return (credentialSubjectId == genesisDID.string() ||
profiles.some((p) => {
return p.id === credentialSubjectId;
}));
});
}
/** {@inheritDoc IIdentityWallet.updateIdentityState} */
async updateIdentityState(issuerDID, published, treeState) {
const latestTreeState = await this.getDIDTreeModel(issuerDID);
await this._storage.identity.saveIdentity({
did: issuerDID.string(),
state: treeState?.state ?? latestTreeState.state,
isStatePublished: published,
isStateGenesis: false
});
}
/** {@inheritdoc IIdentityWallet.transitState} */
async transitState(did, oldTreeState, isOldStateGenesis, ethSigner, prover) {
const newTreeModel = await this.getDIDTreeModel(did);
const claimsRoot = await newTreeModel.claimsTree.root();
const rootOfRoots = await newTreeModel.rootsTree.root();
const revocationRoot = await newTreeModel.revocationTree.root();
const newTreeState = {
revocationRoot,
claimsRoot,
state: newTreeModel.state,
rootOfRoots
};
const userId = js_iden3_core_1.DID.idFromDID(did);
let proof;
const isEthIdentity = (0, utils_1.isEthereumIdentity)(did); // don't generate proof for ethereum identities
let txId;
if (!isEthIdentity) {
if (!prover) {
throw new Error('prover is required to generate proofs for non ethereum identities');
}
// generate the proof
const authInfo = await this._inputsGenerator.prepareAuthBJJCredential(did, oldTreeState);
const challenge = js_crypto_1.Poseidon.hash([oldTreeState.state.bigInt(), newTreeState.state.bigInt()]);
const signature = await this.signChallenge(challenge, authInfo.credential);
const circuitInputs = new circuits_1.StateTransitionInputs();
circuitInputs.id = userId;
circuitInputs.signature = signature;
circuitInputs.isOldStateGenesis = isOldStateGenesis;
const authClaimIncProofNewState = await this.generateCredentialMtp(did, authInfo.credential, newTreeState);
circuitInputs.newTreeState = authClaimIncProofNewState.treeState;
circuitInputs.authClaimNewStateIncProof = authClaimIncProofNewState.proof;
circuitInputs.oldTreeState = oldTreeState;
circuitInputs.authClaim = {
claim: authInfo.coreClaim,
incProof: authInfo.incProof,
nonRevProof: authInfo.nonRevProof
};
const inputs = circuitInputs.inputsMarshal();
proof = await prover.generate(inputs, circuits_1.CircuitId.StateTransition);
txId = await this._storage.states.publishState(proof, ethSigner);
}
else {
const oldUserState = oldTreeState.state;
const newUserState = newTreeState.state;
const userStateTransitionInfo = {
userId,
oldUserState,
newUserState,
isOldStateGenesis,
methodId: BigInt(1),
methodParams: '0x'
};
txId = await this._storage.states.publishStateGeneric(ethSigner, userStateTransitionInfo);
}
await this.updateIdentityState(did, true, newTreeState);
return txId;
}
async getAuthBJJCredential(did, oldTreeState, { nonce, seed, id, type }) {
const { authClaim, pubKey } = await this.createAuthCoreClaim(nonce, seed);
const { hi, hv } = authClaim.hiHv();
await this._storage.mt.addToMerkleTree(did.string(), storage_1.MerkleTreeType.Claims, hi, hv);
// Calculate current state after adding credential to merkle tree
const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(did.string(), storage_1.MerkleTreeType.Claims);
const currentState = (0, js_merkletree_1.hashElems)([
(await claimsTree.root()).bigInt(),
oldTreeState.revocationRoot.bigInt(),
oldTreeState.rootOfRoots.bigInt()
]);
return this.createAuthBJJCredential(did, pubKey, authClaim, currentState, {
id,
type
});
}
/** {@inheritdoc IIdentityWallet.addBJJAuthCredential} */
async addBJJAuthCredential(did, oldTreeState, isOldStateGenesis, ethSigner, opts, prover // it will be needed in case of non ethereum identities
) {
opts.seed = opts.seed ?? (0, js_crypto_1.getRandomBytes)(32);
opts.revocationOpts.nonce =
opts.revocationOpts.nonce ??
(isOldStateGenesis
? 0
: opts.revocationOpts.nonce ?? new DataView((0, js_crypto_1.getRandomBytes)(12).buffer).getUint32(0));
const credential = await this.getAuthBJJCredential(did, oldTreeState, {
nonce: opts.revocationOpts.nonce,
seed: opts.seed,
id: opts.revocationOpts.id,
type: opts.revocationOpts.type
});
const addMtpToCredAndPublishRevState = async () => {
const { receipt, block } = await this._transactionService.getTransactionReceiptAndBlock(txId);
const credsWithIden3MTPProof = await this.generateIden3SparseMerkleTreeProof(did, [credential], txId, receipt?.blockNumber, block?.timestamp, undefined, {
revNonce: opts.revocationOpts.nonce ?? 0,
subjectPosition: verifiable_1.SubjectPosition.None,
merklizedRootPosition: verifiable_1.MerklizedRootPosition.None,
updatable: false,
version: 0,
merklizeOpts: { documentLoader: (0, schema_processor_1.cacheLoader)() }
});
await this._credentialWallet.saveAll(credsWithIden3MTPProof);
await this.publishRevocationInfoByCredentialStatusType(did, opts.revocationOpts.type, {
rhsUrl: opts.revocationOpts.id,
onChain: opts.revocationOpts.onChain
});
return credsWithIden3MTPProof[0];
};
let txId = '';
let attempt = 2;
do {
try {
txId = await this.transitState(did, oldTreeState, isOldStateGenesis, ethSigner, prover);
break;
}
catch (err) {
// eslint-disable-next-line no-console
console.warn(`Error while transiting state, retrying state transition, attempt: ${attempt}`, err);
}
} while (--attempt);
if (!txId) {
const oldTransitStateInfoJson = JSON.stringify({
claimsRoot: oldTreeState.claimsRoot.hex(),
revocationRoot: oldTreeState.revocationRoot.hex(),
rootOfRoots: oldTreeState.rootOfRoots.hex(),
state: oldTreeState.state.hex(),
isOldStateGenesis,
credentialId: credential.id,
did: did.string()
}, null, 2);
await this._credentialWallet.save(credential);
throw new Error(`Error publishing state, info to publish: ${oldTransitStateInfoJson}`);
}
return addMtpToCredAndPublishRevState();
}
}
exports.IdentityWallet = IdentityWallet;