diff --git a/index.html b/index.html
index e0dcafd5..7f1a9150 100644
--- a/index.html
+++ b/index.html
@@ -74,6 +74,9 @@
publishState: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
+ publishStateGeneric: async () => {
+ return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
+ },
getGISTProof: () => {
return Promise.resolve({
root: 0n,
diff --git a/src/credentials/status/reverse-sparse-merkle-tree.ts b/src/credentials/status/reverse-sparse-merkle-tree.ts
index 25474129..c03137a8 100644
--- a/src/credentials/status/reverse-sparse-merkle-tree.ts
+++ b/src/credentials/status/reverse-sparse-merkle-tree.ts
@@ -5,7 +5,7 @@ import { IStateStorage } from '../../storage';
import { CredentialStatusResolver, CredentialStatusResolveOptions } from './resolver';
import { CredentialStatus, RevocationStatus, State } from '../../verifiable';
import { VerifiableConstants, CredentialStatusType } from '../../verifiable/constants';
-import { isGenesisState } from '../../utils';
+import { isEthereumIdentity, isGenesisState } from '../../utils';
import { IssuerResolver } from './sparse-merkle-tree';
/**
@@ -173,11 +173,19 @@ export class RHSResolver implements CredentialStatusResolver {
return this.getRevocationStatusFromIssuerData(issuerDID, issuerData, genesisState);
}
const currentStateBigInt = Hash.fromHex(stateHex).bigInt();
- if (!isGenesisState(issuerDID, currentStateBigInt)) {
+
+ const isEthIdentity = isEthereumIdentity(issuerDID);
+
+ if (!isEthIdentity && !isGenesisState(issuerDID, currentStateBigInt)) {
throw new Error(
`latest state not found and state parameter ${stateHex} is not genesis state`
);
}
+
+ if (isEthIdentity) {
+ throw new Error(`State must be published for Ethereum based identity`);
+ }
+
latestState = currentStateBigInt;
}
diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts
index 831b7444..5580916f 100644
--- a/src/identity/identity-wallet.ts
+++ b/src/identity/identity-wallet.ts
@@ -12,13 +12,20 @@ import {
NetworkId,
SchemaHash
} from '@iden3/js-iden3-core';
-import { poseidon, PublicKey, sha256, Signature, Hex, getRandomBytes } from '@iden3/js-crypto';
-import { hashElems, ZERO_HASH } from '@iden3/js-merkletree';
-
+import {
+ poseidon,
+ PublicKey,
+ sha256,
+ Signature,
+ Hex,
+ getRandomBytes,
+ Poseidon
+} from '@iden3/js-crypto';
+import { Hash, hashElems, ZERO_HASH } from '@iden3/js-merkletree';
import { generateProfileDID, subjectPositionIndex } from './common';
import * as uuid from 'uuid';
import { JSONSchema, JsonSchemaValidator, cacheLoader } from '../schema-processor';
-import { IDataStorage, MerkleTreeType, Profile } from '../storage';
+import { IDataStorage, MerkleTreeType, Profile, UserStateTransitionInfo } from '../storage';
import {
VerifiableConstants,
BJJSignatureProof2021,
@@ -42,14 +49,15 @@ import {
pushHashesToRHS,
TreesModel
} from '../credentials';
-import { TreeState } from '../circuits';
-import { byteEncoder } from '../utils';
+import { CircuitId, StateTransitionInputs, TreeState } from '../circuits';
+import { buildDIDFromEthPubKey, byteEncoder, isEthereumIdentity } from '../utils';
import { Options } from '@iden3/js-jsonld-merklization';
-import { TransactionReceipt } from 'ethers';
+import { Signer, TransactionReceipt } from 'ethers';
import {
CredentialStatusPublisherRegistry,
Iden3SmtRhsCredentialStatusPublisher
} from '../credentials/status/credential-status-publisher';
+import { InputGenerator, IZKProver } from '../proof';
/**
* DID creation options
@@ -62,6 +70,14 @@ export type IdentityCreationOptions = {
method?: string;
blockchain?: string;
networkId?: string;
+} & AuthBJJCredentialCreationOptions;
+
+/**
+ * Options for creating Auth BJJ credential
+ * seed - seed to generate BJJ key pair
+ * revocationOpts -
+ */
+export type AuthBJJCredentialCreationOptions = {
revocationOpts: {
id: string;
type: CredentialStatusType;
@@ -74,6 +90,14 @@ export type IdentityCreationOptions = {
seed?: Uint8Array;
};
+/**
+ * Options for creating Ethereum based identity
+ */
+export type EthereumBasedIdentityCreationOptions = IdentityCreationOptions & {
+ ethSigner?: Signer;
+ createBjjCredential?: boolean;
+};
+
/**
* Options for RevocationInfoOptions.
*/
@@ -109,13 +133,23 @@ export interface IIdentityWallet {
* adds auth BJJ credential to claims tree and generates mtp of inclusion
* based on the resulting state it provides an identifier in DID form.
*
- * @param {IdentityCreationOptions} opts - default is did:iden3:polygon:mumbai** with generated key.
+ * @param {IdentityCreationOptions} opts - default is did:iden3:polygon:amoy** with generated key.
* @returns `Promise<{ did: DID; credential: W3CCredential }>` - returns did and Auth BJJ credential
* @public
*/
-
createIdentity(opts: IdentityCreationOptions): Promise<{ did: DID; credential: W3CCredential }>;
+ /**
+ * Create Identity based in Ethereum address and it provides an identifier in DID form.
+ *
+ * @param {IdentityCreationOptions} opts - default is did:iden3:polygon:amoy** with generated key.
+ * @returns `Promise<{ did: DID; credential: W3CCredential | undefined }>` - returns did and Auth BJJ credential
+ * @public
+ */
+ createEthereumBasedIdentity(
+ opts: EthereumBasedIdentityCreationOptions
+ ): Promise<{ did: DID; credential: W3CCredential | undefined }>;
+
/**
* Creates profile based on genesis identifier
*
@@ -166,6 +200,21 @@ export interface IIdentityWallet {
treeState?: TreeState
): Promise;
+ /**
+ * Generates proof of core claim inclusion / non-inclusion to the given claims tree
+ * and its root or to the current root of the Claims tree in the given Merkle tree storage.
+ *
+ * @param {DID} did - issuer did
+ * @param {core.Claim} core - core claim to generate mtp
+ * @param {TreeState} [treeState] - tree state when to generate a proof
+ * @returns `Promise` - MerkleTreeProof and TreeState on which proof has been generated
+ */
+ generateCoreClaimMtp(
+ did: DID,
+ coreClaim: Claim,
+ treeState?: TreeState
+ ): Promise;
+
/**
* Generates proof of credential revocation nonce (with credential as a param) inclusion / non-inclusion to the given revocation tree
* and its root or to the current root of the Revocation tree in the given Merkle tree storage.
@@ -311,7 +360,7 @@ export interface IIdentityWallet {
*
* gets profile identity by genesis identifiers
*
- * @param {string} did - genesis identifier from which profile has been derived
+ * @param {DID} did - genesis identifier from which profile has been derived
* @returns `{Promise}`
*/
getProfilesByDID(did: DID): Promise;
@@ -320,7 +369,7 @@ export interface IIdentityWallet {
*
* gets profile nonce by it's id. if profile is genesis identifier - 0 is returned
*
- * @param {string} did - profile that has been derived or genesis identity
+ * @param {DID} did - profile that has been derived or genesis identity
* @returns `{Promise<{nonce:number, genesisIdentifier: DID}>}`
*/
getGenesisDIDMetadata(did: DID): Promise<{ nonce: number; genesisDID: DID }>;
@@ -329,7 +378,7 @@ export interface IIdentityWallet {
*
* find all credentials that belong to any profile or genesis identity for the given did
*
- * @param {string} did - profile that has been derived or genesis identity
+ * @param {DID} did - profile that has been derived or genesis identity
* @returns `{Promise}`
*/
findOwnedCredentialsByDID(did: DID, query: ProofQuery): Promise;
@@ -373,6 +422,40 @@ export interface IIdentityWallet {
incProof: MerkleTreeProofWithTreeState;
nonRevProof: MerkleTreeProofWithTreeState;
}>;
+
+ /**
+ * Transit state for the identity with the given DID
+ *
+ * @param {DID} did - identifier of the user
+ * @param {TreeState} oldTreeState - old state of the user
+ * @param {boolean} isOldStateGenesis - if the old state is genesis
+ * @param {IStateStorage} stateStorage - storage to save the new state
+ * @param {Signer} ethSigner - signer to sign the transaction
+ */
+ transitState(
+ did: DID,
+ oldTreeState: TreeState,
+ isOldStateGenesis: boolean,
+ ethSigner: Signer,
+ prover?: IZKProver
+ ): Promise;
+
+ /**
+ * Add BJJ credential and transit state
+ *
+ * @param {DID} did - identifier of the user
+ * @param {TreeState} oldTreeState - old tree state of the user
+ * @param {boolean} isOldTreeState - if the old state is genesis
+ * @param {Signer} ethSigner - signer to sign the transaction
+ * @param {AuthBJJCredentialCreationOptions} opts - additional options
+ */
+ addBJJAuthCredential(
+ did: DID,
+ oldTreeState: TreeState,
+ isOldTreeStateGenesis: boolean,
+ ethSigner: Signer,
+ opts?: AuthBJJCredentialCreationOptions
+ ): Promise;
}
/**
@@ -388,6 +471,7 @@ export interface IIdentityWallet {
*/
export class IdentityWallet implements IIdentityWallet {
private readonly _credentialStatusPublisherRegistry: CredentialStatusPublisherRegistry;
+ private readonly _inputsGenerator: InputGenerator;
/**
* Constructs a new instance of the `IdentityWallet` class
@@ -406,6 +490,7 @@ export class IdentityWallet implements IIdentityWallet {
}
) {
this._credentialStatusPublisherRegistry = this.getCredentialStatusPublisherRegistry(_opts);
+ this._inputsGenerator = new InputGenerator(this, _credentialWallet, _storage.states);
}
private getCredentialStatusPublisherRegistry(
@@ -428,26 +513,12 @@ export class IdentityWallet implements IIdentityWallet {
}
}
- /**
- * {@inheritDoc IIdentityWallet.createIdentity}
- */
- async createIdentity(
- opts: IdentityCreationOptions
- ): Promise<{ did: DID; credential: W3CCredential }> {
- const tmpIdentifier = opts.seed ? uuid.v5(Hex.encode(sha256(opts.seed)), uuid.NIL) : uuid.v4();
-
- opts.method = opts.method ?? DidMethod.Iden3;
- opts.blockchain = opts.blockchain ?? Blockchain.Polygon;
- opts.networkId = opts.networkId ?? NetworkId.Amoy;
-
- await this._storage.mt.createIdentityMerkleTrees(tmpIdentifier);
-
- opts.seed = opts.seed ?? getRandomBytes(32);
-
- const keyId = await this._kms.createKeyFromSeed(KmsKeyType.BabyJubJub, opts.seed);
-
+ private async createAuthCoreClaim(
+ revNonce: number,
+ seed: Uint8Array
+ ): Promise<{ authClaim: Claim; pubKey: PublicKey }> {
+ 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;
@@ -457,35 +528,18 @@ export class IdentityWallet implements IIdentityWallet {
ClaimOptions.withIndexDataInts(pubKey.p[0], pubKey.p[1]),
ClaimOptions.withRevocationNonce(BigInt(0))
);
- const revNonce = opts.revocationOpts.nonce ?? 0;
authClaim.setRevocationNonce(BigInt(revNonce));
- await this._storage.mt.addToMerkleTree(
- tmpIdentifier,
- MerkleTreeType.Claims,
- authClaim.hiHv().hi,
- authClaim.hiHv().hv
- );
-
- const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(
- tmpIdentifier,
- MerkleTreeType.Claims
- );
-
- const currentState = hashElems([
- (await claimsTree.root()).bigInt(),
- ZERO_HASH.bigInt(),
- ZERO_HASH.bigInt()
- ]);
-
- const didType = buildDIDType(opts.method, opts.blockchain, opts.networkId);
- const identifier = Id.idGenesisFromIdenState(didType, currentState.bigInt());
- const did = DID.parseFromId(identifier);
-
- await this._storage.mt.bindMerkleTreeToNewIdentifier(tmpIdentifier, did.string());
-
- const schema = JSON.parse(VerifiableConstants.AUTH.AUTH_BJJ_CREDENTIAL_SCHEMA_JSON);
+ return { authClaim, pubKey };
+ }
+ private async createAuthBJJCredential(
+ did: DID,
+ pubKey: PublicKey,
+ authClaim: Claim,
+ currentState: Hash,
+ revocationOpts: { id: string; type: CredentialStatusType }
+ ): Promise {
const authData = authClaim.getExpirationDate();
const expiration = authData ? getUnixTimestamp(authData) : 0;
@@ -500,23 +554,84 @@ export class IdentityWallet implements IIdentityWallet {
version: 0,
expiration,
revocationOpts: {
- nonce: revNonce,
- id: opts.revocationOpts.id.replace(/\/$/, ''),
- type: opts.revocationOpts.type,
+ 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: W3CCredential = new W3CCredential();
- try {
- credential = this._credentialWallet.createCredential(did, request, schema);
- } catch (e) {
- throw new Error(`Error create w3c credential ${(e as Error).message}`);
+ 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 as Error).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: IdentityCreationOptions
+ ): Promise<{ did: DID; credential: W3CCredential }> {
+ 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 index = authClaim.hIndex();
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 credential = await this.createAuthBJJCredential(
+ did,
+ pubKey,
+ authClaim,
+ currentState,
+ opts.revocationOpts
+ );
+
+ const index = authClaim.hIndex();
const { proof } = await claimsTree.generateProof(index, ctr);
const mtpProof: Iden3SparseMerkleTreeProof = new Iden3SparseMerkleTreeProof({
@@ -524,8 +639,8 @@ export class IdentityWallet implements IIdentityWallet {
issuerData: {
id: did,
state: {
- rootOfRoots: ZERO_HASH,
- revocationTreeRoot: ZERO_HASH,
+ rootOfRoots: oldTreeState.rootOfRoots,
+ revocationTreeRoot: oldTreeState.revocationRoot,
claimsTreeRoot: ctr,
value: currentState
}
@@ -555,6 +670,63 @@ export class IdentityWallet implements IIdentityWallet {
};
}
+ /**
+ * {@inheritDoc IIdentityWallet.createEthereumBasedIdentity}
+ */
+ async createEthereumBasedIdentity(
+ opts: EthereumBasedIdentityCreationOptions
+ ): Promise<{ did: DID; credential: W3CCredential | undefined }> {
+ opts.seed = opts.seed ?? getRandomBytes(32);
+ opts.createBjjCredential = opts.createBjjCredential ?? true;
+
+ let credential;
+ const ethSigner = opts.ethSigner;
+
+ if (!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) {
+ // Old tree state genesis state
+ const oldTreeState: TreeState = {
+ 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: DID): Promise<{ nonce: number; genesisDID: DID }> {
// check if it is a genesis identity
@@ -650,8 +822,15 @@ export class IdentityWallet implements IIdentityWallet {
treeState?: TreeState
): Promise {
const coreClaim = await this.getCoreClaimFromCredential(credential);
- // todo: Parser.parseClaim
+ return this.generateCoreClaimMtp(did, coreClaim, treeState);
+ }
+ /** {@inheritDoc IIdentityWallet.generateClaimMtp} */
+ async generateCoreClaimMtp(
+ did: DID,
+ coreClaim: Claim,
+ treeState?: TreeState
+ ): Promise {
const treesModel = await this.getDIDTreeModel(did);
const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType(
@@ -867,10 +1046,10 @@ export class IdentityWallet implements IIdentityWallet {
async revokeCredential(issuerDID: DID, credential: W3CCredential): Promise {
const issuerTree = await this.getDIDTreeModel(issuerDID);
- const coreClaim = credential.getCoreClaimFromProof(ProofType.BJJSignature);
+ const coreClaim = await this.getCoreClaimFromCredential(credential);
if (!coreClaim) {
- throw new Error('credential must have coreClaim representation in the signature proof');
+ throw new Error('credential must have coreClaim representation in proofs');
}
const nonce = coreClaim.getRevocationNonce();
@@ -946,20 +1125,23 @@ export class IdentityWallet implements IIdentityWallet {
txId: string,
blockNumber?: number,
blockTimestamp?: number,
- treeState?: TreeState
+ treeState?: TreeState,
+ opts?: CoreClaimCreationOptions
): Promise {
for (let index = 0; index < credentials.length; index++) {
const credential = credentials[index];
- const mtpWithProof = await this.generateCredentialMtp(issuerDID, credential, treeState);
-
// TODO: return coreClaim from generateCredentialMtp and use it below
// credential must have a bjj signature proof
- const coreClaim = credential.getCoreClaimFromProof(ProofType.BJJSignature);
+
+ 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: Iden3SparseMerkleTreeProof = new Iden3SparseMerkleTreeProof({
mtp: mtpWithProof.proof,
@@ -981,7 +1163,9 @@ export class IdentityWallet implements IIdentityWallet {
if (Array.isArray(credentials[index].proof)) {
(credentials[index].proof as unknown[]).push(mtpProof);
} else {
- credentials[index].proof = [credentials[index].proof, mtpProof];
+ credentials[index].proof = credentials[index].proof
+ ? [credentials[index].proof, mtpProof]
+ : [mtpProof];
}
}
return credentials;
@@ -1122,4 +1306,162 @@ export class IdentityWallet implements IIdentityWallet {
isStateGenesis: false
});
}
+
+ /** {@inheritdoc IIdentityWallet.transitState} */
+ async transitState(
+ did: DID,
+ oldTreeState: TreeState,
+ isOldStateGenesis: boolean,
+ ethSigner: Signer,
+ prover?: IZKProver
+ ): Promise {
+ 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: TreeState = {
+ 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: UserStateTransitionInfo = {
+ userId,
+ oldUserState,
+ newUserState,
+ isOldStateGenesis,
+ methodId: BigInt(1),
+ methodParams: '0x'
+ } as UserStateTransitionInfo;
+ txId = await this._storage.states.publishStateGeneric(ethSigner, userStateTransitionInfo);
+ }
+ await this.updateIdentityState(did, true, newTreeState);
+
+ return txId;
+ }
+
+ /** {@inheritdoc IIdentityWallet.addBJJAuthCredential} */
+ async addBJJAuthCredential(
+ did: DID,
+ oldTreeState: TreeState,
+ isOldStateGenesis: boolean,
+ ethSigner: Signer,
+ opts: AuthBJJCredentialCreationOptions,
+ prover?: IZKProver // it will be needed in case of non ethereum identities
+ ): Promise {
+ 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 { authClaim, pubKey } = await this.createAuthCoreClaim(
+ opts.revocationOpts.nonce ?? 0,
+ opts.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()
+ ]);
+
+ let credential = await this.createAuthBJJCredential(
+ did,
+ pubKey,
+ authClaim,
+ currentState,
+ opts.revocationOpts
+ );
+
+ const txId = await this.transitState(did, oldTreeState, isOldStateGenesis, ethSigner, prover);
+ // TODO: update to get blockNumber and blockTimestamp from function instead of passing 0s
+ const credsWithIden3MTPProof = await this.generateIden3SparseMerkleTreeProof(
+ did,
+ [credential],
+ txId,
+ 0,
+ 0,
+ undefined,
+ {
+ revNonce: Number(authClaim.getRevocationNonce()),
+ subjectPosition: SubjectPosition.None,
+ merklizedRootPosition: MerklizedRootPosition.None,
+ updatable: false,
+ version: 0,
+ merklizeOpts: { documentLoader: cacheLoader() }
+ }
+ );
+ await this._credentialWallet.saveAll(credsWithIden3MTPProof);
+
+ const credRefreshed = await this._credentialWallet.findById(credential.id);
+ if (!credRefreshed) {
+ throw new Error('Credential not found in credential wallet');
+ }
+ credential = credRefreshed;
+
+ await this.publishRevocationInfoByCredentialStatusType(did, opts.revocationOpts.type, {
+ rhsUrl: opts.revocationOpts.id,
+ onChain: opts.revocationOpts.onChain
+ });
+
+ return credential;
+ }
}
diff --git a/src/kms/key-providers/sec256k1-provider.ts b/src/kms/key-providers/sec256k1-provider.ts
index 3cf002be..a7693a8d 100644
--- a/src/kms/key-providers/sec256k1-provider.ts
+++ b/src/kms/key-providers/sec256k1-provider.ts
@@ -44,7 +44,10 @@ export class Sec256k1Provider implements IKeyProvider {
type: this.keyType,
id: providerHelpers.keyPath(this.keyType, keyPair.getPublic().encode('hex', false))
};
- await this._keyStore.importKey({ alias: kmsId.id, key: keyPair.getPrivate().toString('hex') });
+ await this._keyStore.importKey({
+ alias: kmsId.id,
+ key: keyPair.getPrivate().toString('hex').padStart(64, '0')
+ });
return kmsId;
}
@@ -56,7 +59,7 @@ export class Sec256k1Provider implements IKeyProvider {
*/
async publicKey(keyId: KmsKeyId): Promise {
const privateKeyHex = await this.privateKey(keyId);
- return this._ec.keyFromPrivate(privateKeyHex, 'hex').getPublic().encode('hex', false);
+ return this._ec.keyFromPrivate(privateKeyHex, 'hex').getPublic().encode('hex', false); // 04 + x + y (uncompressed key)
}
/**
diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts
index 618feeb1..2f3770a5 100644
--- a/src/proof/proof-service.ts
+++ b/src/proof/proof-service.ts
@@ -1,15 +1,6 @@
-import { Poseidon } from '@iden3/js-crypto';
import { BytesHelper, DID, MerklizedRootPosition } from '@iden3/js-iden3-core';
import { Hash } from '@iden3/js-merkletree';
-import {
- AuthV2Inputs,
- CircuitId,
- Operators,
- Query,
- StateTransitionInputs,
- TreeState,
- ValueProof
-} from '../circuits';
+import { AuthV2Inputs, CircuitId, Operators, Query, TreeState, ValueProof } from '../circuits';
import { ICredentialWallet } from '../credentials';
import { IIdentityWallet } from '../identity';
import {
@@ -137,7 +128,6 @@ export interface IProofService {
* @param {DID} did - identity that will transit state
* @param {TreeState} oldTreeState - previous tree state
* @param {boolean} isOldStateGenesis - is a transition state is done from genesis state
- * @param {IStateStorage} stateStorage - storage of identity states (only eth based storage currently)
* @param {Signer} ethSigner - signer for transaction
* @returns `{Promise}` - transaction hash is returned
*/
@@ -357,57 +347,16 @@ export class ProofService implements IProofService {
did: DID,
oldTreeState: TreeState,
isOldStateGenesis: boolean,
- stateStorage: IStateStorage,
+ stateStorage: IStateStorage, // for compatibility with previous versions we leave this parameter
ethSigner: Signer
): Promise {
- const authInfo = await this._inputsGenerator.prepareAuthBJJCredential(did, oldTreeState);
-
- const newTreeModel = await this._identityWallet.getDIDTreeModel(did);
- const claimsRoot = await newTreeModel.claimsTree.root();
- const rootOfRoots = await newTreeModel.rootsTree.root();
- const revocationRoot = await newTreeModel.revocationTree.root();
-
- const newTreeState: TreeState = {
- revocationRoot,
- claimsRoot,
- state: newTreeModel.state,
- rootOfRoots
- };
- const challenge = Poseidon.hash([oldTreeState.state.bigInt(), newTreeState.state.bigInt()]);
-
- const signature = await this._identityWallet.signChallenge(challenge, authInfo.credential);
-
- const circuitInputs = new StateTransitionInputs();
- circuitInputs.id = DID.idFromDID(did);
-
- circuitInputs.signature = signature;
- circuitInputs.isOldStateGenesis = isOldStateGenesis;
-
- const authClaimIncProofNewState = await this._identityWallet.generateCredentialMtp(
+ return this._identityWallet.transitState(
did,
- authInfo.credential,
- newTreeState
+ oldTreeState,
+ isOldStateGenesis,
+ ethSigner,
+ this._prover
);
-
- 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();
-
- const proof = await this._prover.generate(inputs, CircuitId.StateTransition);
-
- const txId = await stateStorage.publishState(proof, ethSigner);
-
- await this._identityWallet.updateIdentityState(did, true, newTreeState);
-
- return txId;
}
private async generateInputs(
diff --git a/src/proof/provers/inputs-generator.ts b/src/proof/provers/inputs-generator.ts
index 5a2cba8e..37bbf9f3 100644
--- a/src/proof/provers/inputs-generator.ts
+++ b/src/proof/provers/inputs-generator.ts
@@ -1,4 +1,4 @@
-import { DID, Id, getUnixTimestamp } from '@iden3/js-iden3-core';
+import { DID, getUnixTimestamp } from '@iden3/js-iden3-core';
import {
Iden3SparseMerkleTreeProof,
ProofType,
@@ -37,6 +37,7 @@ import {
ICredentialWallet,
getUserDIDFromCredential
} from '../../credentials';
+import { isEthereumIdentity } from '../../utils';
export type DIDProfileMetadata = {
authProfileNonce: number;
@@ -537,12 +538,7 @@ export class InputGenerator {
? BigInt(proofReq.params?.nullifierSessionId?.toString())
: BigInt(0);
- let isEthIdentity = true;
- try {
- Id.ethAddressFromId(circuitInputs.id);
- } catch {
- isEthIdentity = false;
- }
+ const isEthIdentity = isEthereumIdentity(identifier);
circuitInputs.isBJJAuthEnabled = isEthIdentity ? 0 : 1;
circuitInputs.challenge = BigInt(params.challenge ?? 0);
diff --git a/src/storage/blockchain/state.ts b/src/storage/blockchain/state.ts
index f10641c2..5e55c39b 100644
--- a/src/storage/blockchain/state.ts
+++ b/src/storage/blockchain/state.ts
@@ -1,6 +1,6 @@
import { RootInfo, StateProof } from './../entities/state';
import { ZKProof } from '@iden3/js-jwz';
-import { IStateStorage } from '../interfaces/state';
+import { IStateStorage, UserStateTransitionInfo } from '../interfaces/state';
import { Contract, JsonRpcProvider, Signer, TransactionRequest } from 'ethers';
import { StateInfo } from '../entities/state';
import { StateTransitionPubSignals } from '../../circuits';
@@ -144,18 +144,50 @@ export class EthStateStorage implements IStateStorage {
maxFeePerGas,
maxPriorityFeePerGas
};
- const tx = await signer.sendTransaction(request);
- const txnReceipt = await tx.wait();
- if (!txnReceipt) {
- throw new Error(`transaction: ${tx.hash} failed to mined`);
- }
- const status: number | null = txnReceipt.status;
- const txnHash: string = txnReceipt.hash;
+ const txnHash: string = await this.sendTransactionRequest(signer, request);
- if (!status) {
- throw new Error(`transaction: ${txnHash} failed to mined`);
- }
+ return txnHash;
+ }
+
+ /** {@inheritdoc IStateStorage.publishStateGeneric} */
+ async publishStateGeneric(
+ signer: Signer,
+ userStateTransitionInfo: UserStateTransitionInfo
+ ): Promise {
+ const { userId, oldUserState, newUserState, isOldStateGenesis, methodId, methodParams } =
+ userStateTransitionInfo;
+ const { stateContract, provider } = this.getStateContractAndProviderForId(userId.bigInt());
+ const contract = stateContract.connect(signer) as Contract;
+ const feeData = await provider.getFeeData();
+
+ const maxFeePerGas = defaultEthConnectionConfig.maxFeePerGas
+ ? BigInt(defaultEthConnectionConfig.maxFeePerGas)
+ : feeData.maxFeePerGas;
+ const maxPriorityFeePerGas = defaultEthConnectionConfig.maxPriorityFeePerGas
+ ? BigInt(defaultEthConnectionConfig.maxPriorityFeePerGas)
+ : feeData.maxPriorityFeePerGas;
+
+ const payload = [
+ userId.bigInt().toString(),
+ oldUserState.bigInt().toString(),
+ newUserState.bigInt().toString(),
+ isOldStateGenesis,
+ methodId, //BigInt(1),
+ methodParams //'0x'
+ ];
+ const gasLimit = await contract.transitStateGeneric.estimateGas(...payload);
+ const txData = await contract.transitStateGeneric.populateTransaction(...payload);
+
+ const request: TransactionRequest = {
+ to: txData.to,
+ data: txData.data,
+ gasLimit,
+ maxFeePerGas,
+ maxPriorityFeePerGas
+ };
+
+ const txnHash: string = await this.sendTransactionRequest(signer, request);
return txnHash;
}
@@ -227,4 +259,23 @@ export class EthStateStorage implements IStateStorage {
return this.ethConfig as EthConnectionConfig;
}
+
+ private async sendTransactionRequest(
+ signer: Signer,
+ request: TransactionRequest
+ ): Promise {
+ const tx = await signer.sendTransaction(request);
+ const txnReceipt = await tx.wait();
+ if (!txnReceipt) {
+ throw new Error(`transaction: ${tx.hash} failed to mined`);
+ }
+ const status: number | null = txnReceipt.status;
+ const txnHash: string = txnReceipt.hash;
+
+ if (!status) {
+ throw new Error(`transaction: ${txnHash} failed to mined`);
+ }
+
+ return txnHash;
+ }
}
diff --git a/src/storage/interfaces/state.ts b/src/storage/interfaces/state.ts
index 4996408b..d5cb44b1 100644
--- a/src/storage/interfaces/state.ts
+++ b/src/storage/interfaces/state.ts
@@ -1,6 +1,17 @@
import { ZKProof } from '@iden3/js-jwz';
import { Signer } from 'ethers';
import { RootInfo, StateInfo, StateProof } from '../entities/state';
+import { Id } from '@iden3/js-iden3-core';
+import { Hash } from '@iden3/js-merkletree';
+
+export interface UserStateTransitionInfo {
+ userId: Id; // Identity id
+ oldUserState: Hash; // Previous identity state
+ newUserState: Hash; // New identity state
+ isOldStateGenesis: boolean; // Is the previous state genesis?
+ methodId: bigint; // State transition method id
+ methodParams: string; // State transition method-specific params
+}
/**
* Interface that defines methods for state storage
@@ -32,7 +43,18 @@ export interface IStateStorage {
* @param {Signer} signer - signer of transaction
* @returns `Promise` - transaction identifier
*/
- publishState(proof: ZKProof, signer: Signer): Promise;
+ publishState(proof: ZKProof | undefined, signer: Signer): Promise;
+ /**
+ * method to publish state onchain
+ *
+ * @param {Signer} signer - signer of transaction
+ * @param {UserStateTransitionInfo} userStateTransitionInfo - user state transition information
+ * @returns `Promise` - transaction identifier
+ */
+ publishStateGeneric(
+ signer: Signer,
+ userStateTransitionInfo?: UserStateTransitionInfo
+ ): Promise;
/**
* generates proof of inclusion / non-inclusion to global identity state for given identity
*
diff --git a/src/utils/did-helper.ts b/src/utils/did-helper.ts
index c77b0242..2bb3eb9f 100644
--- a/src/utils/did-helper.ts
+++ b/src/utils/did-helper.ts
@@ -2,11 +2,13 @@ import { Hex } from '@iden3/js-crypto';
import { Id, buildDIDType, genesisFromEthAddress, DID } from '@iden3/js-iden3-core';
import { Hash } from '@iden3/js-merkletree';
import { DIDResolutionResult, VerificationMethod } from 'did-resolver';
+import { keccak256 } from 'js-sha3';
+import { hexToBytes } from './encoding';
/**
* Checks if state is genesis state
*
- * @param {string} did - did
+ * @param {DID} did - did
* @param {bigint|string} state - hash on bigInt or hex string format
* @returns boolean
*/
@@ -22,6 +24,24 @@ export function isGenesisState(did: DID, state: bigint | string): boolean {
return id.bigInt().toString() === idFromState.bigInt().toString();
}
+/**
+ * Checks if DID is an ethereum identity
+ *
+ * @param {DID} did - did
+ * @returns boolean
+ */
+export function isEthereumIdentity(did: DID): boolean {
+ const issuerId = DID.idFromDID(did);
+ try {
+ Id.ethAddressFromId(issuerId);
+ // is an ethereum identity
+ return true;
+ } catch {
+ // not an ethereum identity (BabyJubJub or other)
+ return false;
+ }
+}
+
export const buildVerifierId = (
address: string,
info: { method: string; blockchain: string; networkId: string }
@@ -65,3 +85,16 @@ export const resolveDIDDocumentAuth = async (
(i) => i.type === 'Iden3StateInfo2023'
);
};
+
+export const buildDIDFromEthPubKey = (didType: Uint8Array, pubKeyEth: string): DID => {
+ // Use Keccak-256 hash function to get public key hash
+ const hashOfPublicKey = keccak256(hexToBytes(pubKeyEth));
+ // Convert hash to buffer
+ const ethAddressBuffer = hexToBytes(hashOfPublicKey);
+ // Ethereum Address is '0x' concatenated with last 20 bytes
+ // of the public key hash
+ const ethAddr = ethAddressBuffer.slice(-20);
+ const genesis = genesisFromEthAddress(ethAddr);
+ const identifier = new Id(didType, genesis);
+ return DID.parseFromId(identifier);
+};
diff --git a/tests/credentials/credential-statuses/on-chain-revocation.test.ts b/tests/credentials/credential-statuses/on-chain-revocation.test.ts
index b0ffa923..736a61f3 100644
--- a/tests/credentials/credential-statuses/on-chain-revocation.test.ts
+++ b/tests/credentials/credential-statuses/on-chain-revocation.test.ts
@@ -29,7 +29,7 @@ import {
import {
createIdentity,
- registerBJJIntoInMemoryKMS,
+ registerKeyProvidersInMemoryKMS,
STATE_CONTRACT,
RPC_URL,
WALLET_KEY,
@@ -238,7 +238,7 @@ describe('onchain revocation checks', () => {
new Iden3OnchainSmtCredentialStatusPublisher(storage)
);
- idWallet = new IdentityWallet(registerBJJIntoInMemoryKMS(), dataStorage, credWallet, {
+ idWallet = new IdentityWallet(registerKeyProvidersInMemoryKMS(), dataStorage, credWallet, {
credentialStatusPublisherRegistry
});
proofService = new ProofService(idWallet, credWallet, circuitStorage, ethStorage);
diff --git a/tests/credentials/credential-statuses/rhs.test.ts b/tests/credentials/credential-statuses/rhs.test.ts
index 1e692aa4..9b2bed0e 100644
--- a/tests/credentials/credential-statuses/rhs.test.ts
+++ b/tests/credentials/credential-statuses/rhs.test.ts
@@ -37,6 +37,9 @@ describe('rhs', () => {
publishState: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
+ publishStateGeneric: async () => {
+ return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
+ },
getGISTProof: (): Promise => {
return Promise.resolve({
root: 0n,
@@ -79,6 +82,9 @@ describe('rhs', () => {
publishState: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
+ publishStateGeneric: async () => {
+ return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
+ },
getGISTProof: (): Promise => {
return Promise.resolve({
root: 0n,
@@ -120,6 +126,9 @@ describe('rhs', () => {
publishState: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
+ publishStateGeneric: async () => {
+ return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
+ },
getGISTProof: (): Promise => {
return Promise.resolve({
root: 0n,
diff --git a/tests/credentials/credential-statuses/sparse-merkle-tree-proof.test.ts b/tests/credentials/credential-statuses/sparse-merkle-tree-proof.test.ts
index 56fdc9d4..d8300b10 100644
--- a/tests/credentials/credential-statuses/sparse-merkle-tree-proof.test.ts
+++ b/tests/credentials/credential-statuses/sparse-merkle-tree-proof.test.ts
@@ -8,7 +8,7 @@ import {
SEED_USER,
createIdentity,
getInMemoryDataStorage,
- registerBJJIntoInMemoryKMS
+ registerKeyProvidersInMemoryKMS
} from '../../helpers';
import { DID } from '@iden3/js-iden3-core';
import fetchMock from '@gr2m/fetch-mock';
@@ -26,7 +26,7 @@ describe('SparseMerkleTreeProof', () => {
const issuerWalletUrl = 'https://issuer.com';
beforeEach(async () => {
- const kms = registerBJJIntoInMemoryKMS();
+ const kms = registerKeyProvidersInMemoryKMS();
dataStorage = getInMemoryDataStorage(MOCK_STATE_STORAGE);
credWallet = new CredentialWallet(dataStorage);
diff --git a/tests/credentials/credential-validation.test.ts b/tests/credentials/credential-validation.test.ts
index 9367ef15..57d3ae4b 100644
--- a/tests/credentials/credential-validation.test.ts
+++ b/tests/credentials/credential-validation.test.ts
@@ -50,6 +50,9 @@ const mockStateStorage: IStateStorage = {
publishState: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
+ publishStateGeneric: async () => {
+ return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
+ },
getGISTProof: (): Promise => {
return Promise.resolve({
root: 0n,
diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts
index 3b8639d7..f96984ea 100644
--- a/tests/handlers/auth.test.ts
+++ b/tests/handlers/auth.test.ts
@@ -24,23 +24,42 @@ import {
createAuthorizationRequestWithMessage,
AuthorizationResponseMessage,
ZeroKnowledgeProofResponse,
- ProofType
+ ProofType,
+ KmsKeyType,
+ defaultEthConnectionConfig,
+ InMemoryPrivateKeyStore,
+ BjjProvider,
+ KMS,
+ CredentialStorage,
+ InMemoryDataSource,
+ IdentityStorage,
+ Identity,
+ Profile,
+ InMemoryMerkleTreeStorage,
+ W3CCredential,
+ Sec256k1Provider,
+ StateInfo,
+ hexToBytes,
+ NativeProver
} from '../../src';
import { Token } from '@iden3/js-jwz';
-import { DID } from '@iden3/js-iden3-core';
+import { Blockchain, DID, DidMethod, NetworkId } from '@iden3/js-iden3-core';
import { expect } from 'chai';
import { ethers } from 'ethers';
import * as uuid from 'uuid';
import {
MOCK_STATE_STORAGE,
getInMemoryDataStorage,
- registerBJJIntoInMemoryKMS,
+ registerKeyProvidersInMemoryKMS,
IPFS_URL,
getPackageMgr,
createIdentity,
SEED_USER,
RHS_URL,
- WALLET_KEY
+ WALLET_KEY,
+ STATE_CONTRACT,
+ RPC_URL,
+ SEED_ISSUER
} from '../helpers';
import { testOpts } from './mock';
@@ -57,7 +76,7 @@ describe('auth', () => {
let issuerDID: DID;
beforeEach(async () => {
- const kms = registerBJJIntoInMemoryKMS();
+ const kms = registerKeyProvidersInMemoryKMS();
dataStorage = getInMemoryDataStorage(MOCK_STATE_STORAGE);
const circuitStorage = new FSCircuitStorage({
dirname: path.join(__dirname, '../proofs/testdata')
@@ -389,6 +408,422 @@ describe('auth', () => {
expect(token).to.be.a('object');
});
+ it('auth flow identity (profile) with ethereum identity issuer with circuits V3', async () => {
+ const ethSigner = new ethers.Wallet(
+ WALLET_KEY,
+ (dataStorage.states as EthStateStorage).provider
+ );
+
+ const { did: didIssuer, credential: issuerAuthCredential } =
+ await idWallet.createEthereumBasedIdentity({
+ method: DidMethod.PolygonId,
+ blockchain: Blockchain.Polygon,
+ networkId: NetworkId.Mumbai,
+ seed: SEED_ISSUER,
+ revocationOpts: {
+ type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ id: RHS_URL
+ },
+ ethSigner
+ });
+ expect(issuerAuthCredential).not.to.be.undefined;
+
+ const profileDID = await idWallet.createProfile(userDID, 777, didIssuer.string());
+
+ const claimReq: CredentialRequest = {
+ credentialSchema:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/kyc-nonmerklized.json',
+ type: 'KYCAgeCredential',
+ credentialSubject: {
+ id: userDID.string(),
+ birthday: 19960424,
+ documentType: 99
+ },
+ expiration: 2793526400,
+ revocationOpts: {
+ type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ id: RHS_URL
+ }
+ };
+ const issuerCred = await idWallet.issueCredential(didIssuer, claimReq);
+ const employeeCredRequest: CredentialRequest = {
+ credentialSchema:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCEmployee-v101.json',
+ type: 'KYCEmployee',
+ credentialSubject: {
+ id: profileDID.string(),
+ ZKPexperiance: true,
+ hireDate: '2023-12-11',
+ position: 'boss',
+ salary: 200,
+ documentType: 1
+ },
+ revocationOpts: {
+ type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ id: RHS_URL
+ }
+ };
+ const employeeCred = await idWallet.issueCredential(didIssuer, employeeCredRequest);
+
+ await credWallet.saveAll([employeeCred, issuerCred]);
+
+ const res = await idWallet.addCredentialsToMerkleTree([employeeCred], didIssuer);
+ await idWallet.publishStateToRHS(didIssuer, RHS_URL);
+
+ const txId = await proofService.transitState(
+ didIssuer,
+ res.oldTreeState,
+ true,
+ dataStorage.states,
+ ethSigner
+ );
+
+ const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof(
+ didIssuer,
+ res.credentials,
+ txId
+ );
+
+ await credWallet.saveAll(credsWithIden3MTPProof);
+
+ const proofReqs: ZeroKnowledgeProofRequest[] = [
+ {
+ id: 1,
+ circuitId: CircuitId.AtomicQueryV3,
+ optional: false,
+ query: {
+ proofType: ProofType.BJJSignature,
+ allowedIssuers: ['*'],
+ type: 'KYCAgeCredential',
+ context:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-nonmerklized.jsonld',
+ credentialSubject: {
+ documentType: {
+ $eq: 99
+ }
+ }
+ }
+ },
+ {
+ id: 2,
+ circuitId: CircuitId.LinkedMultiQuery10,
+ optional: false,
+ query: {
+ groupId: 1,
+ proofType: ProofType.Iden3SparseMerkleTreeProof,
+ allowedIssuers: ['*'],
+ type: 'KYCEmployee',
+ context:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v101.json-ld',
+ credentialSubject: {
+ documentType: {
+ $eq: 1
+ },
+ position: {
+ $eq: 'boss',
+ $ne: 'employee'
+ }
+ }
+ }
+ },
+ {
+ id: 3,
+ circuitId: CircuitId.AtomicQueryV3,
+ optional: false,
+ query: {
+ groupId: 1,
+ proofType: ProofType.BJJSignature,
+ allowedIssuers: ['*'],
+ type: 'KYCEmployee',
+ context:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v101.json-ld',
+ credentialSubject: {
+ hireDate: {
+ $eq: '2023-12-11'
+ }
+ }
+ },
+ params: {
+ nullifierSessionId: '12345'
+ }
+ }
+ ];
+
+ const authReqBody: AuthorizationRequestMessageBody = {
+ callbackUrl: 'http://localhost:8080/callback?id=1234442-123123-123123',
+ reason: 'reason',
+ message: 'mesage',
+ did_doc: {},
+ scope: proofReqs
+ };
+
+ const id = uuid.v4();
+ const authReq: AuthorizationRequestMessage = {
+ id,
+ typ: PROTOCOL_CONSTANTS.MediaType.PlainMessage,
+ type: PROTOCOL_CONSTANTS.PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE,
+ thid: id,
+ body: authReqBody,
+ from: didIssuer.string()
+ };
+
+ // Ethereum identities should have a previous state in state storage. We mock it here.
+ const previousGetLatestStateById = MOCK_STATE_STORAGE.getLatestStateById;
+ MOCK_STATE_STORAGE.getLatestStateById = async (id: bigint): Promise => {
+ return {
+ id,
+ state: res.oldTreeState.state.bigInt(),
+ replacedByState: 0n,
+ createdAtTimestamp: 1712062738n,
+ replacedAtTimestamp: 0n,
+ createdAtBlock: 5384981n,
+ replacedAtBlock: 0n
+ };
+ };
+
+ const msgBytes = byteEncoder.encode(JSON.stringify(authReq));
+ const authRes = await authHandler.handleAuthorizationRequest(userDID, msgBytes);
+
+ // Restore the mock state storage
+ MOCK_STATE_STORAGE.getLatestStateById = previousGetLatestStateById;
+
+ // console.log(JSON.stringify(authRes.authResponse));
+ const tokenStr = authRes.token;
+ // console.log(tokenStr);
+ expect(tokenStr).to.be.a('string');
+ const token = await Token.parse(tokenStr);
+ expect(token).to.be.a('object');
+ });
+
+ // SKIPPED : ethereum identity integration test
+ it.skip('auth flow identity (profile) with ethereum identity issuer with circuits V3 (integration)', async () => {
+ const stateEthConfig = defaultEthConnectionConfig;
+ stateEthConfig.url = RPC_URL;
+ stateEthConfig.contractAddress = STATE_CONTRACT;
+ stateEthConfig.chainId = 80002; // Amoy
+
+ const memoryKeyStore = new InMemoryPrivateKeyStore();
+ const bjjProvider = new BjjProvider(KmsKeyType.BabyJubJub, memoryKeyStore);
+ const sec256k1Provider = new Sec256k1Provider(KmsKeyType.Secp256k1, memoryKeyStore);
+ const kms = new KMS();
+ kms.registerKeyProvider(KmsKeyType.BabyJubJub, bjjProvider);
+ kms.registerKeyProvider(KmsKeyType.Secp256k1, sec256k1Provider);
+
+ dataStorage = {
+ credential: new CredentialStorage(new InMemoryDataSource()),
+ identity: new IdentityStorage(
+ new InMemoryDataSource(),
+ new InMemoryDataSource()
+ ),
+ mt: new InMemoryMerkleTreeStorage(40),
+ states: new EthStateStorage(stateEthConfig)
+ };
+ const circuitStorage = new FSCircuitStorage({
+ dirname: path.join(__dirname, '../proofs/testdata')
+ });
+
+ const resolvers = new CredentialStatusResolverRegistry();
+ resolvers.register(
+ CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ new RHSResolver(dataStorage.states)
+ );
+ credWallet = new CredentialWallet(dataStorage, resolvers);
+
+ idWallet = new IdentityWallet(kms, dataStorage, credWallet);
+
+ proofService = new ProofService(idWallet, credWallet, circuitStorage, dataStorage.states, {
+ ipfsNodeURL: IPFS_URL
+ });
+
+ packageMgr = await getPackageMgr(
+ await circuitStorage.loadCircuitData(CircuitId.AuthV2),
+ proofService.generateAuthV2Inputs.bind(proofService),
+ proofService.verifyState.bind(proofService)
+ );
+
+ authHandler = new AuthHandler(packageMgr, proofService);
+
+ const { did: didUser, credential: userAuthCredential } = await idWallet.createIdentity({
+ method: DidMethod.PolygonId,
+ blockchain: Blockchain.Polygon,
+ networkId: NetworkId.Amoy,
+ seed: SEED_USER,
+ revocationOpts: {
+ type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ id: RHS_URL
+ }
+ });
+
+ expect(userAuthCredential).not.to.be.undefined;
+
+ const ethSigner = new ethers.Wallet(
+ WALLET_KEY,
+ (dataStorage.states as EthStateStorage).provider
+ );
+
+ const { did: didIssuer, credential: issuerAuthCredential } =
+ await idWallet.createEthereumBasedIdentity({
+ method: DidMethod.PolygonId,
+ blockchain: Blockchain.Polygon,
+ networkId: NetworkId.Amoy,
+ seed: hexToBytes(WALLET_KEY),
+ revocationOpts: {
+ type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ id: RHS_URL
+ },
+ ethSigner
+ });
+ expect(issuerAuthCredential).not.to.be.undefined;
+
+ const profileDID = await idWallet.createProfile(didUser, 777, didIssuer.string());
+
+ const claimReq: CredentialRequest = {
+ credentialSchema:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/kyc-nonmerklized.json',
+ type: 'KYCAgeCredential',
+ credentialSubject: {
+ id: didUser.string(),
+ birthday: 19960424,
+ documentType: 99
+ },
+ expiration: 2793526400,
+ revocationOpts: {
+ type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ id: RHS_URL
+ }
+ };
+ const issuerCred = await idWallet.issueCredential(didIssuer, claimReq);
+ const employeeCredRequest: CredentialRequest = {
+ credentialSchema:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCEmployee-v101.json',
+ type: 'KYCEmployee',
+ credentialSubject: {
+ id: profileDID.string(),
+ ZKPexperiance: true,
+ hireDate: '2023-12-11',
+ position: 'boss',
+ salary: 200,
+ documentType: 1
+ },
+ revocationOpts: {
+ type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ id: RHS_URL
+ }
+ };
+ const employeeCred = await idWallet.issueCredential(didIssuer, employeeCredRequest);
+
+ await credWallet.saveAll([employeeCred, issuerCred]);
+
+ const res = await idWallet.addCredentialsToMerkleTree([employeeCred], didIssuer);
+ await idWallet.publishStateToRHS(didIssuer, RHS_URL);
+
+ const txId = await proofService.transitState(
+ didIssuer,
+ res.oldTreeState,
+ false,
+ dataStorage.states,
+ ethSigner
+ );
+
+ const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof(
+ didIssuer,
+ res.credentials,
+ txId
+ );
+
+ await credWallet.saveAll(credsWithIden3MTPProof);
+
+ const proofReqs: ZeroKnowledgeProofRequest[] = [
+ {
+ id: 1,
+ circuitId: CircuitId.AtomicQueryV3,
+ optional: false,
+ query: {
+ proofType: ProofType.BJJSignature,
+ allowedIssuers: ['*'],
+ type: 'KYCAgeCredential',
+ context:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-nonmerklized.jsonld',
+ credentialSubject: {
+ documentType: {
+ $eq: 99
+ }
+ }
+ }
+ },
+ {
+ id: 2,
+ circuitId: CircuitId.LinkedMultiQuery10,
+ optional: false,
+ query: {
+ groupId: 1,
+ proofType: ProofType.Iden3SparseMerkleTreeProof,
+ allowedIssuers: ['*'],
+ type: 'KYCEmployee',
+ context:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v101.json-ld',
+ credentialSubject: {
+ documentType: {
+ $eq: 1
+ },
+ position: {
+ $eq: 'boss',
+ $ne: 'employee'
+ }
+ }
+ }
+ },
+ {
+ id: 3,
+ circuitId: CircuitId.AtomicQueryV3,
+ optional: false,
+ query: {
+ groupId: 1,
+ proofType: ProofType.BJJSignature,
+ allowedIssuers: ['*'],
+ type: 'KYCEmployee',
+ context:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v101.json-ld',
+ credentialSubject: {
+ hireDate: {
+ $eq: '2023-12-11'
+ }
+ }
+ },
+ params: {
+ nullifierSessionId: '12345'
+ }
+ }
+ ];
+
+ const authReqBody: AuthorizationRequestMessageBody = {
+ callbackUrl: 'http://localhost:8080/callback?id=1234442-123123-123123',
+ reason: 'reason',
+ message: 'mesage',
+ did_doc: {},
+ scope: proofReqs
+ };
+
+ const id = uuid.v4();
+ const authReq: AuthorizationRequestMessage = {
+ id,
+ typ: PROTOCOL_CONSTANTS.MediaType.PlainMessage,
+ type: PROTOCOL_CONSTANTS.PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE,
+ thid: id,
+ body: authReqBody,
+ from: didIssuer.string()
+ };
+
+ const msgBytes = byteEncoder.encode(JSON.stringify(authReq));
+ const authRes = await authHandler.handleAuthorizationRequest(didUser, msgBytes);
+ // console.log('authRes', JSON.stringify(authRes.authResponse));
+ const tokenStr = authRes.token;
+ // console.log(tokenStr);
+ expect(tokenStr).to.be.a('string');
+ const token = await Token.parse(tokenStr);
+ expect(token).to.be.a('object');
+ });
+
it('auth response: TestVerifyMessageWithoutProof', async () => {
const sender = '1125GJqgw6YEsKFwj63GY87MMxPL9kwDKxPUiwMLNZ';
const userId = '119tqceWdRd2F6WnAyVuFQRFjK3WUXq2LorSPyG9LJ';
@@ -1473,4 +1908,361 @@ describe('auth', () => {
await authHandler.handleAuthorizationResponse(response, request, testOpts);
});
+
+ it('auth flow identity (profile) with circuits V3', async () => {
+ const profileDID = await idWallet.createProfile(userDID, 777, issuerDID.string());
+
+ const claimReq: CredentialRequest = {
+ credentialSchema:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/kyc-nonmerklized.json',
+ type: 'KYCAgeCredential',
+ credentialSubject: {
+ id: userDID.string(),
+ birthday: 19960424,
+ documentType: 99
+ },
+ expiration: 2793526400,
+ revocationOpts: {
+ type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ id: RHS_URL
+ }
+ };
+ const issuerCred = await idWallet.issueCredential(issuerDID, claimReq);
+ const employeeCredRequest: CredentialRequest = {
+ credentialSchema:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCEmployee-v101.json',
+ type: 'KYCEmployee',
+ credentialSubject: {
+ id: profileDID.string(),
+ ZKPexperiance: true,
+ hireDate: '2023-12-11',
+ position: 'boss',
+ salary: 200,
+ documentType: 1
+ },
+ revocationOpts: {
+ type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ id: RHS_URL
+ }
+ };
+ const employeeCred = await idWallet.issueCredential(issuerDID, employeeCredRequest);
+
+ await credWallet.saveAll([employeeCred, issuerCred]);
+
+ const res = await idWallet.addCredentialsToMerkleTree([employeeCred], issuerDID);
+ await idWallet.publishStateToRHS(issuerDID, RHS_URL);
+
+ const ethSigner = new ethers.Wallet(
+ WALLET_KEY,
+ (dataStorage.states as EthStateStorage).provider
+ );
+
+ const txId = await proofService.transitState(
+ issuerDID,
+ res.oldTreeState,
+ true,
+ dataStorage.states,
+ ethSigner
+ );
+
+ const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof(
+ issuerDID,
+ res.credentials,
+ txId
+ );
+
+ await credWallet.saveAll(credsWithIden3MTPProof);
+
+ const proofReqs: ZeroKnowledgeProofRequest[] = [
+ {
+ id: 1,
+ circuitId: CircuitId.AtomicQueryV3,
+ optional: false,
+ query: {
+ proofType: ProofType.BJJSignature,
+ allowedIssuers: ['*'],
+ type: 'KYCAgeCredential',
+ context:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-nonmerklized.jsonld',
+ credentialSubject: {
+ documentType: {
+ $eq: 99
+ }
+ }
+ }
+ },
+ {
+ id: 2,
+ circuitId: CircuitId.LinkedMultiQuery10,
+ optional: false,
+ query: {
+ groupId: 1,
+ proofType: ProofType.Iden3SparseMerkleTreeProof,
+ allowedIssuers: ['*'],
+ type: 'KYCEmployee',
+ context:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v101.json-ld',
+ credentialSubject: {
+ documentType: {
+ $eq: 1
+ },
+ position: {
+ $eq: 'boss',
+ $ne: 'employee'
+ }
+ }
+ }
+ },
+ {
+ id: 3,
+ circuitId: CircuitId.AtomicQueryV3,
+ optional: false,
+ query: {
+ groupId: 1,
+ proofType: ProofType.BJJSignature,
+ allowedIssuers: ['*'],
+ type: 'KYCEmployee',
+ context:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v101.json-ld',
+ credentialSubject: {
+ hireDate: {
+ $eq: '2023-12-11'
+ }
+ }
+ },
+ params: {
+ nullifierSessionId: '12345'
+ }
+ }
+ ];
+
+ const authReqBody: AuthorizationRequestMessageBody = {
+ callbackUrl: 'http://localhost:8080/callback?id=1234442-123123-123123',
+ reason: 'reason',
+ message: 'mesage',
+ did_doc: {},
+ scope: proofReqs
+ };
+
+ const id = uuid.v4();
+ const authReq: AuthorizationRequestMessage = {
+ id,
+ typ: PROTOCOL_CONSTANTS.MediaType.PlainMessage,
+ type: PROTOCOL_CONSTANTS.PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE,
+ thid: id,
+ body: authReqBody,
+ from: issuerDID.string()
+ };
+
+ const msgBytes = byteEncoder.encode(JSON.stringify(authReq));
+ const authRes = await authHandler.handleAuthorizationRequest(userDID, msgBytes);
+ // console.log(JSON.stringify(authRes.authResponse));
+ const tokenStr = authRes.token;
+ // console.log(tokenStr);
+ expect(tokenStr).to.be.a('string');
+ const token = await Token.parse(tokenStr);
+ expect(token).to.be.a('object');
+ });
+
+ it('key rotation use case', async () => {
+ const claimReq: CredentialRequest = {
+ credentialSchema:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCAgeCredential-v4.json',
+ type: 'KYCAgeCredential',
+ credentialSubject: {
+ id: userDID.string(),
+ birthday: 19960424,
+ documentType: 99
+ },
+ expiration: 2793526400,
+ revocationOpts: {
+ type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ id: RHS_URL
+ }
+ };
+ const issuerCred = await idWallet.issueCredential(issuerDID, claimReq);
+ await credWallet.save(issuerCred);
+ const res = await idWallet.addCredentialsToMerkleTree([issuerCred], issuerDID);
+ await idWallet.publishStateToRHS(issuerDID, RHS_URL);
+
+ const proofReq: ZeroKnowledgeProofRequest = {
+ id: 1,
+ circuitId: CircuitId.AtomicQueryV3,
+ optional: false,
+ query: {
+ allowedIssuers: ['*'],
+ type: claimReq.type,
+ context:
+ 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v4.jsonld',
+ credentialSubject: {
+ documentType: {
+ $eq: 99
+ }
+ },
+ proofType: ProofType.BJJSignature
+ }
+ };
+
+ const authReqBody: AuthorizationRequestMessageBody = {
+ callbackUrl: 'http://localhost:8080/callback?id=1234442-123123-123123',
+ reason: 'reason',
+ message: 'mesage',
+ did_doc: {},
+ scope: [proofReq as ZeroKnowledgeProofRequest]
+ };
+
+ const handleAuthorizationRequest = async (
+ userDID: DID,
+ authReqBody: AuthorizationRequestMessageBody
+ ) => {
+ const id = uuid.v4();
+ const authReq: AuthorizationRequestMessage = {
+ id,
+ typ: PROTOCOL_CONSTANTS.MediaType.PlainMessage,
+ type: PROTOCOL_CONSTANTS.PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE,
+ thid: id,
+ body: authReqBody,
+ from: issuerDID.string()
+ };
+
+ const msgBytes = byteEncoder.encode(JSON.stringify(authReq));
+ const authRes = await authHandler.handleAuthorizationRequest(userDID, msgBytes);
+ expect(authRes.token).to.be.a('string');
+ const token = await Token.parse(authRes.token);
+ expect(token).to.be.a('object');
+ };
+
+ await handleAuthorizationRequest(userDID, authReqBody);
+
+ // add second Bjj auth credential
+ const circuitStorage = new FSCircuitStorage({
+ dirname: path.join(__dirname, '../proofs/testdata')
+ });
+ const prover = new NativeProver(circuitStorage);
+
+ const ethSigner = new ethers.Wallet(
+ WALLET_KEY,
+ (dataStorage.states as EthStateStorage).provider
+ );
+ const opts = {
+ seed: SEED_USER,
+ revocationOpts: {
+ type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ id: RHS_URL
+ }
+ };
+
+ const treesModel = await idWallet.getDIDTreeModel(issuerDID);
+ const [ctrHex, rtrHex, rorTrHex] = await Promise.all([
+ treesModel.claimsTree.root(),
+ treesModel.revocationTree.root(),
+ treesModel.rootsTree.root()
+ ]);
+
+ const oldTreeState = {
+ state: treesModel.state,
+ claimsRoot: ctrHex,
+ revocationRoot: rtrHex,
+ rootOfRoots: rorTrHex
+ };
+
+ // add k2 auth credential (we have k1 already)
+ const credential2 = await idWallet.addBJJAuthCredential(
+ issuerDID,
+ oldTreeState,
+ false,
+ ethSigner,
+ opts,
+ prover
+ );
+
+ expect(credential2?.proof).not.to.be.undefined;
+
+ // get actual auth credential (k1)
+ const { authCredential: issuerAuthCredential } = await idWallet.getActualAuthCredential(
+ issuerDID
+ );
+
+ // revoke k1 auth credential
+ const nonce = await idWallet.revokeCredential(issuerDID, issuerAuthCredential);
+ await idWallet.publishStateToRHS(issuerDID, RHS_URL, [nonce]);
+ await proofService.transitState(
+ issuerDID,
+ res.oldTreeState,
+ false,
+ dataStorage.states,
+ ethSigner
+ );
+
+ await handleAuthorizationRequest(userDID, authReqBody);
+
+ // get actual auth credential (k2)
+ const { authCredential: issuerAuthCredential2 } = await idWallet.getActualAuthCredential(
+ issuerDID
+ );
+ expect(issuerAuthCredential2).to.be.deep.equal(credential2);
+
+ // check we can issue new credential with k2
+ const issuerCred2 = await idWallet.issueCredential(issuerDID, claimReq);
+ expect(issuerCred2).to.be.not.undefined;
+
+ const treesModel2 = await idWallet.getDIDTreeModel(issuerDID);
+ const [ctrHex2, rtrHex2, rorTrHex2] = await Promise.all([
+ treesModel2.claimsTree.root(),
+ treesModel2.revocationTree.root(),
+ treesModel2.rootsTree.root()
+ ]);
+
+ const oldTreeState2 = {
+ state: treesModel2.state,
+ claimsRoot: ctrHex2,
+ revocationRoot: rtrHex2,
+ rootOfRoots: rorTrHex2
+ };
+
+ // revoke k2 auth credential
+ const nonce2 = await idWallet.revokeCredential(issuerDID, issuerAuthCredential2);
+ await idWallet.publishStateToRHS(issuerDID, RHS_URL, [nonce2]);
+ await proofService.transitState(issuerDID, oldTreeState2, false, dataStorage.states, ethSigner);
+
+ // check that we don't have auth credentials now
+ await expect(idWallet.getActualAuthCredential(issuerDID)).to.rejectedWith(
+ 'no auth credentials found'
+ );
+
+ // check that we can't issue new credential
+ await expect(idWallet.issueCredential(issuerDID, claimReq)).to.rejectedWith(
+ 'no auth credentials found'
+ );
+
+ // this should this work because we haven't revoked user keys
+ await handleAuthorizationRequest(userDID, authReqBody);
+
+ // get actual auth credential for user
+ const { authCredential: userAuthCredential } = await idWallet.getActualAuthCredential(userDID);
+
+ const treesModel3 = await idWallet.getDIDTreeModel(userDID);
+ const [ctrHex3, rtrHex3, rorTrHex3] = await Promise.all([
+ treesModel3.claimsTree.root(),
+ treesModel3.revocationTree.root(),
+ treesModel3.rootsTree.root()
+ ]);
+
+ const oldTreeState3 = {
+ state: treesModel3.state,
+ claimsRoot: ctrHex3,
+ revocationRoot: rtrHex3,
+ rootOfRoots: rorTrHex3
+ };
+
+ // revoke user keys
+ const nonce3 = await idWallet.revokeCredential(userDID, userAuthCredential);
+ await idWallet.publishStateToRHS(userDID, RHS_URL, [nonce3]);
+ await proofService.transitState(userDID, oldTreeState3, true, dataStorage.states, ethSigner);
+
+ // this should not work because we revoked user keys
+ await expect(handleAuthorizationRequest(userDID, authReqBody)).to.rejectedWith(
+ 'no auth credentials found'
+ );
+ });
});
diff --git a/tests/handlers/contract-request.test.ts b/tests/handlers/contract-request.test.ts
index 089b0f12..aef5df57 100644
--- a/tests/handlers/contract-request.test.ts
+++ b/tests/handlers/contract-request.test.ts
@@ -82,6 +82,9 @@ describe('contract-request', () => {
publishState: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
+ publishStateGeneric: async () => {
+ return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
+ },
getGISTProof: (): Promise => {
return Promise.resolve({
root: 0n,
diff --git a/tests/handlers/fetch.test.ts b/tests/handlers/fetch.test.ts
index 3cd2d63a..3285075d 100644
--- a/tests/handlers/fetch.test.ts
+++ b/tests/handlers/fetch.test.ts
@@ -28,7 +28,7 @@ import {
createIdentity,
getInMemoryDataStorage,
getPackageMgr,
- registerBJJIntoInMemoryKMS
+ registerKeyProvidersInMemoryKMS
} from '../helpers';
import * as uuid from 'uuid';
@@ -112,7 +112,7 @@ describe('fetch', () => {
}`;
beforeEach(async () => {
- const kms = registerBJJIntoInMemoryKMS();
+ const kms = registerKeyProvidersInMemoryKMS();
dataStorage = getInMemoryDataStorage(MOCK_STATE_STORAGE);
const circuitStorage = new FSCircuitStorage({
dirname: path.join(__dirname, '../proofs/testdata')
diff --git a/tests/handlers/revocation-status.test.ts b/tests/handlers/revocation-status.test.ts
index e80b7d24..e538d696 100644
--- a/tests/handlers/revocation-status.test.ts
+++ b/tests/handlers/revocation-status.test.ts
@@ -19,7 +19,7 @@ import {
MOCK_STATE_STORAGE,
getInMemoryDataStorage,
getPackageMgr,
- registerBJJIntoInMemoryKMS,
+ registerKeyProvidersInMemoryKMS,
createIdentity,
SEED_USER,
SEED_ISSUER
@@ -35,7 +35,7 @@ describe('revocation status', () => {
let idWallet: IdentityWallet;
beforeEach(async () => {
- const kms = registerBJJIntoInMemoryKMS();
+ const kms = registerKeyProvidersInMemoryKMS();
const dataStorage = getInMemoryDataStorage(MOCK_STATE_STORAGE);
const circuitStorage = new FSCircuitStorage({
dirname: path.join(__dirname, '../proofs/testdata')
diff --git a/tests/helpers.ts b/tests/helpers.ts
index 21f823d3..8a053415 100644
--- a/tests/helpers.ts
+++ b/tests/helpers.ts
@@ -6,6 +6,7 @@ import {
CredentialStatusType,
CredentialStorage,
DataPrepareHandlerFunc,
+ EthereumBasedIdentityCreationOptions,
IIdentityWallet,
IPackageManager,
IStateStorage,
@@ -22,6 +23,7 @@ import {
Profile,
ProvingParams,
RootInfo,
+ Sec256k1Provider,
StateProof,
StateVerificationFunc,
VerifiableConstants,
@@ -59,6 +61,23 @@ export const createIdentity = async (
});
};
+export const createEthereumBasedIdentity = async (
+ wallet: IIdentityWallet,
+ opts?: Partial
+) => {
+ return await wallet.createEthereumBasedIdentity({
+ method: DidMethod.Iden3,
+ blockchain: Blockchain.Polygon,
+ networkId: NetworkId.Amoy,
+ seed: SEED_ISSUER,
+ revocationOpts: {
+ type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ id: RHS_URL
+ },
+ ...opts
+ });
+};
+
export const MOCK_STATE_STORAGE: IStateStorage = {
getLatestStateById: async () => {
throw new Error(VerifiableConstants.ERRORS.IDENTITY_DOES_NOT_EXIST);
@@ -80,6 +99,9 @@ export const MOCK_STATE_STORAGE: IStateStorage = {
publishState: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
+ publishStateGeneric: async () => {
+ return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
+ },
getGISTProof: (): Promise => {
return Promise.resolve({
root: 0n,
@@ -104,11 +126,13 @@ export const MOCK_STATE_STORAGE: IStateStorage = {
}
};
-export const registerBJJIntoInMemoryKMS = (): KMS => {
+export const registerKeyProvidersInMemoryKMS = (): KMS => {
const memoryKeyStore = new InMemoryPrivateKeyStore();
const bjjProvider = new BjjProvider(KmsKeyType.BabyJubJub, memoryKeyStore);
const kms = new KMS();
kms.registerKeyProvider(KmsKeyType.BabyJubJub, bjjProvider);
+ const sec256k1Provider = new Sec256k1Provider(KmsKeyType.Secp256k1, memoryKeyStore);
+ kms.registerKeyProvider(KmsKeyType.Secp256k1, sec256k1Provider);
return kms;
};
diff --git a/tests/identity/id.test.ts b/tests/identity/id.test.ts
index 2dc9304f..32cf9c61 100644
--- a/tests/identity/id.test.ts
+++ b/tests/identity/id.test.ts
@@ -1,4 +1,5 @@
/* eslint-disable no-console */
+import path from 'path';
import {
IdentityWallet,
byteEncoder,
@@ -9,7 +10,12 @@ import {
CredentialWallet,
CredentialStatusResolverRegistry,
RHSResolver,
- CredentialStatusType
+ CredentialStatusType,
+ EthStateStorage,
+ FSCircuitStorage,
+ NativeProver,
+ Iden3SparseMerkleTreeProof,
+ BJJSignatureProof2021
} from '../../src';
import {
MOCK_STATE_STORAGE,
@@ -17,9 +23,12 @@ import {
createIdentity,
RHS_URL,
getInMemoryDataStorage,
- registerBJJIntoInMemoryKMS
+ registerKeyProvidersInMemoryKMS,
+ WALLET_KEY,
+ createEthereumBasedIdentity
} from '../helpers';
import { expect } from 'chai';
+import { Wallet } from 'ethers';
describe('identity', () => {
let credWallet: ICredentialWallet;
@@ -58,8 +67,9 @@ describe('identity', () => {
new RHSResolver(dataStorage.states)
);
credWallet = new CredentialWallet(dataStorage, resolvers);
- idWallet = new IdentityWallet(registerBJJIntoInMemoryKMS(), dataStorage, credWallet);
+ idWallet = new IdentityWallet(registerKeyProvidersInMemoryKMS(), dataStorage, credWallet);
});
+
it('createIdentity', async () => {
const { did, credential } = await createIdentity(idWallet);
@@ -126,15 +136,6 @@ describe('identity', () => {
expect(proof.proof.existence).to.equal(false);
});
- it('generateNonRevProof', async () => {
- const { did, credential } = await createIdentity(idWallet);
- expect(did.string()).to.equal(expectedDID);
-
- const proof = await idWallet.generateNonRevocationMtp(did, credential);
-
- expect(proof.proof.existence).to.equal(false);
- });
-
it('issueCredential', async () => {
const { did: issuerDID, credential: issuerAuthCredential } = await createIdentity(idWallet);
@@ -167,4 +168,155 @@ describe('identity', () => {
await credWallet.getRevocationStatusFromCredential(issuerCred);
});
+
+ it('createIdentity Secp256k1', async () => {
+ const ethSigner = new Wallet(WALLET_KEY, (dataStorage.states as EthStateStorage).provider);
+
+ const { did, credential } = await createEthereumBasedIdentity(idWallet, {
+ ethSigner
+ });
+
+ expect(did.string()).to.equal(
+ 'did:iden3:polygon:amoy:x6x5sor7zpxsu478u36QvEgaRUfPjmzqFo5PHHzbM'
+ );
+
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ const dbCred = await dataStorage.credential.findCredentialById(credential!.id);
+ expect(credential).to.deep.equal(dbCred);
+
+ const claimsTree = await dataStorage.mt.getMerkleTreeByIdentifierAndType(
+ did.string(),
+ MerkleTreeType.Claims
+ );
+
+ expect((await claimsTree.root()).bigInt()).not.to.equal(0);
+ });
+
+ it('add auth bjj credential', async () => {
+ const { did, credential } = await createIdentity(idWallet);
+ expect(did.string()).to.equal(expectedDID);
+
+ const proof = await idWallet.generateCredentialMtp(did, credential);
+ expect(proof.proof.existence).to.equal(true);
+
+ const circuitStorage = new FSCircuitStorage({
+ dirname: path.join(__dirname, '../proofs/testdata')
+ });
+ const prover = new NativeProver(circuitStorage);
+
+ const ethSigner = new Wallet(WALLET_KEY, (dataStorage.states as EthStateStorage).provider);
+ const opts = {
+ seed: SEED_USER,
+ revocationOpts: {
+ type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ id: RHS_URL
+ }
+ };
+
+ const treesModel = await idWallet.getDIDTreeModel(did);
+ const [ctrHex, rtrHex, rorTrHex] = await Promise.all([
+ treesModel.claimsTree.root(),
+ treesModel.revocationTree.root(),
+ treesModel.rootsTree.root()
+ ]);
+
+ const oldTreeState = {
+ state: treesModel.state,
+ claimsRoot: ctrHex,
+ revocationRoot: rtrHex,
+ rootOfRoots: rorTrHex
+ };
+
+ expect(credential?.proof).not.to.be.undefined;
+ expect((credential?.proof as unknown[])[0]).to.instanceOf(Iden3SparseMerkleTreeProof);
+ expect((credential?.proof as unknown[]).length).to.equal(1);
+
+ const credential2 = await idWallet.addBJJAuthCredential(
+ did,
+ oldTreeState,
+ false,
+ ethSigner,
+ opts,
+ prover
+ );
+ expect(credential2?.proof).not.to.be.undefined;
+ expect((credential2?.proof as unknown[]).length).to.equal(2);
+ expect((credential2?.proof as unknown[])[0]).to.instanceOf(BJJSignatureProof2021);
+ expect((credential2?.proof as unknown[])[1]).to.instanceOf(Iden3SparseMerkleTreeProof);
+
+ const proof2 = await idWallet.generateCredentialMtp(did, credential2);
+ expect(proof2.proof.existence).to.equal(true);
+ });
+
+ it('rotate identity keys', async () => {
+ const { did, credential } = await createIdentity(idWallet);
+ expect(did.string()).to.equal(expectedDID);
+
+ const proof = await idWallet.generateCredentialMtp(did, credential);
+ expect(proof.proof.existence).to.equal(true);
+
+ const circuitStorage = new FSCircuitStorage({
+ dirname: path.join(__dirname, '../proofs/testdata')
+ });
+ const prover = new NativeProver(circuitStorage);
+
+ const ethSigner = new Wallet(WALLET_KEY, (dataStorage.states as EthStateStorage).provider);
+ const opts = {
+ seed: SEED_USER,
+ revocationOpts: {
+ type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof,
+ id: RHS_URL
+ }
+ };
+
+ const treesModel = await idWallet.getDIDTreeModel(did);
+ const [ctrHex, rtrHex, rorTrHex] = await Promise.all([
+ treesModel.claimsTree.root(),
+ treesModel.revocationTree.root(),
+ treesModel.rootsTree.root()
+ ]);
+
+ const oldTreeState = {
+ state: treesModel.state,
+ claimsRoot: ctrHex,
+ revocationRoot: rtrHex,
+ rootOfRoots: rorTrHex
+ };
+
+ expect(credential?.proof).not.to.be.undefined;
+ expect((credential?.proof as unknown[])[0]).to.instanceOf(Iden3SparseMerkleTreeProof);
+ expect((credential?.proof as unknown[]).length).to.equal(1);
+
+ const credential2 = await idWallet.addBJJAuthCredential(
+ did,
+ oldTreeState,
+ false,
+ ethSigner,
+ opts,
+ prover
+ );
+ expect(credential2?.proof).not.to.be.undefined;
+ expect((credential2?.proof as unknown[]).length).to.equal(2);
+ expect((credential2?.proof as unknown[])[0]).to.instanceOf(BJJSignatureProof2021);
+ expect((credential2?.proof as unknown[])[1]).to.instanceOf(Iden3SparseMerkleTreeProof);
+
+ const proof2 = await idWallet.generateCredentialMtp(did, credential2);
+ expect(proof2.proof.existence).to.equal(true);
+
+ const proofNRcredential = await idWallet.generateNonRevocationMtp(did, credential);
+ expect(proofNRcredential.proof.existence).to.equal(false);
+
+ const proofNRcredential2 = await idWallet.generateNonRevocationMtp(did, credential2);
+ expect(proofNRcredential2.proof.existence).to.equal(false);
+
+ const nonce = await idWallet.revokeCredential(did, credential);
+
+ await idWallet.publishStateToRHS(did, RHS_URL, [nonce]);
+
+ const afterRevokeProofNRcredential = await idWallet.generateNonRevocationMtp(did, credential);
+ expect(afterRevokeProofNRcredential.proof.existence).to.equal(true);
+
+ const afterRevokeProofNRcredential2 = await idWallet.generateNonRevocationMtp(did, credential2);
+ expect(afterRevokeProofNRcredential2.proof.existence).to.equal(false);
+ });
});
diff --git a/tests/proofs/mtp-onchain.test.ts b/tests/proofs/mtp-onchain.test.ts
index a8310636..28c61bf1 100644
--- a/tests/proofs/mtp-onchain.test.ts
+++ b/tests/proofs/mtp-onchain.test.ts
@@ -58,6 +58,9 @@ describe('mtp onchain proofs', () => {
publishState: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
+ publishStateGeneric: async () => {
+ return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
+ },
getGISTProof: (): Promise => {
return Promise.resolve({
root: 0n,
diff --git a/tests/proofs/mtp.test.ts b/tests/proofs/mtp.test.ts
index 23a3a107..990afbdd 100644
--- a/tests/proofs/mtp.test.ts
+++ b/tests/proofs/mtp.test.ts
@@ -54,6 +54,9 @@ describe('mtp proofs', () => {
publishState: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
+ publishStateGeneric: async () => {
+ return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
+ },
getGISTProof: (): Promise => {
return Promise.resolve({
root: 0n,
@@ -113,6 +116,7 @@ describe('mtp proofs', () => {
new RHSResolver(dataStorage.states)
);
credWallet = new CredentialWallet(dataStorage, resolvers);
+
idWallet = new IdentityWallet(kms, dataStorage, credWallet);
proofService = new ProofService(idWallet, credWallet, circuitStorage, mockStateStorage);
diff --git a/tests/proofs/sig-onchain.test.ts b/tests/proofs/sig-onchain.test.ts
index 897463e2..b0229c74 100644
--- a/tests/proofs/sig-onchain.test.ts
+++ b/tests/proofs/sig-onchain.test.ts
@@ -45,6 +45,9 @@ describe('sig onchain proofs', () => {
publishState: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
+ publishStateGeneric: async () => {
+ return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
+ },
getGISTProof: (): Promise => {
return Promise.resolve({
root: 0n,
diff --git a/tests/proofs/sig.test.ts b/tests/proofs/sig.test.ts
index b5fe7a47..18534dbb 100644
--- a/tests/proofs/sig.test.ts
+++ b/tests/proofs/sig.test.ts
@@ -44,6 +44,9 @@ describe('sig proofs', () => {
publishState: async () => {
return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
},
+ publishStateGeneric: async () => {
+ return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c';
+ },
getStateInfoByIdAndState: async () => {
throw new Error(VerifiableConstants.ERRORS.IDENTITY_DOES_NOT_EXIST);
},
diff --git a/tests/utils/utils.test.ts b/tests/utils/utils.test.ts
index 5c0a8c75..9c25419e 100644
--- a/tests/utils/utils.test.ts
+++ b/tests/utils/utils.test.ts
@@ -1,5 +1,6 @@
import { expect } from 'chai';
-import { JSONObject, mergeObjects } from '../../src';
+import { buildDIDFromEthPubKey, JSONObject, mergeObjects } from '../../src';
+import { Blockchain, buildDIDType, DidMethod, NetworkId } from '@iden3/js-iden3-core';
describe('merge credential subjects to create query', () => {
it('should merge two valid JSONObjects correctly', () => {
@@ -149,3 +150,16 @@ describe('merge credential subjects to create query', () => {
}
});
});
+
+describe('build did from ethereum public key', () => {
+ it('should build did from ethereum public key correctly', async () => {
+ const pubKeyHexEth =
+ '8318535b54105d4a7aae60c08fc45f9687181b4fdfc625bd1a753fa7397fed753547f11ca8696646f2f3acb08e31016afac23e630c5d11f59f61fef57b0d2aa5';
+ const didType = buildDIDType(DidMethod.Iden3, Blockchain.Polygon, NetworkId.Amoy);
+ const did = buildDIDFromEthPubKey(didType, pubKeyHexEth);
+
+ expect(did.string()).to.equal(
+ 'did:iden3:polygon:amoy:x6x5sor7zpycB7z7Q9348dXJxZ9s5b9AgmPeSccZz'
+ );
+ });
+});