How to use uAgents 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 uAgents 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 uAgents, where messages are not only exchanged but also signed and verified to prevent unauthorized access and tampering.
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:
touch 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
:import 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:def 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 uAgents using the
Agent
class:alice = Agent(name="alice", seed="alice recovery password") bob = Agent(name="bob", seed="bob recovery password")
-
Let's now define a
send_message()
function foralice
to send messages tobob
:@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 every3.0
seconds to send messages tobob
's address. It takes in a single argumentctx
. The function first creates a message,msg
, and computes its digest using theencode
function. The message is then sent tobob
using thectx.send()
method, along with thedigest
and asignature
of the digest using thealice.sign_digest()
function. -
Let's then define an
alice_rx_message()
function used to receive and process messages sent bybob
:@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 typeMessage
. The function takes in three arguments:ctx
,sender
, andmsg
.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
:@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 typeMessage
. It takes in three arguments:ctx
,sender
, andmsg
.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 thectx.logger.info()
method. It then creates a response message,msg
, and computes its digest using theencode
function. The response message is then sent toalice
using thectx.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.bureau = Bureau() bureau.add(alice) bureau.add(bob) if __name__ == "__main__": bureau.run()
-
Save the script.
The overall script should look as follows:
import 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")
bob = Agent(name="bob", seed="bob recovery password")
@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:
[ 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