Skip to content

Commit

Permalink
Merge branch 'main' into feature/contract-request-handler
Browse files Browse the repository at this point in the history
  • Loading branch information
volodymyr-basiuk committed Oct 3, 2023
2 parents 1311369 + 4cbc378 commit b5bdee4
Show file tree
Hide file tree
Showing 17 changed files with 998 additions and 307 deletions.
23 changes: 23 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@microsoft/api-extractor": "^7.9.0",
"@types/chai": "^4.3.4",
"@types/chai-as-promised": "^7.1.5",
"@types/chai-spies": "^1.0.4",
"@types/elliptic": "^6.4.14",
"@types/fs-extra": "^11.0.1",
"@types/jsonld": "^1.5.9",
Expand All @@ -55,6 +56,7 @@
"@typescript-eslint/eslint-plugin": "^5.41.0",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
"chai-spies": "^1.0.0",
"chokidar": "^3.5.3",
"esbuild": "^0.15.15",
"eslint-config-prettier": "^8.8.0",
Expand Down
46 changes: 36 additions & 10 deletions src/credentials/credential-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,15 @@ export interface CredentialRequest {
* id: string;
* nonce?: number;
* type: CredentialStatusType;
* issuerState?: string;
* }}
* @memberof CredentialRequest
*/
revocationOpts: {
id: string;
nonce?: number;
type: CredentialStatusType;
issuerState?: string;
};
}

Expand Down Expand Up @@ -342,19 +344,43 @@ export class CredentialWallet implements ICredentialWallet {
type: VerifiableConstants.JSON_SCHEMA_VALIDATOR
};

const id =
request.revocationOpts.type === CredentialStatusType.SparseMerkleTreeProof
? `${request.revocationOpts.id.replace(/\/$/, '')}/${request.revocationOpts.nonce}`
: request.revocationOpts.id;

cr.credentialStatus = {
id,
revocationNonce: request.revocationOpts.nonce,
type: request.revocationOpts.type
};
cr.credentialStatus = this.buildCredentialStatus(request);

return cr;
};

/**
* Builds credential status
* @param {CredentialRequest} request
* @returns `CredentialStatus`
*/
private buildCredentialStatus(request: CredentialRequest): CredentialStatus {
const credentialStatus: CredentialStatus = {
id: request.revocationOpts.id,
type: request.revocationOpts.type,
revocationNonce: request.revocationOpts.nonce
};

switch (request.revocationOpts.type) {
case CredentialStatusType.SparseMerkleTreeProof:
return {
...credentialStatus,
id: `${credentialStatus.id.replace(/\/$/, '')}/${credentialStatus.revocationNonce}`
};
case CredentialStatusType.Iden3ReverseSparseMerkleTreeProof:
return {
...credentialStatus,
id: request.revocationOpts.issuerState
? `${credentialStatus.id.replace(/\/$/, '')}/node?state=${
request.revocationOpts.issuerState
}`
: `${credentialStatus.id.replace(/\/$/, '')}`
};
default:
return credentialStatus;
}
}

/**
* {@inheritDoc ICredentialWallet.findById}
*/
Expand Down
1 change: 1 addition & 0 deletions src/credentials/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ export * from './status/reverse-sparse-merkle-tree';
export * from './status/sparse-merkle-tree';
export * from './status/resolver';
export * from './status/agent-revocation';
export * from './status/utils';
export * from './credential-wallet';
export * from './rhs';
105 changes: 73 additions & 32 deletions src/credentials/status/on-chain-revocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@ import { EthConnectionConfig } from '../../storage/blockchain';
import { CredentialStatusResolver, CredentialStatusResolveOptions } from './resolver';
import { OnChainRevocationStorage } from '../../storage/blockchain/onchain-revocation';
import { DID, Id } from '@iden3/js-iden3-core';
import { getChainIdByDIDsParts } from '../../storage/blockchain';
import { utils } from 'ethers';

import { VerifiableConstants } from '../../verifiable/constants';
import { isGenesisState } from './utils';
import { newHashFromHex } from '@iden3/js-merkletree';
import { EthStateStorage } from '../../../src/storage/blockchain/state';
import { getChainId } from '../../storage/blockchain';
import { IStateStorage, IOnchainRevocationStore } from '../../storage';
/**
* OnChainIssuer is a class that allows to interact with the onchain contract
* and build the revocation status.
Expand All @@ -18,8 +21,7 @@ export class OnChainResolver implements CredentialStatusResolver {
*
* Creates an instance of OnChainIssuer.
* @public
* @param {Array<EthConnectionConfig>} - onchain contract address
* @param {string} - list of EthConnectionConfig
* @param {Array<EthConnectionConfig>} _configs - list of ethereum network connections
*/
constructor(private readonly _configs: EthConnectionConfig[]) {}

