# Credentials

> A Dart SDK for exchanging Relationship cards (R-Cards), Verifiable Relationship Credentials (VRCs), and optionally proving human liveness without sharing biometric data via Human Zero Knowledge Proof (Human ZKP). Built on DIDComm v2.1 and W3C Verifiable Credentials.

The Credentials SDK is built on top of meeting_place_core and provides the credential layer for Meeting Place applications.

It supports:

- Relationship Cards and Verifiable Relationship Credentials (R-Cards and VRCs): Verifiable Credentials for exchanging contact details (name, email, phone, company, and custom fields) as jCard (RFC 7095), exportable to vCard 3.0 (RFC 6350), and for recording mutual relationships between DIDs through a two-step VDIP handshake. See [R-Card and VRC Feature](#rcard-vrc). No AWS account needed.

- Human ZKP: Prove you are a real human to a contact without sharing biometric data. See [Human Zero Knowledge Proof Feature](#zkp-feature). Requires an external liveness provider such as [AWS Rekognition Face Liveness](https://aws.amazon.com/rekognition/face-liveness/) or [Azure Face Liveness Detection](https://learn.microsoft.com/en-us/azure/ai-services/face/concept-face-liveness-detection) for production; the [Flutter Reference App](/products/affinidi-elements/affinidi-messaging/run-reference-app.md) includes a synthetic demo that works without one.

## Install Dependency

Package: meeting_place_credentials

```bash
dart pub add meeting_place_credentials
```

Check the latest version on the [pub.dev registry](https://pub.dev/packages/meeting_place_credentials) or view the source on [GitHub](https://github.com/affinidi/affinidi-meetingplace-sdk-dart/tree/main/packages/meeting_place_credentials).

## R-Card and VRC Feature

### Relationship Card (R-Card)

An R-Card is a signed W3C Verifiable Credential containing a jCard (RFC 7095) payload. Think of it as a digital business card with verifiable contact and relationship details: name, email, phone, company, and optional custom fields that can be cryptographically verified. R-Cards are exchanged automatically when a DIDComm channel is established and can be resent or shared manually at any time.

### Verifiable Relationship Credential (VRC)

A VRC demonstrates that two DIDs have established a verified relationship. It uses a two-step VDIP handshake: the initiating side requests, and the responding side sends a relationship credential back. Both participants receive a signed copy of the final credential.

## Human Zero Knowledge Proof Feature

The Human Zero Knowledge Proof (Human ZKP) feature lets one participant prove to another that they are a real human, without sharing personal or biometric data. The proof travels over the secure DIDComm channel and the recipient can validate it without seeing any personal data.

This SDK handles Stage 2. Stage 1 (the liveness check) is handled by an external provider such as AWS Rekognition. Stage 3 (proof generation) is performed by the consuming application using utilities provided by this SDK.

### How Human ZKP works

Liveness check

The user performs real-time actions (such as blinking or turning their head) in front of their device camera. The liveness provider analyses the session and scores whether a live person is present.

Owner: External provider (for example AWS Rekognition) · Output: LivenessEvidence

Issue liveness credential

LivenessVcIssuanceService.issue(evidence) signs a W3C Verifiable Credential from the normalised evidence.

Owner: This SDK · Output: Signed LivenessCredential (W3C VC)

Generate proof

The consuming application uses the SDK’s LivenessCredentialSubject and ZKP attachment builders to generate a Groth16 proof from the liveness credential.

Owner: Consuming application (SDK utilities provided) · Output: Groth16 ZKP

Transmit proof

The ZKP is sent over the DIDComm channel with no personal data.

Owner: meeting_place_core transport · Output: ZKP message on channel

Validate proof

The contact’s device validates the proof cryptographically.

Owner: Verifier (contact’s device) · Output: Verified badge shown in UI

Why this is privacy-preserving: The Groth16 proof in Stage 3 is mathematically derived from the liveness credential, but it does not expose the credential contents. The validator learns only that the holder passed a liveness check.

Try it in action: The [Flutter Reference App](/products/affinidi-elements/affinidi-messaging/run-reference-app.md) includes a working end-to-end Human ZKP demo. Enable it with ZKP_ENABLED=true and follow the end-to-end demo flow to see all five stages execute on a real device.

For implementation details and setup guides, refer to:

- [Feature Demonstrations](https://github.com/affinidi/affinidi-meetingplace-sdk-dart/tree/main/packages/meeting_place_credentials#credentials-feature)

- [AWS Rekognition Face Liveness setup](https://github.com/affinidi/affinidi-meetingplace-reference-app/blob/main/doc/ZKP.md#aws-rekognition-face-liveness-setup)

## Classes and Methods

### MeetingPlaceCredentialsSDK

The MeetingPlaceCredentialsSDK is the main facade for R-Card and VRC exchange flows. It wires protocol handlers, stream managers, and repositories on top of MeetingPlaceCoreSDK and exposes a single injectable service for credential operations.

#### MeetingPlaceCredentialsSDK (constructor)

Creates an instance of MeetingPlaceCredentialsSDK.

Parameters

    coreSDK
    MeetingPlaceCoreSDK
    Required
  An initialised MeetingPlaceCoreSDK instance providing DIDComm transport and VDIP primitives.

    rCardRepository
    RCardRepository
    Required
  Repository used to persist every incoming R-Card. Construct one with RCardRepositoryDrift from meeting_place_drift_repository.

    vrcRepository
    VrcRepository
    Required
  Repository used to persist every received VRC. Construct one with VrcRepositoryDrift from meeting_place_drift_repository.

    logger
    MeetingPlaceCoreSDKLogger?
    Optional
  Optional logger for diagnostic output. Defaults to DefaultMeetingPlaceCoreSDKLogger.

Example

```dart
import 'package:meeting_place_credentials/meeting_place_credentials.dart';

final credentialsSDK = MeetingPlaceCredentialsSDK(
  coreSDK: coreSDK,
  rCardRepository: RCardRepositoryDrift(database: rCardDb),
  vrcRepository: VrcRepositoryDrift(database: vrcDb),
);
```

### Streams

#### receivedRCards

A broadcast Stream<RCard> that emits a verified R-Card whenever one arrives over any channel: either via the DIDComm attachment path (OOB / channel inauguration) or the VDIP issued-credential path (chat-time update). Every emitted card is also automatically persisted via RCardRepository.

Example

```dart
import 'package:meeting_place_credentials/meeting_place_credentials.dart';

credentialsSDK.receivedRCards.listen((RCard rCard) {
  print('R-Card received from ${rCard.subjectDid}');
});
```

#### receivedRCardsOnChannel

A broadcast Stream<ChannelRCardEvent> that emits an R-Card and its originating channel for every card received via the connection establishment path (OOB / channel inauguration). VDIP-path R-Cards are not emitted here; use receivedRCards for those.

ChannelRCardEvent.channel exposes permanentChannelDid and otherPartyPermanentChannelDid to correlate the card to its conversation.

Example

```dart
credentialsSDK.receivedRCardsOnChannel.listen((ChannelRCardEvent event) {
  print('R-Card from channel: ${event.channel.permanentChannelDid}');
  print('Contact DID: ${event.rCard.subjectDid}');
});
```

#### receivedVrcRequests

A broadcast Stream<VrcRequest> that emits an incoming VDIP issuance request for every VRC exchange initiated by a contact.

Example

```dart
credentialsSDK.receivedVrcRequests.listen((VrcRequest request) {
  print('VRC request from ${request.senderDid}');
});
```

#### receivedVrcs

A broadcast Stream<VrcIssuance> that emits a signature-verified VRC for each finished credential received over VDIP.

Example

```dart
credentialsSDK.receivedVrcs.listen((VrcIssuance issuance) {
  print('VRC received from ${issuance.senderDid}');
});
```

### R-Card Operations

#### watchReceivedRCards

Returns a live Stream<List<RCard>> of all persisted R-Cards, ordered by receivedAt descending. Backed by RCardRepository.watchAll, it emits a new list whenever any record is added, updated, or removed from local storage.

Parameters

No parameters required.

Example

```dart
import 'package:meeting_place_credentials/meeting_place_credentials.dart';

credentialsSDK.watchReceivedRCards().listen((List cards) {
  print('Contact count: ${cards.length}');
});
```

#### listReceivedRCards

Returns a snapshot Future<List<RCard>> of all persisted R-Cards, ordered by receivedAt descending.

Parameters

No parameters required.

Example

```dart
final cards = await credentialsSDK.listReceivedRCards();
```

#### getReceivedRCardBySubjectDid

Returns the persisted R-Card whose sender DID matches subjectDid, or null if no such record exists.

Parameters

    subjectDid
    String
    Required
  The DID of the contact whose R-Card should be retrieved.

Example

```dart
final rCard = await credentialsSDK.getReceivedRCardBySubjectDid('did:example:alice');
```

#### updateReceivedRCardNotes

Updates the notes field on the R-Card identified by subjectDid. Pass null to clear the notes. Does nothing if no record with that DID exists.

Parameters

    subjectDid
    String
    Required
  The DID of the contact whose notes field should be updated.

    notes
    String?
    Required
  The new notes text, or null to clear existing notes.

Example

```dart
await credentialsSDK.updateReceivedRCardNotes('did:example:alice', 'Met at conference 2025');

// Clear the notes
await credentialsSDK.updateReceivedRCardNotes('did:example:alice', null);
```

#### deleteReceivedRCard

Removes the persisted R-Card identified by subjectDid.

Parameters

    subjectDid
    String
    Required
  The DID of the contact whose R-Card should be deleted.

Example

```dart
await credentialsSDK.deleteReceivedRCard('did:example:alice');
```

#### sendRCard

Builds, signs, and delivers an R-Card to the other party in channel via VDIP. Returns the sent RCard so callers can display or persist the issued card.

Parameters

    channel
    Channel
    Required
  The established DIDComm channel to the contact.

    subjectDid
    String
    Required
  The DID of the credential subject (the card recipient).

    card
    RCardSubject
    Required
  The contact fields to embed in the R-Card VC (name, email, phone, company, and so on).

    issuerDidManager
    DidManager
    Required
  The DidManager used to sign the credential with ecdsa-jcs-2019 Data Integrity proof.

Example

```dart
import 'package:meeting_place_credentials/meeting_place_credentials.dart';

final sent = await credentialsSDK.sendRCard(
  channel: myChannel,
  subjectDid: 'did:example:bob',
  card: RCardSubject(
    firstName: 'Alice',
    lastName: 'Smith',
    email: 'alice@example.com',
    company: 'Acme Corp',
  ),
  issuerDidManager: myDidManager,
);
```

#### parseRCard

Parses and signature-verifies a raw R-Card VC blob. Returns null if the blob is not a valid, verified R-Card.

Parameters

    vcBlob
    String
    Required
  The raw serialised VC JSON string to parse.

Example

```dart
final rCard = await credentialsSDK.parseRCard(vcBlob: rawVcJson);
if (rCard != null) {
  print('Valid R-Card from ${rCard.subjectDid}');
}
```

#### consumePendingRCard

Returns and removes the last RCard from senderDid that arrived while no stream listener was attached. Returns null if no pending card exists.

Parameters

    senderDid
    String
    Required
  The DID of the sender whose pending R-Card should be consumed.

Example

```dart
final pending = credentialsSDK.consumePendingRCard('did:example:alice');
```

### VRC Operations

#### requestVrcExchange

Initiates a VRC exchange by sending a VDIP issuance request to the contact identified by channelDid.

Parameters

    channelDid
    String
    Required
  The permanent channel DID of the contact to whom the request is sent.

    identityDid
    String
    Required
  The DID of the local identity to include in the request metadata.

    identityName
    String
    Required
  The display name of the local identity to include in the request metadata.

Example

```dart
import 'package:meeting_place_credentials/meeting_place_credentials.dart';

await credentialsSDK.requestVrcExchange(
  channelDid: myChannelDid,
  identityDid: myDid,
  identityName: 'Alice',
);
```

#### handleReceivedVrcRequest

Handles the credential-protocol outcome of receiving a VRC request. Returns a VrcRequestProcessingResult indicating whether a reciprocal credential was sent or the exchange was deferred.

Parameters

    permanentChannelDid
    String
    Required
  The permanent channel DID on which the request arrived.

    request
    VrcRequest
    Required
  The received VrcRequest object from the receivedVrcRequests stream.

    hasVrcExchangeInitiated
    bool
    Required
  Whether the local side has already initiated a VRC exchange on this channel.

    isConnectionInitiator
    bool
    Required
  Whether the local side initiated the original DIDComm connection.

    issuerDid
    String?
    Optional
  The local issuer DID to embed in the reciprocal VRC. Required when the handler decides to send a credential.

    issuerName
    String?
    Optional
  The display name to embed in the reciprocal VRC.

Example

```dart
credentialsSDK.receivedVrcRequests.listen((VrcRequest request) async {
  final result = await credentialsSDK.handleReceivedVrcRequest(
    permanentChannelDid: myChannelDid,
    request: request,
    hasVrcExchangeInitiated: false,
    isConnectionInitiator: true,
    issuerDid: myDid,
    issuerName: 'Alice',
  );
  print('VRC request handled: $result');
});
```

#### handleReceivedVrc

Handles the credential-protocol outcome of receiving a finished VRC. Returns VrcProcessingResultIgnored when the exchange is already completed, so no pre-guard is needed.

Parameters

    permanentChannelDid
    String
    Required
  The permanent channel DID on which the VRC arrived.

    vcBlob
    String
    Required
  The raw serialised VC JSON from the VrcIssuance.vcBlob field.

    exchangeState
    VrcExchangeState
    Required
  The current VRC exchange state for this channel.

    issuerDid
    String?
    Optional
  The local issuer DID, required when the handler decides to send a reciprocal credential.

    issuerName
    String?
    Optional
  The display name to embed in the reciprocal VRC.

Example

```dart
credentialsSDK.receivedVrcs.listen((VrcIssuance issuance) async {
  final result = await credentialsSDK.handleReceivedVrc(
    permanentChannelDid: myChannelDid,
    vcBlob: issuance.vcBlob,
    exchangeState: currentExchangeState,
    issuerDid: myDid,
    issuerName: 'Alice',
  );
  print('VRC processed: $result');
});
```

#### watchVrcs

Returns a live Stream<List<Vrc>> of all persisted VRCs. Emits a new list whenever any record is added or removed from local storage.

Parameters

No parameters required.

Example

```dart
credentialsSDK.watchVrcs().listen((List vrcs) {
  print('VRC count: ${vrcs.length}');
});
```

#### listVrcs

Returns a snapshot Future<List<Vrc>> of all persisted VRCs.

Parameters

No parameters required.

Example

```dart
final vrcs = await credentialsSDK.listVrcs();
```

#### getVrcById

Returns the persisted VRC identified by id, or null if no such record exists.

Parameters

    id
    String
    Required
  The unique credential identifier (from the VC id field).

Example

```dart
final vrc = await credentialsSDK.getVrcById('');
```

#### listVrcsByHolderDid

Returns all persisted VRCs where the holder DID matches holderDid.

Parameters

    holderDid
    String
    Required
  The DID of the credential holder to filter by.

Example

```dart
final vrcs = await credentialsSDK.listVrcsByHolderDid('did:example:alice');
```

#### countVrcsByHolderDid

Returns the number of persisted VRCs where the holder DID matches holderDid.

Parameters

    holderDid
    String
    Required
  The DID of the credential holder to count by.

Example

```dart
final count = await credentialsSDK.countVrcsByHolderDid('did:example:alice');
print('Verified relationships: $count');
```

#### deleteVrc

Removes the persisted VRC identified by id.

Parameters

    id
    String
    Required
  The unique credential identifier of the VRC to delete.

Example

```dart
await credentialsSDK.deleteVrc('');
```

#### storeVrc

Parses, verifies, and persists a VRC from a raw VC blob. Throws MeetingPlaceCredentialsSDKException with error code vrcInvalidCredential if the blob cannot be parsed.

Parameters

    vcBlob
    String
    Required
  The raw serialised VC JSON string to parse and store.

    referenceId
    String
    Required
  An app-defined identifier used to correlate this VRC with its exchange context (for example, a channel DID or proposal ID).

    verifiedAt
    DateTime?
    Optional
  Optional timestamp recording when the credential was cryptographically verified.

    receivedAt
    DateTime?
    Optional
  Optional timestamp recording when the credential was received. Defaults to the current UTC time if omitted.

    credentialFormat
    String?
    Optional
  Optional serialisation format identifier from the VDIP issuance message.

Example

```dart
final vrc = await credentialsSDK.storeVrc(
  vcBlob: issuance.vcBlob,
  referenceId: myChannelDid,
  receivedAt: DateTime.now().toUtc(),
);
print('Stored VRC: ${vrc.id}');
```

#### consumePendingVrcRequest

Returns and removes the last VrcRequest from senderDid that arrived while no stream listener was attached. Returns null if no pending request exists.

Parameters

    senderDid
    String
    Required
  The DID of the sender whose pending VRC request should be consumed.

Example

```dart
final pending = credentialsSDK.consumePendingVrcRequest('did:example:alice');
```

#### consumePendingVrc

Returns and removes the last VrcIssuance from senderDid that arrived while no stream listener was attached. Returns null if no pending issuance exists.

Parameters

    senderDid
    String
    Required
  The DID of the sender whose pending VRC issuance should be consumed.

Example

```dart
final pending = credentialsSDK.consumePendingVrc('did:example:alice');
```

### Lifecycle

#### closeCredentialStreams

Cancels all internal stream subscriptions, closes the broadcast stream controller, and releases VDIP resources. Call this when the SDK instance is no longer needed to prevent memory leaks.

Parameters

No parameters required.

Example

```dart
await credentialsSDK.closeCredentialStreams();
```

### RCardSubject

Parsed contact fields from an R-Card Verifiable Credential. All fields are optional because a contact card may be partially filled.

#### RCardSubject (constructor)

Creates an RCardSubject with the given optional contact fields.

Parameters

    id
    String?
    Optional
  Optional DID of the credential subject.

    firstName
    String?
    Optional
  Given name.

    lastName
    String?
    Optional
  Family name.

    email
    String?
    Optional
  Email address.

    phone
    String?
    Optional
  Phone number.

    company
    String?
    Optional
  Organisation name.

    position
    String?
    Optional
  Job title.

    website
    String?
    Optional
  Personal or professional website URL.

    social
    String?
    Optional
  Social profile URL or handle.

    profilePic
    String?
    Optional
  Profile picture URL or base64-encoded image data.

Example

```dart
import 'package:meeting_place_credentials/meeting_place_credentials.dart';

final subject = RCardSubject(
  firstName: 'Alice',
  lastName: 'Smith',
  email: 'alice@example.com',
  phone: '+1-555-0100',
  company: 'Acme Corp',
  position: 'Engineer',
);
```

#### toVCard

An extension method on RCardSubject that serialises the contact fields to a vCard 3.0 string following [RFC 6350](https://datatracker.ietf.org/doc/html/rfc6350).

Parameters

    notes
    String?
    Optional
  Optional text appended as the vCard NOTE field.

Example

```dart
import 'package:meeting_place_credentials/meeting_place_credentials.dart';

final subject = RCardSubject(firstName: 'Alice', email: 'alice@example.com');
final vCard = subject.toVCard(notes: 'Met at conference 2025');
print(vCard);
// BEGIN:VCARD
// VERSION:3.0
// ...
// END:VCARD
```

### LivenessEvidenceSource

An abstract interface that collects LivenessEvidence from a configured liveness provider. Implement this in your application or in a provider-specific package.

#### getEvidence

Collects raw liveness evidence from the configured provider for the given holder.

Parameters

    holderDid
    String
    Required
  The DID of the user whose liveness is being checked.

Example

```dart
import 'package:meeting_place_credentials/meeting_place_credentials.dart';

class AwsRekognitionEvidenceSource implements LivenessEvidenceSource {
  @override
  Future
-  getEvidence({required String holderDid}) async {
    // Call AWS Rekognition and map the response to LivenessEvidence
    return LivenessEvidence(
      providerId: 'aws_rekognition',
      providerTransactionId: '',
      livenessScore: 0.98,
      livenessThreshold: 0.90,
      checkedAt: DateTime.now().toUtc(),
    );
  }
}
```

### LivenessVcIssuanceService

Issues signed W3C liveness credentials from normalised provider evidence. This is Stage 2 of the Human ZKP pipeline.

#### LivenessVcIssuanceService (constructor)

Creates a stateless liveness credential issuance service. Takes no arguments.

Parameters

No parameters required.

#### issue

Issues a signed VcDataModelV2 liveness credential for the given holder.

Parameters

    issuerDid
    String
    Required
  The DID of the credential issuer.

    holderDid
    String
    Required
  The DID of the credential holder (the user who passed the liveness check).

    issuerDidManager
    DidManager
    Required
  The DidManager used to sign the credential.

    evidence
    LivenessEvidence
    Required
  The normalised liveness evidence from the provider.

    validFor
    Duration
    Optional
  How long the credential remains valid. Defaults to 5 days.

Example

```dart
import 'package:meeting_place_credentials/meeting_place_credentials.dart';

const issuanceService = LivenessVcIssuanceService();

final evidence = await evidenceSource.getEvidence(holderDid: myDid);

final livenessVc = await issuanceService.issue(
  issuerDid: myIssuerDid,
  holderDid: myDid,
  issuerDidManager: myDidManager,
  evidence: evidence,
  validFor: const Duration(days: 1),
);
```

