Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add proof predicate evaluation #931

Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ const CredentialCard11: React.FC<CredentialCard11Props> = ({
return (
item && (
<View style={{ marginTop: 15 }}>
{!(item?.value || item?.pValue) ? (
{!(item?.value || item?.satisfied) ? (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Icon
style={{ paddingTop: 2, paddingHorizontal: 2 }}
Expand All @@ -320,6 +320,11 @@ const CredentialCard11: React.FC<CredentialCard11Props> = ({
<AttributeLabel label={label} />
)}
{!(item?.value || item?.pValue) ? null : <AttributeValue value={value} />}
{item?.satisfied != undefined && item?.satisfied === false ? (
<Text style={[styles.errorText]} numberOfLines={1}>
{t('ProofRequest.PredicateNotSatisfied')}
</Text>
) : null}
</View>
)
)
Expand Down
2 changes: 2 additions & 0 deletions packages/legacy/core/App/localization/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,8 +407,10 @@ const translation = {
"RequestedCredentialsCouldNotBeFound": "Requested credentials could not be found",
"NewProofRequest": "New Proof Request",
"NotAvailableInYourWallet": "Not available",
"PredicateNotSatisfied": "Requirement not met",
"IsRequesting": "is requesting",
"IsRequestingSomethingYouDontHaveAvailable": "is requesting something you don't have available",
"YouDoNotHaveDataPredicate": "You do not meet the requirements of this proof request from",
"IsRequestingYouToShare": "is requesting the following information from",
"Credential": "credential.",
"Credentials": "credentials.",
Expand Down
2 changes: 2 additions & 0 deletions packages/legacy/core/App/localization/fr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -403,8 +403,10 @@ const translation = {
"RequestedCredentialsCouldNotBeFound": "Les justificatifs demandés n'ont pas été trouvés",
"NewProofRequest": "Nouvelle demande de preuve",
"NotAvailableInYourWallet": "Non disponible dans votre portefeuille",
"PredicateNotSatisfied": "(FR) Requirement not met",
"IsRequesting": "demande",
"IsRequestingSomethingYouDontHaveAvailable": "demande quelque chose que vous n'avez pas à votre disposition",
"YouDoNotHaveDataPredicate": "(FR) You do not meet the requirements of this proof request from",
"IsRequestingYouToShare": "demande les informations suivantes à partir de ",
"Credential": "justificatif d'identité.",
"Credentials": "justificatifs d'identité.",
Expand Down
2 changes: 2 additions & 0 deletions packages/legacy/core/App/localization/pt-br/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,8 +387,10 @@ const translation = {
"RequestedCredentialsCouldNotBeFound": "Crecenciais requisitadas puderam ser encontradas",
"NewProofRequest": "Nova Prova de Credencial",
"NotAvailableInYourWallet": "Não disponível em sua carteira",
"PredicateNotSatisfied": "Requisito não atendido",
"IsRequesting": "está requisitando",
"IsRequestingSomethingYouDontHaveAvailable": "está requisitando algo que você não tem disponível",
"YouDoNotHaveDataPredicate": "Você não atende aos requisitos deste pedido de prova solicitado por",
"IsRequestingYouToShare": "está requisitando a seguinte informação de",
"Credential": "credencial.",
"Credentials": "credenciais.",
Expand Down
123 changes: 101 additions & 22 deletions packages/legacy/core/App/screens/ProofRequest.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import type { StackScreenProps } from '@react-navigation/stack'

import {
AnonCredsCredentialInfo,
AnonCredsCredentialsForProofRequest,
AnonCredsPredicateType,
AnonCredsRequestedAttributeMatch,
AnonCredsRequestedPredicateMatch,
} from '@aries-framework/anoncreds'
Expand Down Expand Up @@ -30,7 +32,7 @@
import { useOutOfBandByConnectionId } from '../hooks/connections'
import { BifoldError } from '../types/error'
import { NotificationStackParams, Screens, TabStacks } from '../types/navigators'
import { ProofCredentialItems } from '../types/record'
import { Predicate, ProofCredentialItems } from '../types/record'
import { ModalUsage } from '../types/remove'
import { TourID } from '../types/tour'
import { useAppAgent } from '../utils/agent'
Expand Down Expand Up @@ -237,27 +239,97 @@

const toggleDeclineModalVisible = () => setDeclineModalVisible(!declineModalVisible)

const hasAvailableCredentials = (credDefId?: string): boolean => {
const fields: Fields = {
...retrievedCredentials?.attributes,
...retrievedCredentials?.predicates,
const getCredentialsFields = (): Fields => ({
...retrievedCredentials?.attributes,
...retrievedCredentials?.predicates,
})

/**
* Retrieve current credentials info filtered by `credentialDefinitionId` if given.
* @param credDefId Credential Definition Id
* @returns Array of `AnonCredsCredentialInfo`
*/
const getCredentialInfo = (credDefId?: string): AnonCredsCredentialInfo[] => {
const credentialInfo: AnonCredsCredentialInfo[] = []
const fields = getCredentialsFields()

Object.keys(fields).forEach((proofKey) => {
credentialInfo.push(...fields[proofKey].map((attr) => attr.credentialInfo))
})

return credDefId == undefined
? credentialInfo
: credentialInfo.filter((cred) => cred.credentialDefinitionId === credDefId)

Check warning on line 262 in packages/legacy/core/App/screens/ProofRequest.tsx

View check run for this annotation

Codecov / codecov/patch

packages/legacy/core/App/screens/ProofRequest.tsx#L262

Added line #L262 was not covered by tests
}

/**
* Evaluate if given attribute value satisfies the predicate.
* @param attribute Credential attribute value
* @param pValue Predicate value
* @param pType Predicate type ({@link AnonCredsPredicateType})
* @returns `true`if predicate is satisfied, otherwise `false`
*/
const evaluateOperation = (attribute: number, pValue: number, pType: AnonCredsPredicateType): boolean => {
if (pType === '>=') {
return attribute >= pValue

Check warning on line 274 in packages/legacy/core/App/screens/ProofRequest.tsx

View check run for this annotation

Codecov / codecov/patch

packages/legacy/core/App/screens/ProofRequest.tsx#L274

Added line #L274 was not covered by tests
}

if (credDefId) {
let credFound = false
Object.keys(fields).forEach((proofKey) => {
const credDefsInAttrs = fields[proofKey].map((attr) => attr.credentialInfo?.credentialDefinitionId)
if (credDefsInAttrs.includes(credDefId)) {
credFound = true
return
if (pType === '>') {
return attribute > pValue

Check warning on line 278 in packages/legacy/core/App/screens/ProofRequest.tsx

View check run for this annotation

Codecov / codecov/patch

packages/legacy/core/App/screens/ProofRequest.tsx#L278

Added line #L278 was not covered by tests
}

if (pType === '<=') {
return attribute <= pValue
}
if (pType === '<') {
return attribute < pValue

Check warning on line 285 in packages/legacy/core/App/screens/ProofRequest.tsx

View check run for this annotation

Codecov / codecov/patch

packages/legacy/core/App/screens/ProofRequest.tsx#L285

Added line #L285 was not covered by tests
}

return false

Check warning on line 288 in packages/legacy/core/App/screens/ProofRequest.tsx

View check run for this annotation

Codecov / codecov/patch

packages/legacy/core/App/screens/ProofRequest.tsx#L288

Added line #L288 was not covered by tests
}

/**
* Given proof credential items, evaluate and return its predicates, setting `satisfied` property.
* @param proofCredentialsItems
* @returns Array of evaluated predicates
*/
const evaluatePredicates = (proofCredentialItems: ProofCredentialItems): Predicate[] => {
const predicates = proofCredentialItems.predicates

if (!predicates || predicates.length == 0) {
return []

Check warning on line 300 in packages/legacy/core/App/screens/ProofRequest.tsx

View check run for this annotation

Codecov / codecov/patch

packages/legacy/core/App/screens/ProofRequest.tsx#L300

Added line #L300 was not covered by tests
}

const credentialAttributes = getCredentialInfo(proofCredentialItems.credDefId).map((ci) => ci.attributes)

return predicates.map((predicate) => {
const { pType: pType, pValue: pValue, name: field } = predicate
let satisfied = false

if (field) {
const attribute = (credentialAttributes.find((attr) => attr[field] != undefined) ?? {})[field]

if (attribute && pValue) {
satisfied = evaluateOperation(Number(attribute), Number(pValue), pType as AnonCredsPredicateType)
}
})
return credFound
}

return { ...predicate, satisfied }
})
}

const hasAvailableCredentials = (credDefId?: string): boolean => {
const fields = getCredentialsFields()

if (credDefId) {
return getCredentialInfo(credDefId).some((credInfo) => credInfo.credentialDefinitionId === credDefId)

Check warning on line 325 in packages/legacy/core/App/screens/ProofRequest.tsx

View check run for this annotation

Codecov / codecov/patch

packages/legacy/core/App/screens/ProofRequest.tsx#L325

Added line #L325 was not covered by tests
}

return !!retrievedCredentials && Object.values(fields).every((c) => c.length > 0)
}

const hasSatisfiedPredicates = () => proofItems.flatMap(evaluatePredicates).every((p) => p.satisfied)

const handleAcceptPress = async () => {
try {
if (!(agent && proof && assertConnectedNetwork())) {
Expand Down Expand Up @@ -334,18 +406,25 @@
<>
<ConnectionImage connectionId={proof?.connectionId} />
<View style={styles.headerTextContainer}>
{!hasAvailableCredentials() ? (
{!hasAvailableCredentials() || !hasSatisfiedPredicates() ? (
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<Icon
style={{ marginLeft: -2, marginRight: 10 }}
name="highlight-off"
color={ListItems.proofIcon.color}
size={ListItems.proofIcon.fontSize}
/>
<Text style={styles.headerText} testID={testIdWithKey('HeaderText')}>
<Text style={[TextTheme.title]}>{proofConnectionLabel || t('ContactDetails.AContact')}</Text>{' '}
{t('ProofRequest.IsRequestingSomethingYouDontHaveAvailable')}:
</Text>
{hasSatisfiedPredicates() ? (
<Text style={styles.headerText} testID={testIdWithKey('HeaderText')}>

Check warning on line 418 in packages/legacy/core/App/screens/ProofRequest.tsx

View check run for this annotation

Codecov / codecov/patch

packages/legacy/core/App/screens/ProofRequest.tsx#L418

Added line #L418 was not covered by tests
<Text style={[TextTheme.title]}>{proofConnectionLabel || t('ContactDetails.AContact')}</Text>{' '}
{t('ProofRequest.IsRequestingSomethingYouDontHaveAvailable')}
</Text>
) : (
<Text style={styles.headerText} testID={testIdWithKey('HeaderText')}>
{t('ProofRequest.YouDoNotHaveDataPredicate')}{' '}
<Text style={[TextTheme.title]}>{proofConnectionLabel || t('ContactDetails.AContact')}</Text>
</Text>
)}
</View>
) : (
<Text style={styles.headerText} testID={testIdWithKey('HeaderText')}>
Expand Down Expand Up @@ -375,7 +454,7 @@
testID={testIdWithKey('Share')}
buttonType={ButtonType.Primary}
onPress={handleAcceptPress}
disabled={!hasAvailableCredentials()}
disabled={!hasAvailableCredentials() || !hasSatisfiedPredicates()}
/>
</View>
<View style={styles.footerButton}>
Expand Down Expand Up @@ -404,9 +483,9 @@
credential={item.credExchangeRecord}
credDefId={item.credDefId}
schemaId={item.schemaId}
displayItems={[...(item.attributes ?? []), ...(item.predicates ?? [])]}
displayItems={[...(item.attributes ?? []), ...evaluatePredicates(item)]}
credName={item.credName}
existsInWallet={hasAvailableCredentials(item.credDefId)}
existsInWallet={hasAvailableCredentials(item.credDefId) && hasSatisfiedPredicates()}
proof
></CredentialCard>
</View>
Expand Down
3 changes: 3 additions & 0 deletions packages/legacy/core/App/types/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface PredicateParams extends FieldParams {
pValue: string | number | null
pType: string
parameterizable?: boolean
satisfied?: boolean
}

export class Field {
Expand Down Expand Up @@ -66,12 +67,14 @@ export class Predicate extends Field {
public pValue: string | number | null
public pType: string
public parameterizable?: boolean
public satisfied?: boolean

public constructor(params: PredicateParams) {
super(params)
this.pValue = params.pValue
this.pType = params.pType
this.parameterizable = params.parameterizable
this.satisfied = params.satisfied
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const mockProofModule = {
getCredentialsForRequest: jest.fn(),
acceptRequest: jest.fn(),
declineRequest: jest.fn(),
getFormatData: jest.fn(),
}

const mockMediationRecipient = {
Expand Down
Loading