Expand Down Expand Up @@ -52,15 +54,48 @@ export class OnChainResolver implements CredentialStatusResolver {
credentialStatus: CredentialStatus,
issuer: DID
): Promise<RevocationStatus> {
const { contractAddress, chainId, revocationNonce } =
const { contractAddress, chainId, revocationNonce, stateHex } =
this.extractCredentialStatusInfo(credentialStatus);
if (revocationNonce !== credentialStatus.revocationNonce) {
throw new Error('revocationNonce does not match');
}
const networkConfig = this.networkByChainId(chainId);
const onChainCaller = new OnChainRevocationStorage(networkConfig, contractAddress);

const issuerId = DID.idFromDID(issuer);
let latestIssuerState: bigint;
try {
const ethStorage = this._getStateStorageForIssuer(issuerId);
const latestStateInfo = await ethStorage.getLatestStateById(issuerId.bigInt());
if (!latestStateInfo.state) {
throw new Error('state contract returned empty state');
}
latestIssuerState = latestStateInfo.state;
} catch (e) {
const errMsg = (e as { reason: string })?.reason ?? (e as Error).message ?? (e as string);
if (!errMsg.includes(VerifiableConstants.ERRORS.IDENTITY_DOES_NOT_EXIST)) {
throw e;
}

if (!stateHex) {
throw new Error(
'latest state not found and state parameter is not present in credentialStatus.id'
);
}
const stateBigInt = newHashFromHex(stateHex).bigInt();
if (!isGenesisState(issuer, stateBigInt)) {
throw new Error(
`latest state not found and state parameter ${stateHex} is not genesis state`
);
}
latestIssuerState = stateBigInt;
}

const id = DID.idFromDID(issuer);
const revocationStatus = await onChainCaller.getRevocationStatus(id.bigInt(), revocationNonce);
const onChainCaller = this._getOnChainRevocationStorageForIssuer(chainId, contractAddress);
const revocationStatus = await onChainCaller.getRevocationStatusByIdAndState(
id.bigInt(),
latestIssuerState,
revocationNonce
);
return revocationStatus;
}

Expand All @@ -74,7 +109,7 @@ export class OnChainResolver implements CredentialStatusResolver {
contractAddress: string;
chainId: number;
revocationNonce: number;
issuer: string;
stateHex: string;
} {
if (!credentialStatus.id) {
throw new Error('credentialStatus id is empty');
Expand All @@ -85,29 +120,18 @@ export class OnChainResolver implements CredentialStatusResolver {
throw new Error('invalid credentialStatus id');
}

const issuer = idParts[0];
const issuerDID = DID.parse(issuer);

const idURL = new URL(credentialStatus.id);

// if contractAddress is not present in id as param, then it should be parsed from DID
let contractAddress = idURL.searchParams.get('contractAddress');
let chainId: number;
if (!contractAddress) {
const issuerId = DID.idFromDID(issuerDID);
const ethAddr = Id.ethAddressFromId(issuerId);
contractAddress = utils.getAddress(utils.hexDataSlice(ethAddr, 0));
const blockchain = DID.blockchainFromId(issuerId);
const network = DID.networkIdFromId(issuerId);
chainId = getChainIdByDIDsParts(issuerDID.method, blockchain, network);
} else {
const parts = contractAddress.split(':');
if (parts.length != 2) {
throw new Error('invalid contract address encoding. should be chainId:contractAddress');
}
chainId = parseInt(parts[0], 10);
contractAddress = parts[1];
const stateHex = idURL.searchParams.get('state') || '';
const contractIdentifier = idURL.searchParams.get('contractAddress');
if (!contractIdentifier) {
throw new Error('contractAddress not found in credentialStatus.id field');
}
const parts = contractIdentifier.split(':');
if (parts.length != 2) {
throw new Error('invalid contract address encoding. should be chainId:contractAddress');
}
const chainId = parseInt(parts[0], 10);
const contractAddress = parts[1];

// if revocationNonce is not present in id as param, then it should be extract from credentialStatus
const rv = idURL.searchParams.get('revocationNonce') || credentialStatus.revocationNonce;
Expand All @@ -116,7 +140,7 @@ export class OnChainResolver implements CredentialStatusResolver {
}
const revocationNonce = typeof rv === 'number' ? rv : parseInt(rv, 10);

return { contractAddress, chainId, revocationNonce, issuer };
return { contractAddress, chainId, revocationNonce, stateHex };
}

networkByChainId(chainId: number): EthConnectionConfig {
Expand All @@ -126,4 +150,21 @@ export class OnChainResolver implements CredentialStatusResolver {
}
return network;
}

// TODO (illia-korotia): is dirty hack for mock in tests.
// need to pass to constructor list of state stores not list of network configs
private _getStateStorageForIssuer(issuerId: Id): IStateStorage {
const issuerChainId = getChainId(DID.blockchainFromId(issuerId), DID.networkIdFromId(issuerId));
const ethStorage = new EthStateStorage(this.networkByChainId(issuerChainId));
return ethStorage;
}

private _getOnChainRevocationStorageForIssuer(
chainId: number,
contractAddress: string
): IOnchainRevocationStore {
const networkConfig = this.networkByChainId(chainId);
const onChainCaller = new OnChainRevocationStorage(networkConfig, contractAddress);
return onChainCaller;
}
}
Loading

0 comments on commit b5bdee4

Please sign in to comment.