-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add RevocationStatusHandler * fix W3CCredential fromJSON param
- Loading branch information
1 parent
44f87ca
commit 3dff826
Showing
5 changed files
with
309 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import { PROTOCOL_MESSAGE_TYPE } from '../constants'; | ||
import { MediaType } from '../constants'; | ||
import { | ||
IPackageManager, | ||
JWSPackerParams, | ||
RevocationStatusRequestMessage, | ||
RevocationStatusResponseMessage | ||
} from '../types'; | ||
|
||
import { DID } from '@iden3/js-iden3-core'; | ||
import * as uuid from 'uuid'; | ||
import { RevocationStatus } from '../../verifiable'; | ||
import { TreeState } from '../../circuits'; | ||
import { byteEncoder } from '../../utils'; | ||
import { proving } from '@iden3/js-jwz'; | ||
import { IIdentityWallet } from '../../identity'; | ||
|
||
/** | ||
* Interface that allows the processing of the revocation status | ||
* | ||
* @interface IRevocationStatusHandler | ||
*/ | ||
export interface IRevocationStatusHandler { | ||
/** | ||
* unpacks revocation status request | ||
* @param {Uint8Array} request - raw byte message | ||
* @returns `Promise<RevocationStatusRequestMessage>` | ||
*/ | ||
parseRevocationStatusRequest(request: Uint8Array): Promise<RevocationStatusRequestMessage>; | ||
|
||
/** | ||
* handle revocation status request | ||
* @param {did} did - sender DID | ||
* @param {Uint8Array} request - raw byte message | ||
* @param {RevocationStatusHandlerOptions} opts - handler options | ||
* @returns {Promise<Uint8Array>}` - revocation status response message | ||
*/ | ||
handleRevocationStatusRequest( | ||
did: DID, | ||
request: Uint8Array, | ||
opts?: RevocationStatusHandlerOptions | ||
): Promise<Uint8Array>; | ||
} | ||
|
||
/** RevocationStatusHandlerOptions represents revocation status handler options */ | ||
export type RevocationStatusHandlerOptions = { | ||
mediaType: MediaType; | ||
packerOptions?: JWSPackerParams; | ||
treeState?: TreeState; | ||
}; | ||
|
||
/** | ||
* | ||
* Allows to process RevocationStatusRequest protocol message | ||
* | ||
* @class RevocationStatusHandler | ||
* @implements implements IRevocationStatusHandler interface | ||
*/ | ||
export class RevocationStatusHandler implements IRevocationStatusHandler { | ||
/** | ||
* Creates an instance of RevocationStatusHandler. | ||
* @param {IPackageManager} _packerMgr - package manager to unpack message envelope | ||
* @param {IIdentityWallet} _identityWallet - identity wallet | ||
* | ||
*/ | ||
|
||
constructor( | ||
private readonly _packerMgr: IPackageManager, | ||
private readonly _identityWallet: IIdentityWallet | ||
) {} | ||
|
||
/** | ||
* @inheritdoc IRevocationStatusHandler#parseRevocationStatusRequest | ||
*/ | ||
async parseRevocationStatusRequest(request: Uint8Array): Promise<RevocationStatusRequestMessage> { | ||
const { unpackedMessage: message } = await this._packerMgr.unpack(request); | ||
const ciRequest = message as unknown as RevocationStatusRequestMessage; | ||
if (message.type !== PROTOCOL_MESSAGE_TYPE.REVOCATION_STATUS_REQUEST_MESSAGE_TYPE) { | ||
throw new Error('Invalid media type'); | ||
} | ||
return ciRequest; | ||
} | ||
|
||
/** | ||
* @inheritdoc IRevocationStatusHandler#handleRevocationStatusRequest | ||
*/ | ||
async handleRevocationStatusRequest( | ||
did: DID, | ||
request: Uint8Array, | ||
opts?: RevocationStatusHandlerOptions | ||
): Promise<Uint8Array> { | ||
if (!opts) { | ||
opts = { | ||
mediaType: MediaType.PlainMessage | ||
}; | ||
} | ||
|
||
if (opts.mediaType === MediaType.SignedMessage && !opts.packerOptions) { | ||
throw new Error(`jws packer options are required for ${MediaType.SignedMessage}`); | ||
} | ||
|
||
const rsRequest = await this.parseRevocationStatusRequest(request); | ||
|
||
if (!rsRequest.to) { | ||
throw new Error(`failed request. empty 'to' field`); | ||
} | ||
|
||
if (!rsRequest.from) { | ||
throw new Error(`failed request. empty 'from' field`); | ||
} | ||
|
||
if (!rsRequest.body?.revocation_nonce) { | ||
throw new Error(`failed request. empty 'revocation_nonce' field`); | ||
} | ||
|
||
const issuerDID = DID.parse(rsRequest.to); | ||
|
||
const mtpWithTreeState = await this._identityWallet.generateNonRevocationMtpWithNonce( | ||
issuerDID, | ||
BigInt(rsRequest.body.revocation_nonce), | ||
opts?.treeState | ||
); | ||
const treeState = mtpWithTreeState.treeState; | ||
const revStatus: RevocationStatus = { | ||
issuer: { | ||
state: treeState?.state.string(), | ||
claimsTreeRoot: treeState.claimsRoot.string(), | ||
revocationTreeRoot: treeState.revocationRoot.string(), | ||
rootOfRoots: treeState.rootOfRoots.string() | ||
}, | ||
mtp: mtpWithTreeState.proof | ||
}; | ||
|
||
const packerOpts = | ||
opts.mediaType === MediaType.SignedMessage | ||
? opts.packerOptions | ||
: { | ||
provingMethodAlg: proving.provingMethodGroth16AuthV2Instance.methodAlg | ||
}; | ||
|
||
const senderDID = DID.parse(rsRequest.to); | ||
const guid = uuid.v4(); | ||
|
||
const response: RevocationStatusResponseMessage = { | ||
id: guid, | ||
typ: MediaType.PlainMessage, | ||
type: PROTOCOL_MESSAGE_TYPE.REVOCATION_STATUS_RESPONSE_MESSAGE_TYPE, | ||
thid: rsRequest.thid ?? guid, | ||
body: revStatus, | ||
from: did.string(), | ||
to: rsRequest.from | ||
}; | ||
|
||
return this._packerMgr.pack(opts.mediaType, byteEncoder.encode(JSON.stringify(response)), { | ||
senderDID, | ||
...packerOpts | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import { | ||
IPackageManager, | ||
IdentityWallet, | ||
CredentialWallet, | ||
CredentialStatusResolverRegistry, | ||
RHSResolver, | ||
CredentialStatusType, | ||
FSCircuitStorage, | ||
ProofService, | ||
CircuitId, | ||
IRevocationStatusHandler, | ||
RevocationStatusHandler, | ||
RevocationStatusRequestMessage, | ||
PROTOCOL_CONSTANTS, | ||
byteEncoder | ||
} from '../../src'; | ||
|
||
import { | ||
MOCK_STATE_STORAGE, | ||
getInMemoryDataStorage, | ||
getPackageMgr, | ||
registerBJJIntoInMemoryKMS, | ||
createIdentity, | ||
SEED_USER, | ||
SEED_ISSUER | ||
} from '../helpers'; | ||
|
||
import * as uuid from 'uuid'; | ||
import { expect } from 'chai'; | ||
import path from 'path'; | ||
|
||
describe('revocation status', () => { | ||
let packageMgr: IPackageManager; | ||
let rsHandlerr: IRevocationStatusHandler; | ||
let idWallet: IdentityWallet; | ||
|
||
beforeEach(async () => { | ||
const kms = registerBJJIntoInMemoryKMS(); | ||
const dataStorage = getInMemoryDataStorage(MOCK_STATE_STORAGE); | ||
const circuitStorage = new FSCircuitStorage({ | ||
dirname: path.join(__dirname, '../proofs/testdata') | ||
}); | ||
const resolvers = new CredentialStatusResolverRegistry(); | ||
resolvers.register( | ||
CredentialStatusType.Iden3ReverseSparseMerkleTreeProof, | ||
new RHSResolver(dataStorage.states) | ||
); | ||
const credWallet = new CredentialWallet(dataStorage, resolvers); | ||
idWallet = new IdentityWallet(kms, dataStorage, credWallet); | ||
|
||
const proofService = new ProofService(idWallet, credWallet, circuitStorage, MOCK_STATE_STORAGE); | ||
packageMgr = await getPackageMgr( | ||
await circuitStorage.loadCircuitData(CircuitId.AuthV2), | ||
proofService.generateAuthV2Inputs.bind(proofService), | ||
proofService.verifyState.bind(proofService) | ||
); | ||
rsHandlerr = new RevocationStatusHandler(packageMgr, idWallet); | ||
}); | ||
|
||
it('revocation status works', async () => { | ||
const { did: userDID, credential: cred } = await createIdentity(idWallet, { | ||
seed: SEED_USER | ||
}); | ||
|
||
expect(cred).not.to.be.undefined; | ||
|
||
const { did: issuerDID, credential: issuerAuthCredential } = await createIdentity(idWallet, { | ||
seed: SEED_ISSUER | ||
}); | ||
|
||
expect(issuerAuthCredential).not.to.be.undefined; | ||
const id = uuid.v4(); | ||
const rsReq: RevocationStatusRequestMessage = { | ||
id, | ||
typ: PROTOCOL_CONSTANTS.MediaType.PlainMessage, | ||
type: PROTOCOL_CONSTANTS.PROTOCOL_MESSAGE_TYPE.REVOCATION_STATUS_REQUEST_MESSAGE_TYPE, | ||
thid: id, | ||
body: { | ||
revocation_nonce: 1000 | ||
}, | ||
from: userDID.string(), | ||
to: issuerDID.string() | ||
}; | ||
|
||
const msgBytes = byteEncoder.encode(JSON.stringify(rsReq)); | ||
|
||
await rsHandlerr.handleRevocationStatusRequest(userDID, msgBytes); | ||
}); | ||
|
||
it(`revocation status - no 'from' field`, async () => { | ||
const { did: userDID, credential: cred } = await createIdentity(idWallet, { | ||
seed: SEED_USER | ||
}); | ||
|
||
expect(cred).not.to.be.undefined; | ||
|
||
const { did: issuerDID, credential: issuerAuthCredential } = await createIdentity(idWallet, { | ||
seed: SEED_ISSUER | ||
}); | ||
|
||
expect(issuerAuthCredential).not.to.be.undefined; | ||
const id = uuid.v4(); | ||
const rsReq: RevocationStatusRequestMessage = { | ||
id, | ||
typ: PROTOCOL_CONSTANTS.MediaType.PlainMessage, | ||
type: PROTOCOL_CONSTANTS.PROTOCOL_MESSAGE_TYPE.REVOCATION_STATUS_REQUEST_MESSAGE_TYPE, | ||
thid: id, | ||
body: { | ||
revocation_nonce: 1000 | ||
}, | ||
to: issuerDID.string() | ||
}; | ||
|
||
const msgBytes = byteEncoder.encode(JSON.stringify(rsReq)); | ||
|
||
try { | ||
await rsHandlerr.handleRevocationStatusRequest(userDID, msgBytes); | ||
expect.fail(); | ||
} catch (err: unknown) { | ||
expect((err as Error).message).to.be.equal(`failed request. empty 'from' field`); | ||
} | ||
}); | ||
}); |