From 989587307888fe5ac7be45583303d6c406e7ab04 Mon Sep 17 00:00:00 2001 From: daveroga Date: Thu, 21 Mar 2024 16:54:33 +0100 Subject: [PATCH 01/38] create ethereum based identities --- src/identity/identity-wallet.ts | 199 ++++++++++++++---- src/storage/blockchain/state.ts | 62 ++++-- .../on-chain-revocation.test.ts | 4 +- .../sparse-merkle-tree-proof.test.ts | 4 +- tests/handlers/auth.test.ts | 161 +++++++++++++- tests/handlers/fetch.test.ts | 4 +- tests/helpers.ts | 5 +- tests/identity/id.test.ts | 24 ++- 8 files changed, 396 insertions(+), 67 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 831b7444..e322b176 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -7,13 +7,14 @@ import { ClaimOptions, DID, DidMethod, + genesisFromEthAddress, getUnixTimestamp, Id, 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 { Hash, hashElems, ZERO_HASH } from '@iden3/js-merkletree'; import { generateProfileDID, subjectPositionIndex } from './common'; import * as uuid from 'uuid'; @@ -72,6 +73,7 @@ export type IdentityCreationOptions = { }; }; seed?: Uint8Array; + keyType?: KmsKeyType; }; /** @@ -434,20 +436,24 @@ export class IdentityWallet implements IIdentityWallet { 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); + opts.keyType = opts.keyType ?? KmsKeyType.BabyJubJub; + + switch (opts.keyType) { + case KmsKeyType.BabyJubJub: + return this.createBabyJubJubIdentity(opts); + case KmsKeyType.Secp256k1: + return this.createEthereumIdentity(opts); + default: + throw new Error(`Invalid KmsKeyType ${opts.keyType}`); + } + } + private async createAuthClaim( + 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 +463,23 @@ 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 - ); + return { authClaim, pubKey }; + } + private async createAuthCredential( + did: DID, + pubKey: PublicKey, + authClaim: Claim, + currentState: Hash, + revocationOpts: { id: string; type: CredentialStatusType } + ): Promise { const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType( - tmpIdentifier, + did.string(), 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); - const authData = authClaim.getExpirationDate(); const expiration = authData ? getUnixTimestamp(authData) : 0; @@ -500,13 +494,14 @@ 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() } }; + const schema = JSON.parse(VerifiableConstants.AUTH.AUTH_BJJ_CREDENTIAL_SCHEMA_JSON); let credential: W3CCredential = new W3CCredential(); try { credential = this._credentialWallet.createCredential(did, request, schema); @@ -535,6 +530,64 @@ export class IdentityWallet implements IIdentityWallet { credential.proof = [mtpProof]; + return credential; + } + + /** + * + * creates Baby JubJub based identity + * + * @param {IdentityCreationOptions} opts - options for the creation of the identity + * @returns `{did,credential>}` - returns did and Auth BJJ credential + */ + private async createBabyJubJubIdentity( + 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.Mumbai; + opts.seed = opts.seed ?? getRandomBytes(32); + + await this._storage.mt.createIdentityMerkleTrees(tmpIdentifier); + + const revNonce = opts.revocationOpts.nonce ?? 0; + + const { authClaim, pubKey } = await this.createAuthClaim(revNonce, opts.seed); + + 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 credential = await this.createAuthCredential( + did, + pubKey, + authClaim, + currentState, + opts.revocationOpts + ); + await this.publishRevocationInfoByCredentialStatusType(did, opts.revocationOpts.type, { rhsUrl: opts.revocationOpts.id, onChain: opts.revocationOpts.onChain @@ -555,6 +608,76 @@ export class IdentityWallet implements IIdentityWallet { }; } + /** + * + * creates Ethereum based identity + * + * @param {IdentityCreationOptions} opts - options for the creation of the identity + * @returns `{did,credential>}` - returns did and Auth BJJ credential + */ + private async createEthereumIdentity( + opts: IdentityCreationOptions + ): Promise<{ did: DID; credential: W3CCredential }> { + opts.method = opts.method ?? DidMethod.Iden3; + opts.blockchain = opts.blockchain ?? Blockchain.Polygon; + opts.networkId = opts.networkId ?? NetworkId.Mumbai; + opts.seed = opts.seed ?? getRandomBytes(32); + + const currentState = ZERO_HASH; // In Ethereum identities we don't have an initial state with the auth credential + + const didType = buildDIDType(opts.method, opts.blockchain, opts.networkId); + + const keyIdEth = await this._kms.createKeyFromSeed(KmsKeyType.Secp256k1, opts.seed); + const pubKeyHexEth = await this._kms.publicKey(keyIdEth); + const ethAddrBytes = Hex.decodeString(pubKeyHexEth); + const ethAddr = ethAddrBytes.slice(0, 20); + const genesis = genesisFromEthAddress(ethAddr); + const identifier = new Id(didType, genesis); + const did = DID.parseFromId(identifier); + + await this._storage.mt.createIdentityMerkleTrees(did.string()); + + await this._storage.identity.saveIdentity({ + did: did.string(), + state: currentState, + isStatePublished: false, + isStateGenesis: true + }); + + // Add Auth BJJ credential after saving identity for Ethereum identities + const { authClaim, pubKey } = await this.createAuthClaim( + opts.revocationOpts.nonce ?? 0, + opts.seed + ); + + await this._storage.mt.addToMerkleTree( + did.string(), + MerkleTreeType.Claims, + authClaim.hiHv().hi, + authClaim.hiHv().hv + ); + + const credential = await this.createAuthCredential( + did, + pubKey, + authClaim, + currentState, + opts.revocationOpts + ); + + 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.getGenesisDIDMetadata} */ async getGenesisDIDMetadata(did: DID): Promise<{ nonce: number; genesisDID: DID }> { // check if it is a genesis identity diff --git a/src/storage/blockchain/state.ts b/src/storage/blockchain/state.ts index f10641c2..50f84bd6 100644 --- a/src/storage/blockchain/state.ts +++ b/src/storage/blockchain/state.ts @@ -1,12 +1,13 @@ import { RootInfo, StateProof } from './../entities/state'; import { ZKProof } from '@iden3/js-jwz'; import { IStateStorage } from '../interfaces/state'; -import { Contract, JsonRpcProvider, Signer, TransactionRequest } from 'ethers'; +import { Contract, ContractTransaction, JsonRpcProvider, Signer, TransactionRequest } from 'ethers'; import { StateInfo } from '../entities/state'; import { StateTransitionPubSignals } from '../../circuits'; import { byteEncoder } from '../../utils'; import abi from './abi/State.json'; import { DID, getChainId, Id } from '@iden3/js-iden3-core'; +import { KmsKeyType } from '../../kms'; /** * Configuration of ethereum based blockchain connection @@ -102,29 +103,18 @@ export class EthStateStorage implements IStateStorage { } /** {@inheritdoc IStateStorage.publishState} */ - async publishState(proof: ZKProof, signer: Signer): Promise { + async publishState(proof: ZKProof, signer: Signer, keyType?: KmsKeyType): Promise { const stateTransitionPubSig = new StateTransitionPubSignals(); stateTransitionPubSig.pubSignalsUnmarshal( byteEncoder.encode(JSON.stringify(proof.pub_signals)) ); const { userId, oldUserState, newUserState, isOldStateGenesis } = stateTransitionPubSig; + keyType = keyType ?? KmsKeyType.BabyJubJub; + const { stateContract, provider } = this.getStateContractAndProviderForId(userId.bigInt()); const contract = stateContract.connect(signer) as Contract; - const payload = [ - userId.bigInt().toString(), - oldUserState.bigInt().toString(), - newUserState.bigInt().toString(), - isOldStateGenesis, - proof.proof.pi_a.slice(0, 2), - [ - [proof.proof.pi_b[0][1], proof.proof.pi_b[0][0]], - [proof.proof.pi_b[1][1], proof.proof.pi_b[1][0]] - ], - proof.proof.pi_c.slice(0, 2) - ]; - const feeData = await provider.getFeeData(); const maxFeePerGas = defaultEthConnectionConfig.maxFeePerGas @@ -134,8 +124,46 @@ export class EthStateStorage implements IStateStorage { ? BigInt(defaultEthConnectionConfig.maxPriorityFeePerGas) : feeData.maxPriorityFeePerGas; - const gasLimit = await contract.transitState.estimateGas(...payload); - const txData = await contract.transitState.populateTransaction(...payload); + let gasLimit: bigint; + let txData: ContractTransaction; + + switch (keyType) { + case KmsKeyType.BabyJubJub: + { + const payload = [ + userId.bigInt().toString(), + oldUserState.bigInt().toString(), + newUserState.bigInt().toString(), + isOldStateGenesis, + proof.proof.pi_a.slice(0, 2), + [ + [proof.proof.pi_b[0][1], proof.proof.pi_b[0][0]], + [proof.proof.pi_b[1][1], proof.proof.pi_b[1][0]] + ], + proof.proof.pi_c.slice(0, 2) + ]; + gasLimit = await contract.transitState.estimateGas(...payload); + txData = await contract.transitState.populateTransaction(...payload); + } + break; + case KmsKeyType.Secp256k1: + { + const payload = [ + userId.bigInt().toString(), + oldUserState.bigInt().toString(), + newUserState.bigInt().toString(), + isOldStateGenesis, + BigInt(1), + [] + ]; + console.log('Calling transitStateGeneric!!!!!', payload); + gasLimit = await contract.transitStateGeneric.estimateGas(...payload); + txData = await contract.transitStateGeneric.populateTransaction(...payload); + } + break; + default: + throw new Error(`keyType "${keyType}" not supported`); + } const request: TransactionRequest = { to: txData.to, 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/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/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 3b8639d7..12f2e82d 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -34,7 +34,7 @@ import * as uuid from 'uuid'; import { MOCK_STATE_STORAGE, getInMemoryDataStorage, - registerBJJIntoInMemoryKMS, + registerKeyProvidersInMemoryKMS, IPFS_URL, getPackageMgr, createIdentity, @@ -57,7 +57,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 +389,163 @@ describe('auth', () => { expect(token).to.be.a('object'); }); + it('auth flow Ethereum identity with circuits V3', async () => { + const { did: ethereumDID } = await createIdentity(idWallet, { + seed: SEED_USER + }); + + const claimReq: CredentialRequest = { + credentialSchema: + 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/kyc-nonmerklized.json', + type: 'KYCAgeCredential', + credentialSubject: { + id: ethereumDID.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: ethereumDID.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('auth response: TestVerifyMessageWithoutProof', async () => { const sender = '1125GJqgw6YEsKFwj63GY87MMxPL9kwDKxPUiwMLNZ'; const userId = '119tqceWdRd2F6WnAyVuFQRFjK3WUXq2LorSPyG9LJ'; 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/helpers.ts b/tests/helpers.ts index 21f823d3..24e102d3 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -22,6 +22,7 @@ import { Profile, ProvingParams, RootInfo, + Sec256k1Provider, StateProof, StateVerificationFunc, VerifiableConstants, @@ -104,11 +105,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..d77a3cac 100644 --- a/tests/identity/id.test.ts +++ b/tests/identity/id.test.ts @@ -9,7 +9,8 @@ import { CredentialWallet, CredentialStatusResolverRegistry, RHSResolver, - CredentialStatusType + CredentialStatusType, + KmsKeyType } from '../../src'; import { MOCK_STATE_STORAGE, @@ -17,7 +18,7 @@ import { createIdentity, RHS_URL, getInMemoryDataStorage, - registerBJJIntoInMemoryKMS + registerKeyProvidersInMemoryKMS } from '../helpers'; import { expect } from 'chai'; @@ -58,7 +59,7 @@ 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); @@ -167,4 +168,21 @@ describe('identity', () => { await credWallet.getRevocationStatusFromCredential(issuerCred); }); + + it('createIdentity Secp256k1', async () => { + const { did, credential } = await createIdentity(idWallet, { keyType: KmsKeyType.Secp256k1 }); + + expect(did.string()).to.equal( + 'did:iden3:polygon:mumbai:wuL2hHjCC1L1XzC2bdyFwU5KxYGdkXiA8ChDSM2dF' + ); + 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); + }); }); From 5aeda0e094f595277a368fee16cca48778fb7229 Mon Sep 17 00:00:00 2001 From: daveroga Date: Fri, 22 Mar 2024 01:37:10 +0100 Subject: [PATCH 02/38] fix rebase --- tests/handlers/revocation-status.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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') From 3e96c51990e50bf3b79b387470c00d5f9a3e81e0 Mon Sep 17 00:00:00 2001 From: daveroga Date: Fri, 22 Mar 2024 01:42:33 +0100 Subject: [PATCH 03/38] remove log --- src/storage/blockchain/state.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/storage/blockchain/state.ts b/src/storage/blockchain/state.ts index 50f84bd6..4a445e0b 100644 --- a/src/storage/blockchain/state.ts +++ b/src/storage/blockchain/state.ts @@ -156,7 +156,6 @@ export class EthStateStorage implements IStateStorage { BigInt(1), [] ]; - console.log('Calling transitStateGeneric!!!!!', payload); gasLimit = await contract.transitStateGeneric.estimateGas(...payload); txData = await contract.transitStateGeneric.populateTransaction(...payload); } From 6f0c5acddc958bff00b1d3956696c2b9f8cead39 Mon Sep 17 00:00:00 2001 From: daveroga Date: Fri, 22 Mar 2024 10:04:27 +0100 Subject: [PATCH 04/38] pass parameter for ethereum identity --- tests/handlers/auth.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 12f2e82d..30c61ea3 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -24,7 +24,8 @@ import { createAuthorizationRequestWithMessage, AuthorizationResponseMessage, ZeroKnowledgeProofResponse, - ProofType + ProofType, + KmsKeyType } from '../../src'; import { Token } from '@iden3/js-jwz'; import { DID } from '@iden3/js-iden3-core'; @@ -391,7 +392,8 @@ describe('auth', () => { it('auth flow Ethereum identity with circuits V3', async () => { const { did: ethereumDID } = await createIdentity(idWallet, { - seed: SEED_USER + seed: SEED_USER, + keyType: KmsKeyType.Secp256k1 }); const claimReq: CredentialRequest = { @@ -537,7 +539,7 @@ describe('auth', () => { }; const msgBytes = byteEncoder.encode(JSON.stringify(authReq)); - const authRes = await authHandler.handleAuthorizationRequest(userDID, msgBytes); + const authRes = await authHandler.handleAuthorizationRequest(ethereumDID, msgBytes); // console.log(JSON.stringify(authRes.authResponse)); const tokenStr = authRes.token; // console.log(tokenStr); From 2c375b21ae608922c328936cad3c30c8b1f618c9 Mon Sep 17 00:00:00 2001 From: daveroga Date: Wed, 27 Mar 2024 13:43:00 +0100 Subject: [PATCH 05/38] update state transition ethereum identities --- src/identity/identity-wallet.ts | 29 +++++- src/kms/key-providers/sec256k1-provider.ts | 4 +- src/proof/proof-service.ts | 75 +++++++++------ src/storage/blockchain/state.ts | 101 +++++++++++---------- src/storage/interfaces/state.ts | 15 ++- 5 files changed, 144 insertions(+), 80 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index e322b176..59bdcd11 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -51,6 +51,8 @@ import { CredentialStatusPublisherRegistry, Iden3SmtRhsCredentialStatusPublisher } from '../credentials/status/credential-status-publisher'; +import { ProofService } from '../proof'; +import { keccak256 } from 'js-sha3'; /** * DID creation options @@ -74,6 +76,8 @@ export type IdentityCreationOptions = { }; seed?: Uint8Array; keyType?: KmsKeyType; + ethSigner?: any; + proofService?: ProofService; }; /** @@ -623,14 +627,22 @@ export class IdentityWallet implements IIdentityWallet { opts.networkId = opts.networkId ?? NetworkId.Mumbai; opts.seed = opts.seed ?? getRandomBytes(32); + const proofService = opts.proofService; + const ethSigner = opts.ethSigner; + const currentState = ZERO_HASH; // In Ethereum identities we don't have an initial state with the auth credential const didType = buildDIDType(opts.method, opts.blockchain, opts.networkId); const keyIdEth = await this._kms.createKeyFromSeed(KmsKeyType.Secp256k1, opts.seed); - const pubKeyHexEth = await this._kms.publicKey(keyIdEth); - const ethAddrBytes = Hex.decodeString(pubKeyHexEth); - const ethAddr = ethAddrBytes.slice(0, 20); + const pubKeyHexEth = (await this._kms.publicKey(keyIdEth)).slice(2); // 04 + x + y (uncompressed key) + // Use Keccak-256 hash function to get public key hash + const hashOfPublicKey = keccak256(Buffer.from(pubKeyHexEth, 'hex')); + // Convert hash to buffer + const ethAddressBuffer = Buffer.from(hashOfPublicKey, 'hex'); + // 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); const did = DID.parseFromId(identifier); @@ -665,6 +677,17 @@ export class IdentityWallet implements IIdentityWallet { opts.revocationOpts ); + // Old tree state genesis state + const oldTreeState: TreeState = { + revocationRoot: ZERO_HASH, + claimsRoot: ZERO_HASH, + state: currentState, + rootOfRoots: ZERO_HASH + }; + + // Mandatory transit state after adding auth credential in Ethereum identities + await proofService?.transitState(did, oldTreeState, true, this._storage.states, ethSigner); + await this.publishRevocationInfoByCredentialStatusType(did, opts.revocationOpts.type, { rhsUrl: opts.revocationOpts.id, onChain: opts.revocationOpts.onChain diff --git a/src/kms/key-providers/sec256k1-provider.ts b/src/kms/key-providers/sec256k1-provider.ts index 3cf002be..07c20f18 100644 --- a/src/kms/key-providers/sec256k1-provider.ts +++ b/src/kms/key-providers/sec256k1-provider.ts @@ -42,7 +42,7 @@ export class Sec256k1Provider implements IKeyProvider { const keyPair = this._ec.keyFromPrivate(seed); const kmsId = { type: this.keyType, - id: providerHelpers.keyPath(this.keyType, keyPair.getPublic().encode('hex', false)) + id: providerHelpers.keyPath(this.keyType, keyPair.getPublic().encode('hex', false)) // 04 + x + y (uncompressed key) }; await this._keyStore.importKey({ alias: kmsId.id, key: keyPair.getPrivate().toString('hex') }); @@ -56,7 +56,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..defec63d 100644 --- a/src/proof/proof-service.ts +++ b/src/proof/proof-service.ts @@ -1,5 +1,5 @@ import { Poseidon } from '@iden3/js-crypto'; -import { BytesHelper, DID, MerklizedRootPosition } from '@iden3/js-iden3-core'; +import { BytesHelper, DID, Id, MerklizedRootPosition } from '@iden3/js-iden3-core'; import { Hash } from '@iden3/js-merkletree'; import { AuthV2Inputs, @@ -33,7 +33,7 @@ import { ZKProof } from '@iden3/js-jwz'; import { Signer } from 'ethers'; import { JSONObject, ZeroKnowledgeProofRequest, ZeroKnowledgeProofResponse } from '../iden3comm'; import { cacheLoader } from '../schema-processor'; -import { ICircuitStorage, IStateStorage } from '../storage'; +import { ICircuitStorage, IStateStorage, UserStateTransition } from '../storage'; import { byteDecoder, byteEncoder } from '../utils/encoding'; import { InputGenerator, @@ -42,6 +42,7 @@ import { } from './provers/inputs-generator'; import { PubSignalsVerifier, VerifyContext } from './verifiers/pub-signals-verifier'; import { VerifyOpts } from './verifiers'; +import { KmsKeyType } from '../kms'; export interface QueryWithFieldName { query: Query; @@ -360,8 +361,6 @@ export class ProofService implements IProofService { stateStorage: IStateStorage, 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(); @@ -373,38 +372,62 @@ export class ProofService implements IProofService { state: newTreeModel.state, rootOfRoots }; - const challenge = Poseidon.hash([oldTreeState.state.bigInt(), newTreeState.state.bigInt()]); - const signature = await this._identityWallet.signChallenge(challenge, authInfo.credential); + const userId = DID.idFromDID(did); - const circuitInputs = new StateTransitionInputs(); - circuitInputs.id = DID.idFromDID(did); + let proof; + let generateProof = false; // don't generate proof for ethereum identities + try { + Id.ethAddressFromId(userId); + } catch { + // not an ethereum identity + generateProof = true; + } - circuitInputs.signature = signature; - circuitInputs.isOldStateGenesis = isOldStateGenesis; + if (generateProof) { + // generate the proof + const authInfo = await this._inputsGenerator.prepareAuthBJJCredential(did, oldTreeState); + const challenge = Poseidon.hash([oldTreeState.state.bigInt(), newTreeState.state.bigInt()]); - const authClaimIncProofNewState = await this._identityWallet.generateCredentialMtp( - did, - authInfo.credential, - newTreeState - ); + const signature = await this._identityWallet.signChallenge(challenge, authInfo.credential); - circuitInputs.newTreeState = authClaimIncProofNewState.treeState; - circuitInputs.authClaimNewStateIncProof = authClaimIncProofNewState.proof; + const circuitInputs = new StateTransitionInputs(); + circuitInputs.id = userId; - circuitInputs.oldTreeState = oldTreeState; - circuitInputs.authClaim = { - claim: authInfo.coreClaim, - incProof: authInfo.incProof, - nonRevProof: authInfo.nonRevProof - }; + circuitInputs.signature = signature; + circuitInputs.isOldStateGenesis = isOldStateGenesis; - const inputs = circuitInputs.inputsMarshal(); + const authClaimIncProofNewState = await this._identityWallet.generateCredentialMtp( + did, + authInfo.credential, + newTreeState + ); - const proof = await this._prover.generate(inputs, CircuitId.StateTransition); + 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 this._prover.generate(inputs, CircuitId.StateTransition); + } - const txId = await stateStorage.publishState(proof, ethSigner); + const oldUserState = oldTreeState.state; + const newUserState = newTreeState.state; + const userStateTransition: UserStateTransition = { + userId, + oldUserState, + newUserState, + isOldStateGenesis + } as UserStateTransition; + const txId = await stateStorage.publishState(proof, ethSigner, userStateTransition); await this._identityWallet.updateIdentityState(did, true, newTreeState); return txId; diff --git a/src/storage/blockchain/state.ts b/src/storage/blockchain/state.ts index 4a445e0b..6ea52104 100644 --- a/src/storage/blockchain/state.ts +++ b/src/storage/blockchain/state.ts @@ -1,13 +1,12 @@ import { RootInfo, StateProof } from './../entities/state'; import { ZKProof } from '@iden3/js-jwz'; -import { IStateStorage } from '../interfaces/state'; +import { IStateStorage, UserStateTransition } from '../interfaces/state'; import { Contract, ContractTransaction, JsonRpcProvider, Signer, TransactionRequest } from 'ethers'; import { StateInfo } from '../entities/state'; import { StateTransitionPubSignals } from '../../circuits'; import { byteEncoder } from '../../utils'; import abi from './abi/State.json'; import { DID, getChainId, Id } from '@iden3/js-iden3-core'; -import { KmsKeyType } from '../../kms'; /** * Configuration of ethereum based blockchain connection @@ -103,18 +102,14 @@ export class EthStateStorage implements IStateStorage { } /** {@inheritdoc IStateStorage.publishState} */ - async publishState(proof: ZKProof, signer: Signer, keyType?: KmsKeyType): Promise { - const stateTransitionPubSig = new StateTransitionPubSignals(); - stateTransitionPubSig.pubSignalsUnmarshal( - byteEncoder.encode(JSON.stringify(proof.pub_signals)) - ); - const { userId, oldUserState, newUserState, isOldStateGenesis } = stateTransitionPubSig; - - keyType = keyType ?? KmsKeyType.BabyJubJub; - + async publishState( + proof: ZKProof, + signer: Signer, + userStateTranstion: UserStateTransition + ): Promise { + const { userId, oldUserState, newUserState, isOldStateGenesis } = userStateTranstion; const { stateContract, provider } = this.getStateContractAndProviderForId(userId.bigInt()); const contract = stateContract.connect(signer) as Contract; - const feeData = await provider.getFeeData(); const maxFeePerGas = defaultEthConnectionConfig.maxFeePerGas @@ -127,41 +122,52 @@ export class EthStateStorage implements IStateStorage { let gasLimit: bigint; let txData: ContractTransaction; - switch (keyType) { - case KmsKeyType.BabyJubJub: - { - const payload = [ - userId.bigInt().toString(), - oldUserState.bigInt().toString(), - newUserState.bigInt().toString(), - isOldStateGenesis, - proof.proof.pi_a.slice(0, 2), - [ - [proof.proof.pi_b[0][1], proof.proof.pi_b[0][0]], - [proof.proof.pi_b[1][1], proof.proof.pi_b[1][0]] - ], - proof.proof.pi_c.slice(0, 2) - ]; - gasLimit = await contract.transitState.estimateGas(...payload); - txData = await contract.transitState.populateTransaction(...payload); - } - break; - case KmsKeyType.Secp256k1: - { - const payload = [ - userId.bigInt().toString(), - oldUserState.bigInt().toString(), - newUserState.bigInt().toString(), - isOldStateGenesis, - BigInt(1), - [] - ]; - gasLimit = await contract.transitStateGeneric.estimateGas(...payload); - txData = await contract.transitStateGeneric.populateTransaction(...payload); - } - break; - default: - throw new Error(`keyType "${keyType}" not supported`); + if (proof) { + const stateTransitionPubSig = new StateTransitionPubSignals(); + stateTransitionPubSig.pubSignalsUnmarshal( + byteEncoder.encode(JSON.stringify(proof.pub_signals)) + ); + const { + userId: userIdPub, + oldUserState: oldUserStatePub, + newUserState: newUserStatePub, + isOldStateGenesis: isOldStateGenesisPub + } = stateTransitionPubSig; + + if ( + userIdPub.bigInt() !== userId.bigInt() || + oldUserStatePub.bigInt() !== oldUserState.bigInt() || + newUserStatePub.bigInt() !== newUserState.bigInt() || + isOldStateGenesisPub !== isOldStateGenesis + ) { + throw new Error(`public inputs do not match with user state transition`); + } + + const payload = [ + userId.bigInt().toString(), + oldUserState.bigInt().toString(), + newUserState.bigInt().toString(), + isOldStateGenesis, + proof.proof.pi_a.slice(0, 2), + [ + [proof.proof.pi_b[0][1], proof.proof.pi_b[0][0]], + [proof.proof.pi_b[1][1], proof.proof.pi_b[1][0]] + ], + proof.proof.pi_c.slice(0, 2) + ]; + gasLimit = await contract.transitState.estimateGas(...payload); + txData = await contract.transitState.populateTransaction(...payload); + } else { + const payload = [ + userId.bigInt().toString(), + oldUserState.bigInt().toString(), + newUserState.bigInt().toString(), + isOldStateGenesis, + BigInt(1), + '0x' + ]; + gasLimit = await contract.transitStateGeneric.estimateGas(...payload); + txData = await contract.transitStateGeneric.populateTransaction(...payload); } const request: TransactionRequest = { @@ -172,7 +178,6 @@ export class EthStateStorage implements IStateStorage { maxPriorityFeePerGas }; const tx = await signer.sendTransaction(request); - const txnReceipt = await tx.wait(); if (!txnReceipt) { throw new Error(`transaction: ${tx.hash} failed to mined`); diff --git a/src/storage/interfaces/state.ts b/src/storage/interfaces/state.ts index 4996408b..eed8df46 100644 --- a/src/storage/interfaces/state.ts +++ b/src/storage/interfaces/state.ts @@ -1,6 +1,15 @@ 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 UserStateTransition { + userId: Id; + oldUserState: Hash; + newUserState: Hash; + isOldStateGenesis: boolean; +} /** * Interface that defines methods for state storage @@ -32,7 +41,11 @@ 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, + userStateTransition?: UserStateTransition + ): Promise; /** * generates proof of inclusion / non-inclusion to global identity state for given identity * From c6384534532a012c1e2374683c97f916a05a6a88 Mon Sep 17 00:00:00 2001 From: daveroga Date: Wed, 27 Mar 2024 17:42:43 +0100 Subject: [PATCH 06/38] method update for testing in Mumbai deployment --- tests/helpers.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/helpers.ts b/tests/helpers.ts index 24e102d3..9d2d494e 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -47,8 +47,10 @@ export const createIdentity = async ( wallet: IIdentityWallet, opts?: Partial ) => { + // For testing in Mumbai should be "DidMethod.PolygonId" for the State contract already deployed + // Otherwise it will throw an error "msg.sender is not owner of the identity" when checking the Id from the sender return await wallet.createIdentity({ - method: DidMethod.Iden3, + method: DidMethod.PolygonId, blockchain: Blockchain.Polygon, networkId: NetworkId.Amoy, seed: SEED_ISSUER, From dca055663edc38ae7ee78df86b4e61a9555c3730 Mon Sep 17 00:00:00 2001 From: daveroga Date: Wed, 27 Mar 2024 18:40:51 +0100 Subject: [PATCH 07/38] update auth test with integration test for ethereum identities --- tests/handlers/auth.test.ts | 133 +++++++++++++++++++++++++++++------- 1 file changed, 110 insertions(+), 23 deletions(-) diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 30c61ea3..0733506f 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -25,10 +25,22 @@ import { AuthorizationResponseMessage, ZeroKnowledgeProofResponse, ProofType, - KmsKeyType + KmsKeyType, + defaultEthConnectionConfig, + InMemoryPrivateKeyStore, + BjjProvider, + KMS, + CredentialStorage, + InMemoryDataSource, + IdentityStorage, + Identity, + Profile, + InMemoryMerkleTreeStorage, + W3CCredential, + Sec256k1Provider } 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'; @@ -41,7 +53,9 @@ import { createIdentity, SEED_USER, RHS_URL, - WALLET_KEY + WALLET_KEY, + STATE_CONTRACT, + RPC_URL } from '../helpers'; import { testOpts } from './mock'; @@ -390,18 +404,95 @@ describe('auth', () => { expect(token).to.be.a('object'); }); - it('auth flow Ethereum identity with circuits V3', async () => { - const { did: ethereumDID } = await createIdentity(idWallet, { + // SKIPPED : ethereum identity integration test + it.skip('auth flow identity (profile) with ethereum identity issuer with circuits V3', async () => { + const stateEthConfig = defaultEthConnectionConfig; + stateEthConfig.url = RPC_URL; + stateEthConfig.contractAddress = STATE_CONTRACT; + stateEthConfig.chainId = 80001; + + 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.Mumbai, seed: SEED_USER, - keyType: KmsKeyType.Secp256k1 + 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.createIdentity({ + method: DidMethod.PolygonId, + blockchain: Blockchain.Polygon, + networkId: NetworkId.Mumbai, + seed: Buffer.from(WALLET_KEY, 'hex'), + revocationOpts: { + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: RHS_URL + }, + keyType: KmsKeyType.Secp256k1, + ethSigner, + proofService: proofService }); + expect(issuerAuthCredential).not.to.be.undefined; + + const issuerDIDEth = didIssuer; + const profileDID = await idWallet.createProfile(didUser, 777, issuerDIDEth.string()); const claimReq: CredentialRequest = { credentialSchema: 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/kyc-nonmerklized.json', type: 'KYCAgeCredential', credentialSubject: { - id: ethereumDID.string(), + id: didUser.string(), birthday: 19960424, documentType: 99 }, @@ -411,13 +502,13 @@ describe('auth', () => { id: RHS_URL } }; - const issuerCred = await idWallet.issueCredential(issuerDID, claimReq); + const issuerCred = await idWallet.issueCredential(issuerDIDEth, claimReq); const employeeCredRequest: CredentialRequest = { credentialSchema: 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCEmployee-v101.json', type: 'KYCEmployee', credentialSubject: { - id: ethereumDID.string(), + id: profileDID.string(), ZKPexperiance: true, hireDate: '2023-12-11', position: 'boss', @@ -429,28 +520,23 @@ describe('auth', () => { id: RHS_URL } }; - const employeeCred = await idWallet.issueCredential(issuerDID, employeeCredRequest); + const employeeCred = await idWallet.issueCredential(issuerDIDEth, 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 res = await idWallet.addCredentialsToMerkleTree([employeeCred], issuerDIDEth); + await idWallet.publishStateToRHS(issuerDIDEth, RHS_URL); const txId = await proofService.transitState( - issuerDID, + issuerDIDEth, res.oldTreeState, - true, + false, dataStorage.states, ethSigner ); const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof( - issuerDID, + issuerDIDEth, res.credentials, txId ); @@ -535,12 +621,13 @@ describe('auth', () => { type: PROTOCOL_CONSTANTS.PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE, thid: id, body: authReqBody, - from: issuerDID.string() + from: issuerDIDEth.string() }; const msgBytes = byteEncoder.encode(JSON.stringify(authReq)); - const authRes = await authHandler.handleAuthorizationRequest(ethereumDID, msgBytes); - // console.log(JSON.stringify(authRes.authResponse)); + console.log('authReq', 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'); From 2bf7b9124922935cdac7682236fe7e1aa6242cbb Mon Sep 17 00:00:00 2001 From: daveroga Date: Wed, 27 Mar 2024 22:07:33 +0100 Subject: [PATCH 08/38] restore method helper iden3 --- tests/helpers.ts | 4 +--- tests/identity/id.test.ts | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/helpers.ts b/tests/helpers.ts index 9d2d494e..24e102d3 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -47,10 +47,8 @@ export const createIdentity = async ( wallet: IIdentityWallet, opts?: Partial ) => { - // For testing in Mumbai should be "DidMethod.PolygonId" for the State contract already deployed - // Otherwise it will throw an error "msg.sender is not owner of the identity" when checking the Id from the sender return await wallet.createIdentity({ - method: DidMethod.PolygonId, + method: DidMethod.Iden3, blockchain: Blockchain.Polygon, networkId: NetworkId.Amoy, seed: SEED_ISSUER, diff --git a/tests/identity/id.test.ts b/tests/identity/id.test.ts index d77a3cac..39577de0 100644 --- a/tests/identity/id.test.ts +++ b/tests/identity/id.test.ts @@ -173,7 +173,7 @@ describe('identity', () => { const { did, credential } = await createIdentity(idWallet, { keyType: KmsKeyType.Secp256k1 }); expect(did.string()).to.equal( - 'did:iden3:polygon:mumbai:wuL2hHjCC1L1XzC2bdyFwU5KxYGdkXiA8ChDSM2dF' + 'did:iden3:polygon:mumbai:wuL2hHjCC1LQArvVpwTkW6KeJNxAv9PfhCnMK4rnX' ); const dbCred = await dataStorage.credential.findCredentialById(credential.id); expect(credential).to.deep.equal(dbCred); From 8ec2133f75b1d15bf9def15d66be8c31462d36a7 Mon Sep 17 00:00:00 2001 From: daveroga Date: Thu, 28 Mar 2024 09:37:06 +0100 Subject: [PATCH 09/38] update current state when creating auth credential in ethereum identities --- src/identity/identity-wallet.ts | 13 ++++++++++++- tests/handlers/auth.test.ts | 3 +-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 59bdcd11..5813d16c 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -669,11 +669,22 @@ export class IdentityWallet implements IIdentityWallet { authClaim.hiHv().hv ); + const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType( + did.string(), + MerkleTreeType.Claims + ); + + const stateAuthClaim = hashElems([ + (await claimsTree.root()).bigInt(), + ZERO_HASH.bigInt(), + ZERO_HASH.bigInt() + ]); + const credential = await this.createAuthCredential( did, pubKey, authClaim, - currentState, + stateAuthClaim, opts.revocationOpts ); diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 0733506f..8c6c2970 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -625,9 +625,8 @@ describe('auth', () => { }; const msgBytes = byteEncoder.encode(JSON.stringify(authReq)); - console.log('authReq', authReq); const authRes = await authHandler.handleAuthorizationRequest(didUser, msgBytes); - console.log('authRes', JSON.stringify(authRes.authResponse)); + // console.log('authRes', JSON.stringify(authRes.authResponse)); const tokenStr = authRes.token; // console.log(tokenStr); expect(tokenStr).to.be.a('string'); From a86992cae77ef695d8eb6111f75f5ed300574a28 Mon Sep 17 00:00:00 2001 From: daveroga Date: Thu, 28 Mar 2024 10:02:21 +0100 Subject: [PATCH 10/38] remove unused import --- src/proof/proof-service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts index defec63d..3dd45693 100644 --- a/src/proof/proof-service.ts +++ b/src/proof/proof-service.ts @@ -42,7 +42,6 @@ import { } from './provers/inputs-generator'; import { PubSignalsVerifier, VerifyContext } from './verifiers/pub-signals-verifier'; import { VerifyOpts } from './verifiers'; -import { KmsKeyType } from '../kms'; export interface QueryWithFieldName { query: Query; From 7e43f849531f0d0987a125046d37c0e0d9896f05 Mon Sep 17 00:00:00 2001 From: daveroga Date: Thu, 28 Mar 2024 15:03:08 +0100 Subject: [PATCH 11/38] add test ethereum identity flow (not integration) --- .../status/reverse-sparse-merkle-tree.ts | 12 +- tests/handlers/auth.test.ts | 192 +++++++++++++++++- 2 files changed, 192 insertions(+), 12 deletions(-) diff --git a/src/credentials/status/reverse-sparse-merkle-tree.ts b/src/credentials/status/reverse-sparse-merkle-tree.ts index 25474129..76c4f548 100644 --- a/src/credentials/status/reverse-sparse-merkle-tree.ts +++ b/src/credentials/status/reverse-sparse-merkle-tree.ts @@ -173,7 +173,17 @@ export class RHSResolver implements CredentialStatusResolver { return this.getRevocationStatusFromIssuerData(issuerDID, issuerData, genesisState); } const currentStateBigInt = Hash.fromHex(stateHex).bigInt(); - if (!isGenesisState(issuerDID, currentStateBigInt)) { + + const issuerId = DID.idFromDID(issuerDID); + let isBjjIdentity = false; // don't generate proof for ethereum identities + try { + Id.ethAddressFromId(issuerId); + } catch { + // not an ethereum identity + isBjjIdentity = true; + } + + if (isBjjIdentity && !isGenesisState(issuerDID, currentStateBigInt)) { throw new Error( `latest state not found and state parameter ${stateHex} is not genesis state` ); diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 8c6c2970..f93261bb 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -55,7 +55,8 @@ import { RHS_URL, WALLET_KEY, STATE_CONTRACT, - RPC_URL + RPC_URL, + SEED_ISSUER } from '../helpers'; import { testOpts } from './mock'; @@ -404,8 +405,178 @@ describe('auth', () => { expect(token).to.be.a('object'); }); + it.only('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.createIdentity({ + method: DidMethod.PolygonId, + blockchain: Blockchain.Polygon, + networkId: NetworkId.Mumbai, + seed: SEED_ISSUER, + revocationOpts: { + type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, + id: RHS_URL + }, + keyType: KmsKeyType.Secp256k1, + ethSigner, + proofService + }); + 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() + }; + + 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'); + }); + // SKIPPED : ethereum identity integration test - it.skip('auth flow identity (profile) with ethereum identity issuer with circuits V3', async () => { + 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; @@ -484,8 +655,7 @@ describe('auth', () => { }); expect(issuerAuthCredential).not.to.be.undefined; - const issuerDIDEth = didIssuer; - const profileDID = await idWallet.createProfile(didUser, 777, issuerDIDEth.string()); + const profileDID = await idWallet.createProfile(didUser, 777, didIssuer.string()); const claimReq: CredentialRequest = { credentialSchema: @@ -502,7 +672,7 @@ describe('auth', () => { id: RHS_URL } }; - const issuerCred = await idWallet.issueCredential(issuerDIDEth, claimReq); + 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', @@ -520,15 +690,15 @@ describe('auth', () => { id: RHS_URL } }; - const employeeCred = await idWallet.issueCredential(issuerDIDEth, employeeCredRequest); + const employeeCred = await idWallet.issueCredential(didIssuer, employeeCredRequest); await credWallet.saveAll([employeeCred, issuerCred]); - const res = await idWallet.addCredentialsToMerkleTree([employeeCred], issuerDIDEth); - await idWallet.publishStateToRHS(issuerDIDEth, RHS_URL); + const res = await idWallet.addCredentialsToMerkleTree([employeeCred], didIssuer); + await idWallet.publishStateToRHS(didIssuer, RHS_URL); const txId = await proofService.transitState( - issuerDIDEth, + didIssuer, res.oldTreeState, false, dataStorage.states, @@ -536,7 +706,7 @@ describe('auth', () => { ); const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof( - issuerDIDEth, + didIssuer, res.credentials, txId ); @@ -621,7 +791,7 @@ describe('auth', () => { type: PROTOCOL_CONSTANTS.PROTOCOL_MESSAGE_TYPE.AUTHORIZATION_REQUEST_MESSAGE_TYPE, thid: id, body: authReqBody, - from: issuerDIDEth.string() + from: didIssuer.string() }; const msgBytes = byteEncoder.encode(JSON.stringify(authReq)); From 39a2dfa0c91004a19f58a2a7f67a73acc5f8bbf1 Mon Sep 17 00:00:00 2001 From: daveroga Date: Thu, 28 Mar 2024 15:04:32 +0100 Subject: [PATCH 12/38] remove only in test --- tests/handlers/auth.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index f93261bb..53da9759 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -405,7 +405,7 @@ describe('auth', () => { expect(token).to.be.a('object'); }); - it.only('auth flow identity (profile) with ethereum identity issuer with circuits V3', async () => { + 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 From c048e0ba6887d958c221a2f08611d61b3e5c9dd3 Mon Sep 17 00:00:00 2001 From: daveroga Date: Tue, 2 Apr 2024 18:51:04 +0200 Subject: [PATCH 13/38] update mumbai for aloy --- src/identity/identity-wallet.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 5813d16c..e2dc50b2 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -115,7 +115,7 @@ 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:aloy** with generated key. * @returns `Promise<{ did: DID; credential: W3CCredential }>` - returns did and Auth BJJ credential * @public */ @@ -551,7 +551,7 @@ export class IdentityWallet implements IIdentityWallet { opts.method = opts.method ?? DidMethod.Iden3; opts.blockchain = opts.blockchain ?? Blockchain.Polygon; - opts.networkId = opts.networkId ?? NetworkId.Mumbai; + opts.networkId = opts.networkId ?? NetworkId.Aloy; opts.seed = opts.seed ?? getRandomBytes(32); await this._storage.mt.createIdentityMerkleTrees(tmpIdentifier); @@ -624,7 +624,7 @@ export class IdentityWallet implements IIdentityWallet { ): Promise<{ did: DID; credential: W3CCredential }> { opts.method = opts.method ?? DidMethod.Iden3; opts.blockchain = opts.blockchain ?? Blockchain.Polygon; - opts.networkId = opts.networkId ?? NetworkId.Mumbai; + opts.networkId = opts.networkId ?? NetworkId.Aloy; opts.seed = opts.seed ?? getRandomBytes(32); const proofService = opts.proofService; From b31c69be150a5d402b603eb163b6d825db3260a7 Mon Sep 17 00:00:00 2001 From: daveroga Date: Tue, 2 Apr 2024 19:47:43 +0200 Subject: [PATCH 14/38] update id test --- tests/identity/id.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/identity/id.test.ts b/tests/identity/id.test.ts index 39577de0..ca98c846 100644 --- a/tests/identity/id.test.ts +++ b/tests/identity/id.test.ts @@ -173,7 +173,7 @@ describe('identity', () => { const { did, credential } = await createIdentity(idWallet, { keyType: KmsKeyType.Secp256k1 }); expect(did.string()).to.equal( - 'did:iden3:polygon:mumbai:wuL2hHjCC1LQArvVpwTkW6KeJNxAv9PfhCnMK4rnX' + 'did:iden3:polygon:amoy:x6x5sor7zpxsu478u36QvEgaRUfPjmzqFo5PHHzbM' ); const dbCred = await dataStorage.credential.findCredentialById(credential.id); expect(credential).to.deep.equal(dbCred); From a68a6dd8d9f4aeb6e4968dedb00dc721dabd3ec0 Mon Sep 17 00:00:00 2001 From: daveroga Date: Tue, 2 Apr 2024 22:07:10 +0200 Subject: [PATCH 15/38] changes proposed from review --- index.html | 3 + .../status/reverse-sparse-merkle-tree.ts | 18 ++- src/identity/identity-wallet.ts | 16 ++- src/proof/proof-service.ts | 30 ++-- src/proof/provers/inputs-generator.ts | 8 +- src/storage/blockchain/state.ts | 128 ++++++++++-------- src/storage/interfaces/state.ts | 25 ++-- src/utils/did-helper.ts | 18 +++ .../credential-statuses/rhs.test.ts | 9 ++ .../credentials/credential-validation.test.ts | 3 + tests/handlers/contract-request.test.ts | 3 + tests/helpers.ts | 3 + tests/proofs/mtp-onchain.test.ts | 3 + tests/proofs/mtp.test.ts | 3 + tests/proofs/sig-onchain.test.ts | 3 + tests/proofs/sig.test.ts | 3 + 16 files changed, 177 insertions(+), 99 deletions(-) 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 76c4f548..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'; /** @@ -174,20 +174,18 @@ export class RHSResolver implements CredentialStatusResolver { } const currentStateBigInt = Hash.fromHex(stateHex).bigInt(); - const issuerId = DID.idFromDID(issuerDID); - let isBjjIdentity = false; // don't generate proof for ethereum identities - try { - Id.ethAddressFromId(issuerId); - } catch { - // not an ethereum identity - isBjjIdentity = true; - } + const isEthIdentity = isEthereumIdentity(issuerDID); - if (isBjjIdentity && !isGenesisState(issuerDID, currentStateBigInt)) { + 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 e2dc50b2..50d33df6 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -452,7 +452,7 @@ export class IdentityWallet implements IIdentityWallet { } } - private async createAuthClaim( + private async createAuthCoreClaim( revNonce: number, seed: Uint8Array ): Promise<{ authClaim: Claim; pubKey: PublicKey }> { @@ -472,7 +472,7 @@ export class IdentityWallet implements IIdentityWallet { return { authClaim, pubKey }; } - private async createAuthCredential( + private async createAuthBJJCredential( did: DID, pubKey: PublicKey, authClaim: Claim, @@ -558,7 +558,7 @@ export class IdentityWallet implements IIdentityWallet { const revNonce = opts.revocationOpts.nonce ?? 0; - const { authClaim, pubKey } = await this.createAuthClaim(revNonce, opts.seed); + const { authClaim, pubKey } = await this.createAuthCoreClaim(revNonce, opts.seed); await this._storage.mt.addToMerkleTree( tmpIdentifier, @@ -584,7 +584,7 @@ export class IdentityWallet implements IIdentityWallet { await this._storage.mt.bindMerkleTreeToNewIdentifier(tmpIdentifier, did.string()); - const credential = await this.createAuthCredential( + const credential = await this.createAuthBJJCredential( did, pubKey, authClaim, @@ -657,7 +657,7 @@ export class IdentityWallet implements IIdentityWallet { }); // Add Auth BJJ credential after saving identity for Ethereum identities - const { authClaim, pubKey } = await this.createAuthClaim( + const { authClaim, pubKey } = await this.createAuthCoreClaim( opts.revocationOpts.nonce ?? 0, opts.seed ); @@ -680,7 +680,7 @@ export class IdentityWallet implements IIdentityWallet { ZERO_HASH.bigInt() ]); - const credential = await this.createAuthCredential( + const credential = await this.createAuthBJJCredential( did, pubKey, authClaim, @@ -696,6 +696,10 @@ export class IdentityWallet implements IIdentityWallet { rootOfRoots: ZERO_HASH }; + if (!proofService) { + throw new Error('Proof service is required to create Ethereum identities'); + } + // Mandatory transit state after adding auth credential in Ethereum identities await proofService?.transitState(did, oldTreeState, true, this._storage.states, ethSigner); diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts index 3dd45693..905feac5 100644 --- a/src/proof/proof-service.ts +++ b/src/proof/proof-service.ts @@ -33,7 +33,7 @@ import { ZKProof } from '@iden3/js-jwz'; import { Signer } from 'ethers'; import { JSONObject, ZeroKnowledgeProofRequest, ZeroKnowledgeProofResponse } from '../iden3comm'; import { cacheLoader } from '../schema-processor'; -import { ICircuitStorage, IStateStorage, UserStateTransition } from '../storage'; +import { ICircuitStorage, IStateStorage, UserStateTransitionInfo } from '../storage'; import { byteDecoder, byteEncoder } from '../utils/encoding'; import { InputGenerator, @@ -42,6 +42,7 @@ import { } from './provers/inputs-generator'; import { PubSignalsVerifier, VerifyContext } from './verifiers/pub-signals-verifier'; import { VerifyOpts } from './verifiers'; +import { isEthereumIdentity } from '../utils'; export interface QueryWithFieldName { query: Query; @@ -375,15 +376,9 @@ export class ProofService implements IProofService { const userId = DID.idFromDID(did); let proof; - let generateProof = false; // don't generate proof for ethereum identities - try { - Id.ethAddressFromId(userId); - } catch { - // not an ethereum identity - generateProof = true; - } + const isEthIdentity = isEthereumIdentity(did); // don't generate proof for ethereum identities - if (generateProof) { + if (!isEthIdentity) { // generate the proof const authInfo = await this._inputsGenerator.prepareAuthBJJCredential(did, oldTreeState); const challenge = Poseidon.hash([oldTreeState.state.bigInt(), newTreeState.state.bigInt()]); @@ -419,14 +414,21 @@ export class ProofService implements IProofService { const oldUserState = oldTreeState.state; const newUserState = newTreeState.state; - const userStateTransition: UserStateTransition = { + const userStateTransitionInfo: UserStateTransitionInfo = { userId, oldUserState, newUserState, - isOldStateGenesis - } as UserStateTransition; - - const txId = await stateStorage.publishState(proof, ethSigner, userStateTransition); + isOldStateGenesis, + methodId: BigInt(1), + methodParams: '0x' + } as UserStateTransitionInfo; + + let txId; + if (!isEthIdentity) { + txId = await stateStorage.publishState(proof, ethSigner); + } else { + txId = await stateStorage.publishStateGeneric(ethSigner, userStateTransitionInfo); + } await this._identityWallet.updateIdentityState(did, true, newTreeState); return txId; diff --git a/src/proof/provers/inputs-generator.ts b/src/proof/provers/inputs-generator.ts index 5a2cba8e..d026031d 100644 --- a/src/proof/provers/inputs-generator.ts +++ b/src/proof/provers/inputs-generator.ts @@ -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 6ea52104..8b60a0e4 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, UserStateTransition } from '../interfaces/state'; +import { IStateStorage, UserStateTransitionInfo } from '../interfaces/state'; import { Contract, ContractTransaction, JsonRpcProvider, Signer, TransactionRequest } from 'ethers'; import { StateInfo } from '../entities/state'; import { StateTransitionPubSignals } from '../../circuits'; @@ -102,14 +102,29 @@ export class EthStateStorage implements IStateStorage { } /** {@inheritdoc IStateStorage.publishState} */ - async publishState( - proof: ZKProof, - signer: Signer, - userStateTranstion: UserStateTransition - ): Promise { - const { userId, oldUserState, newUserState, isOldStateGenesis } = userStateTranstion; + async publishState(proof: ZKProof, signer: Signer): Promise { + const stateTransitionPubSig = new StateTransitionPubSignals(); + stateTransitionPubSig.pubSignalsUnmarshal( + byteEncoder.encode(JSON.stringify(proof.pub_signals)) + ); + const { userId, oldUserState, newUserState, isOldStateGenesis } = stateTransitionPubSig; + const { stateContract, provider } = this.getStateContractAndProviderForId(userId.bigInt()); const contract = stateContract.connect(signer) as Contract; + + const payload = [ + userId.bigInt().toString(), + oldUserState.bigInt().toString(), + newUserState.bigInt().toString(), + isOldStateGenesis, + proof.proof.pi_a.slice(0, 2), + [ + [proof.proof.pi_b[0][1], proof.proof.pi_b[0][0]], + [proof.proof.pi_b[1][1], proof.proof.pi_b[1][0]] + ], + proof.proof.pi_c.slice(0, 2) + ]; + const feeData = await provider.getFeeData(); const maxFeePerGas = defaultEthConnectionConfig.maxFeePerGas @@ -119,57 +134,60 @@ export class EthStateStorage implements IStateStorage { ? BigInt(defaultEthConnectionConfig.maxPriorityFeePerGas) : feeData.maxPriorityFeePerGas; - let gasLimit: bigint; - let txData: ContractTransaction; - - if (proof) { - const stateTransitionPubSig = new StateTransitionPubSignals(); - stateTransitionPubSig.pubSignalsUnmarshal( - byteEncoder.encode(JSON.stringify(proof.pub_signals)) - ); - const { - userId: userIdPub, - oldUserState: oldUserStatePub, - newUserState: newUserStatePub, - isOldStateGenesis: isOldStateGenesisPub - } = stateTransitionPubSig; - - if ( - userIdPub.bigInt() !== userId.bigInt() || - oldUserStatePub.bigInt() !== oldUserState.bigInt() || - newUserStatePub.bigInt() !== newUserState.bigInt() || - isOldStateGenesisPub !== isOldStateGenesis - ) { - throw new Error(`public inputs do not match with user state transition`); - } + const gasLimit = await contract.transitState.estimateGas(...payload); + const txData = await contract.transitState.populateTransaction(...payload); + + const request: TransactionRequest = { + to: txData.to, + data: txData.data, + gasLimit, + 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 payload = [ - userId.bigInt().toString(), - oldUserState.bigInt().toString(), - newUserState.bigInt().toString(), - isOldStateGenesis, - proof.proof.pi_a.slice(0, 2), - [ - [proof.proof.pi_b[0][1], proof.proof.pi_b[0][0]], - [proof.proof.pi_b[1][1], proof.proof.pi_b[1][0]] - ], - proof.proof.pi_c.slice(0, 2) - ]; - gasLimit = await contract.transitState.estimateGas(...payload); - txData = await contract.transitState.populateTransaction(...payload); - } else { - const payload = [ - userId.bigInt().toString(), - oldUserState.bigInt().toString(), - newUserState.bigInt().toString(), - isOldStateGenesis, - BigInt(1), - '0x' - ]; - gasLimit = await contract.transitStateGeneric.estimateGas(...payload); - txData = await contract.transitStateGeneric.populateTransaction(...payload); + if (!status) { + throw new Error(`transaction: ${txnHash} failed to mined`); } + return txnHash; + } + + /** {@inheritdoc IStateStorage.publishState} */ + async publishStateGeneric( + signer: Signer, + userStateTranstionInfo: UserStateTransitionInfo + ): Promise { + const { userId, oldUserState, newUserState, isOldStateGenesis, methodId, methodParams } = userStateTranstionInfo; + 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, diff --git a/src/storage/interfaces/state.ts b/src/storage/interfaces/state.ts index eed8df46..d5cb44b1 100644 --- a/src/storage/interfaces/state.ts +++ b/src/storage/interfaces/state.ts @@ -4,11 +4,13 @@ import { RootInfo, StateInfo, StateProof } from '../entities/state'; import { Id } from '@iden3/js-iden3-core'; import { Hash } from '@iden3/js-merkletree'; -export interface UserStateTransition { - userId: Id; - oldUserState: Hash; - newUserState: Hash; - isOldStateGenesis: boolean; +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 } /** @@ -41,10 +43,17 @@ export interface IStateStorage { * @param {Signer} signer - signer of transaction * @returns `Promise` - transaction identifier */ - publishState( - proof: ZKProof | undefined, + 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, - userStateTransition?: UserStateTransition + 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..403ef8a3 100644 --- a/src/utils/did-helper.ts +++ b/src/utils/did-helper.ts @@ -22,6 +22,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 {string} 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 } 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-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/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/helpers.ts b/tests/helpers.ts index 24e102d3..59174758 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -81,6 +81,9 @@ export const MOCK_STATE_STORAGE: IStateStorage = { publishState: async () => { return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c'; }, + publishStateGeneric: async () => { + return '0xc837f95c984892dbcc3ac41812ecb145fedc26d7003202c50e1b87e226a9b33c'; + }, getGISTProof: (): Promise => { return Promise.resolve({ root: 0n, 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..da38880d 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, 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); }, From d6740c19d5f27fe5042e149b8dd5f05254ed6ee3 Mon Sep 17 00:00:00 2001 From: daveroga Date: Wed, 3 Apr 2024 02:53:29 +0200 Subject: [PATCH 16/38] update tests --- src/identity/identity-wallet.ts | 16 ++-------------- src/storage/blockchain/state.ts | 5 +++-- src/utils/did-helper.ts | 14 ++++++++++++++ tests/handlers/auth.test.ts | 29 ++++++++++++++++++++++++----- tests/identity/id.test.ts | 27 ++++++++++++++++++++++++--- tests/utils/utils.test.ts | 16 +++++++++++++++- 6 files changed, 82 insertions(+), 25 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 50d33df6..2b07564a 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -7,7 +7,6 @@ import { ClaimOptions, DID, DidMethod, - genesisFromEthAddress, getUnixTimestamp, Id, NetworkId, @@ -15,7 +14,6 @@ import { } from '@iden3/js-iden3-core'; import { poseidon, PublicKey, sha256, Signature, Hex, getRandomBytes } 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'; @@ -44,7 +42,7 @@ import { TreesModel } from '../credentials'; import { TreeState } from '../circuits'; -import { byteEncoder } from '../utils'; +import { buildDIDFromEthPubKey, byteEncoder } from '../utils'; import { Options } from '@iden3/js-jsonld-merklization'; import { TransactionReceipt } from 'ethers'; import { @@ -52,7 +50,6 @@ import { Iden3SmtRhsCredentialStatusPublisher } from '../credentials/status/credential-status-publisher'; import { ProofService } from '../proof'; -import { keccak256 } from 'js-sha3'; /** * DID creation options @@ -636,16 +633,7 @@ export class IdentityWallet implements IIdentityWallet { const keyIdEth = await this._kms.createKeyFromSeed(KmsKeyType.Secp256k1, opts.seed); const pubKeyHexEth = (await this._kms.publicKey(keyIdEth)).slice(2); // 04 + x + y (uncompressed key) - // Use Keccak-256 hash function to get public key hash - const hashOfPublicKey = keccak256(Buffer.from(pubKeyHexEth, 'hex')); - // Convert hash to buffer - const ethAddressBuffer = Buffer.from(hashOfPublicKey, 'hex'); - // 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); - const did = DID.parseFromId(identifier); + const did = buildDIDFromEthPubKey(didType, pubKeyHexEth); await this._storage.mt.createIdentityMerkleTrees(did.string()); diff --git a/src/storage/blockchain/state.ts b/src/storage/blockchain/state.ts index 8b60a0e4..22581856 100644 --- a/src/storage/blockchain/state.ts +++ b/src/storage/blockchain/state.ts @@ -1,7 +1,7 @@ import { RootInfo, StateProof } from './../entities/state'; import { ZKProof } from '@iden3/js-jwz'; import { IStateStorage, UserStateTransitionInfo } from '../interfaces/state'; -import { Contract, ContractTransaction, JsonRpcProvider, Signer, TransactionRequest } from 'ethers'; +import { Contract, JsonRpcProvider, Signer, TransactionRequest } from 'ethers'; import { StateInfo } from '../entities/state'; import { StateTransitionPubSignals } from '../../circuits'; import { byteEncoder } from '../../utils'; @@ -165,7 +165,8 @@ export class EthStateStorage implements IStateStorage { signer: Signer, userStateTranstionInfo: UserStateTransitionInfo ): Promise { - const { userId, oldUserState, newUserState, isOldStateGenesis, methodId, methodParams } = userStateTranstionInfo; + const { userId, oldUserState, newUserState, isOldStateGenesis, methodId, methodParams } = + userStateTranstionInfo; const { stateContract, provider } = this.getStateContractAndProviderForId(userId.bigInt()); const contract = stateContract.connect(signer) as Contract; const feeData = await provider.getFeeData(); diff --git a/src/utils/did-helper.ts b/src/utils/did-helper.ts index 403ef8a3..52728967 100644 --- a/src/utils/did-helper.ts +++ b/src/utils/did-helper.ts @@ -2,6 +2,7 @@ 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'; /** * Checks if state is genesis state @@ -83,3 +84,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(Buffer.from(pubKeyEth, 'hex')); + // Convert hash to buffer + const ethAddressBuffer = Buffer.from(hashOfPublicKey, 'hex'); + // 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/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 53da9759..b79e0d61 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -37,7 +37,8 @@ import { Profile, InMemoryMerkleTreeStorage, W3CCredential, - Sec256k1Provider + Sec256k1Provider, + StateInfo } from '../../src'; import { Token } from '@iden3/js-jwz'; import { Blockchain, DID, DidMethod, NetworkId } from '@iden3/js-iden3-core'; @@ -565,8 +566,26 @@ describe('auth', () => { 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); @@ -580,7 +599,7 @@ describe('auth', () => { const stateEthConfig = defaultEthConnectionConfig; stateEthConfig.url = RPC_URL; stateEthConfig.contractAddress = STATE_CONTRACT; - stateEthConfig.chainId = 80001; + stateEthConfig.chainId = 80002; // Amoy const memoryKeyStore = new InMemoryPrivateKeyStore(); const bjjProvider = new BjjProvider(KmsKeyType.BabyJubJub, memoryKeyStore); @@ -625,7 +644,7 @@ describe('auth', () => { const { did: didUser, credential: userAuthCredential } = await idWallet.createIdentity({ method: DidMethod.PolygonId, blockchain: Blockchain.Polygon, - networkId: NetworkId.Mumbai, + networkId: NetworkId.Amoy, seed: SEED_USER, revocationOpts: { type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, @@ -643,7 +662,7 @@ describe('auth', () => { const { did: didIssuer, credential: issuerAuthCredential } = await idWallet.createIdentity({ method: DidMethod.PolygonId, blockchain: Blockchain.Polygon, - networkId: NetworkId.Mumbai, + networkId: NetworkId.Amoy, seed: Buffer.from(WALLET_KEY, 'hex'), revocationOpts: { type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, @@ -651,7 +670,7 @@ describe('auth', () => { }, keyType: KmsKeyType.Secp256k1, ethSigner, - proofService: proofService + proofService }); expect(issuerAuthCredential).not.to.be.undefined; diff --git a/tests/identity/id.test.ts b/tests/identity/id.test.ts index ca98c846..3a3090f6 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, @@ -10,7 +11,9 @@ import { CredentialStatusResolverRegistry, RHSResolver, CredentialStatusType, - KmsKeyType + KmsKeyType, + ProofService, + FSCircuitStorage } from '../../src'; import { MOCK_STATE_STORAGE, @@ -18,7 +21,8 @@ import { createIdentity, RHS_URL, getInMemoryDataStorage, - registerKeyProvidersInMemoryKMS + registerKeyProvidersInMemoryKMS, + IPFS_URL } from '../helpers'; import { expect } from 'chai'; @@ -170,7 +174,24 @@ describe('identity', () => { }); it('createIdentity Secp256k1', async () => { - const { did, credential } = await createIdentity(idWallet, { keyType: KmsKeyType.Secp256k1 }); + const circuitStorage = new FSCircuitStorage({ + dirname: path.join(__dirname, '../proofs/testdata') + }); + + const proofService = new ProofService( + idWallet, + credWallet, + circuitStorage, + dataStorage.states, + { + ipfsNodeURL: IPFS_URL + } + ); + + const { did, credential } = await createIdentity(idWallet, { + keyType: KmsKeyType.Secp256k1, + proofService + }); expect(did.string()).to.equal( 'did:iden3:polygon:amoy:x6x5sor7zpxsu478u36QvEgaRUfPjmzqFo5PHHzbM' 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' + ); + }); +}); From 17a4bf186b192ba3d337d02f4c48b54083530f43 Mon Sep 17 00:00:00 2001 From: daveroga Date: Wed, 3 Apr 2024 09:58:25 +0200 Subject: [PATCH 17/38] suggested changes from review --- src/identity/identity-wallet.ts | 22 ++++++++++------------ src/storage/blockchain/state.ts | 2 +- src/utils/did-helper.ts | 5 +++-- tests/handlers/auth.test.ts | 5 +++-- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 2b07564a..cdb77331 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -44,7 +44,7 @@ import { import { TreeState } from '../circuits'; import { buildDIDFromEthPubKey, byteEncoder } from '../utils'; import { Options } from '@iden3/js-jsonld-merklization'; -import { TransactionReceipt } from 'ethers'; +import { Signer, TransactionReceipt } from 'ethers'; import { CredentialStatusPublisherRegistry, Iden3SmtRhsCredentialStatusPublisher @@ -73,7 +73,7 @@ export type IdentityCreationOptions = { }; seed?: Uint8Array; keyType?: KmsKeyType; - ethSigner?: any; + ethSigner?: Signer; proofService?: ProofService; }; @@ -112,7 +112,7 @@ 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:aloy** 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 */ @@ -438,6 +438,9 @@ export class IdentityWallet implements IIdentityWallet { opts: IdentityCreationOptions ): Promise<{ did: DID; credential: W3CCredential }> { opts.keyType = opts.keyType ?? KmsKeyType.BabyJubJub; + opts.method = opts.method ?? DidMethod.Iden3; + opts.blockchain = opts.blockchain ?? Blockchain.Polygon; + opts.networkId = opts.networkId ?? NetworkId.Amoy; switch (opts.keyType) { case KmsKeyType.BabyJubJub: @@ -545,10 +548,6 @@ export class IdentityWallet implements IIdentityWallet { 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.Aloy; opts.seed = opts.seed ?? getRandomBytes(32); await this._storage.mt.createIdentityMerkleTrees(tmpIdentifier); @@ -575,7 +574,8 @@ export class IdentityWallet implements IIdentityWallet { ZERO_HASH.bigInt() ]); - const didType = buildDIDType(opts.method, opts.blockchain, opts.networkId); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const didType = buildDIDType(opts.method!, opts.blockchain!, opts.networkId!); const identifier = Id.idGenesisFromIdenState(didType, currentState.bigInt()); const did = DID.parseFromId(identifier); @@ -619,9 +619,6 @@ export class IdentityWallet implements IIdentityWallet { private async createEthereumIdentity( opts: IdentityCreationOptions ): Promise<{ did: DID; credential: W3CCredential }> { - opts.method = opts.method ?? DidMethod.Iden3; - opts.blockchain = opts.blockchain ?? Blockchain.Polygon; - opts.networkId = opts.networkId ?? NetworkId.Aloy; opts.seed = opts.seed ?? getRandomBytes(32); const proofService = opts.proofService; @@ -629,7 +626,8 @@ export class IdentityWallet implements IIdentityWallet { const currentState = ZERO_HASH; // In Ethereum identities we don't have an initial state with the auth credential - const didType = buildDIDType(opts.method, opts.blockchain, opts.networkId); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const didType = buildDIDType(opts.method!, opts.blockchain!, opts.networkId!); const keyIdEth = await this._kms.createKeyFromSeed(KmsKeyType.Secp256k1, opts.seed); const pubKeyHexEth = (await this._kms.publicKey(keyIdEth)).slice(2); // 04 + x + y (uncompressed key) diff --git a/src/storage/blockchain/state.ts b/src/storage/blockchain/state.ts index 22581856..abc79a09 100644 --- a/src/storage/blockchain/state.ts +++ b/src/storage/blockchain/state.ts @@ -160,7 +160,7 @@ export class EthStateStorage implements IStateStorage { return txnHash; } - /** {@inheritdoc IStateStorage.publishState} */ + /** {@inheritdoc IStateStorage.publishStateGeneric} */ async publishStateGeneric( signer: Signer, userStateTranstionInfo: UserStateTransitionInfo diff --git a/src/utils/did-helper.ts b/src/utils/did-helper.ts index 52728967..81b5830b 100644 --- a/src/utils/did-helper.ts +++ b/src/utils/did-helper.ts @@ -3,6 +3,7 @@ import { Id, buildDIDType, genesisFromEthAddress, DID } from '@iden3/js-iden3-co 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 @@ -87,9 +88,9 @@ export const resolveDIDDocumentAuth = async ( export const buildDIDFromEthPubKey = (didType: Uint8Array, pubKeyEth: string): DID => { // Use Keccak-256 hash function to get public key hash - const hashOfPublicKey = keccak256(Buffer.from(pubKeyEth, 'hex')); + const hashOfPublicKey = keccak256(hexToBytes(pubKeyEth)); // Convert hash to buffer - const ethAddressBuffer = Buffer.from(hashOfPublicKey, 'hex'); + const ethAddressBuffer = hexToBytes(hashOfPublicKey); // Ethereum Address is '0x' concatenated with last 20 bytes // of the public key hash const ethAddr = ethAddressBuffer.slice(-20); diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index b79e0d61..db632571 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -38,7 +38,8 @@ import { InMemoryMerkleTreeStorage, W3CCredential, Sec256k1Provider, - StateInfo + StateInfo, + hexToBytes } from '../../src'; import { Token } from '@iden3/js-jwz'; import { Blockchain, DID, DidMethod, NetworkId } from '@iden3/js-iden3-core'; @@ -663,7 +664,7 @@ describe('auth', () => { method: DidMethod.PolygonId, blockchain: Blockchain.Polygon, networkId: NetworkId.Amoy, - seed: Buffer.from(WALLET_KEY, 'hex'), + seed: hexToBytes(WALLET_KEY), revocationOpts: { type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, id: RHS_URL From bf4d2087eb06fb23cc840a754bc9880dfb990c0b Mon Sep 17 00:00:00 2001 From: daveroga Date: Wed, 3 Apr 2024 10:04:22 +0200 Subject: [PATCH 18/38] throw error if not ethSigner --- src/identity/identity-wallet.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index cdb77331..3149c6af 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -624,6 +624,12 @@ export class IdentityWallet implements IIdentityWallet { const proofService = opts.proofService; 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 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion From f7e784dee5b8636e6666180bce959423cc176db2 Mon Sep 17 00:00:00 2001 From: daveroga Date: Wed, 3 Apr 2024 14:13:41 +0200 Subject: [PATCH 19/38] add transit state logic to identiy wallet --- src/identity/identity-wallet.ts | 129 ++++++++++++++++-- src/proof/proof-service.ts | 75 +--------- .../on-chain-revocation.test.ts | 9 +- tests/handlers/auth.test.ts | 37 ++--- tests/identity/id.test.ts | 34 +++-- tests/proofs/mtp-onchain.test.ts | 13 +- tests/proofs/mtp.test.ts | 22 +-- 7 files changed, 160 insertions(+), 159 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 3149c6af..f45962d0 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -12,12 +12,20 @@ import { NetworkId, SchemaHash } from '@iden3/js-iden3-core'; -import { poseidon, PublicKey, sha256, Signature, Hex, getRandomBytes } from '@iden3/js-crypto'; +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, @@ -41,15 +49,15 @@ import { pushHashesToRHS, TreesModel } from '../credentials'; -import { TreeState } from '../circuits'; -import { buildDIDFromEthPubKey, byteEncoder } from '../utils'; +import { CircuitId, StateTransitionInputs, TreeState } from '../circuits'; +import { buildDIDFromEthPubKey, byteEncoder, isEthereumIdentity } from '../utils'; import { Options } from '@iden3/js-jsonld-merklization'; import { Signer, TransactionReceipt } from 'ethers'; import { CredentialStatusPublisherRegistry, Iden3SmtRhsCredentialStatusPublisher } from '../credentials/status/credential-status-publisher'; -import { ProofService } from '../proof'; +import { InputGenerator, IZKProver, NativeProver } from '../proof'; /** * DID creation options @@ -74,7 +82,6 @@ export type IdentityCreationOptions = { seed?: Uint8Array; keyType?: KmsKeyType; ethSigner?: Signer; - proofService?: ProofService; }; /** @@ -376,6 +383,22 @@ 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 + ): Promise; } /** @@ -391,6 +414,8 @@ export interface IIdentityWallet { */ export class IdentityWallet implements IIdentityWallet { private readonly _credentialStatusPublisherRegistry: CredentialStatusPublisherRegistry; + private readonly _prover: IZKProver | undefined; + private readonly _inputsGenerator: InputGenerator; /** * Constructs a new instance of the `IdentityWallet` class @@ -406,9 +431,14 @@ export class IdentityWallet implements IIdentityWallet { private readonly _credentialWallet: ICredentialWallet, private readonly _opts?: { credentialStatusPublisherRegistry?: CredentialStatusPublisherRegistry; + prover?: IZKProver; } ) { this._credentialStatusPublisherRegistry = this.getCredentialStatusPublisherRegistry(_opts); + if (_opts?.prover) { + this._prover = _opts?.prover; + } + this._inputsGenerator = new InputGenerator(this, _credentialWallet, _storage.states); } private getCredentialStatusPublisherRegistry( @@ -621,7 +651,6 @@ export class IdentityWallet implements IIdentityWallet { ): Promise<{ did: DID; credential: W3CCredential }> { opts.seed = opts.seed ?? getRandomBytes(32); - const proofService = opts.proofService; const ethSigner = opts.ethSigner; if (!ethSigner) { @@ -688,12 +717,8 @@ export class IdentityWallet implements IIdentityWallet { rootOfRoots: ZERO_HASH }; - if (!proofService) { - throw new Error('Proof service is required to create Ethereum identities'); - } - // Mandatory transit state after adding auth credential in Ethereum identities - await proofService?.transitState(did, oldTreeState, true, this._storage.states, ethSigner); + await this.transitState(did, oldTreeState, true, ethSigner); await this.publishRevocationInfoByCredentialStatusType(did, opts.revocationOpts.type, { rhsUrl: opts.revocationOpts.id, @@ -1275,4 +1300,84 @@ export class IdentityWallet implements IIdentityWallet { isStateGenesis: false }); } + + /** {@inheritdoc IIdentityWallet.transitState} */ + async transitState( + did: DID, + oldTreeState: TreeState, + isOldStateGenesis: boolean, + ethSigner: Signer + ): 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 + + if (!isEthIdentity && this._prover) { + // 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 this._prover.generate(inputs, CircuitId.StateTransition); + } + + const oldUserState = oldTreeState.state; + const newUserState = newTreeState.state; + const userStateTransitionInfo: UserStateTransitionInfo = { + userId, + oldUserState, + newUserState, + isOldStateGenesis, + methodId: BigInt(1), + methodParams: '0x' + } as UserStateTransitionInfo; + + let txId; + if (!isEthIdentity) { + txId = await this._storage.states.publishState(proof, ethSigner); + } else { + txId = await this._storage.states.publishStateGeneric(ethSigner, userStateTransitionInfo); + } + await this.updateIdentityState(did, true, newTreeState); + + return txId; + } } diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts index 905feac5..8bba21e5 100644 --- a/src/proof/proof-service.ts +++ b/src/proof/proof-service.ts @@ -138,7 +138,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 */ @@ -146,7 +145,6 @@ export interface IProofService { did: DID, oldTreeState: TreeState, isOldStateGenesis: boolean, - stateStorage: IStateStorage, ethSigner: Signer ): Promise; @@ -358,80 +356,9 @@ export class ProofService implements IProofService { did: DID, oldTreeState: TreeState, isOldStateGenesis: boolean, - stateStorage: IStateStorage, ethSigner: Signer ): Promise { - 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 userId = DID.idFromDID(did); - - let proof; - const isEthIdentity = isEthereumIdentity(did); // don't generate proof for ethereum identities - - if (!isEthIdentity) { - // 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._identityWallet.signChallenge(challenge, authInfo.credential); - - const circuitInputs = new StateTransitionInputs(); - circuitInputs.id = userId; - - circuitInputs.signature = signature; - circuitInputs.isOldStateGenesis = isOldStateGenesis; - - const authClaimIncProofNewState = await this._identityWallet.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 this._prover.generate(inputs, CircuitId.StateTransition); - } - - const oldUserState = oldTreeState.state; - const newUserState = newTreeState.state; - const userStateTransitionInfo: UserStateTransitionInfo = { - userId, - oldUserState, - newUserState, - isOldStateGenesis, - methodId: BigInt(1), - methodParams: '0x' - } as UserStateTransitionInfo; - - let txId; - if (!isEthIdentity) { - txId = await stateStorage.publishState(proof, ethSigner); - } else { - txId = await stateStorage.publishStateGeneric(ethSigner, userStateTransitionInfo); - } - await this._identityWallet.updateIdentityState(did, true, newTreeState); - - return txId; + return this._identityWallet.transitState(did, oldTreeState, isOldStateGenesis, ethSigner); } private async generateInputs( diff --git a/tests/credentials/credential-statuses/on-chain-revocation.test.ts b/tests/credentials/credential-statuses/on-chain-revocation.test.ts index 736a61f3..a67dea47 100644 --- a/tests/credentials/credential-statuses/on-chain-revocation.test.ts +++ b/tests/credentials/credential-statuses/on-chain-revocation.test.ts @@ -24,7 +24,8 @@ import { CredentialStatusPublisherRegistry, Iden3OnchainSmtCredentialStatusPublisher, SDK_EVENTS, - MessageBus + MessageBus, + NativeProver } from '../../../src'; import { @@ -238,8 +239,10 @@ describe('onchain revocation checks', () => { new Iden3OnchainSmtCredentialStatusPublisher(storage) ); + const prover = new NativeProver(circuitStorage); idWallet = new IdentityWallet(registerKeyProvidersInMemoryKMS(), dataStorage, credWallet, { - credentialStatusPublisherRegistry + credentialStatusPublisherRegistry, + prover }); proofService = new ProofService(idWallet, credWallet, circuitStorage, ethStorage); }); @@ -300,7 +303,7 @@ describe('onchain revocation checks', () => { CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023 ); - await proofService.transitState(issuerDID, res.oldTreeState, true, dataStorage.states, signer); + await proofService.transitState(issuerDID, res.oldTreeState, true, signer); const [ctrHexL, rtrHexL, rorTrHexL] = [ res.newTreeState.claimsRoot.hex(), diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index db632571..0e8cb914 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -39,7 +39,8 @@ import { W3CCredential, Sec256k1Provider, StateInfo, - hexToBytes + hexToBytes, + NativeProver } from '../../src'; import { Token } from '@iden3/js-jwz'; import { Blockchain, DID, DidMethod, NetworkId } from '@iden3/js-iden3-core'; @@ -300,13 +301,7 @@ describe('auth', () => { (dataStorage.states as EthStateStorage).provider ); - const txId = await proofService.transitState( - issuerDID, - res.oldTreeState, - true, - dataStorage.states, - ethSigner - ); + const txId = await proofService.transitState(issuerDID, res.oldTreeState, true, ethSigner); const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof( issuerDID, @@ -423,8 +418,7 @@ describe('auth', () => { id: RHS_URL }, keyType: KmsKeyType.Secp256k1, - ethSigner, - proofService + ethSigner }); expect(issuerAuthCredential).not.to.be.undefined; @@ -470,13 +464,7 @@ describe('auth', () => { 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 txId = await proofService.transitState(didIssuer, res.oldTreeState, true, ethSigner); const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof( didIssuer, @@ -628,7 +616,9 @@ describe('auth', () => { new RHSResolver(dataStorage.states) ); credWallet = new CredentialWallet(dataStorage, resolvers); - idWallet = new IdentityWallet(kms, dataStorage, credWallet); + + const prover = new NativeProver(circuitStorage); + idWallet = new IdentityWallet(kms, dataStorage, credWallet, { prover }); proofService = new ProofService(idWallet, credWallet, circuitStorage, dataStorage.states, { ipfsNodeURL: IPFS_URL @@ -670,8 +660,7 @@ describe('auth', () => { id: RHS_URL }, keyType: KmsKeyType.Secp256k1, - ethSigner, - proofService + ethSigner }); expect(issuerAuthCredential).not.to.be.undefined; @@ -717,13 +706,7 @@ describe('auth', () => { 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 txId = await proofService.transitState(didIssuer, res.oldTreeState, false, ethSigner); const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof( didIssuer, diff --git a/tests/identity/id.test.ts b/tests/identity/id.test.ts index 3a3090f6..8da25090 100644 --- a/tests/identity/id.test.ts +++ b/tests/identity/id.test.ts @@ -12,8 +12,9 @@ import { RHSResolver, CredentialStatusType, KmsKeyType, - ProofService, - FSCircuitStorage + FSCircuitStorage, + EthStateStorage, + NativeProver } from '../../src'; import { MOCK_STATE_STORAGE, @@ -22,9 +23,10 @@ import { RHS_URL, getInMemoryDataStorage, registerKeyProvidersInMemoryKMS, - IPFS_URL + WALLET_KEY } from '../helpers'; import { expect } from 'chai'; +import { Wallet } from 'ethers'; describe('identity', () => { let credWallet: ICredentialWallet; @@ -63,8 +65,16 @@ describe('identity', () => { new RHSResolver(dataStorage.states) ); credWallet = new CredentialWallet(dataStorage, resolvers); - idWallet = new IdentityWallet(registerKeyProvidersInMemoryKMS(), dataStorage, credWallet); + const circuitStorage = new FSCircuitStorage({ + dirname: path.join(__dirname, '../proofs/testdata') + }); + + const prover = new NativeProver(circuitStorage); + idWallet = new IdentityWallet(registerKeyProvidersInMemoryKMS(), dataStorage, credWallet, { + prover + }); }); + it('createIdentity', async () => { const { did, credential } = await createIdentity(idWallet); @@ -174,23 +184,11 @@ describe('identity', () => { }); it('createIdentity Secp256k1', async () => { - const circuitStorage = new FSCircuitStorage({ - dirname: path.join(__dirname, '../proofs/testdata') - }); - - const proofService = new ProofService( - idWallet, - credWallet, - circuitStorage, - dataStorage.states, - { - ipfsNodeURL: IPFS_URL - } - ); + const ethSigner = new Wallet(WALLET_KEY, (dataStorage.states as EthStateStorage).provider); const { did, credential } = await createIdentity(idWallet, { keyType: KmsKeyType.Secp256k1, - proofService + ethSigner }); expect(did.string()).to.equal( diff --git a/tests/proofs/mtp-onchain.test.ts b/tests/proofs/mtp-onchain.test.ts index 28c61bf1..2057a9ef 100644 --- a/tests/proofs/mtp-onchain.test.ts +++ b/tests/proofs/mtp-onchain.test.ts @@ -18,7 +18,7 @@ import { CredentialWallet, RHSResolver } from '../../src/credentials'; -import { ProofService } from '../../src/proof'; +import { NativeProver, ProofService } from '../../src/proof'; import { CircuitId } from '../../src/circuits'; import { ethers } from 'ethers'; import { EthStateStorage } from '../../src/storage/blockchain/state'; @@ -116,7 +116,8 @@ describe('mtp onchain proofs', () => { new RHSResolver(dataStorage.states) ); credWallet = new CredentialWallet(dataStorage, resolvers); - idWallet = new IdentityWallet(kms, dataStorage, credWallet); + const prover = new NativeProver(circuitStorage); + idWallet = new IdentityWallet(kms, dataStorage, credWallet, { prover }); proofService = new ProofService(idWallet, credWallet, circuitStorage, mockStateStorage); }); @@ -190,13 +191,7 @@ describe('mtp onchain proofs', () => { (dataStorage.states as EthStateStorage).provider ); - const txId = await proofService.transitState( - issuerDID, - res.oldTreeState, - true, - dataStorage.states, - ethSigner - ); + const txId = await proofService.transitState(issuerDID, res.oldTreeState, true, ethSigner); const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof( issuerDID, diff --git a/tests/proofs/mtp.test.ts b/tests/proofs/mtp.test.ts index da38880d..4062203b 100644 --- a/tests/proofs/mtp.test.ts +++ b/tests/proofs/mtp.test.ts @@ -13,7 +13,7 @@ import { InMemoryPrivateKeyStore } from '../../src/kms/store'; import { IDataStorage, IStateStorage } from '../../src/storage/interfaces'; import { InMemoryDataSource, InMemoryMerkleTreeStorage } from '../../src/storage/memory'; import { CredentialRequest, CredentialWallet } from '../../src/credentials'; -import { ProofService } from '../../src/proof'; +import { NativeProver, ProofService } from '../../src/proof'; import { CircuitId } from '../../src/circuits'; import { ethers } from 'ethers'; import { EthStateStorage } from '../../src/storage/blockchain/state'; @@ -116,7 +116,9 @@ describe('mtp proofs', () => { new RHSResolver(dataStorage.states) ); credWallet = new CredentialWallet(dataStorage, resolvers); - idWallet = new IdentityWallet(kms, dataStorage, credWallet); + + const prover = new NativeProver(circuitStorage); + idWallet = new IdentityWallet(kms, dataStorage, credWallet, { prover }); proofService = new ProofService(idWallet, credWallet, circuitStorage, mockStateStorage); }); @@ -166,13 +168,7 @@ describe('mtp proofs', () => { walletKey, (dataStorage.states as EthStateStorage).provider ); - const txId = await proofService.transitState( - issuerDID, - res.oldTreeState, - true, - dataStorage.states, - ethSigner - ); + const txId = await proofService.transitState(issuerDID, res.oldTreeState, true, ethSigner); const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof( issuerDID, @@ -257,13 +253,7 @@ describe('mtp proofs', () => { (dataStorage.states as EthStateStorage).provider ); - const txId = await proofService.transitState( - issuerDID, - res.oldTreeState, - true, - dataStorage.states, - ethSigner - ); + const txId = await proofService.transitState(issuerDID, res.oldTreeState, true, ethSigner); const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof( issuerDID, From 881937445b1f0ff1342c57981214e7c8d6685672 Mon Sep 17 00:00:00 2001 From: daveroga Date: Wed, 3 Apr 2024 23:46:59 +0200 Subject: [PATCH 20/38] typo and make new function to add bjj key and transit state --- src/identity/identity-wallet.ts | 135 ++++++++++++++++++++------------ src/storage/blockchain/state.ts | 4 +- 2 files changed, 86 insertions(+), 53 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index f45962d0..60873c7a 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -82,6 +82,7 @@ export type IdentityCreationOptions = { seed?: Uint8Array; keyType?: KmsKeyType; ethSigner?: Signer; + createBjjCredential?: boolean; }; /** @@ -124,7 +125,9 @@ export interface IIdentityWallet { * @public */ - createIdentity(opts: IdentityCreationOptions): Promise<{ did: DID; credential: W3CCredential }>; + createIdentity( + opts: IdentityCreationOptions + ): Promise<{ did: DID; credential: W3CCredential | undefined }>; /** * Creates profile based on genesis identifier @@ -399,6 +402,20 @@ export interface IIdentityWallet { isOldStateGenesis: boolean, ethSigner: Signer ): Promise; + + /** + * Add BJJ credential and transit state + * + * @param {DID} did - identifier of the user + * @param {TreeState} oldTreeState - old tree state of the user + * @param {Signer} ethSigner - signer to sign the transaction + */ + addBjjCredentialAndTransitState( + did: DID, + oldTreeState: TreeState, + ethSigner: Signer, + opts?: object + ): Promise; } /** @@ -466,7 +483,7 @@ export class IdentityWallet implements IIdentityWallet { */ async createIdentity( opts: IdentityCreationOptions - ): Promise<{ did: DID; credential: W3CCredential }> { + ): Promise<{ did: DID; credential: W3CCredential | undefined }> { opts.keyType = opts.keyType ?? KmsKeyType.BabyJubJub; opts.method = opts.method ?? DidMethod.Iden3; opts.blockchain = opts.blockchain ?? Blockchain.Polygon; @@ -648,9 +665,11 @@ export class IdentityWallet implements IIdentityWallet { */ private async createEthereumIdentity( opts: IdentityCreationOptions - ): Promise<{ did: DID; credential: W3CCredential }> { + ): 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) { @@ -677,55 +696,17 @@ export class IdentityWallet implements IIdentityWallet { isStateGenesis: true }); - // Add Auth BJJ credential after saving identity for Ethereum identities - const { authClaim, pubKey } = await this.createAuthCoreClaim( - opts.revocationOpts.nonce ?? 0, - opts.seed - ); - - await this._storage.mt.addToMerkleTree( - did.string(), - MerkleTreeType.Claims, - authClaim.hiHv().hi, - authClaim.hiHv().hv - ); - - const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType( - did.string(), - MerkleTreeType.Claims - ); - - const stateAuthClaim = hashElems([ - (await claimsTree.root()).bigInt(), - ZERO_HASH.bigInt(), - ZERO_HASH.bigInt() - ]); - - const credential = await this.createAuthBJJCredential( - did, - pubKey, - authClaim, - stateAuthClaim, - opts.revocationOpts - ); - - // Old tree state genesis state - const oldTreeState: TreeState = { - revocationRoot: ZERO_HASH, - claimsRoot: ZERO_HASH, - state: currentState, - rootOfRoots: ZERO_HASH - }; - - // Mandatory transit state after adding auth credential in Ethereum identities - await this.transitState(did, oldTreeState, true, ethSigner); - - await this.publishRevocationInfoByCredentialStatusType(did, opts.revocationOpts.type, { - rhsUrl: opts.revocationOpts.id, - onChain: opts.revocationOpts.onChain - }); + if (opts.createBjjCredential) { + // Old tree state genesis state + const oldTreeState: TreeState = { + revocationRoot: ZERO_HASH, + claimsRoot: ZERO_HASH, + state: currentState, + rootOfRoots: ZERO_HASH + }; - await this._credentialWallet.save(credential); + credential = await this.addBjjCredentialAndTransitState(did, oldTreeState, ethSigner, opts); + } return { did, @@ -1380,4 +1361,56 @@ export class IdentityWallet implements IIdentityWallet { return txId; } + + /** {@inheritdoc IIdentityWallet.addBjjCredentialAndTransitState} */ + async addBjjCredentialAndTransitState( + did: DID, + oldTreeState: TreeState, + ethSigner: Signer, + opts: IdentityCreationOptions + ): Promise { + opts.seed = opts.seed ?? getRandomBytes(32); + // Add Auth BJJ credential after saving identity for Ethereum identities + const { authClaim, pubKey } = await this.createAuthCoreClaim( + opts.revocationOpts.nonce ?? 0, + opts.seed + ); + + await this._storage.mt.addToMerkleTree( + did.string(), + MerkleTreeType.Claims, + authClaim.hiHv().hi, + authClaim.hiHv().hv + ); + + const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType( + did.string(), + MerkleTreeType.Claims + ); + + const stateAuthClaim = hashElems([ + (await claimsTree.root()).bigInt(), + oldTreeState.revocationRoot.bigInt(), + oldTreeState.rootOfRoots.bigInt() + ]); + + const credential = await this.createAuthBJJCredential( + did, + pubKey, + authClaim, + stateAuthClaim, + opts.revocationOpts + ); + + // Mandatory transit state after adding auth credential in Ethereum identities + await this.transitState(did, oldTreeState, true, ethSigner); + + await this.publishRevocationInfoByCredentialStatusType(did, opts.revocationOpts.type, { + rhsUrl: opts.revocationOpts.id, + onChain: opts.revocationOpts.onChain + }); + + await this._credentialWallet.save(credential); + return credential; + } } diff --git a/src/storage/blockchain/state.ts b/src/storage/blockchain/state.ts index abc79a09..e589abdf 100644 --- a/src/storage/blockchain/state.ts +++ b/src/storage/blockchain/state.ts @@ -163,10 +163,10 @@ export class EthStateStorage implements IStateStorage { /** {@inheritdoc IStateStorage.publishStateGeneric} */ async publishStateGeneric( signer: Signer, - userStateTranstionInfo: UserStateTransitionInfo + userStateTransitionInfo: UserStateTransitionInfo ): Promise { const { userId, oldUserState, newUserState, isOldStateGenesis, methodId, methodParams } = - userStateTranstionInfo; + userStateTransitionInfo; const { stateContract, provider } = this.getStateContractAndProviderForId(userId.bigInt()); const contract = stateContract.connect(signer) as Contract; const feeData = await provider.getFeeData(); From 42c2c25fd73a2b9a9a76dbaac9cbc22c3e23863a Mon Sep 17 00:00:00 2001 From: daveroga Date: Thu, 4 Apr 2024 10:46:58 +0200 Subject: [PATCH 21/38] fix sec256k1 private key generation less than 32 bytes --- src/kms/key-providers/sec256k1-provider.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/kms/key-providers/sec256k1-provider.ts b/src/kms/key-providers/sec256k1-provider.ts index 07c20f18..72ed2fc2 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)) // 04 + x + y (uncompressed key) }; - 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; } From 4a1030ad2fbab669ee337703a4c869e7edde4a10 Mon Sep 17 00:00:00 2001 From: daveroga Date: Thu, 4 Apr 2024 14:32:09 +0200 Subject: [PATCH 22/38] updates from reviewed comments --- src/identity/identity-wallet.ts | 51 +++++++++++++++------------ src/proof/proof-service.ts | 16 ++------- src/proof/provers/inputs-generator.ts | 2 +- src/storage/blockchain/state.ts | 42 +++++++++++----------- tests/handlers/auth.test.ts | 48 ++++++++++++------------- tests/helpers.ts | 17 +++++++++ tests/identity/id.test.ts | 11 +++--- 7 files changed, 101 insertions(+), 86 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 60873c7a..14d0ee2b 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -57,7 +57,7 @@ import { CredentialStatusPublisherRegistry, Iden3SmtRhsCredentialStatusPublisher } from '../credentials/status/credential-status-publisher'; -import { InputGenerator, IZKProver, NativeProver } from '../proof'; +import { InputGenerator, IZKProver } from '../proof'; /** * DID creation options @@ -80,7 +80,6 @@ export type IdentityCreationOptions = { }; }; seed?: Uint8Array; - keyType?: KmsKeyType; ethSigner?: Signer; createBjjCredential?: boolean; }; @@ -124,8 +123,16 @@ export interface IIdentityWallet { * @returns `Promise<{ did: DID; credential: W3CCredential }>` - returns did and Auth BJJ credential * @public */ + createIdentity(opts: IdentityCreationOptions): Promise<{ did: DID; credential: W3CCredential }>; - createIdentity( + /** + * 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: IdentityCreationOptions ): Promise<{ did: DID; credential: W3CCredential | undefined }>; @@ -410,7 +417,7 @@ export interface IIdentityWallet { * @param {TreeState} oldTreeState - old tree state of the user * @param {Signer} ethSigner - signer to sign the transaction */ - addBjjCredentialAndTransitState( + addBJJAuthjjCredential( did: DID, oldTreeState: TreeState, ethSigner: Signer, @@ -481,9 +488,9 @@ export class IdentityWallet implements IIdentityWallet { /** * {@inheritDoc IIdentityWallet.createIdentity} */ - async createIdentity( + /* async createIdentity( opts: IdentityCreationOptions - ): Promise<{ did: DID; credential: W3CCredential | undefined }> { + ): Promise<{ did: DID; credential: W3CCredential }> { opts.keyType = opts.keyType ?? KmsKeyType.BabyJubJub; opts.method = opts.method ?? DidMethod.Iden3; opts.blockchain = opts.blockchain ?? Blockchain.Polygon; @@ -497,7 +504,7 @@ export class IdentityWallet implements IIdentityWallet { default: throw new Error(`Invalid KmsKeyType ${opts.keyType}`); } - } + } */ private async createAuthCoreClaim( revNonce: number, @@ -585,15 +592,15 @@ export class IdentityWallet implements IIdentityWallet { } /** - * - * creates Baby JubJub based identity - * - * @param {IdentityCreationOptions} opts - options for the creation of the identity - * @returns `{did,credential>}` - returns did and Auth BJJ credential + * {@inheritDoc IIdentityWallet.createIdentity} */ - private async createBabyJubJubIdentity( + async createIdentity( opts: IdentityCreationOptions ): Promise<{ did: DID; credential: W3CCredential }> { + opts.method = opts.method ?? DidMethod.Iden3; + opts.blockchain = opts.blockchain ?? Blockchain.Polygon; + opts.networkId = opts.networkId ?? NetworkId.Amoy; + const tmpIdentifier = opts.seed ? uuid.v5(Hex.encode(sha256(opts.seed)), uuid.NIL) : uuid.v4(); opts.seed = opts.seed ?? getRandomBytes(32); @@ -657,15 +664,15 @@ export class IdentityWallet implements IIdentityWallet { } /** - * - * creates Ethereum based identity - * - * @param {IdentityCreationOptions} opts - options for the creation of the identity - * @returns `{did,credential>}` - returns did and Auth BJJ credential + * {@inheritDoc IIdentityWallet.createEthereumBasedIdentity} */ - private async createEthereumIdentity( + async createEthereumBasedIdentity( opts: IdentityCreationOptions ): Promise<{ did: DID; credential: W3CCredential | undefined }> { + opts.method = opts.method ?? DidMethod.Iden3; + opts.blockchain = opts.blockchain ?? Blockchain.Polygon; + opts.networkId = opts.networkId ?? NetworkId.Amoy; + opts.seed = opts.seed ?? getRandomBytes(32); opts.createBjjCredential = opts.createBjjCredential ?? true; @@ -705,7 +712,7 @@ export class IdentityWallet implements IIdentityWallet { rootOfRoots: ZERO_HASH }; - credential = await this.addBjjCredentialAndTransitState(did, oldTreeState, ethSigner, opts); + credential = await this.addBJJAuthjjCredential(did, oldTreeState, ethSigner, opts); } return { @@ -1362,8 +1369,8 @@ export class IdentityWallet implements IIdentityWallet { return txId; } - /** {@inheritdoc IIdentityWallet.addBjjCredentialAndTransitState} */ - async addBjjCredentialAndTransitState( + /** {@inheritdoc IIdentityWallet.addBJJAuthjjCredential} */ + async addBJJAuthjjCredential( did: DID, oldTreeState: TreeState, ethSigner: Signer, diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts index 8bba21e5..a21875d1 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, Id, MerklizedRootPosition } from '@iden3/js-iden3-core'; +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 { @@ -33,7 +24,7 @@ import { ZKProof } from '@iden3/js-jwz'; import { Signer } from 'ethers'; import { JSONObject, ZeroKnowledgeProofRequest, ZeroKnowledgeProofResponse } from '../iden3comm'; import { cacheLoader } from '../schema-processor'; -import { ICircuitStorage, IStateStorage, UserStateTransitionInfo } from '../storage'; +import { ICircuitStorage, IStateStorage } from '../storage'; import { byteDecoder, byteEncoder } from '../utils/encoding'; import { InputGenerator, @@ -42,7 +33,6 @@ import { } from './provers/inputs-generator'; import { PubSignalsVerifier, VerifyContext } from './verifiers/pub-signals-verifier'; import { VerifyOpts } from './verifiers'; -import { isEthereumIdentity } from '../utils'; export interface QueryWithFieldName { query: Query; diff --git a/src/proof/provers/inputs-generator.ts b/src/proof/provers/inputs-generator.ts index d026031d..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, diff --git a/src/storage/blockchain/state.ts b/src/storage/blockchain/state.ts index e589abdf..5e55c39b 100644 --- a/src/storage/blockchain/state.ts +++ b/src/storage/blockchain/state.ts @@ -144,18 +144,8 @@ 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; - - if (!status) { - throw new Error(`transaction: ${txnHash} failed to mined`); - } + const txnHash: string = await this.sendTransactionRequest(signer, request); return txnHash; } @@ -196,17 +186,8 @@ 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; - if (!status) { - throw new Error(`transaction: ${txnHash} failed to mined`); - } + const txnHash: string = await this.sendTransactionRequest(signer, request); return txnHash; } @@ -278,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/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 0e8cb914..77e0b74a 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -408,18 +408,18 @@ describe('auth', () => { (dataStorage.states as EthStateStorage).provider ); - const { did: didIssuer, credential: issuerAuthCredential } = await idWallet.createIdentity({ - method: DidMethod.PolygonId, - blockchain: Blockchain.Polygon, - networkId: NetworkId.Mumbai, - seed: SEED_ISSUER, - revocationOpts: { - type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, - id: RHS_URL - }, - keyType: KmsKeyType.Secp256k1, - ethSigner - }); + 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()); @@ -650,18 +650,18 @@ describe('auth', () => { (dataStorage.states as EthStateStorage).provider ); - const { did: didIssuer, credential: issuerAuthCredential } = await idWallet.createIdentity({ - method: DidMethod.PolygonId, - blockchain: Blockchain.Polygon, - networkId: NetworkId.Amoy, - seed: hexToBytes(WALLET_KEY), - revocationOpts: { - type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, - id: RHS_URL - }, - keyType: KmsKeyType.Secp256k1, - ethSigner - }); + 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()); diff --git a/tests/helpers.ts b/tests/helpers.ts index 59174758..e361ed4f 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -60,6 +60,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); diff --git a/tests/identity/id.test.ts b/tests/identity/id.test.ts index 8da25090..7d0f1166 100644 --- a/tests/identity/id.test.ts +++ b/tests/identity/id.test.ts @@ -11,7 +11,6 @@ import { CredentialStatusResolverRegistry, RHSResolver, CredentialStatusType, - KmsKeyType, FSCircuitStorage, EthStateStorage, NativeProver @@ -23,7 +22,8 @@ import { RHS_URL, getInMemoryDataStorage, registerKeyProvidersInMemoryKMS, - WALLET_KEY + WALLET_KEY, + createEthereumBasedIdentity } from '../helpers'; import { expect } from 'chai'; import { Wallet } from 'ethers'; @@ -186,15 +186,16 @@ describe('identity', () => { it('createIdentity Secp256k1', async () => { const ethSigner = new Wallet(WALLET_KEY, (dataStorage.states as EthStateStorage).provider); - const { did, credential } = await createIdentity(idWallet, { - keyType: KmsKeyType.Secp256k1, + const { did, credential } = await createEthereumBasedIdentity(idWallet, { ethSigner }); expect(did.string()).to.equal( 'did:iden3:polygon:amoy:x6x5sor7zpxsu478u36QvEgaRUfPjmzqFo5PHHzbM' ); - const dbCred = await dataStorage.credential.findCredentialById(credential.id); + + // 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( From 48e126a3f7c572ee8044e553e07b55281b66816d Mon Sep 17 00:00:00 2001 From: daveroga Date: Fri, 5 Apr 2024 10:11:18 +0200 Subject: [PATCH 23/38] updates from review comments --- src/identity/identity-wallet.ts | 99 ++++++++-------------- src/kms/key-providers/sec256k1-provider.ts | 2 +- src/utils/did-helper.ts | 4 +- 3 files changed, 37 insertions(+), 68 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 14d0ee2b..5e1f9593 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -331,7 +331,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; @@ -340,7 +340,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 }>; @@ -349,7 +349,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; @@ -416,6 +416,7 @@ export interface IIdentityWallet { * @param {DID} did - identifier of the user * @param {TreeState} oldTreeState - old tree state of the user * @param {Signer} ethSigner - signer to sign the transaction + * @param {object} opts - additional options */ addBJJAuthjjCredential( did: DID, @@ -485,27 +486,6 @@ export class IdentityWallet implements IIdentityWallet { } } - /** - * {@inheritDoc IIdentityWallet.createIdentity} - */ - /* async createIdentity( - opts: IdentityCreationOptions - ): Promise<{ did: DID; credential: W3CCredential }> { - opts.keyType = opts.keyType ?? KmsKeyType.BabyJubJub; - opts.method = opts.method ?? DidMethod.Iden3; - opts.blockchain = opts.blockchain ?? Blockchain.Polygon; - opts.networkId = opts.networkId ?? NetworkId.Amoy; - - switch (opts.keyType) { - case KmsKeyType.BabyJubJub: - return this.createBabyJubJubIdentity(opts); - case KmsKeyType.Secp256k1: - return this.createEthereumIdentity(opts); - default: - throw new Error(`Invalid KmsKeyType ${opts.keyType}`); - } - } */ - private async createAuthCoreClaim( revNonce: number, seed: Uint8Array @@ -597,10 +577,6 @@ export class IdentityWallet implements IIdentityWallet { async createIdentity( opts: IdentityCreationOptions ): Promise<{ did: DID; credential: W3CCredential }> { - opts.method = opts.method ?? DidMethod.Iden3; - opts.blockchain = opts.blockchain ?? Blockchain.Polygon; - opts.networkId = opts.networkId ?? NetworkId.Amoy; - const tmpIdentifier = opts.seed ? uuid.v5(Hex.encode(sha256(opts.seed)), uuid.NIL) : uuid.v4(); opts.seed = opts.seed ?? getRandomBytes(32); @@ -610,12 +586,8 @@ export class IdentityWallet implements IIdentityWallet { const { authClaim, pubKey } = await this.createAuthCoreClaim(revNonce, opts.seed); - await this._storage.mt.addToMerkleTree( - tmpIdentifier, - MerkleTreeType.Claims, - authClaim.hiHv().hi, - authClaim.hiHv().hv - ); + const { hi, hv } = authClaim.hiHv(); + await this._storage.mt.addToMerkleTree(tmpIdentifier, MerkleTreeType.Claims, hi, hv); const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType( tmpIdentifier, @@ -628,8 +600,11 @@ export class IdentityWallet implements IIdentityWallet { ZERO_HASH.bigInt() ]); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const didType = buildDIDType(opts.method!, opts.blockchain!, opts.networkId!); + 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); @@ -669,10 +644,6 @@ export class IdentityWallet implements IIdentityWallet { async createEthereumBasedIdentity( opts: IdentityCreationOptions ): Promise<{ did: DID; credential: W3CCredential | undefined }> { - opts.method = opts.method ?? DidMethod.Iden3; - opts.blockchain = opts.blockchain ?? Blockchain.Polygon; - opts.networkId = opts.networkId ?? NetworkId.Amoy; - opts.seed = opts.seed ?? getRandomBytes(32); opts.createBjjCredential = opts.createBjjCredential ?? true; @@ -687,8 +658,11 @@ export class IdentityWallet implements IIdentityWallet { const currentState = ZERO_HASH; // In Ethereum identities we don't have an initial state with the auth credential - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const didType = buildDIDType(opts.method!, opts.blockchain!, opts.networkId!); + 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) @@ -1313,7 +1287,11 @@ export class IdentityWallet implements IIdentityWallet { let proof; const isEthIdentity = isEthereumIdentity(did); // don't generate proof for ethereum identities - if (!isEthIdentity && this._prover) { + let txId; + if (!isEthIdentity) { + if (!this._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()]); @@ -1345,23 +1323,19 @@ export class IdentityWallet implements IIdentityWallet { const inputs = circuitInputs.inputsMarshal(); proof = await this._prover.generate(inputs, CircuitId.StateTransition); - } - - const oldUserState = oldTreeState.state; - const newUserState = newTreeState.state; - const userStateTransitionInfo: UserStateTransitionInfo = { - userId, - oldUserState, - newUserState, - isOldStateGenesis, - methodId: BigInt(1), - methodParams: '0x' - } as UserStateTransitionInfo; - let txId; - if (!isEthIdentity) { 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); @@ -1377,18 +1351,14 @@ export class IdentityWallet implements IIdentityWallet { opts: IdentityCreationOptions ): Promise { opts.seed = opts.seed ?? getRandomBytes(32); - // Add Auth BJJ credential after saving identity for Ethereum identities + const { authClaim, pubKey } = await this.createAuthCoreClaim( opts.revocationOpts.nonce ?? 0, opts.seed ); - await this._storage.mt.addToMerkleTree( - did.string(), - MerkleTreeType.Claims, - authClaim.hiHv().hi, - authClaim.hiHv().hv - ); + const { hi, hv } = authClaim.hiHv(); + await this._storage.mt.addToMerkleTree(did.string(), MerkleTreeType.Claims, hi, hv); const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType( did.string(), @@ -1409,7 +1379,6 @@ export class IdentityWallet implements IIdentityWallet { opts.revocationOpts ); - // Mandatory transit state after adding auth credential in Ethereum identities await this.transitState(did, oldTreeState, true, ethSigner); await this.publishRevocationInfoByCredentialStatusType(did, opts.revocationOpts.type, { diff --git a/src/kms/key-providers/sec256k1-provider.ts b/src/kms/key-providers/sec256k1-provider.ts index 72ed2fc2..a7693a8d 100644 --- a/src/kms/key-providers/sec256k1-provider.ts +++ b/src/kms/key-providers/sec256k1-provider.ts @@ -42,7 +42,7 @@ export class Sec256k1Provider implements IKeyProvider { const keyPair = this._ec.keyFromPrivate(seed); const kmsId = { type: this.keyType, - id: providerHelpers.keyPath(this.keyType, keyPair.getPublic().encode('hex', false)) // 04 + x + y (uncompressed key) + id: providerHelpers.keyPath(this.keyType, keyPair.getPublic().encode('hex', false)) }; await this._keyStore.importKey({ alias: kmsId.id, diff --git a/src/utils/did-helper.ts b/src/utils/did-helper.ts index 81b5830b..2bb3eb9f 100644 --- a/src/utils/did-helper.ts +++ b/src/utils/did-helper.ts @@ -8,7 +8,7 @@ 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 */ @@ -27,7 +27,7 @@ export function isGenesisState(did: DID, state: bigint | string): boolean { /** * Checks if DID is an ethereum identity * - * @param {string} did - did + * @param {DID} did - did * @returns boolean */ export function isEthereumIdentity(did: DID): boolean { From 368fdf89ca8d4cb24286d567d9d6ed772a4a159d Mon Sep 17 00:00:00 2001 From: daveroga Date: Fri, 5 Apr 2024 11:24:29 +0200 Subject: [PATCH 24/38] updates from review --- src/identity/identity-wallet.ts | 15 +++++--- src/proof/proof-service.ts | 2 ++ .../on-chain-revocation.test.ts | 2 +- tests/handlers/auth.test.ts | 35 +++++++++++++++---- tests/identity/id.test.ts | 4 ++- tests/proofs/mtp-onchain.test.ts | 8 ++++- tests/proofs/mtp.test.ts | 16 +++++++-- 7 files changed, 66 insertions(+), 16 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 5e1f9593..64dd6808 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -80,8 +80,10 @@ export type IdentityCreationOptions = { }; }; seed?: Uint8Array; - ethSigner?: Signer; - createBjjCredential?: boolean; + ethereumBasedIdentityOpts?: { + ethSigner?: Signer; + createBjjCredential?: boolean; + }; }; /** @@ -645,10 +647,13 @@ export class IdentityWallet implements IIdentityWallet { opts: IdentityCreationOptions ): Promise<{ did: DID; credential: W3CCredential | undefined }> { opts.seed = opts.seed ?? getRandomBytes(32); - opts.createBjjCredential = opts.createBjjCredential ?? true; + if (opts.ethereumBasedIdentityOpts) { + opts.ethereumBasedIdentityOpts.createBjjCredential = + opts.ethereumBasedIdentityOpts?.createBjjCredential ?? true; + } let credential; - const ethSigner = opts.ethSigner; + const ethSigner = opts.ethereumBasedIdentityOpts?.ethSigner; if (!ethSigner) { throw new Error( @@ -677,7 +682,7 @@ export class IdentityWallet implements IIdentityWallet { isStateGenesis: true }); - if (opts.createBjjCredential) { + if (opts.ethereumBasedIdentityOpts?.createBjjCredential) { // Old tree state genesis state const oldTreeState: TreeState = { revocationRoot: ZERO_HASH, diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts index a21875d1..f1d9bca2 100644 --- a/src/proof/proof-service.ts +++ b/src/proof/proof-service.ts @@ -135,6 +135,7 @@ export interface IProofService { did: DID, oldTreeState: TreeState, isOldStateGenesis: boolean, + stateStorage: IStateStorage, ethSigner: Signer ): Promise; @@ -346,6 +347,7 @@ export class ProofService implements IProofService { did: DID, oldTreeState: TreeState, isOldStateGenesis: boolean, + stateStorage: IStateStorage, // for compatibility with previous versions we leave this parameter ethSigner: Signer ): Promise { return this._identityWallet.transitState(did, oldTreeState, isOldStateGenesis, ethSigner); diff --git a/tests/credentials/credential-statuses/on-chain-revocation.test.ts b/tests/credentials/credential-statuses/on-chain-revocation.test.ts index a67dea47..38568b06 100644 --- a/tests/credentials/credential-statuses/on-chain-revocation.test.ts +++ b/tests/credentials/credential-statuses/on-chain-revocation.test.ts @@ -303,7 +303,7 @@ describe('onchain revocation checks', () => { CredentialStatusType.Iden3OnchainSparseMerkleTreeProof2023 ); - await proofService.transitState(issuerDID, res.oldTreeState, true, signer); + await proofService.transitState(issuerDID, res.oldTreeState, true, dataStorage.states, signer); const [ctrHexL, rtrHexL, rorTrHexL] = [ res.newTreeState.claimsRoot.hex(), diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 77e0b74a..8d34aa4b 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -88,7 +88,8 @@ describe('auth', () => { new RHSResolver(dataStorage.states) ); credWallet = new CredentialWallet(dataStorage, resolvers); - idWallet = new IdentityWallet(kms, dataStorage, credWallet); + const prover = new NativeProver(circuitStorage); + idWallet = new IdentityWallet(kms, dataStorage, credWallet, { prover }); proofService = new ProofService(idWallet, credWallet, circuitStorage, MOCK_STATE_STORAGE, { ipfsNodeURL: IPFS_URL @@ -301,7 +302,13 @@ describe('auth', () => { (dataStorage.states as EthStateStorage).provider ); - const txId = await proofService.transitState(issuerDID, res.oldTreeState, true, ethSigner); + const txId = await proofService.transitState( + issuerDID, + res.oldTreeState, + true, + dataStorage.states, + ethSigner + ); const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof( issuerDID, @@ -418,7 +425,9 @@ describe('auth', () => { type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, id: RHS_URL }, - ethSigner + ethereumBasedIdentityOpts: { + ethSigner + } }); expect(issuerAuthCredential).not.to.be.undefined; @@ -464,7 +473,13 @@ describe('auth', () => { const res = await idWallet.addCredentialsToMerkleTree([employeeCred], didIssuer); await idWallet.publishStateToRHS(didIssuer, RHS_URL); - const txId = await proofService.transitState(didIssuer, res.oldTreeState, true, ethSigner); + const txId = await proofService.transitState( + didIssuer, + res.oldTreeState, + true, + dataStorage.states, + ethSigner + ); const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof( didIssuer, @@ -660,7 +675,9 @@ describe('auth', () => { type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, id: RHS_URL }, - ethSigner + ethereumBasedIdentityOpts: { + ethSigner + } }); expect(issuerAuthCredential).not.to.be.undefined; @@ -706,7 +723,13 @@ describe('auth', () => { const res = await idWallet.addCredentialsToMerkleTree([employeeCred], didIssuer); await idWallet.publishStateToRHS(didIssuer, RHS_URL); - const txId = await proofService.transitState(didIssuer, res.oldTreeState, false, ethSigner); + const txId = await proofService.transitState( + didIssuer, + res.oldTreeState, + false, + dataStorage.states, + ethSigner + ); const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof( didIssuer, diff --git a/tests/identity/id.test.ts b/tests/identity/id.test.ts index 7d0f1166..ca0e5022 100644 --- a/tests/identity/id.test.ts +++ b/tests/identity/id.test.ts @@ -187,7 +187,9 @@ describe('identity', () => { const ethSigner = new Wallet(WALLET_KEY, (dataStorage.states as EthStateStorage).provider); const { did, credential } = await createEthereumBasedIdentity(idWallet, { - ethSigner + ethereumBasedIdentityOpts: { + ethSigner + } }); expect(did.string()).to.equal( diff --git a/tests/proofs/mtp-onchain.test.ts b/tests/proofs/mtp-onchain.test.ts index 2057a9ef..fce4db13 100644 --- a/tests/proofs/mtp-onchain.test.ts +++ b/tests/proofs/mtp-onchain.test.ts @@ -191,7 +191,13 @@ describe('mtp onchain proofs', () => { (dataStorage.states as EthStateStorage).provider ); - const txId = await proofService.transitState(issuerDID, res.oldTreeState, true, ethSigner); + const txId = await proofService.transitState( + issuerDID, + res.oldTreeState, + true, + dataStorage.states, + ethSigner + ); const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof( issuerDID, diff --git a/tests/proofs/mtp.test.ts b/tests/proofs/mtp.test.ts index 4062203b..4b51ca43 100644 --- a/tests/proofs/mtp.test.ts +++ b/tests/proofs/mtp.test.ts @@ -168,7 +168,13 @@ describe('mtp proofs', () => { walletKey, (dataStorage.states as EthStateStorage).provider ); - const txId = await proofService.transitState(issuerDID, res.oldTreeState, true, ethSigner); + const txId = await proofService.transitState( + issuerDID, + res.oldTreeState, + true, + dataStorage.states, + ethSigner + ); const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof( issuerDID, @@ -253,7 +259,13 @@ describe('mtp proofs', () => { (dataStorage.states as EthStateStorage).provider ); - const txId = await proofService.transitState(issuerDID, res.oldTreeState, true, ethSigner); + const txId = await proofService.transitState( + issuerDID, + res.oldTreeState, + true, + dataStorage.states, + ethSigner + ); const credsWithIden3MTPProof = await idWallet.generateIden3SparseMerkleTreeProof( issuerDID, From a19871a4dd5250dd8ae6764aa6539f00ecb1c053 Mon Sep 17 00:00:00 2001 From: daveroga Date: Fri, 5 Apr 2024 13:35:01 +0200 Subject: [PATCH 25/38] update prover parameter in identity wallet --- src/identity/identity-wallet.ts | 28 +++++++++---------- src/proof/proof-service.ts | 8 +++++- .../on-chain-revocation.test.ts | 4 +-- tests/handlers/auth.test.ts | 6 ++-- tests/identity/id.test.ts | 9 +----- tests/proofs/mtp-onchain.test.ts | 3 +- tests/proofs/mtp.test.ts | 3 +- 7 files changed, 26 insertions(+), 35 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 64dd6808..ae340ea6 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -409,7 +409,8 @@ export interface IIdentityWallet { did: DID, oldTreeState: TreeState, isOldStateGenesis: boolean, - ethSigner: Signer + ethSigner: Signer, + prover?: IZKProver ): Promise; /** @@ -420,7 +421,7 @@ export interface IIdentityWallet { * @param {Signer} ethSigner - signer to sign the transaction * @param {object} opts - additional options */ - addBJJAuthjjCredential( + addBJJAuthCredential( did: DID, oldTreeState: TreeState, ethSigner: Signer, @@ -441,7 +442,6 @@ export interface IIdentityWallet { */ export class IdentityWallet implements IIdentityWallet { private readonly _credentialStatusPublisherRegistry: CredentialStatusPublisherRegistry; - private readonly _prover: IZKProver | undefined; private readonly _inputsGenerator: InputGenerator; /** @@ -458,13 +458,9 @@ export class IdentityWallet implements IIdentityWallet { private readonly _credentialWallet: ICredentialWallet, private readonly _opts?: { credentialStatusPublisherRegistry?: CredentialStatusPublisherRegistry; - prover?: IZKProver; } ) { this._credentialStatusPublisherRegistry = this.getCredentialStatusPublisherRegistry(_opts); - if (_opts?.prover) { - this._prover = _opts?.prover; - } this._inputsGenerator = new InputGenerator(this, _credentialWallet, _storage.states); } @@ -691,7 +687,7 @@ export class IdentityWallet implements IIdentityWallet { rootOfRoots: ZERO_HASH }; - credential = await this.addBJJAuthjjCredential(did, oldTreeState, ethSigner, opts); + credential = await this.addBJJAuthCredential(did, oldTreeState, ethSigner, opts); } return { @@ -1273,7 +1269,8 @@ export class IdentityWallet implements IIdentityWallet { did: DID, oldTreeState: TreeState, isOldStateGenesis: boolean, - ethSigner: Signer + ethSigner: Signer, + prover?: IZKProver ): Promise { const newTreeModel = await this.getDIDTreeModel(did); const claimsRoot = await newTreeModel.claimsTree.root(); @@ -1294,7 +1291,7 @@ export class IdentityWallet implements IIdentityWallet { let txId; if (!isEthIdentity) { - if (!this._prover) { + if (!prover) { throw new Error('prover is required to generate proofs for non ethereum identities'); } // generate the proof @@ -1327,7 +1324,7 @@ export class IdentityWallet implements IIdentityWallet { const inputs = circuitInputs.inputsMarshal(); - proof = await this._prover.generate(inputs, CircuitId.StateTransition); + proof = await prover.generate(inputs, CircuitId.StateTransition); txId = await this._storage.states.publishState(proof, ethSigner); } else { @@ -1348,12 +1345,13 @@ export class IdentityWallet implements IIdentityWallet { return txId; } - /** {@inheritdoc IIdentityWallet.addBJJAuthjjCredential} */ - async addBJJAuthjjCredential( + /** {@inheritdoc IIdentityWallet.addBJJAuthCredential} */ + async addBJJAuthCredential( did: DID, oldTreeState: TreeState, ethSigner: Signer, - opts: IdentityCreationOptions + opts: IdentityCreationOptions, + prover?: IZKProver // it will be needed in case of non ethereum identities ): Promise { opts.seed = opts.seed ?? getRandomBytes(32); @@ -1384,7 +1382,7 @@ export class IdentityWallet implements IIdentityWallet { opts.revocationOpts ); - await this.transitState(did, oldTreeState, true, ethSigner); + await this.transitState(did, oldTreeState, true, ethSigner, prover); await this.publishRevocationInfoByCredentialStatusType(did, opts.revocationOpts.type, { rhsUrl: opts.revocationOpts.id, diff --git a/src/proof/proof-service.ts b/src/proof/proof-service.ts index f1d9bca2..2f3770a5 100644 --- a/src/proof/proof-service.ts +++ b/src/proof/proof-service.ts @@ -350,7 +350,13 @@ export class ProofService implements IProofService { stateStorage: IStateStorage, // for compatibility with previous versions we leave this parameter ethSigner: Signer ): Promise { - return this._identityWallet.transitState(did, oldTreeState, isOldStateGenesis, ethSigner); + return this._identityWallet.transitState( + did, + oldTreeState, + isOldStateGenesis, + ethSigner, + this._prover + ); } private async generateInputs( diff --git a/tests/credentials/credential-statuses/on-chain-revocation.test.ts b/tests/credentials/credential-statuses/on-chain-revocation.test.ts index 38568b06..67d38721 100644 --- a/tests/credentials/credential-statuses/on-chain-revocation.test.ts +++ b/tests/credentials/credential-statuses/on-chain-revocation.test.ts @@ -239,10 +239,8 @@ describe('onchain revocation checks', () => { new Iden3OnchainSmtCredentialStatusPublisher(storage) ); - const prover = new NativeProver(circuitStorage); idWallet = new IdentityWallet(registerKeyProvidersInMemoryKMS(), dataStorage, credWallet, { - credentialStatusPublisherRegistry, - prover + credentialStatusPublisherRegistry }); proofService = new ProofService(idWallet, credWallet, circuitStorage, ethStorage); }); diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 8d34aa4b..38beab7b 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -88,8 +88,7 @@ describe('auth', () => { new RHSResolver(dataStorage.states) ); credWallet = new CredentialWallet(dataStorage, resolvers); - const prover = new NativeProver(circuitStorage); - idWallet = new IdentityWallet(kms, dataStorage, credWallet, { prover }); + idWallet = new IdentityWallet(kms, dataStorage, credWallet); proofService = new ProofService(idWallet, credWallet, circuitStorage, MOCK_STATE_STORAGE, { ipfsNodeURL: IPFS_URL @@ -632,8 +631,7 @@ describe('auth', () => { ); credWallet = new CredentialWallet(dataStorage, resolvers); - const prover = new NativeProver(circuitStorage); - idWallet = new IdentityWallet(kms, dataStorage, credWallet, { prover }); + idWallet = new IdentityWallet(kms, dataStorage, credWallet); proofService = new ProofService(idWallet, credWallet, circuitStorage, dataStorage.states, { ipfsNodeURL: IPFS_URL diff --git a/tests/identity/id.test.ts b/tests/identity/id.test.ts index ca0e5022..5b59802c 100644 --- a/tests/identity/id.test.ts +++ b/tests/identity/id.test.ts @@ -65,14 +65,7 @@ describe('identity', () => { new RHSResolver(dataStorage.states) ); credWallet = new CredentialWallet(dataStorage, resolvers); - const circuitStorage = new FSCircuitStorage({ - dirname: path.join(__dirname, '../proofs/testdata') - }); - - const prover = new NativeProver(circuitStorage); - idWallet = new IdentityWallet(registerKeyProvidersInMemoryKMS(), dataStorage, credWallet, { - prover - }); + idWallet = new IdentityWallet(registerKeyProvidersInMemoryKMS(), dataStorage, credWallet); }); it('createIdentity', async () => { diff --git a/tests/proofs/mtp-onchain.test.ts b/tests/proofs/mtp-onchain.test.ts index fce4db13..f0e25db5 100644 --- a/tests/proofs/mtp-onchain.test.ts +++ b/tests/proofs/mtp-onchain.test.ts @@ -116,8 +116,7 @@ describe('mtp onchain proofs', () => { new RHSResolver(dataStorage.states) ); credWallet = new CredentialWallet(dataStorage, resolvers); - const prover = new NativeProver(circuitStorage); - idWallet = new IdentityWallet(kms, dataStorage, credWallet, { prover }); + idWallet = new IdentityWallet(kms, dataStorage, credWallet); proofService = new ProofService(idWallet, credWallet, circuitStorage, mockStateStorage); }); diff --git a/tests/proofs/mtp.test.ts b/tests/proofs/mtp.test.ts index 4b51ca43..9f4b13e8 100644 --- a/tests/proofs/mtp.test.ts +++ b/tests/proofs/mtp.test.ts @@ -117,8 +117,7 @@ describe('mtp proofs', () => { ); credWallet = new CredentialWallet(dataStorage, resolvers); - const prover = new NativeProver(circuitStorage); - idWallet = new IdentityWallet(kms, dataStorage, credWallet, { prover }); + idWallet = new IdentityWallet(kms, dataStorage, credWallet); proofService = new ProofService(idWallet, credWallet, circuitStorage, mockStateStorage); }); From bb236e30e1a8b0bf12db046392ec65cebe7e3899 Mon Sep 17 00:00:00 2001 From: daveroga Date: Fri, 5 Apr 2024 14:03:55 +0200 Subject: [PATCH 26/38] separate creation options for createIdentity and createEthereumBasedIdentity --- src/identity/identity-wallet.ts | 8 +++++--- .../credential-statuses/on-chain-revocation.test.ts | 3 +-- tests/handlers/auth.test.ts | 3 +-- tests/helpers.ts | 3 ++- tests/identity/id.test.ts | 5 +---- tests/proofs/mtp-onchain.test.ts | 2 +- tests/proofs/mtp.test.ts | 2 +- 7 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index ae340ea6..2821e7a3 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -80,12 +80,14 @@ export type IdentityCreationOptions = { }; }; seed?: Uint8Array; +}; + +export type EthereumBasedIdentityCreationOptions = IdentityCreationOptions & { ethereumBasedIdentityOpts?: { ethSigner?: Signer; createBjjCredential?: boolean; }; }; - /** * Options for RevocationInfoOptions. */ @@ -135,7 +137,7 @@ export interface IIdentityWallet { * @public */ createEthereumBasedIdentity( - opts: IdentityCreationOptions + opts: EthereumBasedIdentityCreationOptions ): Promise<{ did: DID; credential: W3CCredential | undefined }>; /** @@ -640,7 +642,7 @@ export class IdentityWallet implements IIdentityWallet { * {@inheritDoc IIdentityWallet.createEthereumBasedIdentity} */ async createEthereumBasedIdentity( - opts: IdentityCreationOptions + opts: EthereumBasedIdentityCreationOptions ): Promise<{ did: DID; credential: W3CCredential | undefined }> { opts.seed = opts.seed ?? getRandomBytes(32); if (opts.ethereumBasedIdentityOpts) { diff --git a/tests/credentials/credential-statuses/on-chain-revocation.test.ts b/tests/credentials/credential-statuses/on-chain-revocation.test.ts index 67d38721..736a61f3 100644 --- a/tests/credentials/credential-statuses/on-chain-revocation.test.ts +++ b/tests/credentials/credential-statuses/on-chain-revocation.test.ts @@ -24,8 +24,7 @@ import { CredentialStatusPublisherRegistry, Iden3OnchainSmtCredentialStatusPublisher, SDK_EVENTS, - MessageBus, - NativeProver + MessageBus } from '../../../src'; import { diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 38beab7b..a6cdd9ba 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -39,8 +39,7 @@ import { W3CCredential, Sec256k1Provider, StateInfo, - hexToBytes, - NativeProver + hexToBytes } from '../../src'; import { Token } from '@iden3/js-jwz'; import { Blockchain, DID, DidMethod, NetworkId } from '@iden3/js-iden3-core'; diff --git a/tests/helpers.ts b/tests/helpers.ts index e361ed4f..8a053415 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -6,6 +6,7 @@ import { CredentialStatusType, CredentialStorage, DataPrepareHandlerFunc, + EthereumBasedIdentityCreationOptions, IIdentityWallet, IPackageManager, IStateStorage, @@ -62,7 +63,7 @@ export const createIdentity = async ( export const createEthereumBasedIdentity = async ( wallet: IIdentityWallet, - opts?: Partial + opts?: Partial ) => { return await wallet.createEthereumBasedIdentity({ method: DidMethod.Iden3, diff --git a/tests/identity/id.test.ts b/tests/identity/id.test.ts index 5b59802c..fdf2ca5d 100644 --- a/tests/identity/id.test.ts +++ b/tests/identity/id.test.ts @@ -1,5 +1,4 @@ /* eslint-disable no-console */ -import path from 'path'; import { IdentityWallet, byteEncoder, @@ -11,9 +10,7 @@ import { CredentialStatusResolverRegistry, RHSResolver, CredentialStatusType, - FSCircuitStorage, - EthStateStorage, - NativeProver + EthStateStorage } from '../../src'; import { MOCK_STATE_STORAGE, diff --git a/tests/proofs/mtp-onchain.test.ts b/tests/proofs/mtp-onchain.test.ts index f0e25db5..28c61bf1 100644 --- a/tests/proofs/mtp-onchain.test.ts +++ b/tests/proofs/mtp-onchain.test.ts @@ -18,7 +18,7 @@ import { CredentialWallet, RHSResolver } from '../../src/credentials'; -import { NativeProver, ProofService } from '../../src/proof'; +import { ProofService } from '../../src/proof'; import { CircuitId } from '../../src/circuits'; import { ethers } from 'ethers'; import { EthStateStorage } from '../../src/storage/blockchain/state'; diff --git a/tests/proofs/mtp.test.ts b/tests/proofs/mtp.test.ts index 9f4b13e8..990afbdd 100644 --- a/tests/proofs/mtp.test.ts +++ b/tests/proofs/mtp.test.ts @@ -13,7 +13,7 @@ import { InMemoryPrivateKeyStore } from '../../src/kms/store'; import { IDataStorage, IStateStorage } from '../../src/storage/interfaces'; import { InMemoryDataSource, InMemoryMerkleTreeStorage } from '../../src/storage/memory'; import { CredentialRequest, CredentialWallet } from '../../src/credentials'; -import { NativeProver, ProofService } from '../../src/proof'; +import { ProofService } from '../../src/proof'; import { CircuitId } from '../../src/circuits'; import { ethers } from 'ethers'; import { EthStateStorage } from '../../src/storage/blockchain/state'; From c7409cce4ce063d5b66103d112c7091e8df9162d Mon Sep 17 00:00:00 2001 From: daveroga Date: Mon, 8 Apr 2024 13:02:46 +0200 Subject: [PATCH 27/38] update add bjj credential and test --- src/identity/identity-wallet.ts | 127 ++++++++++++++++++++------------ tests/identity/id.test.ts | 63 +++++++++++++++- 2 files changed, 141 insertions(+), 49 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 2821e7a3..f59bc64f 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -70,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; @@ -82,12 +90,16 @@ export type IdentityCreationOptions = { seed?: Uint8Array; }; +/** + * Options for creating Ethereum based identity + */ export type EthereumBasedIdentityCreationOptions = IdentityCreationOptions & { ethereumBasedIdentityOpts?: { ethSigner?: Signer; createBjjCredential?: boolean; }; }; + /** * Options for RevocationInfoOptions. */ @@ -420,12 +432,14 @@ export interface IIdentityWallet { * * @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 {object} opts - additional options */ addBJJAuthCredential( did: DID, oldTreeState: TreeState, + isOldTreeStateGenesis: boolean, ethSigner: Signer, opts?: object ): Promise; @@ -510,7 +524,7 @@ export class IdentityWallet implements IIdentityWallet { did: DID, pubKey: PublicKey, authClaim: Claim, - currentState: Hash, + oldTreeState: TreeState, revocationOpts: { id: string; type: CredentialStatusType } ): Promise { const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType( @@ -518,6 +532,14 @@ export class IdentityWallet implements IIdentityWallet { MerkleTreeType.Claims ); + const ctr = await claimsTree.root(); + + const currentState = hashElems([ + ctr.bigInt(), + oldTreeState.revocationRoot.bigInt(), + oldTreeState.rootOfRoots.bigInt() + ]); + const authData = authClaim.getExpirationDate(); const expiration = authData ? getUnixTimestamp(authData) : 0; @@ -539,35 +561,40 @@ export class IdentityWallet implements IIdentityWallet { } }; - const schema = JSON.parse(VerifiableConstants.AUTH.AUTH_BJJ_CREDENTIAL_SCHEMA_JSON); - 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}`); - } - - const index = authClaim.hIndex(); - const ctr = await claimsTree.root(); + // Check if has already an auth credential + const authCredentials = await this._credentialWallet.getAllAuthBJJCredentials(did); - const { proof } = await claimsTree.generateProof(index, ctr); + let credential: W3CCredential = 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 as Error).message}`); + } - const mtpProof: Iden3SparseMerkleTreeProof = new Iden3SparseMerkleTreeProof({ - mtp: proof, - issuerData: { - id: did, - state: { - rootOfRoots: ZERO_HASH, - revocationTreeRoot: ZERO_HASH, - claimsTreeRoot: ctr, - value: currentState - } - }, - coreClaim: authClaim - }); + const index = authClaim.hIndex(); + const { proof } = await claimsTree.generateProof(index, ctr); - credential.proof = [mtpProof]; + const mtpProof: Iden3SparseMerkleTreeProof = new Iden3SparseMerkleTreeProof({ + mtp: proof, + issuerData: { + id: did, + state: { + rootOfRoots: oldTreeState.rootOfRoots, + revocationTreeRoot: oldTreeState.revocationRoot, + claimsTreeRoot: ctr, + value: currentState + } + }, + coreClaim: authClaim + }); + credential.proof = [mtpProof]; + } else { + // credential with sigProof signed with previous auth bjj credential + credential = await this.issueCredential(did, request); + } return credential; } @@ -594,11 +621,9 @@ export class IdentityWallet implements IIdentityWallet { MerkleTreeType.Claims ); - const currentState = hashElems([ - (await claimsTree.root()).bigInt(), - ZERO_HASH.bigInt(), - ZERO_HASH.bigInt() - ]); + const ctr = await claimsTree.root(); + + const currentState = hashElems([ctr.bigInt(), ZERO_HASH.bigInt(), ZERO_HASH.bigInt()]); const didType = buildDIDType( opts.method || DidMethod.Iden3, @@ -614,7 +639,12 @@ export class IdentityWallet implements IIdentityWallet { did, pubKey, authClaim, - currentState, + { + revocationRoot: ZERO_HASH, + claimsRoot: ctr, + state: currentState, + rootOfRoots: ZERO_HASH + }, opts.revocationOpts ); @@ -689,7 +719,7 @@ export class IdentityWallet implements IIdentityWallet { rootOfRoots: ZERO_HASH }; - credential = await this.addBJJAuthCredential(did, oldTreeState, ethSigner, opts); + credential = await this.addBJJAuthCredential(did, oldTreeState, true, ethSigner, opts); } return { @@ -1351,6 +1381,7 @@ export class IdentityWallet implements IIdentityWallet { async addBJJAuthCredential( did: DID, oldTreeState: TreeState, + isOldStateGenesis: boolean, ethSigner: Signer, opts: IdentityCreationOptions, prover?: IZKProver // it will be needed in case of non ethereum identities @@ -1365,33 +1396,33 @@ export class IdentityWallet implements IIdentityWallet { const { hi, hv } = authClaim.hiHv(); await this._storage.mt.addToMerkleTree(did.string(), MerkleTreeType.Claims, hi, hv); - const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType( - did.string(), - MerkleTreeType.Claims - ); - - const stateAuthClaim = hashElems([ - (await claimsTree.root()).bigInt(), - oldTreeState.revocationRoot.bigInt(), - oldTreeState.rootOfRoots.bigInt() - ]); - - const credential = await this.createAuthBJJCredential( + let credential = await this.createAuthBJJCredential( did, pubKey, authClaim, - stateAuthClaim, + oldTreeState, opts.revocationOpts ); - await this.transitState(did, oldTreeState, true, ethSigner, prover); + const txId = await this.transitState(did, oldTreeState, isOldStateGenesis, ethSigner, prover); + const credsWithIden3MTPProof = await this.generateIden3SparseMerkleTreeProof( + did, + [credential], + txId + ); + 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 }); - await this._credentialWallet.save(credential); return credential; } } diff --git a/tests/identity/id.test.ts b/tests/identity/id.test.ts index fdf2ca5d..5fea72e8 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, @@ -10,7 +11,11 @@ import { CredentialStatusResolverRegistry, RHSResolver, CredentialStatusType, - EthStateStorage + EthStateStorage, + FSCircuitStorage, + NativeProver, + Iden3SparseMerkleTreeProof, + BJJSignatureProof2021 } from '../../src'; import { MOCK_STATE_STORAGE, @@ -197,4 +202,60 @@ describe('identity', () => { 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); + }); }); From cc254638e58a02d0ee17b18e111c3f223d5faa3d Mon Sep 17 00:00:00 2001 From: daveroga Date: Mon, 8 Apr 2024 15:29:18 +0200 Subject: [PATCH 28/38] update core claim in mtp proof and revocation --- src/identity/identity-wallet.ts | 31 +++++++++---- tests/identity/id.test.ts | 82 +++++++++++++++++++++++++++++---- 2 files changed, 96 insertions(+), 17 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index f59bc64f..58a82746 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -434,14 +434,14 @@ export interface IIdentityWallet { * @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 {object} opts - additional options + * @param {AuthBJJCredentialCreationOptions} opts - additional options */ addBJJAuthCredential( did: DID, oldTreeState: TreeState, isOldTreeStateGenesis: boolean, ethSigner: Signer, - opts?: object + opts?: AuthBJJCredentialCreationOptions ): Promise; } @@ -1040,10 +1040,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(); @@ -1119,7 +1119,8 @@ 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]; @@ -1128,7 +1129,10 @@ export class IdentityWallet implements IIdentityWallet { // 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'); @@ -1383,7 +1387,7 @@ export class IdentityWallet implements IIdentityWallet { oldTreeState: TreeState, isOldStateGenesis: boolean, ethSigner: Signer, - opts: IdentityCreationOptions, + opts: AuthBJJCredentialCreationOptions, prover?: IZKProver // it will be needed in case of non ethereum identities ): Promise { opts.seed = opts.seed ?? getRandomBytes(32); @@ -1408,7 +1412,18 @@ export class IdentityWallet implements IIdentityWallet { const credsWithIden3MTPProof = await this.generateIden3SparseMerkleTreeProof( did, [credential], - txId + 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); diff --git a/tests/identity/id.test.ts b/tests/identity/id.test.ts index 5fea72e8..b3c4d51f 100644 --- a/tests/identity/id.test.ts +++ b/tests/identity/id.test.ts @@ -136,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); @@ -258,4 +249,77 @@ describe('identity', () => { 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); + + // credential2 was generated with sigproof from credential, so it should be revoked as well + const afterRevokeProofNRcredential2 = await idWallet.generateNonRevocationMtp(did, credential2); + expect(afterRevokeProofNRcredential2.proof.existence).to.equal(true); + }); }); From a6eba8aa0d7178e54ec88906b0e8f7b895b18124 Mon Sep 17 00:00:00 2001 From: daveroga Date: Mon, 8 Apr 2024 18:22:28 +0200 Subject: [PATCH 29/38] add key rotation use case test --- src/identity/identity-wallet.ts | 7 +- tests/handlers/auth.test.ts | 301 +++++++++++++++++++++++++++++++- tests/identity/id.test.ts | 3 +- 3 files changed, 307 insertions(+), 4 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 58a82746..50ab57a5 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -21,7 +21,7 @@ import { getRandomBytes, Poseidon } from '@iden3/js-crypto'; -import { Hash, hashElems, ZERO_HASH } from '@iden3/js-merkletree'; +import { hashElems, ZERO_HASH } from '@iden3/js-merkletree'; import { generateProfileDID, subjectPositionIndex } from './common'; import * as uuid from 'uuid'; import { JSONSchema, JsonSchemaValidator, cacheLoader } from '../schema-processor'; @@ -1391,6 +1391,11 @@ export class IdentityWallet implements IIdentityWallet { 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, diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index a6cdd9ba..fb27286d 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -39,7 +39,8 @@ import { W3CCredential, Sec256k1Provider, StateInfo, - hexToBytes + hexToBytes, + NativeProver } from '../../src'; import { Token } from '@iden3/js-jwz'; import { Blockchain, DID, DidMethod, NetworkId } from '@iden3/js-iden3-core'; @@ -1911,4 +1912,302 @@ 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.only('key rotation use case', async () => { + 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); + + await credWallet.save(issuerCred); + + const proofReq: ZeroKnowledgeProofRequest = { + id: 1, + circuitId: CircuitId.AtomicQuerySigV2, + optional: false, + query: { + allowedIssuers: ['*'], + type: claimReq.type, + context: + 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-nonmerklized.jsonld', + credentialSubject: { + documentType: { + $eq: 99 + } + } + } + }; + + 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 handleAuthorizationRequest(userDID, authReqBody); + + // get actual auth credential (k2) + const { authCredential: issuerAuthCredential2 } = await idWallet.getActualAuthCredential( + issuerDID + ); + + expect(issuerAuthCredential2).to.be.deep.equal(credential2); + + // revoke k2 auth credential + const nonce2 = await idWallet.revokeCredential(issuerDID, issuerAuthCredential2); + await idWallet.publishStateToRHS(issuerDID, RHS_URL, [nonce2]); + + // check that we don't have auth credentials now + await expect(idWallet.getActualAuthCredential(issuerDID)).to.rejectedWith( + 'no auth credentials found' + ); + + // should this work? + await handleAuthorizationRequest(userDID, authReqBody); + }); }); diff --git a/tests/identity/id.test.ts b/tests/identity/id.test.ts index b3c4d51f..5695346c 100644 --- a/tests/identity/id.test.ts +++ b/tests/identity/id.test.ts @@ -318,8 +318,7 @@ describe('identity', () => { const afterRevokeProofNRcredential = await idWallet.generateNonRevocationMtp(did, credential); expect(afterRevokeProofNRcredential.proof.existence).to.equal(true); - // credential2 was generated with sigproof from credential, so it should be revoked as well const afterRevokeProofNRcredential2 = await idWallet.generateNonRevocationMtp(did, credential2); - expect(afterRevokeProofNRcredential2.proof.existence).to.equal(true); + expect(afterRevokeProofNRcredential2.proof.existence).to.equal(false); }); }); From 2b29963602366627619734156f095df4eefdceaa Mon Sep 17 00:00:00 2001 From: daveroga Date: Mon, 8 Apr 2024 18:56:36 +0200 Subject: [PATCH 30/38] remove only in test --- tests/handlers/auth.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index fb27286d..eb748162 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -2068,7 +2068,7 @@ describe('auth', () => { expect(token).to.be.a('object'); }); - it.only('key rotation use case', async () => { + it('key rotation use case', async () => { const claimReq: CredentialRequest = { credentialSchema: 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/kyc-nonmerklized.json', From e6f118737651a14ad7035b80dc019ba13ac0398f Mon Sep 17 00:00:00 2001 From: daveroga Date: Mon, 8 Apr 2024 23:40:55 +0200 Subject: [PATCH 31/38] add transit state after revoke credential --- tests/handlers/auth.test.ts | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index eb748162..a7aa8d15 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -2085,8 +2085,9 @@ describe('auth', () => { } }; 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, @@ -2188,6 +2189,13 @@ describe('auth', () => { // 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); @@ -2195,12 +2203,26 @@ describe('auth', () => { const { authCredential: issuerAuthCredential2 } = await idWallet.getActualAuthCredential( issuerDID ); - expect(issuerAuthCredential2).to.be.deep.equal(credential2); + 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( From 60a0c50674fe747047f9da9730fc7947d5ad0e97 Mon Sep 17 00:00:00 2001 From: daveroga Date: Tue, 9 Apr 2024 09:29:11 +0200 Subject: [PATCH 32/38] update query proof with v3 --- tests/handlers/auth.test.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index a7aa8d15..0ed81c39 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -2071,7 +2071,7 @@ describe('auth', () => { it('key rotation use case', async () => { const claimReq: CredentialRequest = { credentialSchema: - 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/kyc-nonmerklized.json', + 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json/KYCAgeCredential-v4.json', type: 'KYCAgeCredential', credentialSubject: { id: userDID.string(), @@ -2091,18 +2091,19 @@ describe('auth', () => { const proofReq: ZeroKnowledgeProofRequest = { id: 1, - circuitId: CircuitId.AtomicQuerySigV2, + circuitId: CircuitId.AtomicQueryV3, optional: false, query: { allowedIssuers: ['*'], type: claimReq.type, context: - 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-nonmerklized.jsonld', + 'https://raw.githubusercontent.com/iden3/claim-schema-vocab/main/schemas/json-ld/kyc-v4.jsonld', credentialSubject: { documentType: { $eq: 99 } - } + }, + proofType: ProofType.BJJSignature } }; From 571234c6760935c8c6c6ad8fe6f80e2bf5b6adba Mon Sep 17 00:00:00 2001 From: daveroga Date: Tue, 9 Apr 2024 09:46:25 +0200 Subject: [PATCH 33/38] check issue credentials in issuer revocation keys --- tests/handlers/auth.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 0ed81c39..63d2bd3d 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -2206,6 +2206,10 @@ describe('auth', () => { ); 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(), @@ -2230,6 +2234,11 @@ describe('auth', () => { 'no auth credentials found' ); + // check that we can't issue new credential + await expect(idWallet.issueCredential(issuerDID, claimReq)).to.rejectedWith( + 'no auth credentials found' + ); + // should this work? await handleAuthorizationRequest(userDID, authReqBody); }); From f6728c927685c68f1d8e251afeb70585a6c8df16 Mon Sep 17 00:00:00 2001 From: daveroga Date: Tue, 9 Apr 2024 11:40:13 +0200 Subject: [PATCH 34/38] revoke user keys and check --- tests/handlers/auth.test.ts | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 63d2bd3d..7ecfdbd4 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -2239,7 +2239,34 @@ describe('auth', () => { 'no auth credentials found' ); - // should this work? + // 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' + ); }); }); From a48de784d0167a792f0fa80d3bbca46f59b78a2d Mon Sep 17 00:00:00 2001 From: daveroga Date: Wed, 10 Apr 2024 12:44:14 +0200 Subject: [PATCH 35/38] changes --- src/identity/identity-wallet.ts | 83 +++++++++++++++++---------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 50ab57a5..83f97e9e 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -21,7 +21,7 @@ import { getRandomBytes, Poseidon } from '@iden3/js-crypto'; -import { hashElems, ZERO_HASH } from '@iden3/js-merkletree'; +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'; @@ -524,22 +524,9 @@ export class IdentityWallet implements IIdentityWallet { did: DID, pubKey: PublicKey, authClaim: Claim, - oldTreeState: TreeState, + currentState: Hash, revocationOpts: { id: string; type: CredentialStatusType } ): Promise { - const claimsTree = await this._storage.mt.getMerkleTreeByIdentifierAndType( - did.string(), - MerkleTreeType.Claims - ); - - const ctr = await claimsTree.root(); - - const currentState = hashElems([ - ctr.bigInt(), - oldTreeState.revocationRoot.bigInt(), - oldTreeState.rootOfRoots.bigInt() - ]); - const authData = authClaim.getExpirationDate(); const expiration = authData ? getUnixTimestamp(authData) : 0; @@ -572,25 +559,6 @@ export class IdentityWallet implements IIdentityWallet { } catch (e) { throw new Error(`Error create w3c credential ${(e as Error).message}`); } - - const index = authClaim.hIndex(); - const { proof } = await claimsTree.generateProof(index, ctr); - - const mtpProof: Iden3SparseMerkleTreeProof = new Iden3SparseMerkleTreeProof({ - mtp: proof, - issuerData: { - id: did, - state: { - rootOfRoots: oldTreeState.rootOfRoots, - revocationTreeRoot: oldTreeState.revocationRoot, - claimsTreeRoot: ctr, - value: currentState - } - }, - coreClaim: authClaim - }); - - credential.proof = [mtpProof]; } else { // credential with sigProof signed with previous auth bjj credential credential = await this.issueCredential(did, request); @@ -635,19 +603,40 @@ export class IdentityWallet implements IIdentityWallet { 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, - { - revocationRoot: ZERO_HASH, - claimsRoot: ctr, - state: currentState, - rootOfRoots: ZERO_HASH - }, + currentState, opts.revocationOpts ); + const index = authClaim.hIndex(); + const { proof } = await claimsTree.generateProof(index, ctr); + + const mtpProof: Iden3SparseMerkleTreeProof = new Iden3SparseMerkleTreeProof({ + mtp: proof, + issuerData: { + id: did, + state: { + rootOfRoots: oldTreeState.rootOfRoots, + revocationTreeRoot: oldTreeState.revocationRoot, + claimsTreeRoot: ctr, + value: currentState + } + }, + coreClaim: authClaim + }); + + credential.proof = [mtpProof]; + await this.publishRevocationInfoByCredentialStatusType(did, opts.revocationOpts.type, { rhsUrl: opts.revocationOpts.id, onChain: opts.revocationOpts.onChain @@ -1405,15 +1394,27 @@ export class IdentityWallet implements IIdentityWallet { 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, - oldTreeState, + 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], From c65271843f18eeb23c4328f022012c8ed90e77fc Mon Sep 17 00:00:00 2001 From: vmidyllic <74898029+vmidyllic@users.noreply.github.com> Date: Wed, 10 Apr 2024 15:31:24 +0300 Subject: [PATCH 36/38] add function to get mtp from core claim --- src/identity/identity-wallet.ts | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 83f97e9e..18369347 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -202,6 +202,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. @@ -812,8 +827,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( @@ -1114,8 +1136,6 @@ export class IdentityWallet implements IIdentityWallet { 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 @@ -1126,6 +1146,7 @@ export class IdentityWallet implements IIdentityWallet { 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, From 38fad2106863c3d9b5837138edf301466bd87d93 Mon Sep 17 00:00:00 2001 From: vmidyllic <74898029+vmidyllic@users.noreply.github.com> Date: Wed, 10 Apr 2024 17:28:09 +0300 Subject: [PATCH 37/38] fix --- src/identity/identity-wallet.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 18369347..38d0621e 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -1168,7 +1168,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; From ca6cf87d121db790288e546da11d503d6489a5a3 Mon Sep 17 00:00:00 2001 From: daveroga Date: Fri, 12 Apr 2024 11:16:31 +0200 Subject: [PATCH 38/38] remove level of nested structure --- src/identity/identity-wallet.ts | 15 +++++---------- tests/handlers/auth.test.ts | 8 ++------ tests/identity/id.test.ts | 4 +--- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/identity/identity-wallet.ts b/src/identity/identity-wallet.ts index 38d0621e..5580916f 100644 --- a/src/identity/identity-wallet.ts +++ b/src/identity/identity-wallet.ts @@ -94,10 +94,8 @@ export type AuthBJJCredentialCreationOptions = { * Options for creating Ethereum based identity */ export type EthereumBasedIdentityCreationOptions = IdentityCreationOptions & { - ethereumBasedIdentityOpts?: { - ethSigner?: Signer; - createBjjCredential?: boolean; - }; + ethSigner?: Signer; + createBjjCredential?: boolean; }; /** @@ -679,13 +677,10 @@ export class IdentityWallet implements IIdentityWallet { opts: EthereumBasedIdentityCreationOptions ): Promise<{ did: DID; credential: W3CCredential | undefined }> { opts.seed = opts.seed ?? getRandomBytes(32); - if (opts.ethereumBasedIdentityOpts) { - opts.ethereumBasedIdentityOpts.createBjjCredential = - opts.ethereumBasedIdentityOpts?.createBjjCredential ?? true; - } + opts.createBjjCredential = opts.createBjjCredential ?? true; let credential; - const ethSigner = opts.ethereumBasedIdentityOpts?.ethSigner; + const ethSigner = opts.ethSigner; if (!ethSigner) { throw new Error( @@ -714,7 +709,7 @@ export class IdentityWallet implements IIdentityWallet { isStateGenesis: true }); - if (opts.ethereumBasedIdentityOpts?.createBjjCredential) { + if (opts.createBjjCredential) { // Old tree state genesis state const oldTreeState: TreeState = { revocationRoot: ZERO_HASH, diff --git a/tests/handlers/auth.test.ts b/tests/handlers/auth.test.ts index 7ecfdbd4..f96984ea 100644 --- a/tests/handlers/auth.test.ts +++ b/tests/handlers/auth.test.ts @@ -424,9 +424,7 @@ describe('auth', () => { type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, id: RHS_URL }, - ethereumBasedIdentityOpts: { - ethSigner - } + ethSigner }); expect(issuerAuthCredential).not.to.be.undefined; @@ -673,9 +671,7 @@ describe('auth', () => { type: CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, id: RHS_URL }, - ethereumBasedIdentityOpts: { - ethSigner - } + ethSigner }); expect(issuerAuthCredential).not.to.be.undefined; diff --git a/tests/identity/id.test.ts b/tests/identity/id.test.ts index 5695346c..32cf9c61 100644 --- a/tests/identity/id.test.ts +++ b/tests/identity/id.test.ts @@ -173,9 +173,7 @@ describe('identity', () => { const ethSigner = new Wallet(WALLET_KEY, (dataStorage.states as EthStateStorage).provider); const { did, credential } = await createEthereumBasedIdentity(idWallet, { - ethereumBasedIdentityOpts: { - ethSigner - } + ethSigner }); expect(did.string()).to.equal(