/server
192.168.1.72
/server
/server/aries-framework-javascript
to
/server/afj
/server/mywallet
@aries-framework/core
@aries-framework/react-native
react-native-fs
react-native-get-random-values
indy-sdk-react-native
Installed Libindy for Android & iOS according to the guides
iOS: https://github.com/hyperledger/aries-framework-javascript/blob/main/docs/libindy/ios.md
Android: https://github.com/hyperledger/aries-framework-javascript/blob/main/docs/libindy/android.md
/server/von-network
cd /server/von-network
./manage build
cd /server/von-network
./manage start --logs
http://localhost:9000/
http://192.168.1.72:9000/
/server/afj/config/von/pool-config.ts
"use strict";
exports.__esModule = true;
var genesis_file_1 = require("./genesis-file");
var config = {
id: "VON",
genesisTransactions: genesis_file_1["default"],
isProduction: false,
};
exports["default"] = config;
http://192.168.1.72:9000/
and clicked
the "Genesis Transaction" button to download the JSON needed for the
genesis file.
/server/afj/config/von/genesis-file.ts
using the downloaded genesis transaction JSON.
export default `{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node1","blskey":"4N8aUNHSgjQVgkpm8nhNEfDf6txHznoYREg9kirmJrkivgL4oSEimFF6nsQ6M41QvhM2Z33nves5vfSn9n1UwNFJBYtWVnHYMATn76vLuL3zU88KyeAYcHfsih3He6UHcXDxcaecHVz6jhCYz1P2UZn2bDVruL5wXpehgBfBaLKm3Ba","blskey_pop":"RahHYiCvoNCtPTrVtP7nMC5eTYrsUA8WjXbdhNc8debh1agE9bGiJxWBXYNFbnJXoXhWFMvyqhqhRoq737YQemH5ik9oL7R4NTTCz2LEZhkgLJzB3QRQqJyBNyv7acbdHrAT8nQ9UkLbaVL9NBpnWXBTw4LEMePaSHEw66RzPNdAX1","client_ip":"172.17.0.1","client_port":9702,"node_ip":"172.17.0.1","node_port":9701,"services":["VALIDATOR"]},"dest":"Gw6pDLhcBcoQesN72qfotTgFa7cbuqZpkX3Xo6pLhPhv"},"metadata":{"from":"Th7MpTaRZVRYnPiabds81Y"},"type":"0"},"txnMetadata":{"seqNo":1,"txnId":"fea82e10e894419fe2bea7d96296a6d46f50f93f9eeda954ec461b2ed2950b62"},"ver":"1"}
{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node2","blskey":"37rAPpXVoxzKhz7d9gkUe52XuXryuLXoM6P6LbWDB7LSbG62Lsb33sfG7zqS8TK1MXwuCHj1FKNzVpsnafmqLG1vXN88rt38mNFs9TENzm4QHdBzsvCuoBnPH7rpYYDo9DZNJePaDvRvqJKByCabubJz3XXKbEeshzpz4Ma5QYpJqjk","blskey_pop":"Qr658mWZ2YC8JXGXwMDQTzuZCWF7NK9EwxphGmcBvCh6ybUuLxbG65nsX4JvD4SPNtkJ2w9ug1yLTj6fgmuDg41TgECXjLCij3RMsV8CwewBVgVN67wsA45DFWvqvLtu4rjNnE9JbdFTc1Z4WCPA3Xan44K1HoHAq9EVeaRYs8zoF5","client_ip":"172.17.0.1","client_port":9704,"node_ip":"172.17.0.1","node_port":9703,"services":["VALIDATOR"]},"dest":"8ECVSk179mjsjKRLWiQtssMLgp6EPhWXtaYyStWPSGAb"},"metadata":{"from":"EbP4aYNeTHL6q385GuVpRV"},"type":"0"},"txnMetadata":{"seqNo":2,"txnId":"1ac8aece2a18ced660fef8694b61aac3af08ba875ce3026a160acbc3a3af35fc"},"ver":"1"}
{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node3","blskey":"3WFpdbg7C5cnLYZwFZevJqhubkFALBfCBBok15GdrKMUhUjGsk3jV6QKj6MZgEubF7oqCafxNdkm7eswgA4sdKTRc82tLGzZBd6vNqU8dupzup6uYUf32KTHTPQbuUM8Yk4QFXjEf2Usu2TJcNkdgpyeUSX42u5LqdDDpNSWUK5deC5","blskey_pop":"QwDeb2CkNSx6r8QC8vGQK3GRv7Yndn84TGNijX8YXHPiagXajyfTjoR87rXUu4G4QLk2cF8NNyqWiYMus1623dELWwx57rLCFqGh7N4ZRbGDRP4fnVcaKg1BcUxQ866Ven4gw8y4N56S5HzxXNBZtLYmhGHvDtk6PFkFwCvxYrNYjh","client_ip":"172.17.0.1","client_port":9706,"node_ip":"172.17.0.1","node_port":9705,"services":["VALIDATOR"]},"dest":"DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya"},"metadata":{"from":"4cU41vWW82ArfxJxHkzXPG"},"type":"0"},"txnMetadata":{"seqNo":3,"txnId":"7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4"},"ver":"1"}
{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node4","blskey":"2zN3bHM1m4rLz54MJHYSwvqzPchYp8jkHswveCLAEJVcX6Mm1wHQD1SkPYMzUDTZvWvhuE6VNAkK3KxVeEmsanSmvjVkReDeBEMxeDaayjcZjFGPydyey1qxBHmTvAnBKoPydvuTAqx5f7YNNRAdeLmUi99gERUU7TD8KfAa6MpQ9bw","blskey_pop":"RPLagxaR5xdimFzwmzYnz4ZhWtYQEj8iR5ZU53T2gitPCyCHQneUn2Huc4oeLd2B2HzkGnjAff4hWTJT6C7qHYB1Mv2wU5iHHGFWkhnTX9WsEAbunJCV2qcaXScKj4tTfvdDKfLiVuU2av6hbsMztirRze7LvYBkRHV3tGwyCptsrP","client_ip":"172.17.0.1","client_port":9708,"node_ip":"172.17.0.1","node_port":9707,"services":["VALIDATOR"]},"dest":"4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA"},"metadata":{"from":"TWwCRQRZ2ZHMJFn9TzLp7W"},"type":"0"},"txnMetadata":{"seqNo":4,"txnId":"aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008"},"ver":"1"}`;
/server/afj/mediator.ts
based
on
/samples/mediator.ts
from the aries-framework-javascript repository
import { InitConfig } from "@aries-framework/core";
import * as express from "express";
import { Server } from "ws";
import { TestLogger } from "./packages/core/tests/logger";
import {
HttpOutboundTransport,
Agent,
LogLevel,
AgentConfig,
WsOutboundTransport,
AutoAcceptProof,
AutoAcceptCredential,
} from "@aries-framework/core";
import {
HttpInboundTransport,
agentDependencies,
WsInboundTransport,
} from "@aries-framework/node";
import indyConfig from "./configs/von/pool-config";
const port = 3001;
const app = express();
const socketServer = new Server({ noServer: true });
const endpoints = [`http://192.168.1.72:${port}`, `ws://192.168.1.72:${port}`];
const logger = new TestLogger(LogLevel.info);
const agentConfig: InitConfig = {
endpoints,
label: process.env.AGENT_LABEL || "Private Data Vault",
walletConfig: {
id: process.env.WALLET_NAME || "PrivateDataVault",
key: process.env.WALLET_KEY || "PrivateDataVault",
},
autoAcceptProofs: AutoAcceptProof.ContentApproved,
autoAcceptConnections: true,
autoAcceptMediationRequests: true,
indyLedgers: [indyConfig],
genesisTransactions: indyConfig.genesisTransactions,
publicDidSeed: "privatedatavaultpublicdidseeddid",
autoAcceptCredentials: AutoAcceptCredential.ContentApproved,
logger,
};
// Set up agent
const agent = new Agent(agentConfig, agentDependencies);
const config = agent.injectionContainer.resolve(AgentConfig);
// Create transports
const httpInboundTransport = new HttpInboundTransport({ app, port });
const httpOutboundTransport = new HttpOutboundTransport();
const wsInboundTransport = new WsInboundTransport({ server: socketServer });
const wsOutboundTransport = new WsOutboundTransport();
// Register transports
agent.registerInboundTransport(httpInboundTransport);
agent.registerOutboundTransport(httpOutboundTransport);
agent.registerInboundTransport(wsInboundTransport);
agent.registerOutboundTransport(wsOutboundTransport);
const runAgent = async () => {
await agent.initialize();
// forward upgrade on http server to ws server
httpInboundTransport.server?.on("upgrade", (request, socket, head) => {
socketServer.handleUpgrade(request, socket, head, (socket) => {
socketServer.emit("connection", socket, request);
});
});
};
httpInboundTransport.app.get("/invitation", async (req, res) => {
if (typeof req.query.c_i === "string") {
const invitation = await ConnectionInvitationMessage.fromUrl(req.url);
res.send(invitation.toJSON());
} else {
const { invitation } = await agent.connections.createConnection();
const httpEndpoint = config.endpoints.find((e) => e.startsWith("http"));
var inv = invitation.toUrl({ domain: httpEndpoint + "/invitation" });
res.send(inv);
}
});
runAgent();
tsc mediator.ts ; node mediator.js
http://192.168.1.72:3001/invitation
http://192.168.1.72:3001/invitation?c_i=eyJAdHlwZSI6Imh0dHBzOi8vZGlkY29tbS5vcmcvY29ubmVjdGlvbnMvMS4wL2ludml0YXRpb24iLCJAaWQiOiIyOGMxZTBmNS01YzVhLTRkYjMtYjhkZC02Y2IwZDgxN2Y4MTIiLCJsYWJlbCI6Ik1pZ3JhdGUgRGF0YSBWYXVsdCIsInJlY2lwaWVudEtleXMiOlsiQmNhQ3E2dzdiZmtvZGI4aUxhR0tTQXMzaEZEOEpENHVVZlNDeXd1REdUcTEiXSwic2VydmljZUVuZHBvaW50Ijoid3M6Ly8xOTIuMTY4LjEuNzI6MzAwMSIsInJvdXRpbmdLZXlzIjpbXX0
{
"@type": "https://didcomm.org/connections/1.0/invitation",
"@id": "28c1e0f5-5c5a-4db3-b8dd-6cb0d817f812",
"label": "Private Data Vault",
"recipientKeys": ["BcaCq6w7bfkodb8iLaGKSAs3hFD8JD4uUfSCywuDGTq1"],
"serviceEndpoint": "ws://192.168.1.72:3001",
"routingKeys": []
}
The mediator agent is now running on Node.js and able to create connection invitations.
/server/afj/configs
folder
that contains the VON genesis and pool files to the React Native
project at /server/mywallet/configs
/server/mywallet/App.js
file and
added the ability to initialize an agent with a customized wallet
label
import {
Agent,
HttpOutboundTransport,
MediatorPickupStrategy,
WsOutboundTransport,
AutoAcceptProof,
AutoAcceptCredential,
ConsoleLogger,
} from "@aries-framework/core";
import { agentDependencies } from "@aries-framework/react-native";
import React from "react";
import type { Node } from "react";
import { SafeAreaView, StatusBar, Text, View, Button } from "react-native";
import indyConfig from "./configs/von/pool-config";
const App: () => Node = () => {
const [agent, setAgent] = React.useState(null);
const [myConnections, setMyConnections] = React.useState([]);
const initializeAgent = async (walletLabel) => {
var invite = await fetch("http://192.168.1.72:3001/request");
var data = await invite.text();
const newAgent = new Agent(
{
label: "My Wallet " + walletLabel,
mediatorConnectionsInvite: data,
mediatorPickupStrategy: MediatorPickupStrategy.Explicit,
walletConfig: { id: "mywalletid", key: "mywalletkey" },
autoAcceptConnections: true,
autoAcceptProofs: AutoAcceptProof.ContentApproved,
autoAcceptCredentials: AutoAcceptCredential.ContentApproved,
logger: new ConsoleLogger(LogLevel.trace),
indyLedgers: [indyConfig],
genesisTransactions: indyConfig.genesisTransactions,
},
agentDependencies
);
const wsTransport = new WsOutboundTransport();
const httpTransport = new HttpOutboundTransport();
newAgent.registerOutboundTransport(wsTransport);
newAgent.registerOutboundTransport(httpTransport);
await newAgent.initialize();
setAgent(newAgent);
};
return (
<SafeAreaView>
<StatusBar barStyle={"light-content"} />
<View>
<Text>My Wallet</Text>
</View>
<View>
<Button
onPress={() => {
initializeAgent("Alice");
}}
title="Initialize Agent with name Alice"
/>
<Button
onPress={() => {
initializeAgent("Bob");
}}
title="Initialize Agent with name Bob"
/>
</View>
</SafeAreaView>
);
};
export default App;
sudo ./emulator -avd Pixel_3a_API_30_x86
to create a Pixel 3a device
npx react-native run-android
sudo ./emulator -avd Pixel_3a_API_30_x86 -read-only
to create a second Pixel 3a device (since the same command
was used to launch the emulator, the React Native app is already
installed on the second emulator when it launches)
The React Native "My Wallet" app is now running on both emulated devices.
The first device initialized the agent using the wallet label My Wallet Alice and the second device initialized the agent using the wallet label My Wallet Bob
The My Wallet Alice agent will be now be referred to as Alice's agent and the My Wallet Bob agent will now be referred to as Bob's agent.
/server/afj/mediator.ts
file to list
all connections to the mediator
httpInboundTransport.app.get("/connections", async (req, res) => {
var connections = await agent.connections.getAll();
var conns = [];
connections.forEach((c, i) => {
conns.push({
label: c.theirLabel,
id: c.id,
});
});
var list = "";
conns.forEach((conn, i) => {
list = list + conn.label + `<br />`;
});
res.send(list);
});
Private Wallet Alice
Private Wallet Bob
The "My Wallet" React Native app running on both emulators is successfully connected to the Node.js mediator
/server/mywallet/App.js
file to
create connection invitations from the React Native app
const createInvite = async () => {
const { invitation, connectionRecord } =
await agent.connections.createConnection({
autoAcceptConnection: true,
alias: "inviter",
});
var inv = invitation.toUrl({
domain: "http://192.168.1.72:3001" + "/invitation",
});
setAgentInvite(inv);
};
const [agentInvite, setAgentInvite] = React.useState("");
createInvite()
method
<View>
<Button
onPress={() => {
createInvite();
}}
title="Create Invite"
/>
</View>
agentInvite
variable after
pressing the "Create Invite" button
agentInvite
variable
<View>
<TextInput
value={agentInvite}
onChangeText={(text) => {
setAgentInvite(text);
}}
/>
</View>
agentInvite
variable
const acceptInvite = async () => {
agent.connections.receiveInvitationFromUrl(agentInvite, {
autoAcceptConnection: true,
});
};
acceptInvite()
method
<View>
<Button
onPress={() => {
acceptInvite();
}}
title="Accept Invite"
/>
agentInvite
variable on Alice's agent
and appears in the text input field on Alice's agent
http://192.168.1.72:3001/invitation?c_i=eyJAdHlwZSI6Imh0dHBzOi8vZGlkY29tbS5vcmcvY29ubmVjdGlvbnMvMS4wL2ludml0YXRpb24iLCJAaWQiOiI1ODNhYzM0Zi0wN2NjLTQ2MWQtOTZhNC0zNGRmYTM2NDcxODYiLCJsYWJlbCI6Ik1pZ3JhdGUgV2FsbGV0IEFsaWNlIiwicmVjaXBpZW50S2V5cyI6WyI1VUFMOEpUaVZ2N3A4alBVUUFXVVFOR3lMTlc0SHVyMWtYREI1eTF4Vnl6aCJdLCJzZXJ2aWNlRW5kcG9pbnQiOiJodHRwOi8vMTkyLjE2OC4xLjcyOjMwMDEiLCJyb3V0aW5nS2V5cyI6WyJDY3dNWXdzMUtKUG5VQjlEMXpMWm80N2ZwQ2ExeW1pUHhXM21ETjVLVGJtWSJdfQ
{
"@type": "https://didcomm.org/connections/1.0/invitation",
"@id": "583ac34f-07cc-461d-96a4-34dfa3647186",
"label": "Private Wallet Alice",
"recipientKeys": ["5UAL8JTiVv7p8jPUQAWUQNGyLNW4Hur1kXDB5y1xVyzh"],
"serviceEndpoint": "http://192.168.1.72:3001",
"routingKeys": ["CcwMYws1KJPnUB9D1zLZo47fpCa1ymiPxW3mDN5KTbmY"]
}
/server/mywallet/App.js
file to
display a list of connections for the React Native app
const listConnections = async () => {
var connections = await agent.connections.getAll();
var connectionList = [];
connections.forEach((c, i) => {
connectionList.push({
name: c.theirLabel,
id: c.id,
});
});
setMyConnections(connectionList);
};
const [myConnections, setMyConnections] = React.useState([]);
getConnections()
method
<View>
<Button
onPress={() => {
listConnections();
}}
title="List Connections"
/>
</View>
<FlatList
data={myConnections}
renderItem={({ item }) => (
<View style={{ flexDirection: "row", marginBottom: 20 }}>
<Text>{item.name}</Text>
</View>
)}
keyExtractor={(item) => item.id}
/>
Pressing "List Connections" on Alice's agent shows the following:
Private Data Vault
Private Wallet Bob
Pressing "List Connections" on Bob's agent shows the following:
Private Data Vault
Private Wallet Alice
The two React Native edge agents have connected succesfully using the invitation that was created by Alice's agent and pasted into Bob's agent.
/server/mywallet/App.js
file to send
a basic message to a specified connection ID
const sendMessage = (conn, content) => {
agent.basicMessages.sendMessage(conn, content);
};
<View>
<TextInput
value={message}
onChangeText={(text) => {
setMessage(text);
}}
placeholder="type message here"
/>
</View>
const [message, setMessage] = React.useState("");
sendMessage()
method with the ID of
that connection along with the content of the typed message
<FlatList
data={myConnections}
renderItem={({ item }) => (
<View style={{ flexDirection: "row", marginBottom: 20 }}>
<Button
onPress={() => {
sendMessage(item.id, message);
}}
title="Send Message"
/>
<Text>{item.name}</Text>
</View>
)}
keyExtractor={(item) => item.id}
/>
BasicMessageEventsTypes
to the
list of imports
import { BasicMessageEventTypes } from "@aries-framework/core";
agent.events.on(BasicMessageEventTypes.BasicMessageStateChanged, (e) => {
if (e.payload.basicMessageRecord.role === "receiver") {
alert(e.payload.message.content);
}
});
/server/afj/mediator.ts
file to log
basic messages received by the mediator to the console and respond to
the sender with a basic message that says "message received"
agent.events.on(BasicMessageEventTypes.BasicMessageStateChanged, (e) => {
if (e.payload.basicMessageRecord.role === "receiver") {
console.log(e.payload.message.content);
agent.basicMessages.sendMessage(
e.payload.basicMessageRecord.connectionId,
"message received"
);
}
});
Alice
Bob
Both Alice and Bob can successfully exchange basic messages with the mediator.
Alice to Bob
Bob to Alice
Both Alice's agent and Bob's agent can successfully exchange basic messages with each other.
The Indy ledger running on the local VON network is permissioned to ony allow Trust Anchors or Trustees to write to it.
The DID and Verkey of the Node.js mediator agent must be registered / authenticated as an endorser via the VON webserver UI before the mediator will be allowed to write to the ledger.
httpInboundTransport.app.get("/publicdid", async (req, res) => {
res.send(agent.publicDid);
});
http://192.168.1.72:3001/publicdid
in
a browser to display the public DID and Verkey for the mediator
{
"did": "HJUyRbeMTesrgShkNqbAYP",
"verkey": "9tKZRphq5Jwoo79CVnTEv75fXQ3BQzBqFquWGARM9bUx"
}
http://192.168.1.72:9000/
and
performed the following steps in the "Authenticate a New DID" section
of the UI:
HJUyRbeMTesrgShkNqbAYP
9tKZRphq5Jwoo79CVnTEv75fXQ3BQzBqFquWGARM9bUx
Type: NYM
Nym: HJUyRbeMTesrgShkNqbAYP
Role: ENDORSER
Verkey: 9tKZRphq5Jwoo79CVnTEv75fXQ3BQzBqFquWGARM9bUx
The Node.js mediator is now successfully registered as an Endorser on the ledger with permission to write new entries to the ledger.
Next the mediator must create and register a new schema and credential definition on the ledger.
/server/afj/mediator.ts
to register
the schema
const registerSchema = (agent: Agent) =>
agent.ledger.registerSchema({
name: `Person`,
version: "1.0.0",
attributes: ["FullName", "Phone"],
});
/server/afj/mediator.ts
to register
the credential definition
const registerCredentialDefinition = (agent: Agent, schema: Schema) =>
agent.ledger.registerCredentialDefinition({
schema,
tag: "latest",
supportRevocation: true,
});
/server/afj/mediator.ts
to call the
registerSchema()
and
registerCredentialDefinition()
functions
httpInboundTransport.app.get("/createschema", async (req, res) => {
const schema = await registerSchema(agent);
const credentialDefinition = await registerCredentialDefinition(
agent,
schema
);
res.send("schema and credential definition registered");
});
http://192.168.1.72:3001/createschema
in a browser
http://192.168.1.72:9000/
and clicked
the "Domain" link under the "Ledger State" section of the UI
The first new entry is for the schema and contains the following information under the "Transaction" header
Type: SCHEMA
Schema name: Person
Schema version: 1.0.0
Schema attributes:
• FullName
• Phone
The first new entry on the ledger has a "Transaction ID" value of
HJUyRbeMTesrgShkNqbAYP:2:Neighbor:1.0.0
under its "Message Wrapper" section. This value is the schema ID.
The second new entry is for the credential definition and contains the following information under the "Transaction" header
Type: CRED_DEF
Reference: 7
Signature type: CL
Tag: latest
Attributes:
• master_secret
• fullname
• phone
The second new entry on the ledger has a "Transaction ID" value of
HJUyRbeMTesrgShkNqbAYP:3:CL:7:latest
under its "Message Wrapper" section. This value is the credential
definition ID.
The schema and credential definition are now successfully registered on the ledger.
/server/afj/mediator.ts
to offer a
credential based on the newly created credential definition with ID
HJUyRbeMTesrgShkNqbAYP:3:CL:7:latest
to a specific connection by supplying the following query parameters:
httpInboundTransport.app.get("/offer", async (req, res) => {
var conn = req.query.connection;
var definitionID = "HJUyRbeMTesrgShkNqbAYP:3:CL:7:latest";
const credentialPreview = CredentialPreview.fromRecord({
Name: req.query.name,
Phone: "None",
});
await agent.credentials.offerCredential(conn, {
credentialDefinitionId: definitionID,
preview: credentialPreview,
});
res.send("credential offered");
});
/server/afj/mediator.ts
file to list
all connections to the mediator as links to the
/offer
method with the connection's
ID as the connection
query parameter
and a shortened version of the agent's label as the
name
query parameter
httpInboundTransport.app.get("/offerlist", async (req, res) => {
var connections = await agent.connections.getAll();
var conns = [];
connections.forEach((c, i) => {
conns.push({
label: c.theirLabel,
id: c.id,
});
});
var list = "";
conns.forEach((conn, i) => {
// use only the last part of the connection label as the "name" query parameter to be used as the "Name" attribute when issuing the credential
// "Private Wallet Alice" becomes "Alice" and "Private Wallet Bob" becomes "Bob"
var shortName = conn.label.split(" ")[2];
// create a link to the /offer method with the connection ID as the "connection" query parameter and the shortened label as the "name" query parameter
list =
list +
`<a href="http://192.168.1.72:3001/offer?connection=` +
conn.id +
`&name=` +
shortName +
`">` +
conn.label +
`</a><br />`;
});
res.send(list);
});
CredentialEventTypes
to the
list of imports for the React Native edge agent
import { CredentialEventTypes } from "@aries-framework/core";
agent.events.on(
CredentialEventTypes.CredentialStateChanged,
async ({ payload }: CredentialStateChangedEvent) => {
if (payload.credentialRecord.state === CredentialState.OfferReceived) {
agent.credentials.acceptOffer(payload.credentialRecord.id);
}
}
);
http://192.168.1.72:3001/offerlist
to
display a list of the mediator's connections as links to the
/offer
method resulting in the
following output
<a href="http://192.168.1.72:3001/offer?connection=4b0f8b90-058b-4fd1-9b2e-26faa635bae5&name=Alice">Issue credential to Private Wallet Alice</a>
<a href="http://192.168.1.72:3001/offer?connection=003f038c-576a-4ab6-bc9e-c3bd458384cc&name=Bob">Issue credential to Private Wallet Bob</a>
/offer
method and issue a credential
to Alice's agent with the "Name" attribute having a value of "Alice"
import { Buffer } from "buffer";
/server/mywallet/App.js
of the React
Native app to decode any credentials the agent has received and log
them to the console
const showCredentials = async () => {
var credentials = await agent.credentials.getAll();
credentials.forEach((c, i) => {
if (c.state === "done") {
var s = Buffer.from(
c.credentialMessage.credentialAttachments[0].data.base64,
"base64"
).toString();
var sp = JSON.parse(s);
console.log(JSON.stringify(sp.values, null, 2));
}
});
return false;
};
showCredentials()
method
<View>
<Button
onPress={() => {
showCredentials();
}}
title="Show Credentials"
/>
</View>
{
"schema_id": "HJUyRbeMTesrgShkNqbAYP:2:Person:1.0.0",
"cred_def_id": "HJUyRbeMTesrgShkNqbAYP:3:CL:7:latest",
"rev_reg_id": null,
"values": {
"Name": {
"raw": "Alice",
"encoded": "49134673262984350012203662936972218187780495801618915546443112853445148770448"
}
},
"signature": {
"p_credential": {
"m_2": "76092918671082413225206030085402336436948677394289535359059308875779241743382",
"a": "10423598576783233265270818089957266406991114630011914353583266141408488991894647945752982508933783796949169509843591780929535780819619194812735176485369151515807341173514262541798505127910940009944844767491139121412767402761133032911229326871289208700364209836853327919536420064125060049912518767595324239371736669060222086085331830954339396180867530055976844001861270821405288100540643093466496589705102304146934651831215596407338087822828411757276870056477606949302739956154301273379177379917153098374681628245718608288944517279488445313736909681346493962261130541245467992909835209086231008115370704281325330918825",
"e": "259344723055062059907025491480697571938277889515152306249728583105665800713306759149981690559193987143012367913206299323899696942213235956742930076853569989116441333963922006014813",
"v": "5220185797045082548314232643500235417996463517433225198460165875693179296576285879219216410847081406095193848339501569049134435400099326154323123894037272866740279248935600732380800492678910869510961744421687244711203202543613005729237703834091168978421413308884686493953804126946690636665971013617689934765226516320115208625820176544759291198337391653211132083794675253250671299749030178402766654457984549542202573287963303145088719151432916828992472098783742560673341581521665459561407198660441420947248958804708836806564801249440485158423606207128312775842437255538446147107035260419629257122542841896696586001251492168978175447029368613220771569786144373979537979848545803425589258086097921284870876652639117534024014438879541125215226022969347562299852323455218351949630537433167024751657672908556710616811109998499"
},
"r_credential": null
},
"signature_correctness_proof": {
"se": "13832383249391622270697153024037102999325064059393813703137547000669371745548580217683165507738473366773591663799660304141541083591009553188032105212220578272890863069531526807379620154661445412755700440071447592871454415264801496507310982907084800277324805256716882791567315366689149543165026886112829706479891873023365411826129422764118954398088622890964848647139086439736156494300340386976715499174197711943847811055584824878345916377426092707005438272124999831720640383454745536251062640017881724132418681520850102476293953527104432778744712937740500340950900126763991273163515998138950429572173044115915387983988",
"c": "50780147548059214428577806855475787157956488583197774544606194457608437769429"
},
"rev_reg": null,
"witness": null
}
"schema_id": "HJUyRbeMTesrgShkNqbAYP:2:Person:1.0.0",
"cred_def_id": "HJUyRbeMTesrgShkNqbAYP:3:CL:7:latest"
Alice has successfully been issued a credential by the Node.js mediator
for the credential definition with ID of
HJUyRbeMTesrgShkNqbAYP:3:CL
containing
a value of "Alice" for the "Name" attribute.
requestProof()
method in
the /server/mywallet/App.js
file to
request proof of a credential from a connection with "Name" as the
only requested attribute
const requestProof = async (conn) => {
var pr = await agent.proofs.requestProof(
conn,
{
requestedAttributes: {
person_name: { name: "Name", restrictions: [] },
phone: { name: "Phone", restrictions: [] },
},
},
{
autoAcceptProof: true,
}
);
};
requestProof()
method with the ID of
that connection
<FlatList
data={myConnections}
renderItem={({ item }) => (
<View style={{ flexDirection: "row", marginBottom: 20 }}>
<Button
onPress={() => {
sendMessage(item.id, message);
}}
title="Send Message"
/>
<Button
onPress={() => {
requestProof(item.id);
}}
title="Request Proof"
/>
<Text>{item.name}</Text>
</View>
)}
keyExtractor={(item) => item.id}
/>
ProofEventTypes
to the list of
imports for the React Native edge agent
import { ProofEventTypes } from "@aries-framework/core";
/server/mywallet/App.js
to accept
incoming proof requests received by the edge agent and present proof
to the edge agent that requested the proof
agent.events.on <
ProofStateChangedEvent >
(ProofEventTypes.ProofStateChanged,
async (event) => {
var payload = event.payload;
if (payload.proofRecord.state === "request-received") {
const creds = await agent.proofs.getRequestedCredentialsForProofRequest(
payload.proofRecord.id
);
const automaticRequestedCreds =
await myAgent.proofs.autoSelectCredentialsForProofRequest(creds, {
filterByPresentationPreview: true,
});
await myAgent.proofs.acceptRequest(
payload.proofRecord.id,
automaticRequestedCreds
);
}
});
Pressed the "Request Proof" button on Bob's agent next to the "Private Wallet Alice" connection
A proof request is created and sent to Alice's agent
The results of
autoSelectCredentialsForProofRequest()
display a matching set of requested attributes when logged to the
console:
{
"requested_attributes": {
"fullname": {
"cred_id": "d163265d-3808-40c1-9fb3-3b3ae7d85ecc",
"revealed": true
},
"phone": {
"cred_id": "d163265d-3808-40c1-9fb3-3b3ae7d85ecc",
"revealed": true
}
},
"requested_predicates": {},
"self_attested_attributes": {}
}
await myAgent.proofs.acceptRequest(
payload.proofRecord.id,
automaticRequestedCreds
);
{
"requested_proof": {
"revealed_attrs": {
"fullname": {
"sub_proof_index": 0,
"raw": "Alice",
"encoded": "89726007198401840605074565745888726433925488607221336232242457427649158911824"
},
"phone": {
"sub_proof_index": 0,
"raw": "None",
"encoded": "43392548860722133623224245742764915891182489726007198401840605074565745888726"
}
},
"self_attested_attrs": {},
"unrevealed_attrs": {},
"predicates": {}
},
"identifiers": [
{
"schema_id": "HJUyRbeMTesrgShkNqbAYP:2:Connection:1.0.0",
"cred_def_id": "HJUyRbeMTesrgShkNqbAYP:3:CL:7:latest",
"rev_reg_id": null,
"timestamp": null
}
]
}
/server/mywallet/App.js
to accept
incoming proof presentations:
agent.events.on <
ProofStateChangedEvent >
(ProofEventTypes.ProofStateChanged,
async (event) => {
var payload = event.payload;
if (payload.proofRecord.state === "request-received") {
const creds = await agent.proofs.getRequestedCredentialsForProofRequest(
payload.proofRecord.id
);
const automaticRequestedCreds =
await myAgent.proofs.autoSelectCredentialsForProofRequest(creds, {
filterByPresentationPreview: true,
});
await myAgent.proofs.acceptRequest(
payload.proofRecord.id,
automaticRequestedCreds
);
}
if (payload.proofRecord.state === "presentation-received") {
var attached =
payload.proofRecord.presentationMessage.presentationAttachments[0].data
.base64;
var s = Buffer.from(attached, "base64").toString();
var sp = JSON.parse(s);
if (payload.proofRecord.isVerified) {
var vc = {
connection: payload.proofRecord.connectionId,
fullname: sp.requested_proof.revealed_attrs.fullname.raw,
phone: sp.requested_proof.revealed_attrs.phone.raw,
};
//We can do something here now that we know the credential is valid.
alert("Verified");
}
}
});
Bob's agent displays an alert with the following text:
Verified
Everything works as it should.
Alice and Bob can now exchange messages and verify each other's credentials.
If either Alice or Bob sends a message to the other while the other does not have the "My Wallet" application open the message will be queued at the mediator until the person the message is intended for opens their "My Wallet" application and it connects to the mediator.