UNPKG

@0xpolygonid/js-sdk

Version:
739 lines (738 loc) 36.3 kB
import { KmsKeyType } from '../kms'; import { Blockchain, buildDIDType, BytesHelper, Claim, ClaimOptions, DID, DidMethod, getUnixTimestamp, Id, NetworkId, SchemaHash } from '@iden3/js-iden3-core'; import { poseidon, PublicKey, sha256, Signature, Hex, getRandomBytes, Poseidon } from '@iden3/js-crypto'; import { hashElems, ZERO_HASH } from '@iden3/js-merkletree'; import { generateProfileDID, subjectPositionIndex } from './common'; import * as uuid from 'uuid'; import { JsonSchemaValidator, cacheLoader } from '../schema-processor'; import { MerkleTreeType } from '../storage'; import { VerifiableConstants, BJJSignatureProof2021, MerklizedRootPosition, SubjectPosition, W3CCredential, Iden3SparseMerkleTreeProof, ProofType, CredentialStatusType } from '../verifiable'; import { getKMSIdByAuthCredential, getNodesRepresentation, pushHashesToRHS } from '../credentials'; import { CircuitId, StateTransitionInputs } from '../circuits'; import { buildDIDFromEthPubKey, byteEncoder, isEthereumIdentity } from '../utils'; import { CredentialStatusPublisherRegistry, Iden3SmtRhsCredentialStatusPublisher } from '../credentials/status/credential-status-publisher'; import { InputGenerator } from '../proof'; import { TransactionService } from '../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 */ export 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 InputGenerator(this, _credentialWallet, _storage.states); this._transactionService = new TransactionService(_storage.states.getRpcProvider()); } get credentialWallet() { return this._credentialWallet; } getCredentialStatusPublisherRegistry(_opts) { if (!_opts?.credentialStatusPublisherRegistry) { const registry = new CredentialStatusPublisherRegistry(); const emptyPublisher = { publish: () => Promise.resolve() }; registry.register(CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, new Iden3SmtRhsCredentialStatusPublisher()); registry.register(CredentialStatusType.SparseMerkleTreeProof, emptyPublisher); registry.register(CredentialStatusType.Iden3commRevocationStatusV1, emptyPublisher); return registry; } else { return this._opts?.credentialStatusPublisherRegistry; } } async createAuthCoreClaim(revNonce, seed) { const keyId = await this._kms.createKeyFromSeed(KmsKeyType.BabyJubJub, seed); const pubKeyHex = await this._kms.publicKey(keyId); const pubKey = PublicKey.newFromHex(pubKeyHex); const schemaHash = SchemaHash.authSchemaHash; const authClaim = Claim.newClaim(schemaHash, ClaimOptions.withIndexDataInts(pubKey.p[0], pubKey.p[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 ? getUnixTimestamp(authData) : 0; const request = { credentialSchema: VerifiableConstants.AUTH.AUTH_BJJ_CREDENTIAL_SCHEMA_JSON_URL, type: VerifiableConstants.AUTH.AUTH_BJJ_CREDENTIAL_TYPE, credentialSubject: { x: pubKey.p[0].toString(), y: pubKey.p[1].toString() }, subjectPosition: 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 W3CCredential(); if (authCredentials.length === 0) { const schema = JSON.parse(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(Hex.encode(sha256(opts.seed)), uuid.NIL) : uuid.v4(); opts.seed = opts.seed ?? 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, MerkleTreeType.Claims, hi, hv); const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(tmpIdentifier, MerkleTreeType.Claims); const ctr = await claimsTree.root(); const currentState = hashElems([ctr.bigInt(), ZERO_HASH.bigInt(), ZERO_HASH.bigInt()]); const didType = buildDIDType(opts.method || DidMethod.Iden3, opts.blockchain || Blockchain.Polygon, opts.networkId || NetworkId.Amoy); const identifier = Id.idGenesisFromIdenState(didType, currentState.bigInt()); const did = DID.parseFromId(identifier); await this._storage.mt.bindMerkleTreeToNewIdentifier(tmpIdentifier, did.string()); const oldTreeState = { revocationRoot: ZERO_HASH, claimsRoot: ctr, state: currentState, rootOfRoots: 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 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 ?? 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 = ZERO_HASH; // In Ethereum identities we don't have an initial state with the auth credential const didType = buildDIDType(opts.method || DidMethod.Iden3, opts.blockchain || Blockchain.Polygon, opts.networkId || NetworkId.Amoy); const keyIdEth = await this._kms.createKeyFromSeed(KmsKeyType.Secp256k1, opts.seed); const pubKeyHexEth = (await this._kms.publicKey(keyIdEth)).slice(2); // 04 + x + y (uncompressed key) const did = 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: ZERO_HASH, claimsRoot: ZERO_HASH, state: currentState, rootOfRoots: 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: 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: DID.parse(profile.genesisIdentifier) }; } /** {@inheritDoc IIdentityWallet.createProfile} */ async createProfile(did, nonce, verifier) { const profileDID = 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, 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, MerkleTreeType.Claims); const revocationTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(didStr, MerkleTreeType.Revocations); const rootsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(didStr, MerkleTreeType.Roots); const state = 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(), 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(), 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 = getKMSIdByAuthCredential(credential); const payload = poseidon.hashBytes(message); const signature = await this._kms.sign(keyKMSId, BytesHelper.intToBytes(payload)); return Signature.newFromCompressed(signature); } /** {@inheritDoc IIdentityWallet.signChallenge} */ async signChallenge(challenge, credential) { const keyKMSId = getKMSIdByAuthCredential(credential); const signature = await this._kms.sign(keyKMSId, BytesHelper.intToBytes(challenge)); return Signature.newFromCompressed(signature); } /** {@inheritDoc IIdentityWallet.issueCredential} */ async issueCredential(issuerDID, req, opts) { req.revocationOpts.id = req.revocationOpts.id.replace(/\/$/, ''); let schema; const loader = opts?.documentLoader ?? 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 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(getRandomBytes(16).buffer).getUint32(0, false); req.subjectPosition = req.subjectPosition ?? SubjectPosition.Index; try { credential = this._credentialWallet.createCredential(issuerDID, req, jsonSchema); const encodedCred = byteEncoder.encode(JSON.stringify(credential)); const encodedSchema = byteEncoder.encode(JSON.stringify(schema)); await new 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 ?? MerklizedRootPosition.None, updatable: false, version: 0, merklizeOpts: { ...opts, documentLoader: loader } }; const coreClaim = await credential.toCoreClaim(coreClaimOpts); const { hi, hv } = coreClaim.hiHv(); const coreClaimHash = 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 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(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(ProofType.BJJSignature); if (!coreClaim) { throw new Error('credential must have coreClaim representation in the signature proof'); } await this._storage.mt.addToMerkleTree(issuerDID.string(), 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(), 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(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 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 pushHashesToRHS(treeModel.state, treeModel, rhsURL, revokedNonces); } /** {@inheritDoc IIdentityWallet.publishStateToRHS} */ async publishStateToRHS(issuerDID, rhsURL, revokedNonces) { const treeState = await this.getDIDTreeModel(issuerDID); await 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 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(ProofType.BJJSignature); const coreClaimFromMtpProof = credential.getCoreClaimFromProof(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 = DID.idFromDID(did); let proof; const isEthIdentity = 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 = Poseidon.hash([oldTreeState.state.bigInt(), newTreeState.state.bigInt()]); const signature = await this.signChallenge(challenge, authInfo.credential); const circuitInputs = new 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, 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(), MerkleTreeType.Claims, hi, hv); // Calculate current state after adding credential to merkle tree const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(did.string(), MerkleTreeType.Claims); const currentState = 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 ?? getRandomBytes(32); opts.revocationOpts.nonce = opts.revocationOpts.nonce ?? (isOldStateGenesis ? 0 : opts.revocationOpts.nonce ?? new DataView(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: SubjectPosition.None, merklizedRootPosition: MerklizedRootPosition.None, updatable: false, version: 0, merklizeOpts: { documentLoader: 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(); } }