Skip to content

Commit

Permalink
Refactor and extend verifier proof api, add nodejs tests
Browse files Browse the repository at this point in the history
Signed-off-by: Patrik Stas <patrik.stas@absa.africa>
  • Loading branch information
Patrik-Stas committed Mar 2, 2023
1 parent 4e83b6e commit 6783636
Show file tree
Hide file tree
Showing 21 changed files with 287 additions and 124 deletions.
19 changes: 10 additions & 9 deletions agents/node/vcxagent-core/demo/faber.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,30 +111,31 @@ async function runFaber (options) {
}

logger.info('#27 Process the proof provided by alice.')
const { proofState, proof } = await vcxProof.getProof()
logger.info(`#27 Proof: proofState=${proofState}, proof=${proof}`)
assert(proofState)
assert(proof)
const presentation = vcxProof.getPresentationMsg()
const verificationState = vcxProof.getPresentationVerificationState()
logger.info(`#27 Proof: proofState=${verificationState}, proof=${presentation}`)
assert(verificationState)
assert(presentation)
logger.info(`Proof protocol state = ${JSON.stringify(proofProtocolState)}`)
logger.info(`Proof verification state =${proofState}`)
logger.debug(`Proof presentation = ${JSON.stringify(proof, null, 2)}`)
logger.info(`Proof verification state =${verificationState}`)
logger.debug(`Proof presentation = ${JSON.stringify(presentation, null, 2)}`)
logger.debug(`Serialized Proof state machine ${JSON.stringify(await vcxProof.serialize())}`)

