Sample Codes

Sample codes to implement Affinidi Messaging with your application.

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.Vz6Mkihn2R3M8nY62EFJ7MAVXu7YxsTnuS5iAhmn3qKJbkdFf.EzQ3shpZRBUtewwzYiueXgDqs1bvGNkSyGoRgsbZJXt3TTb9jD.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.

  1. We have added the DIDs of the Sender, Recipient, and Mediator.
  2. 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.
  3. 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.
  4. 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.
  5. 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.Vz6Mkurt7yqPtwDD5d6PRHfZy9xPwtR1GNJvxi6kXaEcqj9GP.EzQ3shs2AY7ETV2phU2M5fEuESfdRMjTF6AZUboF3kd1r7jeKw.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHBzOi8vbG9jYWxob3N0OjcwMzcvIiwiYWNjZXB0IjpbImRpZGNvbW0vdjIiXSwicm91dGluZ19rZXlzIjpbXX19";

    // 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:

  1. We have added the DIDs of the Recipient, and the Mediator.
  2. We have added the private key info of the recipient’s DID to the Client SDK in order to verify and descrypt the messages.
  3. After adding the key info of the recipient, we used the fetch_messages method of the ATM SDK to fetch messages with DoNotDelete policy.
  4. 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:

  1. We have added the DIDs for the Sender and the Mediator.
  2. 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.
  3. After adding the recipient’s key info, we used the protocol’s send_ping method to send a trust ping using the message type https://didcomm.org/trust-ping/2.0/ping.
  4. After sending the PING, we retrieve the messages from the mediator to retrieve the PONG message.
  5. 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.