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: agent flow for proof presentation #98

Merged
merged 6 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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')
Comment on lines +22 to +24
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we make the example a bit more in-depth? Would you usually do some reconnection stuff here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really depends on the flow. I see them more as like UI update items. they are not too relevant.


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