From c7c5bb6f09717470b54017687ad3e12cdedbb61b Mon Sep 17 00:00:00 2001 From: ilya-korotya Date: Mon, 2 Dec 2024 10:53:32 +0100 Subject: [PATCH] improvments --- src/iden3comm/handlers/fetch.ts | 8 +- .../onchain-non-merklized-issuer-adapter.ts | 26 +++--- src/storage/blockchain/onchain-issuer.ts | 83 +++++++++---------- src/storage/interfaces/onchain-issuer.ts | 6 +- tests/adapter/onchain-issuer.test.ts | 17 ++-- ...chain-non-merklized-issuer-adapter.test.ts | 46 +++++----- tests/handlers/fetch.test.ts | 45 +++++++++- 7 files changed, 139 insertions(+), 92 deletions(-) diff --git a/src/iden3comm/handlers/fetch.ts b/src/iden3comm/handlers/fetch.ts index 630af3af..0bb31c02 100644 --- a/src/iden3comm/handlers/fetch.ts +++ b/src/iden3comm/handlers/fetch.ts @@ -149,17 +149,17 @@ export class FetchHandler if (!this.opts?.onchainIssuer) { throw new Error('onchain issuer is not provided'); } - const credentials: W3CCredential[] = []; for (const credentialInfo of offerMessage.body.credentials) { - const userId = DID.idFromDID(DID.parse(offerMessage.from)); + const issuerDID = DID.parse(offerMessage.from); + const userDID = DID.parse(offerMessage.to); const credential = await this.opts.onchainIssuer.getCredential( - userId, + issuerDID, + userDID, BigInt(credentialInfo.id) ); credentials.push(credential); } - return credentials; } diff --git a/src/storage/blockchain/onchain-issuer-adapter/non-merklized/version/v0.0.1/onchain-non-merklized-issuer-adapter.ts b/src/storage/blockchain/onchain-issuer-adapter/non-merklized/version/v0.0.1/onchain-non-merklized-issuer-adapter.ts index fef45ae4..e76b5f85 100644 --- a/src/storage/blockchain/onchain-issuer-adapter/non-merklized/version/v0.0.1/onchain-non-merklized-issuer-adapter.ts +++ b/src/storage/blockchain/onchain-issuer-adapter/non-merklized/version/v0.0.1/onchain-non-merklized-issuer-adapter.ts @@ -16,6 +16,7 @@ import { JsonDocumentObject } from '../../../../../../iden3comm'; import { ethers } from 'ethers'; import { getDateFromUnixTimestamp } from '@iden3/js-iden3-core'; import { Options } from '@iden3/js-jsonld-merklization'; +import { EthConnectionConfig } from '../../../../state'; enum NonMerklizedIssuerInterfaces { InterfaceDetection = '0x01ffc9a7', @@ -48,25 +49,30 @@ export class OnchainNonMerklizedIssuerAdapter { /** * Initializes an instance of `OnchainNonMerklizedIssuerAdapter`. * - * @param address The contract address of the non-merklized issuer. - * @param rpcUrl The URL of the blockchain RPC provider. - * @param chainId The chain ID of the blockchain network. + * @param ethConnectionConfig The configuration for the Ethereum connection. * @param issuerDid The decentralized identifier (DID) of the issuer. * @param merklizationOptions Optional settings for merklization. */ constructor( - address: string, + ethConnectionConfig: EthConnectionConfig, options: { - rpcUrl: string; - chainId: number; issuerDid: DID; merklizationOptions?: Options; } ) { - const rpcProvider = new ethers.JsonRpcProvider(options.rpcUrl); - this._contract = NonMerklizedIssuerBase__factory.connect(address, rpcProvider); - this._contractAddress = address; - this._chainId = options.chainId; + if (!ethConnectionConfig.chainId) { + throw new Error('Chain ID is required'); + } + this._chainId = ethConnectionConfig.chainId; + + this._contractAddress = ethers.getAddress( + ethers.hexlify(Id.ethAddressFromId(DID.idFromDID(options.issuerDid))) + ); + this._contract = NonMerklizedIssuerBase__factory.connect( + this._contractAddress, + new ethers.JsonRpcProvider(ethConnectionConfig.url) + ); + this._issuerDid = options.issuerDid; this._merklizationOptions = options.merklizationOptions; } diff --git a/src/storage/blockchain/onchain-issuer.ts b/src/storage/blockchain/onchain-issuer.ts index a952982c..e7588a1c 100644 --- a/src/storage/blockchain/onchain-issuer.ts +++ b/src/storage/blockchain/onchain-issuer.ts @@ -7,10 +7,6 @@ import { OnchainNonMerklizedIssuerAdapter } from './onchain-issuer-adapter/non-m import { EthConnectionConfig } from './state'; import { IOnchainIssuer } from '../interfaces/onchain-issuer'; -enum OnchainIssuerVersion { - 'v0.0.1' = '0.0.1' -} - /** * Represents an adapter for interacting with on-chain issuers. * @@ -19,57 +15,41 @@ enum OnchainIssuerVersion { * @class OnchainIssuer */ export class OnchainIssuer implements IOnchainIssuer { - private readonly _url: string; - private readonly _chainId: number; - private readonly _contractAddress: string; - private readonly _contract: Contract; - - private readonly _issuerDid: DID; - + private readonly _ethConnectionConfig: EthConnectionConfig[]; private readonly _merklizationOptions?: Options; /** * Initializes an instance of `Adapter`. * @param config The configuration for the Ethereum connection. - * @param did The decentralized identifier (DID) of the issuer. The DID provides the blockchain and network information. * @param merklizationOptions Optional settings for merklization. */ - constructor(config: EthConnectionConfig[], did: DID, options?: Options) { - const issuerId = DID.idFromDID(did); - this._contractAddress = ethers.getAddress(ethers.hexlify(Id.ethAddressFromId(issuerId))); - this._chainId = chainIDfromDID(did); - const url = config.find((c) => c.chainId === this._chainId)?.url; - if (!url) { - throw new Error(`No URL found for chain ID ${this._chainId}`); - } - this._url = url; + constructor(config: EthConnectionConfig[], options?: Options) { + this._ethConnectionConfig = config; this._merklizationOptions = options; - this._contract = new Contract( - this._contractAddress, - abi, - new ethers.JsonRpcProvider(this._url) - ); - this._issuerDid = did; } /** * Retrieves a credential from the on-chain issuer. + * @param issuerDID The issuer's core.DID. * @param userId The user's core.Id. * @param credentialId The unique identifier of the credential. */ - public async getCredential(userId: Id, credentialId: bigint): Promise { - const response = await this._contract.getCredentialAdapterVersion(); + public async getCredential( + issuerDID: DID, + userDID: DID, + credentialId: bigint + ): Promise { + const { contract, connection } = this.getContractConnection(issuerDID); + const response = await contract.getCredentialAdapterVersion(); switch (response) { - case OnchainIssuerVersion['v0.0.1']: { - const adapter = new OnchainNonMerklizedIssuerAdapter(this._contractAddress, { - rpcUrl: this._url, - chainId: this._chainId, - issuerDid: this._issuerDid, + case '0.0.1': { + const adapter = new OnchainNonMerklizedIssuerAdapter(connection, { + issuerDid: issuerDID, merklizationOptions: this._merklizationOptions }); await adapter.isSupportsInterface(); const { credentialData, coreClaimBigInts, credentialSubjectFields } = - await adapter.getCredential(userId, credentialId); + await adapter.getCredential(DID.idFromDID(userDID), credentialId); return await adapter.convertOnChainInfoToW3CCredential( credentialData, coreClaimBigInts, @@ -83,23 +63,40 @@ export class OnchainIssuer implements IOnchainIssuer { /** * Retrieves the credential identifiers for a user from the on-chain issuer. + * @param issuerDID The issuer's core.DID. * @param userId The user's core.Id. */ - public async getUserCredentialIds(userId: Id): Promise { - const response = await this._contract.getCredentialAdapterVersion(); + public async getUserCredentialIds(issuerDID: DID, userDID: DID): Promise { + const { contract, connection } = this.getContractConnection(issuerDID); + const response = await contract.getCredentialAdapterVersion(); switch (response) { - case OnchainIssuerVersion['v0.0.1']: { - const adapter = new OnchainNonMerklizedIssuerAdapter(this._contractAddress, { - rpcUrl: this._url, - chainId: this._chainId, - issuerDid: this._issuerDid, + case '0.0.1': { + const adapter = new OnchainNonMerklizedIssuerAdapter(connection, { + issuerDid: issuerDID, merklizationOptions: this._merklizationOptions }); await adapter.isSupportsInterface(); - return await adapter.getUserCredentialsIds(userId); + return await adapter.getUserCredentialsIds(DID.idFromDID(userDID)); } default: throw new Error(`Unsupported adapter version ${response}`); } } + + private getContractConnection(did: DID): { contract: Contract; connection: EthConnectionConfig } { + const issuerId = DID.idFromDID(did); + const chainId = chainIDfromDID(did); + const contractAddress = ethers.getAddress(ethers.hexlify(Id.ethAddressFromId(issuerId))); + const connection = this._ethConnectionConfig.find((c) => c.chainId === chainId); + if (!connection) { + throw new Error(`No connection found for chain ID ${chainId}`); + } + if (!connection.url) { + throw new Error(`No URL found for chain ID ${chainId}`); + } + + const contract = new Contract(contractAddress, abi, new ethers.JsonRpcProvider(connection.url)); + + return { contract, connection }; + } } diff --git a/src/storage/interfaces/onchain-issuer.ts b/src/storage/interfaces/onchain-issuer.ts index d2588079..4fa7a3d4 100644 --- a/src/storage/interfaces/onchain-issuer.ts +++ b/src/storage/interfaces/onchain-issuer.ts @@ -1,4 +1,4 @@ -import { Id } from '@iden3/js-iden3-core'; +import { Id, DID } from '@iden3/js-iden3-core'; import { W3CCredential } from '../../verifiable'; /** @@ -8,6 +8,6 @@ import { W3CCredential } from '../../verifiable'; * @interface IOnchainIssuer */ export interface IOnchainIssuer { - getCredential(userId: Id, credentialId: bigint): Promise; - getUserCredentialIds(userId: Id): Promise; + getCredential(issuerDID: DID, userDID: DID, credentialId: bigint): Promise; + getUserCredentialIds(issuerDID: DID, userDID: DID): Promise; } diff --git a/tests/adapter/onchain-issuer.test.ts b/tests/adapter/onchain-issuer.test.ts index 78f95052..d15612d4 100644 --- a/tests/adapter/onchain-issuer.test.ts +++ b/tests/adapter/onchain-issuer.test.ts @@ -21,12 +21,11 @@ describe('OnchainIssuer', () => { const issuerDid = DID.parse( 'did:polygonid:polygon:amoy:2qQ68JkRcf3xyDFsGSWU5QqxbKpzM75quxS628JgvJ' ); - const userId = DID.idFromDID( - DID.parse('did:polygonid:polygon:amoy:2qQ68JkRcf3xyDFsGSWU5QqxbKpzM75quxS628JgvJ') + const userDid = DID.parse( + 'did:polygonid:polygon:amoy:2qQ68JkRcf3xyDFsGSWU5QqxbKpzM75quxS628JgvJ' ); - const adapter = new OnchainIssuer([copyDefaultEthConnectionConfig], issuerDid); - const cred = await adapter.getCredential(userId, BigInt(6)); - console.log(JSON.stringify(cred.toJSON(), null, 2)); + const adapter = new OnchainIssuer([copyDefaultEthConnectionConfig]); + const cred = await adapter.getCredential(issuerDid, userDid, BigInt(6)); expect(W3CCredential.fromJSON(balanceCredentialHttpSchema)).to.deep.equal(cred); }); @@ -34,13 +33,13 @@ describe('OnchainIssuer', () => { const issuerDid = DID.parse( 'did:polygonid:polygon:amoy:2qQ68JkRcf3z3923i5rrszrsJ4kdu4GKWARQ5eftsB' ); - const userId = DID.idFromDID( - DID.parse('did:polygonid:polygon:amoy:2qZYiH9CFMoo6oTjSEot3qzkHFHhjLRLKp8yfwCYng') + const userId = DID.parse( + 'did:polygonid:polygon:amoy:2qZYiH9CFMoo6oTjSEot3qzkHFHhjLRLKp8yfwCYng' ); - const adapter = new OnchainIssuer([copyDefaultEthConnectionConfig], issuerDid, { + const adapter = new OnchainIssuer([copyDefaultEthConnectionConfig], { ipfsNodeURL: IPFS_URL }); - const cred = await adapter.getCredential(userId, BigInt(0)); + const cred = await adapter.getCredential(issuerDid, userId, BigInt(0)); expect(W3CCredential.fromJSON(balanceCredentialIpfsSchema)).to.deep.equal(cred); }); }); diff --git a/tests/adapter/onchain-non-merklized-issuer-adapter.test.ts b/tests/adapter/onchain-non-merklized-issuer-adapter.test.ts index a4f9000a..9182d0d1 100644 --- a/tests/adapter/onchain-non-merklized-issuer-adapter.test.ts +++ b/tests/adapter/onchain-non-merklized-issuer-adapter.test.ts @@ -1,7 +1,7 @@ import { OnchainNonMerklizedIssuerAdapter } from '../../src/storage/blockchain/onchain-issuer-adapter/non-merklized/version/v0.0.1/onchain-non-merklized-issuer-adapter'; import { ethers } from 'ethers'; import nock from 'nock'; -import { DID, Id, ChainIds } from '@iden3/js-iden3-core'; +import { DID } from '@iden3/js-iden3-core'; import { NonMerklizedIssuerBaseABI as abi, INonMerklizedIssuer @@ -9,6 +9,7 @@ import { import { expect } from 'chai'; import { W3CCredential } from '../../src/verifiable'; import { IPFS_URL } from '../helpers'; +import { defaultEthConnectionConfig } from '../../src'; // prettier-ignore const w3cHttpSchemaExpect = @@ -20,13 +21,6 @@ const w3cIpfsSchemaExpect = const mockedState = '0x00000000000000000000000000000000000000000000000000000000000000a026ab96fbcfb7a137e7acd773909a14ce77de394252318b5cca4c00131fd95d6f2ec78fb0a9f5898469243a85cc4a2fe97a0a95c00a95c4b50edad7d4ea01a73c00000000000000000000000000000000000000000000000000000000000000000ec7af27591b23ea49b849ddd31c1073bde30753b6cfbb727611a2cffe33a6192ec78fb0a9f5898469243a85cc4a2fe97a0a95c00a95c4b50edad7d4ea01a73c0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000010015bbb043d40cadbd377aeb19ef410cd8adb55a41c63707a628fefa0fac2c3ba126280d1f3ca6d144c0fedf0519644786c96d821dcc947576ecbbb4a175279ab700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000280a6002414282733c9a4a2b7ccf5ad7cd2d9eea2c6cf0c03d7dbac53da6a60577000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; -const chainInfo = (did: DID): { contractAddress: string; chainId: number } => { - const issuerId = DID.idFromDID(did); - const contractAddress = ethers.getAddress(ethers.hexlify(Id.ethAddressFromId(issuerId))); - const chainId = ChainIds[`${DID.blockchainFromId(issuerId)}:${DID.networkIdFromId(issuerId)}`]; - return { contractAddress, chainId }; -}; - describe('Convertor v0.0.1', () => { it('Test adapter with https schema', async () => { nock('http://localhost:8545') @@ -85,12 +79,16 @@ describe('Convertor v0.0.1', () => { const iface = new ethers.Interface(abi); const res = iface.decodeFunctionResult('getCredential', hexResponse); - const { chainId, contractAddress } = chainInfo(issuerDid); - const adapter = new OnchainNonMerklizedIssuerAdapter(contractAddress, { - rpcUrl: 'http://localhost:8545', - chainId: chainId, - issuerDid: issuerDid - }); + const adapter = new OnchainNonMerklizedIssuerAdapter( + { + ...defaultEthConnectionConfig, + url: 'http://localhost:8545', + chainId: 80001 + }, + { + issuerDid: issuerDid + } + ); const w3cCredential = await adapter.convertOnChainInfoToW3CCredential( res[0] as INonMerklizedIssuer.CredentialDataStructOutput, res[1] as bigint[], @@ -155,15 +153,19 @@ describe('Convertor v0.0.1', () => { const iface = new ethers.Interface(abi); const res = iface.decodeFunctionResult('getCredential', hexResponse); - const { chainId, contractAddress } = chainInfo(issuerDid); - const adapter = new OnchainNonMerklizedIssuerAdapter(contractAddress, { - rpcUrl: 'http://localhost:8545', - chainId: chainId, - issuerDid: issuerDid, - merklizationOptions: { - ipfsNodeURL: IPFS_URL + const adapter = new OnchainNonMerklizedIssuerAdapter( + { + ...defaultEthConnectionConfig, + url: 'http://localhost:8545', + chainId: 80002 + }, + { + issuerDid: issuerDid, + merklizationOptions: { + ipfsNodeURL: IPFS_URL + } } - }); + ); const w3cCredential = await adapter.convertOnChainInfoToW3CCredential( res[0] as INonMerklizedIssuer.CredentialDataStructOutput, res[1] as bigint[], diff --git a/tests/handlers/fetch.test.ts b/tests/handlers/fetch.test.ts index 5470539b..d1abef10 100644 --- a/tests/handlers/fetch.test.ts +++ b/tests/handlers/fetch.test.ts @@ -1,5 +1,6 @@ import { CredentialsOfferMessage, + CredentialsOnchainOfferMessage, FetchHandler, IPackageManager, IDataStorage, @@ -23,6 +24,7 @@ import { import { MOCK_STATE_STORAGE, RHS_URL, + RPC_URL, SEED_ISSUER, SEED_USER, createIdentity, @@ -31,6 +33,9 @@ import { registerKeyProvidersInMemoryKMS } from '../helpers'; +import { OnchainIssuer } from '../../src/storage/blockchain/onchain-issuer'; +import { defaultEthConnectionConfig } from '../../src'; + import * as uuid from 'uuid'; import { expect } from 'chai'; import path from 'path'; @@ -137,7 +142,14 @@ describe('fetch', () => { proofService.verifyState.bind(proofService) ); fetchHandler = new FetchHandler(packageMgr, { - credentialWallet: credWallet + credentialWallet: credWallet, + onchainIssuer: new OnchainIssuer([ + { + ...defaultEthConnectionConfig, + url: RPC_URL, + chainId: 80002 + } + ]) }); msgHandler = new MessageHandler({ @@ -287,4 +299,35 @@ describe('fetch', () => { expect(response).to.be.null; expect(await credWallet.list()).to.have.length(5); }); + + it('onchain credential offer', async () => { + const { did: userDID } = await createIdentity(idWallet, { + seed: SEED_ISSUER + }); + + const onchainOffer: CredentialsOnchainOfferMessage = { + id: uuid.v4(), + typ: PROTOCOL_CONSTANTS.MediaType.PlainMessage, + type: PROTOCOL_CONSTANTS.PROTOCOL_MESSAGE_TYPE.CREDENTIAL_ONCHAIN_OFFER_MESSAGE_TYPE, + thid: uuid.v4(), + body: { + credentials: [{ id: '6', description: 'balance credential' }], + transaction_data: { + contract_address: '0x19875eA86503734f2f9Ed461463e0312A3b42563', + method_id: '0', + chain_id: 80002 + } + }, + from: 'did:polygonid:polygon:amoy:2qQ68JkRcf3xyDFsGSWU5QqxbKpzM75quxS628JgvJ', + to: userDID.string() + }; + + const bytes = await packageMgr.packMessage( + PROTOCOL_CONSTANTS.MediaType.PlainMessage, + onchainOffer, + {} + ); + const response = await fetchHandler.handleOnchainOffer(bytes); + console.log(response); + }); });