if (proofState === ProofState.Verified) {
if (verificationState === ProofState.Verified) {
if (options.revocation) {
throw Error('Proof was verified, but was expected to be invalid, because revocation was enabled.')
} else {
logger.info('Proof was verified.')
}
} else if (proofState === ProofState.Invalid) {
} else if (verificationState === ProofState.Invalid) {
if (options.revocation) {
logger.info('Proof was determined as invalid, which was expected because the used credential was revoked.')
} else {
throw Error('Proof was invalid, but was expected to be verified. Revocation was not enabled.')
}
await sleepPromise(1000)
} else {
logger.error(`Unexpected proof state '${proofState}'.`)
logger.error(`Unexpected proof state '${verificationState}'.`)
process.exit(-1)
}

Expand Down
17 changes: 10 additions & 7 deletions agents/node/vcxagent-core/src/agent.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
const { getLedgerAuthorAgreement, setActiveTxnAuthorAgreementMeta, defaultLogger } = require('@hyperledger/node-vcx-wrapper')
const {
getLedgerAuthorAgreement,
setActiveTxnAuthorAgreementMeta
} = require('@hyperledger/node-vcx-wrapper')
const { createServiceLedgerCredDef } = require('./services/service-ledger-creddef')
const { createServiceLedgerSchema } = require('./services/service-ledger-schema')
const { createServiceVerifier } = require('./services/service-verifier')
Expand All @@ -21,7 +24,7 @@ const {
const { createStorageService } = require('./storage/storage-service')
const { waitUntilAgencyIsReady, getAgencyConfig } = require('./common')

async function createVcxAgent({ agentName, genesisPath, agencyUrl, seed, walletExtraConfigs, endpointInfo, logger }) {
async function createVcxAgent ({ agentName, genesisPath, agencyUrl, seed, walletExtraConfigs, endpointInfo, logger }) {
genesisPath = genesisPath || `${__dirname}/../resources/docker.txn`

await waitUntilAgencyIsReady(agencyUrl, logger)
Expand All @@ -35,7 +38,7 @@ async function createVcxAgent({ agentName, genesisPath, agencyUrl, seed, walletE
const agentProvision = await storageService.loadAgentProvision()
const issuerDid = agentProvision.issuerConfig.institution_did

async function agentInitVcx() {
async function agentInitVcx () {
logger.info(`Initializing ${agentName} vcx session.`)

logger.silly(`Using following agent provision to initialize VCX settings ${JSON.stringify(agentProvision, null, 2)}`)
Expand All @@ -49,17 +52,17 @@ async function createVcxAgent({ agentName, genesisPath, agencyUrl, seed, walletE
await openMainPool({ genesis_path: genesisPath })
}

async function agentShutdownVcx() {
async function agentShutdownVcx () {
logger.debug(`Shutting down ${agentName} vcx session.`)
shutdownVcx()
}

async function updateWebhookUrl(webhookUrl) {
async function updateWebhookUrl (webhookUrl) {
logger.info(`Updating webhook url to ${webhookUrl}`)
await vcxUpdateWebhookUrl({ webhookUrl })
}

async function acceptTaa() {
async function acceptTaa () {
const taa = await getLedgerAuthorAgreement()
const taaJson = JSON.parse(taa)
const utime = Math.floor(new Date() / 1000)
Expand All @@ -68,7 +71,7 @@ async function createVcxAgent({ agentName, genesisPath, agencyUrl, seed, walletE
await setActiveTxnAuthorAgreementMeta(taaJson.text, taaJson.version, null, acceptanceMechanism, utime)
}

function getInstitutionDid() {
function getInstitutionDid () {
return issuerDid
}

Expand Down
5 changes: 0 additions & 5 deletions agents/node/vcxagent-core/src/services/service-prover.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,6 @@ module.exports.createServiceProver = function createServiceProver ({ logger, loa
}
}

async function getVcxDisclosedProof (disclosedProofId) {
logger.warn('Usage of getVcxDisclosedProof is not recommended. You should use vcxagent-core API rather than work with vcx object directly.')
return loadDisclosedProof(disclosedProofId)
}

return {
generateProof,
selectCredentials,
Expand Down
26 changes: 22 additions & 4 deletions agents/node/vcxagent-core/src/services/service-verifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,24 @@ module.exports.createServiceVerifier = function createServiceVerifier ({ logger,
return await proof.getState()
}

async function getProofState (proofId) {
async function getPresentationMsg (proofId) {
const proof = await loadProof(proofId)
const { proofState } = await proof.getProof()
return proofState
return proof.getPresentationMsg()
}

async function getPresentationAttachment (proofId) {
const proof = await loadProof(proofId)
return proof.getPresentationAttachment()
}

async function getPresentationRequestAttachment (proofId) {
const proof = await loadProof(proofId)
return proof.getPresentationRequestAttachment()
}

async function getPresentationVerificationState (proofId) {
const proof = await loadProof(proofId)
return proof.getPresentationVerificationState()
}

async function listIds () {
Expand Down Expand Up @@ -84,6 +98,10 @@ module.exports.createServiceVerifier = function createServiceVerifier ({ logger,
listIds,
printInfo,
getState,
getProofState

getPresentationMsg,
getPresentationAttachment,
getPresentationRequestAttachment,
getPresentationVerificationState
}
}
14 changes: 8 additions & 6 deletions agents/node/vcxagent-core/test/distribute-tails.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const uuid = require('uuid')
const sleep = require('sleep-promise')
const fs = require('fs')
const mkdirp = require('mkdirp')
const path = require('path')
const { proofRequestDataStandard } = require('./utils/data')

beforeAll(async () => {
jest.setTimeout(1000 * 60 * 4)
Expand All @@ -24,7 +26,7 @@ describe('test tails distribution', () => {
const port = 5468
const tailsUrlId = uuid.v4()
const tailsUrl = `http://127.0.0.1:${port}/${tailsUrlId}`
await faber.buildLedgerPrimitives(buildRevocationDetails({ supportRevocation: true, tailsDir: `${__dirname}/tmp/faber/tails`, maxCreds: 5, tailsUrl }))
await faber.buildLedgerPrimitives(buildRevocationDetails({ supportRevocation: true, tailsDir: path.join(__dirname, '/tmp/faber/tails'), maxCreds: 5, tailsUrl }))
await faber.sendCredentialOffer()
await alice.acceptCredentialOffer()
await faber.updateStateCredential(IssuerStateType.RequestReceived)
Expand All @@ -33,20 +35,20 @@ describe('test tails distribution', () => {

const faberTailsHash = await faber.getTailsHash()
const app = express()
app.use(`/${tailsUrlId}`, express.static(`${__dirname}/tmp/faber/tails/${faberTailsHash}`))
app.use(`/${tailsUrlId}`, express.static(path.join(__dirname, `/tmp/faber/tails/${faberTailsHash}`)))
server = app.listen(port)

const aliceTailsLocation = await alice.getTailsLocation()
const aliceTailsHash = await alice.getTailsHash()
const aliceTailsFileDir = `${__dirname}/tmp/alice/tails`
const aliceTailsFileDir = path.join(__dirname, '/tmp/alice/tails')
const aliceTailsFilePath = aliceTailsFileDir + `/${aliceTailsHash}`
await mkdirp(aliceTailsFileDir)
axios.default.get(`${aliceTailsLocation}`, { responseType: 'stream' }).then(res => {
res.data.pipe(fs.createWriteStream(aliceTailsFilePath))
})

const request = await faber.requestProofFromAlice()
await alice.sendHolderProof(JSON.parse(request), revRegId => aliceTailsFileDir)
const issuerDid = faber.getFaberDid()
const request = await faber.requestProofFromAlice(proofRequestDataStandard(issuerDid))
await alice.sendHolderProof(JSON.parse(request), revRegId => aliceTailsFileDir, { attribute_3: 'Smith' })
await faber.updateStateVerifierProof(VerifierStateType.Finished)
await alice.updateStateHolderProof(ProverStateType.Finished)
} catch (err) {
Expand Down
78 changes: 62 additions & 16 deletions agents/node/vcxagent-core/test/issue-verify.spec.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,82 @@
/* eslint-env jest */
require('jest')
const { createPairedAliceAndFaber } = require('./utils/utils')
const { IssuerStateType, HolderStateType, ProverStateType, VerifierStateType } = require('@hyperledger/node-vcx-wrapper')
const { IssuerStateType, HolderStateType, ProverStateType, VerifierStateType, ProofState } = require('@hyperledger/node-vcx-wrapper')
const sleep = require('sleep-promise')
const { initRustLogger } = require('../src')
const { proofRequestDataStandard, proofRequestDataSelfAttest } = require('./utils/data')
const path = require('path')

beforeAll(async () => {
jest.setTimeout(1000 * 60 * 4)
initRustLogger(process.env.RUST_LOG || 'vcx=error')
})

afterAll(async () => {
await sleep(500)
})

describe('test update state', () => {
it('Faber should send credential to Alice', async () => {
try {
const { alice, faber } = await createPairedAliceAndFaber()
const tailsDir = `${__dirname}/tmp/faber/tails`
await faber.buildLedgerPrimitives({ tailsDir, maxCreds: 5 })
await faber.rotateRevReg(tailsDir, 5)
await faber.sendCredentialOffer()
await alice.acceptCredentialOffer()
it('Faber should issue credential, verify proof', async () => {
const { alice, faber } = await createPairedAliceAndFaber()
const issuerDid = faber.getDi
const tailsDir = path.join(__dirname, '/tmp/faber/tails')
await faber.buildLedgerPrimitives({ tailsDir, maxCreds: 5 })
await faber.rotateRevReg(tailsDir, 5)
await faber.sendCredentialOffer()
await alice.acceptCredentialOffer()

await faber.updateStateCredential(IssuerStateType.RequestReceived)
await faber.sendCredential()
await alice.updateStateCredential(HolderStateType.Finished)
await faber.receiveCredentialAck()

await faber.updateStateCredential(IssuerStateType.RequestReceived)
await faber.sendCredential()
await alice.updateStateCredential(HolderStateType.Finished)
await faber.receiveCredentialAck()
const request = await faber.requestProofFromAlice(proofRequestDataStandard(issuerDid))
await alice.sendHolderProof(JSON.parse(request), revRegId => tailsDir, { attribute_3: 'Smith' })
await faber.updateStateVerifierProof(VerifierStateType.Finished)
await alice.updateStateHolderProof(ProverStateType.Finished)
const { presentationVerificationState, presentationAttachment, presentationRequestAttachment } = await faber.getPresentationInfo()
expect(presentationVerificationState).toBe(ProofState.Verified)
// console.log(`presentationMsg = ${JSON.stringify(presentationMsg)}`)
// console.log(`presentationAttachment = ${JSON.stringify(presentationAttachment)}`)
// console.log(`presentationRequestAttachment = ${JSON.stringify(presentationRequestAttachment)}`)
expect(presentationRequestAttachment.requested_attributes.attribute_0.names).toStrictEqual(['name', 'last_name', 'sex'])
expect(presentationRequestAttachment.requested_attributes.attribute_1.name).toBe('date')
expect(presentationRequestAttachment.requested_attributes.attribute_2.name).toBe('degree')
expect(presentationRequestAttachment.requested_attributes.attribute_2.restrictions['attr::degree::value']).toBe('maths')
expect(presentationRequestAttachment.requested_attributes.attribute_3.name).toBe('nickname')
expect(presentationRequestAttachment.requested_attributes.attribute_3.self_attest_allowed).toBe(true)

const request = await faber.requestProofFromAlice()
await alice.sendHolderProof(JSON.parse(request), revRegId => tailsDir)
expect(presentationAttachment).toBeDefined()
expect(presentationAttachment.requested_proof).toBeDefined()
expect(presentationAttachment.requested_proof.revealed_attrs).toBeDefined()
expect(presentationAttachment.requested_proof.revealed_attrs.attribute_1.raw).toBe('05-2018')
expect(presentationAttachment.requested_proof.revealed_attrs.attribute_2.raw).toBe('maths')
expect(presentationAttachment.requested_proof.revealed_attr_groups.attribute_0.values.name.raw).toBe('alice')
expect(presentationAttachment.requested_proof.revealed_attr_groups.attribute_0.values.sex.raw).toBe('female')
expect(presentationAttachment.requested_proof.revealed_attr_groups.attribute_0.values.last_name.raw).toBe('clark')
expect(presentationAttachment.requested_proof.predicates.predicate_0).toBeDefined()
expect(presentationAttachment.requested_proof.self_attested_attrs.attribute_3).toBe('Smith')
})

it('Faber should verify proof with self attestation', async () => {
try {
const { alice, faber } = await createPairedAliceAndFaber()
const request = await faber.requestProofFromAlice(proofRequestDataSelfAttest())
await alice.sendHolderProofSelfAttested(JSON.parse(request), { attribute_0: 'Smith' })
await faber.updateStateVerifierProof(VerifierStateType.Finished)
await alice.updateStateHolderProof(ProverStateType.Finished)
const { presentationVerificationState, presentationAttachment, presentationRequestAttachment } = await faber.getPresentationInfo()
expect(presentationVerificationState).toBe(ProofState.Verified)
// console.log(`presentationMsg = ${JSON.stringify(presentationMsg)}`)
// console.log(`presentationAttachment = ${JSON.stringify(presentationAttachment)}`)
// console.log(`presentationRequestAttachment = ${JSON.stringify(presentationRequestAttachment)}`)
expect(presentationAttachment.requested_proof.self_attested_attrs.attribute_0).toBe('Smith')
expect(presentationRequestAttachment.requested_attributes.attribute_0.name).toBe('nickname')
expect(presentationRequestAttachment.requested_attributes.attribute_0.self_attest_allowed).toBe(true)
await sleep(500)
} catch (err) {
await sleep(2000)
await sleep(500)
console.error(`err = ${err.message} stack = ${err.stack}`)
throw Error(err)
}
Expand Down
10 changes: 3 additions & 7 deletions agents/node/vcxagent-core/test/nonmediated-connection.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ beforeAll(async () => {

describe('test establishing and exchanging messages via nonmediated connections', () => {
it('Establish nonmediated connection via public endpoint using public invite, exchange messages', async () => {
let faberServer
let faberServer, aliceServer
try {
const path = '/msg'
const faberPort = 5400
Expand Down Expand Up @@ -65,7 +65,6 @@ describe('test establishing and exchanging messages via nonmediated connections'
await faber.nonmediatedConnectionSendMessage('Hello Alice')
const { message: msgAlice } = await alice.unpackMsg(aliceEncryptedMsg)
expect(JSON.parse(msgAlice).content).toBe('Hello Alice')

} catch (err) {
console.error(`err = ${err.message} stack = ${err.stack}`)
throw Error(err)
Expand All @@ -81,7 +80,7 @@ describe('test establishing and exchanging messages via nonmediated connections'
})

it('Establish nonmediated connection via public endpoint using pairwise invite, exchange messages', async () => {
let faberServer
let faberServer, aliceServer
try {
const path = '/msg'
const faberPort = 5402
Expand Down Expand Up @@ -129,7 +128,6 @@ describe('test establishing and exchanging messages via nonmediated connections'
await faber.nonmediatedConnectionSendMessage('Hello Alice')
const { message: msgAlice } = await alice.unpackMsg(aliceEncryptedMsg)
expect(JSON.parse(msgAlice).content).toBe('Hello Alice')

} catch (err) {
console.error(`err = ${err.message} stack = ${err.stack}`)
throw Error(err)
Expand All @@ -145,7 +143,7 @@ describe('test establishing and exchanging messages via nonmediated connections'
})

it('Establish nonmediated connection via public endpoint using OOB invite, exchange messages', async () => {
let faberServer
let faberServer, aliceServer
try {
const path = '/msg'
const faberPort = 5404
Expand Down Expand Up @@ -194,7 +192,6 @@ describe('test establishing and exchanging messages via nonmediated connections'
await faber.nonmediatedConnectionSendMessage('Hello Alice')
const { message: msgAlice } = await alice.unpackMsg(aliceEncryptedMsg)
expect(JSON.parse(msgAlice).content).toBe('Hello Alice')

} catch (err) {
console.error(`err = ${err.message} stack = ${err.stack}`)
throw Error(err)
Expand Down Expand Up @@ -252,7 +249,6 @@ describe('test establishing and exchanging messages via nonmediated connections'
expect(payloadAlice['@id']).toBeDefined()
expect(payloadAlice['@type']).toBeDefined()
expect(payloadAlice.content).toBe('Hello Alice')

} catch (err) {
console.error(`err = ${err.message} stack = ${err.stack}`)
throw Error(err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ describe('test connecting via unmediated endpoint', () => {
expect(payloadAlice['@id']).toBeDefined()
expect(payloadAlice['@type']).toBeDefined()
expect(payloadAlice.content).toBe('Hello Alice')

} catch (err) {
console.error(`err = ${err.message} stack = ${err.stack}`)
await sleep(2000)
Expand Down
Loading

0 comments on commit 6783636

Please sign in to comment.