How to use agents to verify messages
Introduction
The emergence of decentralized technologies has introduced new possibilities for secure communication and data exchange. In this guide, we will delve into the process of setting up a scenario where two Agents communicate with each other, employing cryptographic methods to verify the messages exchanged between them. We will showcase how to create a secure messaging environment using Agents, where messages are not only exchanged but also signed and verified to prevent unauthorized access and tampering.
Prerequisites
Make sure you have read the following resources before going on with this guide:
- Quick Start Guide for uAgents Framework
- Creating your first agent
- Agents address
- Almanac contract
- Register in Almanac
Imports needed
Walk-through
-
First of all, you need to navigate towards the directory you created for your project.
-
In here, let's create a Python script for this task and name it:
windowsecho. > message_verification.py
-
We now need to import the necessary classes from
uagents
(Agent
,Bureau
,Context
, andModel
),uagents.crypto
(Identity
) andhashlib
. Then we would need to define the messages format using theMessage
class as a subclass ofModel
:
message_verification.pyimport hashlib from uagents import Agent, Bureau, Context, Model from uagents.crypto import Identity class Message(Model): message: str digest: str signature: str
The message format has three attributes:
message
: a string representing the message text.digest
: a string representing the SHA-256 hash of the message.signature
: a string representing the digital signature of the hash using the sender's private key.
- Let's now define an
encode()
function used to generate the digest for each message before it is signed:
message_verification.pydef encode(message: str) -> bytes: hasher = hashlib.sha256() hasher.update(message.encode()) return hasher.digest()
This function is used to hash a string message using the SHA-256 algorithm and return the resulting digest as bytes.
- We can now proceed and create our agents using the
Agent
class:
message_verification.pyalice = Agent(name="alice", seed="alice recovery password", port=8000, endpoint=["http://127.0.0.1:8000/submit"]) bob = Agent(name="bob", seed="bob recovery password", port=8001, endpoint=["http://127.0.0.1:8001/submit"])
- Let's now define a
send_message()
function foralice
to send messages tobob
:
message_verification.py@alice.on_interval(period=3.0) async def send_message(ctx: Context): msg = "hello there bob" digest = encode(msg) await ctx.send( bob.address, Message(message=msg, digest=digest.hex(), signature=alice.sign_digest(digest)), )
This function is decorated using the .on_interval()
decorator, which indicates that the function is called periodically every 3.0
seconds to send messages to bob
's address. It takes in a single argument ctx
. The function first creates a message, msg
, and computes its digest using the encode
function. The message is then sent to bob
using the ctx.send()
method, along with the digest
and a signature
of the digest using the alice.sign_digest()
function.
- Let's then define an
alice_rx_message()
function used to receive and process messages sent bybob
:
message_verification.py@alice.on_message(model=Message) async def alice_rx_message(ctx: Context, sender: str, msg: Message): assert Identity.verify_digest( sender, bytes.fromhex(msg.digest), msg.signature ), "couldn't verify bob's message" ctx.logger.info("Bob's message verified!") ctx.logger.info(f"Received message from {sender}: {msg.message}")
This function is decorated using the .on_message()
, indicating that the function is triggered when a message is being received of type Message
. The function takes in three arguments: ctx
, sender
, and msg
.
The function first verifies the authenticity of the message using the Identity.verify_digest()
function. If the message cannot be verified, the function raises an assertion error. Assuming the message is verified, the function logs a message indicating that the message was verified and another message indicating the contents of the message.
- We can now define a
bob_rx_message()
function used bybob
to receive and process messages sent byalice
:
message_verification.py@bob.on_message(model=Message) async def bob_rx_message(ctx: Context, sender: str, msg: Message): assert Identity.verify_digest( sender, bytes.fromhex(msg.digest), msg.signature ), "couldn't verify alice's message" ctx.logger.info("Alice's message verified!") ctx.logger.info(f"Received message from {sender}: {msg.message}") msg = "hello there alice" digest = encode(msg) await ctx.send( alice.address, Message(message=msg, digest=digest.hex(), signature=bob.sign_digest(digest)), )
This function is decorated using the .on_message()
decorator, indicating that the function is triggered when a message is being received of type Message
. It takes in three arguments: ctx
, sender
, and msg
.
The function first verifies the authenticity of the message using the Identity.verify_digest()
function. If the message cannot be verified, the function raises an assertion error. On the other hand, if the message is verified, the function logs a message indicating that the message was verified and another message indicating the contents of the message using the ctx.logger.info()
method. It then creates a response message, msg
, and computes its digest using the encode()
function. The response message is then sent to alice
using the ctx.send()
method.
- We can now create a
bureau
object from theBureau
class and then add both agents to it so for them to be run together.
message_verification.pybureau = Bureau() bureau.add(alice) bureau.add(bob) if __name__ == "__main__": bureau.run()
- Save the script.
The overall script should look as follows:
message_verification.pyimport hashlib from uagents import Agent, Bureau, Context, Model from uagents.crypto import Identity class Message(Model): message: str digest: str signature: str def encode(message: str) -> bytes: hasher = hashlib.sha256() hasher.update(message.encode()) return hasher.digest() alice = Agent(name="alice", seed="alice recovery password", port=8000, endpoint=["http://127.0.0.1:8000/submit"]) bob = Agent(name="bob", seed="bob recovery password", port=8001, endpoint=["http://127.0.0.1:8001/submit"]) @alice.on_interval(period=3.0) async def send_message(ctx: Context): msg = "hello there bob" digest = encode(msg) await ctx.send( bob.address, Message(message=msg, digest=digest.hex(), signature=alice.sign_digest(digest)), ) @alice.on_message(model=Message) async def alice_rx_message(ctx: Context, sender: str, msg: Message): assert Identity.verify_digest( sender, bytes.fromhex(msg.digest), msg.signature ), "couldn't verify bob's message" ctx.logger.info("Bob's message verified!") ctx.logger.info(f"Received message from {sender}: {msg.message}") @bob.on_message(model=Message) async def bob_rx_message(ctx: Context, sender: str, msg: Message): assert Identity.verify_digest( sender, bytes.fromhex(msg.digest), msg.signature ), "couldn't verify alice's message" ctx.logger.info("Alice's message verified!") ctx.logger.info(f"Received message from {sender}: {msg.message}") msg = "hello there alice" digest = encode(msg) await ctx.send( alice.address, Message(message=msg, digest=digest.hex(), signature=bob.sign_digest(digest)), ) bureau = Bureau() bureau.add(alice) bureau.add(bob) if __name__ == "__main__": bureau.run()
Run your script
On your terminal, make sure to have activated your virtual environment.
Run the script: python message_verification.py
.
The output should be as follows:
WARNING: [alice]: No endpoints provided. Skipping registration: Agent won't be reachable. WARNING: [ bob]: No endpoints provided. Skipping registration: Agent won't be reachable. [ bob]: Alice's message verified! [ bob]: Received message from agent1qf5gfqm48k9acegez3sg82ney2aa6l5fvpwh3n3z0ajh0nam3ssgwnn5me7: hello there bob [alice]: Bob's message verified! [alice]: Received message from agent1qvjjle8dlf22ff7zsh6wr3gl8tdepzygftdxpc2vn8539ngt962a709c90s: hello there alice