Skip to content

Commit

Permalink
support agent revocation status; refactring resolve opts
Browse files Browse the repository at this point in the history
  • Loading branch information
ilya-korotya committed Jul 19, 2023
1 parent 2030824 commit 0f8d1ae
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 48 deletions.
38 changes: 29 additions & 9 deletions src/credentials/credential-wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { JSONSchema } from '../schema-processor';
import * as uuid from 'uuid';
import { CredentialStatusResolverRegistry } from './status/resolver';
import { IssuerResolver } from './status/sparse-merkle-tree';
import { AgentResolver } from './status/agent-revocation';
import { CredentialStatusResolveOptions } from './status/resolver';

// ErrAllClaimsRevoked all claims are revoked.
const ErrAllClaimsRevoked = 'all claims are revoked';
Expand Down Expand Up @@ -166,8 +168,7 @@ export interface ICredentialWallet {
*/
getRevocationStatus(
credStatus: CredentialStatus,
issuerDID: DID,
issuerData: IssuerData
credentialStatusResolveOptions?: CredentialStatusResolveOptions
): Promise<RevocationStatus>;
/**
* Creates a W3C verifiable Credential object
Expand Down Expand Up @@ -224,6 +225,10 @@ export class CredentialWallet implements ICredentialWallet {
CredentialStatusType.SparseMerkleTreeProof,
new IssuerResolver()
);
this._credentialStatusResolverRegistry.register(
CredentialStatusType.Iden3commRevocationStatusV1,
new AgentResolver()
);
}
}

Expand Down Expand Up @@ -269,26 +274,41 @@ export class CredentialWallet implements ICredentialWallet {
}
const issuerDID = DID.parse(cred.issuer);

return await this.getRevocationStatus(cred.credentialStatus, issuerDID, issuerData);
let userDID: DID;
if (!cred.credentialSubject.id) {
userDID = issuerDID;
} else {
try {
if (typeof cred.credentialSubject.id !== 'string') {
throw new Error('credential status `id` is not a string');
}
userDID = DID.parse(cred.credentialSubject.id);
} catch (e) {
throw new Error('credential status `id` is not a valid DID');
}
}

const opts: CredentialStatusResolveOptions = {
issuerData,
issuerDID,
userDID
};
return await this.getRevocationStatus(cred.credentialStatus, opts);
}

/**
* {@inheritDoc ICredentialWallet.getRevocationStatus}
*/
async getRevocationStatus(
credStatus: CredentialStatus,
issuerDID: DID,
issuerData: IssuerData
credentialStatusResolveOptions?: CredentialStatusResolveOptions
): Promise<RevocationStatus> {
const statusResolver = this._credentialStatusResolverRegistry.get(credStatus.type);
if (!statusResolver) {
throw new Error(`credential status resolver does not exist for ${credStatus.type} type`);
}

const cs = await statusResolver.resolve(credStatus, {
issuer: issuerDID,
issuerData: issuerData
});
const cs = await statusResolver.resolve(credStatus, credentialStatusResolveOptions);

return cs;
}
Expand Down
1 change: 1 addition & 0 deletions src/credentials/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ export * from './status/on-chain-revocation';
export * from './status/reverse-sparse-merkle-tree';
export * from './status/sparse-merkle-tree';
export * from './status/resolver';
export * from './status/agent-revocation';
export * from './credential-wallet';
export * from './rhs';
65 changes: 65 additions & 0 deletions src/credentials/status/agent-revocation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { CredentialStatus, RevocationStatus } from '../../verifiable';
import { CredentialStatusResolver, CredentialStatusResolveOptions } from './resolver';
import {
RevocationStatusRequestMessage,
RevocationStatusResponseMessage
} from '../../iden3comm/types';
import { MediaType, PROTOCOL_MESSAGE_TYPE } from '../../iden3comm/constants';
import * as uuid from 'uuid';

export class AgentResolver implements CredentialStatusResolver {
async resolve(
credentialStatus: CredentialStatus,
credentialStatusResolveOptions?: CredentialStatusResolveOptions
): Promise<RevocationStatus> {
if (!credentialStatusResolveOptions?.issuerDID) {
throw new Error('IssuerDID is not set in options');
}
if (!credentialStatusResolveOptions?.userDID) {
throw new Error('UserDID is not set in options');
}

const from = credentialStatusResolveOptions.userDID.toString();
const to = credentialStatusResolveOptions.issuerDID.toString();
const msg = buildRevocationMessageRequest(from, to, credentialStatus.revocationNonce);
const response = await fetch(credentialStatus.id, {
method: 'POST',
body: JSON.stringify(msg),
headers: {
'Content-Type': 'application/json'
}
});
const agentResponse = await response.json();
return Object.assign(new RevocationStatusAgent(), { agentResponse }).toRevocationStatus();
}
}

function buildRevocationMessageRequest(
from: string,
to: string,
revocationNonce: number
): RevocationStatusRequestMessage {
const revocationStatusRequestMessage: RevocationStatusRequestMessage = {
id: uuid.v4(),
typ: MediaType.PlainMessage,
type: PROTOCOL_MESSAGE_TYPE.REVOCATION_STATUS_REQUEST_MESSAGE_TYPE,
body: {
revocation_nonce: revocationNonce
},
thid: uuid.v4(),
from: from,
to: to
};
return revocationStatusRequestMessage;
}

class RevocationStatusAgent {
agentResponse: RevocationStatusResponseMessage;

toRevocationStatus(): RevocationStatus {
return {
mtp: this.agentResponse.body.mtp,
issuer: this.agentResponse.body.issuer
};
}
}
17 changes: 9 additions & 8 deletions src/credentials/status/on-chain-revocation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { RevocationStatus, CredentialStatus } from '../../verifiable';
import { EthConnectionConfig } from '../../storage/blockchain';
import { CredentialStatusResolver } from './resolver';
import { CredentialStatusResolver, CredentialStatusResolveOptions } from './resolver';
import { OnChainRevocationStorage } from '../../storage/blockchain/onchain-revocation';
import { DID } from '@iden3/js-iden3-core';

Expand All @@ -23,11 +23,12 @@ export class OnChainResolver implements CredentialStatusResolver {

async resolve(
credentialStatus: CredentialStatus,
opts: {
issuer: DID;
}
credentialStatusResolveOptions?: CredentialStatusResolveOptions
): Promise<RevocationStatus> {
return this.getRevocationOnChain(credentialStatus, opts.issuer);
if (!credentialStatusResolveOptions?.issuerDID) {
throw new Error('IssuerDID is not set in options');
}
return this.getRevocationOnChain(credentialStatus, credentialStatusResolveOptions.issuerDID);
}

/**
Expand Down Expand Up @@ -74,8 +75,8 @@ export class OnChainResolver implements CredentialStatusResolver {
throw new Error('revocationNonce not found');
}

const issuerDID = id.split('/')[0];
if (!issuerDID) {
const issuer = id.split('/')[0];
if (!issuer) {
throw new Error('issuer not found in credentialStatus id');
}
// TODO (illia-korotia): after merging core v2 need to parse contract address from did if `contractAddress` is not present in id as param
Expand All @@ -89,7 +90,7 @@ export class OnChainResolver implements CredentialStatusResolver {
const chainId = parseInt(parts[0], 10);
const contractAddress = parts[1];

return { contractAddress, chainId, revocationNonce, issuer: issuerDID };
return { contractAddress, chainId, revocationNonce, issuer };
}

networkByChainId(chainId: number): EthConnectionConfig {
Expand Down
19 changes: 17 additions & 2 deletions src/credentials/status/resolver.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
import { CredentialStatusType, CredentialStatus, RevocationStatus } from '../../verifiable';
import { DID } from '@iden3/js-iden3-core';
import {
IssuerData,
RevocationStatus,
CredentialStatus,
CredentialStatusType
} from '../../verifiable';

export type CredentialStatusResolveOptions = {
issuerData?: IssuerData;
issuerDID?: DID;
userDID?: DID;
};

export interface CredentialStatusResolver {
resolve(credentialStatus: CredentialStatus, opts?: object): Promise<RevocationStatus>;
resolve(
credentialStatus: CredentialStatus,
opts?: CredentialStatusResolveOptions
): Promise<RevocationStatus>;
}

export class CredentialStatusResolverRegistry {
Expand Down
39 changes: 20 additions & 19 deletions src/credentials/status/reverse-sparse-merkle-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import {
testBit
} from '@iden3/js-merkletree';
import { IStateStorage } from '../../storage';
import { CredentialStatusResolver } from './resolver';
import { CredentialStatus, IssuerData, RevocationStatus } from '../../verifiable';
import { CredentialStatusResolver, CredentialStatusResolveOptions } from './resolver';
import { CredentialStatus, RevocationStatus } from '../../verifiable';
import { strMTHex } from '../../circuits';
import { VerifiableConstants, CredentialStatusType } from '../../verifiable/constants';

Expand Down Expand Up @@ -104,12 +104,9 @@ export class RHSResolver implements CredentialStatusResolver {

public async getStatus(
credentialStatus: CredentialStatus,
opts: {
issuer: DID;
issuerData?: IssuerData;
}
issuerDID: DID
): Promise<RevocationStatus> {
const latestStateInfo = await this._state.getLatestStateById(opts.issuer.id.bigInt());
const latestStateInfo = await this._state.getLatestStateById(issuerDID.id.bigInt());
const hashedRevNonce = newHashFromBigInt(BigInt(credentialStatus.revocationNonce ?? 0));
const hashedIssuerRoot = newHashFromBigInt(BigInt(latestStateInfo?.state ?? 0));
return await this.getRevocationStatusFromRHS(
Expand All @@ -119,29 +116,33 @@ export class RHSResolver implements CredentialStatusResolver {
);
}

public async resolve(
async resolve(
credentialStatus: CredentialStatus,
opts: {
issuer: DID;
issuerData?: IssuerData;
}
credentialStatusResolveOptions?: CredentialStatusResolveOptions
): Promise<RevocationStatus> {
if (!credentialStatusResolveOptions?.issuerDID) {
throw new Error('IssuerDID is not set in options');
}

try {
return await this.getStatus(credentialStatus, opts);
return await this.getStatus(credentialStatus, credentialStatusResolveOptions.issuerDID);
} catch (e) {
const errMsg = e.reason ?? e.message;
if (
!!opts.issuerData &&
!!credentialStatusResolveOptions.issuerData &&
errMsg.includes(VerifiableConstants.ERRORS.IDENTITY_DOES_NOT_EXIST) &&
isIssuerGenesis(opts.issuer.toString(), opts.issuerData.state.value)
isIssuerGenesis(
credentialStatusResolveOptions.issuerDID.toString(),
credentialStatusResolveOptions.issuerData.state.value
)
) {
return {
mtp: new Proof(),
issuer: {
state: opts.issuerData.state.value,
revocationTreeRoot: opts.issuerData.state.revocationTreeRoot,
rootOfRoots: opts.issuerData.state.rootOfRoots,
claimsTreeRoot: opts.issuerData.state.claimsTreeRoot
state: credentialStatusResolveOptions.issuerData.state.value,
revocationTreeRoot: credentialStatusResolveOptions.issuerData.state.revocationTreeRoot,
rootOfRoots: credentialStatusResolveOptions.issuerData.state.rootOfRoots,
claimsTreeRoot: credentialStatusResolveOptions.issuerData.state.claimsTreeRoot
}
};
}
Expand Down
3 changes: 2 additions & 1 deletion src/iden3comm/handlers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,12 @@ export class AuthHandler implements IAuthHandler {
if (credsForGenesisDID.length == 0) {
throw new Error(`no credential were issued on the given id ${did.toString()}`);
}
const { cred } = await this._credentialWallet.findNonRevokedCredential(credsForGenesisDID);

const zkpRes: ZeroKnowledgeProofResponse = await this._proofService.generateProof(
zkpReq,
did,
credsForGenesisDID[0]
cred
);

authResponse.body.scope.push(zkpRes);
Expand Down
3 changes: 2 additions & 1 deletion src/iden3comm/types/protocol/revocation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { MediaType } from '../../constants';
export type RevocationStatusRequestMessage = {
id: string;
typ?: MediaType;
type: RTCIceProtocol;
type: ProtocolMessage;
thid?: string;
body?: RevocationStatusRequestMessageBody;
from?: string;
to?: string;
Expand Down
26 changes: 21 additions & 5 deletions src/proof/proof-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
TreeState,
ValueProof
} from '../circuits';
import { ICredentialWallet } from '../credentials';
import { CredentialStatusResolveOptions, ICredentialWallet } from '../credentials';
import { IIdentityWallet } from '../identity';
import {
createVerifiablePresentation,
Expand Down Expand Up @@ -572,15 +572,31 @@ export class ProofService implements IProofService {

if (sigProof) {
const signature = await bJJSignatureFromHexString(sigProof.signature);
const issuer = DID.parse(sigProof.issuerData.id);
const issuerDID = DID.parse(sigProof.issuerData.id);
let userDID: DID;
if (!credential.credentialSubject.id) {
userDID = issuerDID;
} else {
try {
if (typeof credential.credentialSubject.id !== 'string') {
throw new Error('credential status `id` is not a string');
}
userDID = DID.parse(credential.credentialSubject.id);
} catch (e) {
throw new Error('credential status `id` is not a valid DID');
}
}

const opts: CredentialStatusResolveOptions = {
issuerData: sigProof.issuerData,
issuerDID,
userDID
};
const rs: RevocationStatus = await this._credentialWallet.getRevocationStatus(
sigProof.issuerData.credentialStatus,
issuer,
sigProof.issuerData
opts
);

//todo: check if this is correct
const issuerAuthNonRevProof: MTProof = {
treeState: {
state: strMTHex(rs.issuer.state),
Expand Down
6 changes: 3 additions & 3 deletions tests/rhs/rhs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ describe('rhs', () => {
const rhsResolver = new RHSResolver(mockStateStorageForGenesisState);

return rhsResolver
.getStatus(credRHSStatus, { issuer: issuerDID })
.getStatus(credRHSStatus, issuerDID)
.then(function (m) {
throw new Error('was not supposed to succeed');
})
Expand Down Expand Up @@ -262,7 +262,7 @@ describe('rhs', () => {
await idWallet.publishStateToRHS(issuerDID, rhsUrl);

const rhsResolver = new RHSResolver(mockStateStorageForDefinedState);
const rhsStatus = await rhsResolver.resolve(credRHSStatus, { issuer: issuerDID });
const rhsStatus = await rhsResolver.resolve(credRHSStatus, { issuerDID: issuerDID });

expect(rhsStatus.issuer.state).to.equal(res.newTreeState.state.hex());
expect(rhsStatus.issuer.claimsTreeRoot).to.equal(res.newTreeState.claimsRoot.hex());
Expand Down Expand Up @@ -369,7 +369,7 @@ describe('rhs', () => {
// state is published to blockchain (2)

const rhsResolver = new RHSResolver(dataStorage.states);
const rhsStatus = await rhsResolver.getStatus(credRHSStatus, { issuer: issuerDID });
const rhsStatus = await rhsResolver.getStatus(credRHSStatus, issuerDID);

expect(rhsStatus.issuer.state).to.equal(latestTree.state.hex());
expect(rhsStatus.issuer.claimsTreeRoot).to.equal((await latestTree.claimsTree.root()).hex());
Expand Down

0 comments on commit 0f8d1ae

Please sign in to comment.