Skip to content

Commit

Permalink
feat: agent flow for proof presentation (#98)
Browse files Browse the repository at this point in the history
* feat: added setup for credo agent

Signed-off-by: Berend Sliedrecht <[email protected]>

* feat: credo demo integration

Signed-off-by: Berend Sliedrecht <[email protected]>

* chore: moved functionality into functions

Signed-off-by: Berend Sliedrecht <[email protected]>

* feat: whole flow working with better UX

Signed-off-by: Berend Sliedrecht <[email protected]>

* resolved feedback

Signed-off-by: Berend Sliedrecht <[email protected]>

* chore: clean up example

Signed-off-by: Berend Sliedrecht <[email protected]>

---------

Signed-off-by: Berend Sliedrecht <[email protected]>
  • Loading branch information
berendsliedrecht authored Oct 3, 2024
1 parent 8640fdb commit a282608
Show file tree
Hide file tree
Showing 31 changed files with 3,874 additions and 275 deletions.
15 changes: 14 additions & 1 deletion example/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,20 @@
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"plugins": ["@animo-id/react-native-ble-didcomm"],
"plugins": [
[
"@animo-id/react-native-ble-didcomm",
{
"bluetoothAlwaysPermission": "Allow $(PRODUCT_NAME) to connect to bluetooth devices for DIDComm"
}
],
[
"expo-camera",
{
"cameraPermission": "Allow $(PRODUCT_NAME) to access your camera"
}
]
],
"ios": {
"supportsTablet": true,
"bundleIdentifier": "id.animo.BluetoothDidcommExample"
Expand Down
3 changes: 3 additions & 0 deletions example/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'react-native-get-random-values'
import 'fast-text-encoding'

import { registerRootComponent } from 'expo'

import { App } from './src/App'
Expand Down
2 changes: 2 additions & 0 deletions example/metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@ config.resolver.nodeModulesPaths = [
path.resolve(monorepoRoot, 'node_modules'),
]

config.resolver.sourceExts = ['js', 'json', 'ts', 'tsx', 'cjs']

module.exports = config
27 changes: 26 additions & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,46 @@
"version": "1.0.0",
"main": "index.js",
"scripts": {
"issue": "ts-node scripts/issueCredential.ts",
"start": "expo start",
"android": "expo run:android",
"prebuild": "expo prebuild",
"ios": "expo run:ios"
},
"dependencies": {
"@animo-id/react-native-ble-didcomm": "workspace:*",
"@credo-ts/anoncreds": "^0.5.11",
"@credo-ts/askar": "^0.5.11",
"@credo-ts/core": "^0.5.11",
"@credo-ts/indy-vdr": "^0.5.11",
"@credo-ts/node": "^0.5.11",
"@credo-ts/react-hooks": "^0.6.1",
"@credo-ts/react-native": "^0.5.11",
"@credo-ts/transport-ble": "^0.3.0",
"@hyperledger/anoncreds-nodejs": "^0.2.4",
"@hyperledger/anoncreds-react-native": "^0.2.4",
"@hyperledger/aries-askar-nodejs": "^0.2.3",
"@hyperledger/aries-askar-react-native": "^0.2.3",
"@hyperledger/indy-vdr-nodejs": "^0.2.2",
"@hyperledger/indy-vdr-react-native": "^0.2.2",
"expo": "*",
"expo-camera": "~15.0.16",
"expo-status-bar": "~1.12.1",
"fast-text-encoding": "^1.0.6",
"qrcode": "^1.5.4",
"qrcode-terminal": "^0.12.0",
"react": "*",
"react-native": "*"
"react-native": "*",
"react-native-fs": "^2.20.0",
"react-native-get-random-values": "^1.11.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@types/fast-text-encoding": "^1.0.3",
"@types/qrcode": "^1.5.5",
"@types/qrcode-terminal": "^0.12.2",
"babel-plugin-module-resolver": "^5.0.2",
"ts-node": "^10.9.2",
"typescript": "*"
},
"private": true
Expand Down
174 changes: 174 additions & 0 deletions example/scripts/issueCredential.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { AnonCredsCredentialFormatService, AnonCredsModule } from '@credo-ts/anoncreds'
import { AskarModule } from '@credo-ts/askar'
import {
Agent,
AutoAcceptCredential,
ConnectionEventTypes,
type ConnectionStateChangedEvent,
ConnectionsModule,
ConsoleLogger,
CredentialEventTypes,
CredentialState,
type CredentialStateChangedEvent,
CredentialsModule,
DidExchangeState,
DidsModule,
HttpOutboundTransport,
KeyType,
LogLevel,
TypedArrayEncoder,
V2CredentialProtocol,
WsOutboundTransport,
} from '@credo-ts/core'
import {
IndyVdrAnonCredsRegistry,
IndyVdrIndyDidRegistrar,
IndyVdrIndyDidResolver,
IndyVdrModule,
} from '@credo-ts/indy-vdr'
import { HttpInboundTransport, agentDependencies } from '@credo-ts/node'
import { anoncreds } from '@hyperledger/anoncreds-nodejs'
import { ariesAskar } from '@hyperledger/aries-askar-nodejs'
import { indyVdr } from '@hyperledger/indy-vdr-nodejs'
import QRCode from 'qrcode'
import { BC_GOV_TEST_NET } from '../src/credo/utils/genesis'

const modules = {
askar: new AskarModule({ ariesAskar }),
indyVdr: new IndyVdrModule({
indyVdr,
networks: [
{
isProduction: false,
indyNamespace: 'bcovrin:test',
genesisTransactions: BC_GOV_TEST_NET,
},
],
}),
anoncreds: new AnonCredsModule({
anoncreds,
registries: [new IndyVdrAnonCredsRegistry()],
}),
dids: new DidsModule({
resolvers: [new IndyVdrIndyDidResolver()],
registrars: [new IndyVdrIndyDidRegistrar()],
}),
connections: new ConnectionsModule({ autoAcceptConnections: true }),
credentials: new CredentialsModule({
autoAcceptCredentials: AutoAcceptCredential.Always,
credentialProtocols: [
new V2CredentialProtocol({
credentialFormats: [new AnonCredsCredentialFormatService()],
}),
],
}),
}

void (async () => {
const agent = new Agent({
config: {
label: 'nodejs-register-agent',
walletConfig: { id: 'nodejs-register-agent', key: 'nodejs-register-key' },
logger: new ConsoleLogger(LogLevel.off),
endpoints: ['https://44a5-94-157-0-163.ngrok-free.app'],
},
modules,
dependencies: agentDependencies,
})

agent.registerOutboundTransport(new WsOutboundTransport())
agent.registerOutboundTransport(new HttpOutboundTransport())
agent.registerInboundTransport(new HttpInboundTransport({ port: 3001 }))

await agent.initialize()

const { outOfBandInvitation } = await agent.oob.createInvitation()
const url = outOfBandInvitation.toUrl({ domain: 'https://example.org' })
const qrcode = await QRCode.toString(url, { type: 'terminal', small: true })
console.log(qrcode)

agent.events.on<ConnectionStateChangedEvent>(
ConnectionEventTypes.ConnectionStateChanged,
async ({ payload: { connectionRecord } }) => {
console.log(`NEW CONNECTION STATE: ${connectionRecord.state}`)
if (connectionRecord.state === DidExchangeState.Completed) {
await issue(agent, connectionRecord.id)
}
}
)

agent.events.on<CredentialStateChangedEvent>(
CredentialEventTypes.CredentialStateChanged,
async ({ payload: { credentialRecord } }) => {
console.log(`NEW CREDENTIAL STATE: ${credentialRecord.state}`)
if (credentialRecord.state === CredentialState.Done) {
console.log('Issued credential!')
process.exit(0)
}
}
)
})()

const issue = async (agent: Agent<typeof modules>, connectionId: string) => {
const seed = TypedArrayEncoder.fromString('abbakabba00000000000000000000000')
const unqualifiedIndyDid = 'NyKUCBjGVmmaUopiXAnLpj'
const indyDid = `did:indy:bcovrin:test:${unqualifiedIndyDid}`
await agent.dids.import({
did: indyDid,
overwrite: true,
privateKeys: [{ keyType: KeyType.Ed25519, privateKey: seed }],
})

const schemaResult = await agent.modules.anoncreds.registerSchema({
schema: {
name: 'react-native-ble-didcomm-schema',
version: `1.0.${Date.now()}`,
attrNames: ['name', 'age'],
issuerId: indyDid,
},
options: {},
})

if (schemaResult.schemaState.state === 'failed') {
throw new Error(`Error creating schema: ${schemaResult.schemaState.reason}`)
}

console.log('registered schema')

const credentialDefinitionResult = await agent.modules.anoncreds.registerCredentialDefinition({
credentialDefinition: {
tag: 'default',
issuerId: indyDid,
schemaId: schemaResult.schemaState.schemaId,
},
options: {
supportRevocation: false,
},
})

if (credentialDefinitionResult.credentialDefinitionState.state === 'failed') {
throw new Error(
`Error creating credential definition: ${credentialDefinitionResult.credentialDefinitionState.reason}`
)
}

if (!credentialDefinitionResult.credentialDefinitionState.credentialDefinitionId) {
throw new Error('Could not find credential definition id on state')
}

console.log('registered credential definition!')

await agent.credentials.offerCredential({
protocolVersion: 'v2',
connectionId: connectionId,
credentialFormats: {
anoncreds: {
credentialDefinitionId: credentialDefinitionResult.credentialDefinitionState.credentialDefinitionId,
attributes: [
{ name: 'name', value: 'Jane Doe' },
{ name: 'age', value: '23' },
],
},
},
})
}
14 changes: 11 additions & 3 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import React, { type ReactElement, useState } from 'react'
import { Button, StyleSheet, View } from 'react-native'
import React, { type ReactElement, useState, useEffect } from 'react'
import { Button, Platform, StyleSheet, View } from 'react-native'

import { requestPermissions } from './RequestPermissions'
import { Spacer } from './Spacer'
import { CredoScreen } from './credo/CredoScreen'
import { RegularScreen } from './regular/Screen'

export const App = () => {
const [flow, setFlow] = useState<'regular' | 'credo'>(undefined)

let component: ReactElement

useEffect(() => {
if (Platform.OS === 'android') {
void requestPermissions()
}
}, [])

if (!flow) {
component = (
<>
Expand All @@ -24,7 +32,7 @@ export const App = () => {
}

if (flow === 'credo') {
component = <RegularScreen />
component = <CredoScreen />
}

return (
Expand Down
11 changes: 11 additions & 0 deletions example/src/RequestPermissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { type Permission, PermissionsAndroid } from 'react-native'

const PERMISSIONS = [
'android.permission.ACCESS_FINE_LOCATION',
'android.permission.BLUETOOTH_CONNECT',
'android.permission.BLUETOOTH_SCAN',
'android.permission.BLUETOOTH_ADVERTISE',
'android.permission.ACCESS_COARSE_LOCATION',
] as const as Permission[]

export const requestPermissions = async () => PermissionsAndroid.requestMultiple(PERMISSIONS)
11 changes: 0 additions & 11 deletions example/src/RequestPermissions.tsx

This file was deleted.

48 changes: 48 additions & 0 deletions example/src/credo/BleProver.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {
bleShareProof,
useCentral,
useCentralShutdownOnUnmount,
useCloseTransportsOnUnmount,
} from '@animo-id/react-native-ble-didcomm'
import type { Agent } from '@credo-ts/core'
import type React from 'react'
import { useState } from 'react'
import { Button, Text, View } from 'react-native'
import { Spacer } from '../Spacer'

type BleProverProps = {
agent: Agent
serviceUuid: string
}

export const BleProver: React.FC<BleProverProps> = ({ agent, serviceUuid }) => {
const [hasSharedProof, setHasSharedProof] = useState(false)
const { central } = useCentral()

const onFailure = () => console.error('[CENTRAL]: failure')
const onConnected = () => console.log('[CENTRAL]: connected')
const onDisconnected = () => console.log('[CENTRAL]: disconnected')

useCentralShutdownOnUnmount()
useCloseTransportsOnUnmount(agent)

const shareProof = () =>
bleShareProof({
onFailure,
serviceUuid,
central,
agent,
onConnected,
onDisconnected,
}).then(() => setHasSharedProof(true))

return (
<View>
<Text>Ble Prover</Text>
<Spacer />
<Button title="Ready to share" onPress={shareProof} />
<Spacer />
<Text>Proof has{hasSharedProof ? ' ' : ' not '}been shared!</Text>
</View>
)
}
Loading

0 comments on commit a282608

Please sign in to comment.