Sample Codes
Explore sample codes to know more about Affinidi Messaging and how to implement it in your application using the Affinidi Messaging SDK.
Sending a Message to Another DID
In this example, we send a message to another DID by forwarding it to the mediator and routing it to the final recipient.
use affinidi_messaging_sdk::{
config::Config,
conversions::secret_from_str,
errors::ATMError,
messages::sending::InboundMessageResponse,
ATM,
};
use affinidi_messaging_didcomm::{Attachment, Message};
use clap::Parser;
use serde_json::json;
use std::time::SystemTime;
use tracing_subscriber::filter;
use uuid::Uuid;
use std::error::Error;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
#[arg(short, long)]
network_address: Option<String>,
#[arg(short, long)]
mediator_did: String,
}
#[tokio::main]
async fn main()-> Result<(), Box<dyn Error>> {
// **************************************************************
// *** Initial setup
// **************************************************************
let args = Args::parse();
// construct a subscriber that prints formatted traces to stdout
let subscriber = tracing_subscriber::fmt()
// Use a more compact, abbreviated log format
.with_env_filter(filter::EnvFilter::from_default_env())
.finish();
// use that subscriber to process traces emitted after this point
tracing::subscriber::set_global_default(subscriber).expect("Logging failed, exiting...");
// DIDs of sender(your DID) and recipient
let sender_did = "did:peer:2.Vz6MkgWJfVmPELozq6aCycK3CpxHN8Upphn3WSuQkWY6iqsjF.EzQ3shfb7vwQaTJqFkt8nRfo7Nu98tmeYpdDfWgrqQitDaqXRz";
let recipient_did = "did:peer:2.Vz6MkrvahQoRYxv9JcwKnQhGnSTX7n8jiPs21rkLgrD3XRsH2.EzQ3shssuTxDVenPApGBMScQPTfWfeVrJwm51etYenRJb3G4Nj.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHBzOi8vbG9jYWxob3N0OjcwMzcvIiwiYWNjZXB0IjpbImRpZGNvbW0vdjIiXSwicm91dGluZ19rZXlzIjpbXX0sImlkIjpudWxsfQ";
// DID of the Mediator
let mediator_did = &args.mediator_did;
// Signing and verification key
let verification_key1 = json!({
"crv": "Ed25519",
"d": "LLWCf83n8VsUYq31zlZRe0NNMCcn1N4Dh85dGpIqSFw",
"kty": "OKP",
"x": "Hn8T4ZjjT0oJ6rjhqox8AykwC3GDFsJF6KkaYZExwQo"
});
// Encryption key
let encryption_key1 = json!({
"crv": "secp256k1",
"d": "oi-dXG4EqfNODFPjv2vkieoLdbQZH9k6dwPDV8HDoms",
"kty": "EC",
"x": "DhfaXbhwo0KkOiyA5V1K1RZx6Ikr86h_lX5GOwxjmjE",
"y": "PpYqybOwMsm64vftt-7gBCQPIUbglMmyy_6rloSSAPk"
});
// TODO: in the future we likely want to pull this from the DID itself
let mut config = Config::builder()
.with_my_did(sender_did)
.with_atm_did(mediator_did)
.with_websocket_disabled();
if let Some(address) = &args.network_address {
println!("Running in network mode with address: {}", address);
config = config
.with_ssl_certificates(&mut vec!["./certs/mediator-key.pem".into()])
.with_atm_api(address);
} else {
println!("Running in local mode.");
config = config.with_ssl_certificates(&mut vec![
"../affinidi-messaging-mediator/conf/keys/client.chain".into(),
]);
}
// Create a new ATM Client
let mut atm = ATM::new(config.build()?).await?;
// Add sender's secrets to ATM Client - these keys stays local.
atm.add_secret(secret_from_str(&format!("{}#key-1", sender_did), &verification_key1));
atm.add_secret(secret_from_str(&format!("{}#key-2", sender_did), &encryption_key1));
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
let msg = Message::build(
Uuid::new_v4().into(),
"https://didcomm.org/routing/2.0/forward".to_owned(),
json!({ "message": "Body of message, can be read only by mediator", "next": recipient_did }),
)
.to(mediator_did.to_owned())
.from(sender_did.to_string())
.attachment(Attachment::json(json!({ "message": "plaintext attachment, mediator can read this" })).finalize())
.attachment(Attachment::base64(String::from("ciphertext and iv which is encrypted by the recipient public key")).finalize());
let msg = msg.created_time(now).expires_time(now + 300).finalize();
println!("Message Build");
println!("Send message: {:?}", msg);
// Pack the message
let (msg, _) = atm
.pack_encrypted(
&msg,
mediator_did,
Some(sender_did),
Some(sender_did)
)
.await
.map_err(|e| ATMError::MsgSendError(format!("Error packing message: {}", e)))?;
let response = atm
.send_didcomm_message::<InboundMessageResponse>(&msg, true)
.await?;
println!("---====---");
println!("Response message: {:?}", response);
Ok(())
}
Sample Code Highlights
In this sample code, using the forward
message type of the DIDComm Messaging protocol, we have made the following changes to successfully send the message to the recipient.
- We have added the DIDs of the Sender, Recipient, and Mediator.
- Using the Private keys of the Sender, we have added the key info to the Client SDK. These keys stay in the local machine and is not sent to the mediator. It is used to sign and encrypt the message that will be sent to the mediator and the recipient as the final destination.
- After adding the key info of the sender, we build the message by providing the following details:
- Type of the message to send:
https://didcomm.org/routing/2.0/forward
- The DID of the recipient where the message will be routed from the mediator.
- Finally, we have added an attachment encrypted with the Recipient’s public key, which can be found by resolving the recipient’s DID, to ensure that only the intended recipient can read the attached message.
- Type of the message to send:
- After building the message, we pack it using the
pack_encrypted
method of ATM SDK by passing the message we build, the DID of the mediator to forward the message and the DID of the sender, including the DID of the signer, in this case, the same DID as the sender. - After packing the message, we called ATM SDK’s
send_didcomm_message
method to send the message to the mediator.
Fetching Message Sent to a DID
In this example, we are fetching the messages sent to a particular DID, in this case, the recipient’s DID from the first example.
use affinidi_messaging_sdk::{
config::Config,
conversions::secret_from_str,
messages::{fetch::FetchOptions, FetchDeletePolicy},
ATM,
};
use clap::Parser;
use serde_json::json;
use tracing_subscriber::filter;
use std::error::Error;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
#[arg(short, long)]
network_address: Option<String>,
}
#[tokio::main]
async fn main()-> Result<(), Box<dyn Error>> {
// **************************************************************
// *** Initial setup
// **************************************************************
let args = Args::parse();
// construct a subscriber that prints formatted traces to stdout
let subscriber = tracing_subscriber::fmt()
// Use a more compact, abbreviated log format
.with_env_filter(filter::EnvFilter::from_default_env())
.finish();
// use that subscriber to process traces emitted after this point
tracing::subscriber::set_global_default(subscriber).expect("Logging failed, exiting...");
// DIDs of sender(your DID) and recipient
let recipient_did = "did:peer:2.Vz6Mkihn2R3M8nY62EFJ7MAVXu7YxsTnuS5iAhmn3qKJbkdFf.EzQ3shpZRBUtewwzYiueXgDqs1bvGNkSyGoRgsbZJXt3TTb9jD.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHBzOi8vbG9jYWxob3N0OjcwMzcvIiwiYWNjZXB0IjpbImRpZGNvbW0vdjIiXSwicm91dGluZ19rZXlzIjpbXX0sImlkIjpudWxsfQ";
// DID of the Mediator
let mediator_did = "did:peer:2.Vz6MkrvahQoRYxv9JcwKnQhGnSTX7n8jiPs21rkLgrD3XRsH2.EzQ3shssuTxDVenPApGBMScQPTfWfeVrJwm51etYenRJb3G4Nj.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHBzOi8vbG9jYWxob3N0OjcwMzcvIiwiYWNjZXB0IjpbImRpZGNvbW0vdjIiXSwicm91dGluZ19rZXlzIjpbXX0sImlkIjpudWxsfQ";
// Signing and verification key
let verification_key1 = json!({
"crv": "Ed25519",
"d": "FZMJijqdcp7PCQShgtFj6Ud3vjZY7jFZBVvahziaMMM",
"kty": "OKP",
"x": "PybG95kyeSfGRebp4T7hzA7JQuysc6mZ97nM2ety6Vo"
});
// Encryption key
let encryption_key1 = json!({
"crv": "secp256k1",
"d": "ai7B5fgT3pCBHec0I4Y1xXpSyrEHlTy0hivSlddWHZE",
"kty": "EC",
"x": "k2FhEi8WMxr4Ztr4u2xjKzDESqVnGg_WKrN1820wPeA",
"y": "fq0DnZ_duPWyeFK0k93bAzjNJVVHEjHFRlGOJXKDS18"
});
// TODO: in the future we likely want to pull this from the DID itself
let mut config = Config::builder()
.with_ssl_certificates(&mut vec![
"../affinidi-messaging-mediator/conf/keys/client.chain".into(),
])
.with_my_did(recipient_did)
.with_atm_did(mediator_did)
.with_websocket_disabled();
// Create a new ATM Client
let mut atm = ATM::new(config.build()?).await?;
// Add our secrets to ATM Client - stays local.
atm.add_secret(secret_from_str(&format!("{}#key-1", recipient_did), &verification_key1));
atm.add_secret(secret_from_str(&format!("{}#key-2", recipient_did), &encryption_key1));
// Get the messages from ATM
let msgs = atm
.fetch_messages(&FetchOptions {
limit: 10,
start_id: None,
delete_policy: FetchDeletePolicy::DoNotDelete
})
.await?;
for msg in msgs.success {
let (received_msg_unpacked,_) = atm.unpack(&msg.msg.unwrap()).await?;
println!("Message received: {:?}", received_msg_unpacked);
}
Ok(())
}
Sample Code Highlights
In this example of fetching the message sent to a particular, we have made the following changes to retrieve the messages successfully:
- We have added the DIDs of the Recipient, and the Mediator.
- We have added the private key info of the recipient’s DID to the Client SDK in order to verify and descrypt the messages.
- After adding the key info of the recipient, we used the
fetch_messages
method of the ATM SDK to fetch messages with DoNotDelete policy. - After retrieving the messages, we loop through the messages and unpack the messages to read the raw data using the
unpack
method of the ATM SDK.
Send Trust Ping to Mediator
In this example, we will send a trust ping to the mediator to ensure that our application can communicate successfully with the mediator.
use std::time::SystemTime;
use affinidi_did_resolver_cache_sdk::{config::ClientConfigBuilder, DIDCacheClient};
use affinidi_messaging_sdk::{
config::Config, conversions::secret_from_str, errors::ATMError, messages::GetMessagesRequest,
protocols::Protocols, ATM,
};
use clap::Parser;
use serde_json::json;
use tracing::info;
use tracing_subscriber::filter;
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
/// network address if running in network mode (https://localhost:7037/atm/v1)
#[arg(short, long)]
network_address: Option<String>,
#[arg(short, long)]
mediator_did: String,
}
#[tokio::main]
async fn main() -> Result<(), ATMError> {
let args = Args::parse();
// construct a subscriber that prints formatted traces to stdout
let subscriber = tracing_subscriber::fmt()
// Use a more compact, abbreviated log format
.with_env_filter(filter::EnvFilter::from_default_env())
.finish();
// use that subscriber to process traces emitted after this point
tracing::subscriber::set_global_default(subscriber).expect("Logging failed, exiting...");
let sender_did = "did:peer:2.Vz6MkgWJfVmPELozq6aCycK3CpxHN8Upphn3WSuQkWY6iqsjF.EzQ3shfb7vwQaTJqFkt8nRfo7Nu98tmeYpdDfWgrqQitDaqXRz";
// Signing and verification key
let verification_key1 = json!({
"crv": "Ed25519",
"d": "LLWCf83n8VsUYq31zlZRe0NNMCcn1N4Dh85dGpIqSFw",
"kty": "OKP",
"x": "Hn8T4ZjjT0oJ6rjhqox8AykwC3GDFsJF6KkaYZExwQo"
});
// Encryption key
let encryption_key1 = json!({
"crv": "secp256k1",
"d": "oi-dXG4EqfNODFPjv2vkieoLdbQZH9k6dwPDV8HDoms",
"kty": "EC",
"x": "DhfaXbhwo0KkOiyA5V1K1RZx6Ikr86h_lX5GOwxjmjE",
"y": "PpYqybOwMsm64vftt-7gBCQPIUbglMmyy_6rloSSAPk"
});
let mediator_did = &args.mediator_did;
// ATM SDK supports an externally created DID Cache Resolver
let did_resolver = DIDCacheClient::new(ClientConfigBuilder::default().build())
.await
.expect("Couldn't create DID Resolver!");
let mut config = Config::builder()
.with_my_did(sender_did)
.with_atm_did(mediator_did)
.with_websocket_disabled()
.with_external_did_resolver(&did_resolver);
if let Some(address) = &args.network_address {
println!("Running in network mode with address: {}", address);
config = config
.with_ssl_certificates(&mut vec!["./certs/mediator-key.pem".into()])
.with_atm_api(address);
} else {
config = config.with_ssl_certificates(&mut vec![
"../affinidi-messaging-mediator/conf/keys/client.chain".into(),
]);
println!("Running in local mode.");
}
// Create a new ATM Client
let mut atm = ATM::new(config.build()?).await?;
let protocols = Protocols::new();
// Add our secrets to ATM Client - stays local.
atm.add_secret(secret_from_str(&format!("{}#key-1", sender_did), &verification_key1));
atm.add_secret(secret_from_str(&format!("{}#key-2", sender_did), &encryption_key1));
// Ready to send a trust-ping to ATM
let start = SystemTime::now();
let well_know_res = atm.well_known_did_json().await?;
println!("DID: {}", well_know_res);
// You normally don't need to call authenticate() as it is called automatically
// We do this here so we can time the auth cycle
atm.authenticate().await?;
let after_auth = SystemTime::now();
// Send a trust-ping message to ATM, will generate a PONG response
let response = protocols
.trust_ping
.send_ping(&mut atm, mediator_did, true, true)
.await?;
let after_ping = SystemTime::now();
info!("PING sent: {}", response.message_hash);
// Get the PONG message from ATM
let msgs = atm
.get_messages(&GetMessagesRequest {
delete: false,
message_ids: vec![response.response.unwrap()],
})
.await?;
let after_get = SystemTime::now();
// Unpack the messages retrieved
for msg in msgs.success {
atm.unpack(&msg.msg.unwrap()).await?;
info!("PONG received: {}", msg.msg_id);
}
let after_unpack = SystemTime::now();
// Print out timing information
info!(
"Authenticating took {}ms :: total {}ms to complete",
after_auth.duration_since(start).unwrap().as_millis(),
after_auth.duration_since(start).unwrap().as_millis()
);
info!(
"Sending Ping took {}ms :: total {}ms to complete",
after_ping.duration_since(after_auth).unwrap().as_millis(),
after_ping.duration_since(start).unwrap().as_millis()
);
info!(
"Get response took {}ms :: total {}ms to complete",
after_get.duration_since(after_ping).unwrap().as_millis(),
after_get.duration_since(start).unwrap().as_millis()
);
info!(
"Unpack took {}ms :: total {}ms to complete",
after_unpack.duration_since(after_get).unwrap().as_millis(),
after_unpack.duration_since(start).unwrap().as_millis()
);
info!(
"Total trust-ping took {}ms to complete",
after_unpack.duration_since(start).unwrap().as_millis()
);
Ok(())
}
Sample Code Highlights
In this example, to successfully send a PING message to the mediator, we have made the following changes:
- We have added the DIDs for the Sender and the Mediator.
- We have added the private key info of the recipient’s DID to the Client SDK so that we can verify and decrypt the messages.
- After adding the recipient’s key info, we used the protocol’s
send_ping
method to send a trust ping using the message typehttps://didcomm.org/trust-ping/2.0/ping
. - After sending the PING, we retrieve the messages from the mediator to retrieve the PONG message.
- After retrieving the messages, we loop through them and unpack them using the
unpack
method of the ATM SDK to confirm that we can decrypt the message, and then we display the Message ID.
Explore sample codes from the examples folder of Affinidi Messaging SDK to learn more.
Glad to hear it! Please tell us how we can improve more.
Sorry to hear that. Please tell us how we can improve.
Thank you for sharing your feedback so we can improve your experience.