2022-09-12 00:23:14 +00:00
{. used . }
2024-03-15 23:08:47 +00:00
import std / tables , stew / [ results , byteutils ] , testutils / unittests
2022-11-04 09:52:08 +00:00
import
2024-07-05 22:03:38 +00:00
waku / [
common / protobuf ,
utils / noise as waku_message_utils ,
waku_noise / noise_types ,
waku_noise / noise_utils ,
waku_noise / noise_handshake_processing ,
waku_core ,
] ,
2023-02-13 10:43:49 +00:00
. / testlib / common
2022-09-12 00:23:14 +00:00
procSuite " Waku Noise Sessions " :
randomize ( )
2023-02-07 09:45:25 +00:00
# This test implements the Device pairing and Secure Transfers with Noise
2022-09-12 00:23:14 +00:00
# detailed in the 43/WAKU2-DEVICE-PAIRING RFC https://rfc.vac.dev/spec/43/
test " Noise Waku Pairing Handhshake and Secure transfer " :
#########################
# Pairing Phase
#########################
let hsPattern = NoiseHandshakePatterns [ " WakuPairing " ]
# Alice static/ephemeral key initialization and commitment
let aliceStaticKey = genKeyPair ( rng [ ] )
let aliceEphemeralKey = genKeyPair ( rng [ ] )
let s = randomSeqByte ( rng [ ] , 32 )
let aliceCommittedStaticKey = commitPublicKey ( getPublicKey ( aliceStaticKey ) , s )
# Bob static/ephemeral key initialization and commitment
let bobStaticKey = genKeyPair ( rng [ ] )
let bobEphemeralKey = genKeyPair ( rng [ ] )
let r = randomSeqByte ( rng [ ] , 32 )
let bobCommittedStaticKey = commitPublicKey ( getPublicKey ( bobStaticKey ) , r )
# Content Topic information
let applicationName = " waku-noise-sessions "
let applicationVersion = " 0.1 "
let shardId = " 10 "
let qrMessageNametag = randomSeqByte ( rng [ ] , MessageNametagLength )
# Out-of-band Communication
# Bob prepares the QR and sends it out-of-band to Alice
2024-03-15 23:08:47 +00:00
let qr = toQr (
applicationName ,
applicationVersion ,
shardId ,
getPublicKey ( bobEphemeralKey ) ,
bobCommittedStaticKey ,
)
2022-09-12 00:23:14 +00:00
# Alice deserializes the QR code
2024-03-15 23:08:47 +00:00
let (
readApplicationName , readApplicationVersion , readShardId , readEphemeralKey ,
readCommittedStaticKey ,
) = fromQr ( qr )
2022-09-12 00:23:14 +00:00
# We check if QR serialization/deserialization works
check :
applicationName = = readApplicationName
applicationVersion = = readApplicationVersion
shardId = = readShardId
getPublicKey ( bobEphemeralKey ) = = readEphemeralKey
bobCommittedStaticKey = = readCommittedStaticKey
# We set the contentTopic from the content topic parameters exchanged in the QR
2024-03-15 23:08:47 +00:00
let contentTopic : ContentTopic =
" / " & applicationName & " / " & applicationVersion & " /wakunoise/1/sessions_shard- " &
shardId & " /proto "
2022-09-12 00:23:14 +00:00
###############
# Pre-handshake message
#
# <- eB {H(sB||r), contentTopicParams, messageNametag}
###############
2024-03-15 23:08:47 +00:00
let preMessagePKs : seq [ NoisePublicKey ] =
@ [ toNoisePublicKey ( getPublicKey ( bobEphemeralKey ) ) ]
2022-09-12 00:23:14 +00:00
2023-02-07 09:45:25 +00:00
# We initialize the Handshake states.
2022-09-12 00:23:14 +00:00
# Note that we pass the whole qr serialization as prologue information
2024-03-15 23:08:47 +00:00
var aliceHS = initialize (
hsPattern = hsPattern ,
ephemeralKey = aliceEphemeralKey ,
staticKey = aliceStaticKey ,
prologue = qr . toBytes ,
preMessagePKs = preMessagePKs ,
initiator = true ,
)
var bobHS = initialize (
hsPattern = hsPattern ,
ephemeralKey = bobEphemeralKey ,
staticKey = bobStaticKey ,
prologue = qr . toBytes ,
preMessagePKs = preMessagePKs ,
)
2022-09-12 00:23:14 +00:00
###############
# Pairing Handshake
###############
2023-02-07 09:45:25 +00:00
var
2022-09-12 00:23:14 +00:00
sentTransportMessage : seq [ byte ]
aliceStep , bobStep : HandshakeStepResult
2023-02-20 14:03:32 +00:00
msgFromPb : ProtobufResult [ WakuMessage ]
2023-02-07 09:45:25 +00:00
wakuMsg : Result [ WakuMessage , cstring ]
2022-09-12 00:23:14 +00:00
pb : ProtoBuffer
readPayloadV2 : PayloadV2
aliceMessageNametag , bobMessageNametag : MessageNametag
2023-02-07 09:45:25 +00:00
2022-09-12 00:23:14 +00:00
# Write and read calls alternate between Alice and Bob: the handhshake progresses by alternatively calling stepHandshake for each user
###############
# 1st step
2023-02-07 09:45:25 +00:00
#
2022-09-12 00:23:14 +00:00
# -> eA, eAeB {H(sA||s)} [authcode]
###############
2023-02-07 09:45:25 +00:00
# The messageNametag for the first handshake message is randomly generated and exchanged out-of-band
2022-09-12 00:23:14 +00:00
# and corresponds to qrMessageNametag
# We set the transport message to be H(sA||s)
sentTransportMessage = digestToSeq ( aliceCommittedStaticKey )
# We ensure that digestToSeq and its inverse seqToDigest256 are correct
check :
seqToDigest256 ( sentTransportMessage ) = = aliceCommittedStaticKey
2023-02-07 09:45:25 +00:00
# By being the handshake initiator, Alice writes a Waku2 payload v2 containing her handshake message
2022-09-12 00:23:14 +00:00
# and the (encrypted) transport message
# The message is sent with a messageNametag equal to the one received through the QR code
2024-03-15 23:08:47 +00:00
aliceStep = stepHandshake (
rng [ ] ,
aliceHS ,
transportMessage = sentTransportMessage ,
messageNametag = qrMessageNametag ,
)
. get ( )
2022-09-12 00:23:14 +00:00
###############################################
# We prepare a Waku message from Alice's payload2
wakuMsg = encodePayloadV2 ( aliceStep . payload2 , contentTopic )
check :
wakuMsg . isOk ( )
wakuMsg . get ( ) . contentTopic = = contentTopic
# At this point wakuMsg is sent over the Waku network and is received
# We simulate this by creating the ProtoBuffer from wakuMsg
pb = wakuMsg . get ( ) . encode ( )
# We decode the WakuMessage from the ProtoBuffer
2022-11-07 15:24:16 +00:00
msgFromPb = WakuMessage . decode ( pb . buffer )
2023-02-07 09:45:25 +00:00
2022-09-12 00:23:14 +00:00
check :
msgFromPb . isOk ( )
# We decode the payloadV2 from the WakuMessage
readPayloadV2 = decodePayloadV2 ( msgFromPb . get ( ) ) . get ( )
check :
readPayloadV2 = = aliceStep . payload2
###############################################
# Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him
# Note that Bob verifies if the received payloadv2 has the expected messageNametag set
2024-03-15 23:08:47 +00:00
bobStep = stepHandshake (
rng [ ] , bobHS , readPayloadV2 = readPayloadV2 , messageNametag = qrMessageNametag
)
. get ( )
2023-02-07 09:45:25 +00:00
2022-09-12 00:23:14 +00:00
check :
bobStep . transportMessage = = sentTransportMessage
# We generate an authorization code using the handshake state
let aliceAuthcode = genAuthcode ( aliceHS )
let bobAuthcode = genAuthcode ( bobHS )
2023-02-07 09:45:25 +00:00
2022-09-12 00:23:14 +00:00
# We check that they are equal. Note that this check has to be confirmed with a user interaction.
check :
aliceAuthcode = = bobAuthcode
2023-02-07 09:45:25 +00:00
2022-09-12 00:23:14 +00:00
###############
# 2nd step
#
# <- sB, eAsB {r}
###############
# Alice and Bob update their local next messageNametag using the available handshake information
# During the handshake, messageNametag = HKDF(h), where h is the handshake hash value at the end of the last processed message
aliceMessageNametag = toMessageNametag ( aliceHS )
bobMessageNametag = toMessageNametag ( bobHS )
# We set as a transport message the commitment randomness r
sentTransportMessage = r
# At this step, Bob writes and returns a payload
2024-03-15 23:08:47 +00:00
bobStep = stepHandshake (
rng [ ] ,
bobHS ,
transportMessage = sentTransportMessage ,
messageNametag = bobMessageNametag ,
)
. get ( )
2022-09-12 00:23:14 +00:00
###############################################
# We prepare a Waku message from Bob's payload2
wakuMsg = encodePayloadV2 ( bobStep . payload2 , contentTopic )
check :
wakuMsg . isOk ( )
wakuMsg . get ( ) . contentTopic = = contentTopic
# At this point wakuMsg is sent over the Waku network and is received
# We simulate this by creating the ProtoBuffer from wakuMsg
pb = wakuMsg . get ( ) . encode ( )
# We decode the WakuMessage from the ProtoBuffer
2022-11-07 15:24:16 +00:00
msgFromPb = WakuMessage . decode ( pb . buffer )
2023-02-07 09:45:25 +00:00
2022-09-12 00:23:14 +00:00
check :
msgFromPb . isOk ( )
# We decode the payloadV2 from the WakuMessage
readPayloadV2 = decodePayloadV2 ( msgFromPb . get ( ) ) . get ( )
check :
readPayloadV2 = = bobStep . payload2
###############################################
# While Alice reads and returns the (decrypted) transport message
2024-03-15 23:08:47 +00:00
aliceStep = stepHandshake (
rng [ ] ,
aliceHS ,
readPayloadV2 = readPayloadV2 ,
messageNametag = aliceMessageNametag ,
)
. get ( )
2023-02-07 09:45:25 +00:00
2022-09-12 00:23:14 +00:00
check :
aliceStep . transportMessage = = sentTransportMessage
# Alice further checks if Bob's commitment opens to Bob's static key she just received
2024-03-15 23:08:47 +00:00
let expectedBobCommittedStaticKey =
commitPublicKey ( aliceHS . rs , aliceStep . transportMessage )
2022-09-12 00:23:14 +00:00
check :
expectedBobCommittedStaticKey = = bobCommittedStaticKey
2023-02-07 09:45:25 +00:00
2022-09-12 00:23:14 +00:00
###############
# 3rd step
#
# -> sA, sAeB, sAsB {s}
###############
# Alice and Bob update their local next messageNametag using the available handshake information
aliceMessageNametag = toMessageNametag ( aliceHS )
bobMessageNametag = toMessageNametag ( bobHS )
# We set as a transport message the commitment randomness s
sentTransportMessage = s
# Similarly as in first step, Alice writes a Waku2 payload containing the handshake message and the (encrypted) transport message
2024-03-15 23:08:47 +00:00
aliceStep = stepHandshake (
rng [ ] ,
aliceHS ,
transportMessage = sentTransportMessage ,
messageNametag = aliceMessageNametag ,
)
. get ( )
2022-09-12 00:23:14 +00:00
###############################################
# We prepare a Waku message from Bob's payload2
wakuMsg = encodePayloadV2 ( aliceStep . payload2 , contentTopic )
check :
wakuMsg . isOk ( )
wakuMsg . get ( ) . contentTopic = = contentTopic
# At this point wakuMsg is sent over the Waku network and is received
# We simulate this by creating the ProtoBuffer from wakuMsg
pb = wakuMsg . get ( ) . encode ( )
# We decode the WakuMessage from the ProtoBuffer
2022-11-07 15:24:16 +00:00
msgFromPb = WakuMessage . decode ( pb . buffer )
2023-02-07 09:45:25 +00:00
2022-09-12 00:23:14 +00:00
check :
msgFromPb . isOk ( )
# We decode the payloadV2 from the WakuMessage
readPayloadV2 = decodePayloadV2 ( msgFromPb . get ( ) ) . get ( )
check :
readPayloadV2 = = aliceStep . payload2
###############################################
# Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him
2024-03-15 23:08:47 +00:00
bobStep = stepHandshake (
rng [ ] , bobHS , readPayloadV2 = readPayloadV2 , messageNametag = bobMessageNametag
)
. get ( )
2023-02-07 09:45:25 +00:00
2022-09-12 00:23:14 +00:00
check :
bobStep . transportMessage = = sentTransportMessage
# Bob further checks if Alice's commitment opens to Alice's static key he just received
2024-03-15 23:08:47 +00:00
let expectedAliceCommittedStaticKey =
commitPublicKey ( bobHS . rs , bobStep . transportMessage )
2022-09-12 00:23:14 +00:00
check :
expectedAliceCommittedStaticKey = = aliceCommittedStaticKey
2023-02-07 09:45:25 +00:00
2022-09-12 00:23:14 +00:00
#########################
# Secure Transfer Phase
#########################
# We finalize the handshake to retrieve the Inbound/Outbound Symmetric States
var aliceHSResult , bobHSResult : HandshakeResult
aliceHSResult = finalizeHandshake ( aliceHS )
bobHSResult = finalizeHandshake ( bobHS )
# We test read/write of random messages exchanged between Alice and Bob
2023-02-07 09:45:25 +00:00
var
2022-09-12 00:23:14 +00:00
payload2 : PayloadV2
message : seq [ byte ]
readMessage : seq [ byte ]
# We test message exchange
# Note that we exchange more than the number of messages contained in the nametag buffer to test if they are filled correctly as the communication proceeds
for i in 0 .. 10 * MessageNametagBufferSize :
# Alice writes to Bob
message = randomSeqByte ( rng [ ] , 32 )
2024-03-15 23:08:47 +00:00
payload2 = writeMessage (
aliceHSResult ,
message ,
outboundMessageNametagBuffer = aliceHSResult . nametagsOutbound ,
)
readMessage = readMessage (
bobHSResult ,
payload2 ,
inboundMessageNametagBuffer = bobHSResult . nametagsInbound ,
)
. get ( )
2023-02-07 09:45:25 +00:00
check :
2022-09-12 00:23:14 +00:00
message = = readMessage
2023-02-07 09:45:25 +00:00
2022-09-12 00:23:14 +00:00
# Bob writes to Alice
message = randomSeqByte ( rng [ ] , 32 )
2024-03-15 23:08:47 +00:00
payload2 = writeMessage (
bobHSResult ,
message ,
outboundMessageNametagBuffer = bobHSResult . nametagsOutbound ,
)
readMessage = readMessage (
aliceHSResult ,
payload2 ,
inboundMessageNametagBuffer = aliceHSResult . nametagsInbound ,
)
. get ( )
2023-02-07 09:45:25 +00:00
2022-09-12 00:23:14 +00:00
check :
message = = readMessage
# We test how nametag buffers help in detecting lost messages
# Alice writes two messages to Bob, but only the second is received
message = randomSeqByte ( rng [ ] , 32 )
2024-03-15 23:08:47 +00:00
payload2 = writeMessage (
aliceHSResult ,
message ,
outboundMessageNametagBuffer = aliceHSResult . nametagsOutbound ,
)
2022-09-12 00:23:14 +00:00
message = randomSeqByte ( rng [ ] , 32 )
2024-03-15 23:08:47 +00:00
payload2 = writeMessage (
aliceHSResult ,
message ,
outboundMessageNametagBuffer = aliceHSResult . nametagsOutbound ,
)
2022-09-12 00:23:14 +00:00
expect NoiseSomeMessagesWereLost :
2024-03-15 23:08:47 +00:00
readMessage = readMessage (
bobHSResult ,
payload2 ,
inboundMessageNametagBuffer = bobHSResult . nametagsInbound ,
)
. get ( )
2022-09-12 00:23:14 +00:00
# We adjust bob nametag buffer for next test (i.e. the missed message is correctly recovered)
delete ( bobHSResult . nametagsInbound , 2 )
message = randomSeqByte ( rng [ ] , 32 )
2024-03-15 23:08:47 +00:00
payload2 = writeMessage (
bobHSResult , message , outboundMessageNametagBuffer = bobHSResult . nametagsOutbound
)
readMessage = readMessage (
aliceHSResult ,
payload2 ,
inboundMessageNametagBuffer = aliceHSResult . nametagsInbound ,
)
. get ( )
2023-02-07 09:45:25 +00:00
2022-09-12 00:23:14 +00:00
check :
2024-03-15 23:08:47 +00:00
message = = readMessage
2022-09-12 00:23:14 +00:00
# We test if a missing nametag is correctly detected
message = randomSeqByte ( rng [ ] , 32 )
2024-03-15 23:08:47 +00:00
payload2 = writeMessage (
aliceHSResult ,
message ,
outboundMessageNametagBuffer = aliceHSResult . nametagsOutbound ,
)
2022-09-12 00:23:14 +00:00
delete ( bobHSResult . nametagsInbound , 1 )
expect NoiseMessageNametagError :
2024-03-15 23:08:47 +00:00
readMessage = readMessage (
bobHSResult ,
payload2 ,
inboundMessageNametagBuffer = bobHSResult . nametagsInbound ,
)
. get ( )