{.used.} import testutils/unittests, std/random, std/tables, stew/byteutils, libp2p/crypto/chacha20poly1305, libp2p/protobuf/minprotobuf, stew/endians2 import waku/[ utils/noise as waku_message_utils, waku_noise/noise_types, waku_noise/noise_utils, waku_noise/noise, waku_noise/noise_handshake_processing, waku_core, ], ./testlib/common procSuite "Waku Noise": common.randomize() test "PKCS#7 Padding/Unpadding": # We test padding for different message lengths let maxMessageLength = 3 * NoisePaddingBlockSize for messageLen in 0 .. maxMessageLength: let message = randomSeqByte(rng[], messageLen) padded = pkcs7_pad(message, NoisePaddingBlockSize) unpadded = pkcs7_unpad(padded, NoisePaddingBlockSize) check: padded.len != 0 padded.len mod NoisePaddingBlockSize == 0 message == unpadded test "ChaChaPoly Encryption/Decryption: random byte sequences": let cipherState = randomChaChaPolyCipherState(rng[]) # We encrypt/decrypt random byte sequences let plaintext: seq[byte] = randomSeqByte(rng[], rand(1 .. 128)) ciphertext: ChaChaPolyCiphertext = encrypt(cipherState, plaintext) decryptedCiphertext: seq[byte] = decrypt(cipherState, ciphertext) check: plaintext == decryptedCiphertext test "ChaChaPoly Encryption/Decryption: random strings": let cipherState = randomChaChaPolyCipherState(rng[]) # We encrypt/decrypt random strings var plaintext: string for _ in 1 .. rand(1 .. 128): add(plaintext, char(rand(int('A') .. int('z')))) let ciphertext: ChaChaPolyCiphertext = encrypt(cipherState, plaintext.toBytes()) decryptedCiphertext: seq[byte] = decrypt(cipherState, ciphertext) check: plaintext.toBytes() == decryptedCiphertext test "Noise public keys: encrypt and decrypt a public key": let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[]) let cs: ChaChaPolyCipherState = randomChaChaPolyCipherState(rng[]) encryptedPk: NoisePublicKey = encryptNoisePublicKey(cs, noisePublicKey) decryptedPk: NoisePublicKey = decryptNoisePublicKey(cs, encryptedPk) check: noisePublicKey == decryptedPk test "Noise public keys: decrypt an unencrypted public key": let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[]) let cs: ChaChaPolyCipherState = randomChaChaPolyCipherState(rng[]) decryptedPk: NoisePublicKey = decryptNoisePublicKey(cs, noisePublicKey) check: noisePublicKey == decryptedPk test "Noise public keys: encrypt an encrypted public key": let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[]) let cs: ChaChaPolyCipherState = randomChaChaPolyCipherState(rng[]) encryptedPk: NoisePublicKey = encryptNoisePublicKey(cs, noisePublicKey) encryptedPk2: NoisePublicKey = encryptNoisePublicKey(cs, encryptedPk) check: encryptedPk == encryptedPk2 test "Noise public keys: encrypt, decrypt and decrypt a public key": let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[]) let cs: ChaChaPolyCipherState = randomChaChaPolyCipherState(rng[]) encryptedPk: NoisePublicKey = encryptNoisePublicKey(cs, noisePublicKey) decryptedPk: NoisePublicKey = decryptNoisePublicKey(cs, encryptedPk) decryptedPk2: NoisePublicKey = decryptNoisePublicKey(cs, decryptedPk) check: decryptedPk == decryptedPk2 test "Noise public keys: serialize and deserialize an unencrypted public key": let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[]) serializedNoisePublicKey: seq[byte] = serializeNoisePublicKey(noisePublicKey) deserializedNoisePublicKey: NoisePublicKey = intoNoisePublicKey(serializedNoisePublicKey) check: noisePublicKey == deserializedNoisePublicKey test "Noise public keys: encrypt, serialize, deserialize and decrypt a public key": let noisePublicKey: NoisePublicKey = genNoisePublicKey(rng[]) let cs: ChaChaPolyCipherState = randomChaChaPolyCipherState(rng[]) encryptedPk: NoisePublicKey = encryptNoisePublicKey(cs, noisePublicKey) serializedNoisePublicKey: seq[byte] = serializeNoisePublicKey(encryptedPk) deserializedNoisePublicKey: NoisePublicKey = intoNoisePublicKey(serializedNoisePublicKey) decryptedPk: NoisePublicKey = decryptNoisePublicKey(cs, deserializedNoisePublicKey) check: noisePublicKey == decryptedPk test "PayloadV2: serialize/deserialize PayloadV2 to byte sequence": let payload2: PayloadV2 = randomPayloadV2(rng[]) serializedPayload = serializePayloadV2(payload2) check: serializedPayload.isOk() let deserializedPayload = deserializePayloadV2(serializedPayload.get()) check: deserializedPayload.isOk() payload2 == deserializedPayload.get() test "PayloadV2: Encode/Decode a Waku Message (version 2) to a PayloadV2": # We encode to a WakuMessage a random PayloadV2 let payload2 = randomPayloadV2(rng[]) msg = encodePayloadV2(payload2) check: msg.isOk() # We create ProtoBuffer from WakuMessage let pb = msg.get().encode() # We decode the WakuMessage from the ProtoBuffer let msgFromPb = WakuMessage.decode(pb.buffer) check: msgFromPb.isOk() let decoded = decodePayloadV2(msgFromPb.get()) check: decoded.isOk() payload2 == decoded.get() test "Noise State Machine: Diffie-Hellman operation": #We generate random keypairs let aliceKey = genKeyPair(rng[]) bobKey = genKeyPair(rng[]) # A Diffie-Hellman operation between Alice's private key and Bob's public key must be equal to # a Diffie-hellman operation between Alice's public key and Bob's private key let dh1 = dh(getPrivateKey(aliceKey), getPublicKey(bobKey)) dh2 = dh(getPrivateKey(bobKey), getPublicKey(aliceKey)) check: dh1 == dh2 test "Noise State Machine: Cipher State primitives": # We generate a random Cipher State, associated data ad and plaintext var cipherState: CipherState = randomCipherState(rng[]) nonce: uint64 = uint64(rand(0 .. int.high)) ad: seq[byte] = randomSeqByte(rng[], rand(1 .. 128)) plaintext: seq[byte] = randomSeqByte(rng[], rand(1 .. 128)) # We set the random nonce generated in the cipher state setNonce(cipherState, nonce) # We perform encryption var ciphertext: seq[byte] = encryptWithAd(cipherState, ad, plaintext) # After any encryption/decryption operation, the Cipher State's nonce increases by 1 check: getNonce(cipherState) == nonce + 1 # We set the nonce back to its original value for decryption setNonce(cipherState, nonce) # We decrypt (using the original nonce) var decrypted: seq[byte] = decryptWithAd(cipherState, ad, ciphertext) # We check if encryption and decryption are correct and that nonce correctly increased after decryption check: getNonce(cipherState) == nonce + 1 plaintext == decrypted # If a Cipher State has no key set, encryptWithAd should return the plaintext without increasing the nonce setCipherStateKey(cipherState, EmptyKey) nonce = getNonce(cipherState) plaintext = randomSeqByte(rng[], rand(1 .. 128)) ciphertext = encryptWithAd(cipherState, ad, plaintext) check: ciphertext == plaintext getNonce(cipherState) == nonce # If a Cipher State has no key set, decryptWithAd should return the ciphertext without increasing the nonce setCipherStateKey(cipherState, EmptyKey) nonce = getNonce(cipherState) # Note that we set ciphertext minimum length to 16 to not trigger checks on authentication tag length ciphertext = randomSeqByte(rng[], rand(16 .. 128)) plaintext = decryptWithAd(cipherState, ad, ciphertext) check: ciphertext == plaintext getNonce(cipherState) == nonce # A Cipher State cannot have a nonce greater or equal 2^64-1 # Note that NonceMax is uint64.high - 1 = 2^64-1-1 and that nonce is increased after each encryption and decryption operation # We generate a test Cipher State with nonce set to MaxNonce cipherState = randomCipherState(rng[]) setNonce(cipherState, NonceMax) plaintext = randomSeqByte(rng[], rand(1 .. 128)) # We test if encryption fails with a NoiseNonceMaxError error. Any subsequent encryption call over the Cipher State should fail similarly and leave the nonce unchanged for _ in [1 .. 5]: expect NoiseNonceMaxError: ciphertext = encryptWithAd(cipherState, ad, plaintext) check: getNonce(cipherState) == NonceMax + 1 # We generate a test Cipher State # Since nonce is increased after decryption as well, we need to generate a proper ciphertext in order to test MaxNonceError error handling # We cannot call encryptWithAd to encrypt a plaintext using a nonce equal MaxNonce, since this will trigger a MaxNonceError. # To perform such test, we then need to encrypt a test plaintext using directly ChaChaPoly primitive cipherState = randomCipherState(rng[]) setNonce(cipherState, NonceMax) plaintext = randomSeqByte(rng[], rand(1 .. 128)) # We perform encryption using the Cipher State key, NonceMax and ad # By Noise specification the nonce is 8 bytes long out of the 12 bytes supported by ChaChaPoly, thus we copy the Little endian conversion of the nonce to a ChaChaPolyNonce var encNonce: ChaChaPolyNonce authorizationTag: ChaChaPolyTag encNonce[4 ..< 12] = toBytesLE(NonceMax) ChaChaPoly.encrypt(getKey(cipherState), encNonce, authorizationTag, plaintext, ad) # The output ciphertext is stored in the plaintext variable after ChaChaPoly.encrypt is called: we copy it along with the authorization tag. ciphertext = @[] ciphertext.add(plaintext) ciphertext.add(authorizationTag) # At this point ciphertext is a proper encryption of the original plaintext obtained with nonce equal to NonceMax # We can now test if decryption fails with a NoiseNonceMaxError error. Any subsequent decryption call over the Cipher State should fail similarly and leave the nonce unchanged # Note that decryptWithAd doesn't fail in decrypting the ciphertext (otherwise a NoiseDecryptTagError would have been triggered) for _ in [1 .. 5]: expect NoiseNonceMaxError: plaintext = decryptWithAd(cipherState, ad, ciphertext) check: getNonce(cipherState) == NonceMax + 1 test "Noise State Machine: Symmetric State primitives": # We select one supported handshake pattern and we initialize a symmetric state var hsPattern = NoiseHandshakePatterns["XX"] symmetricState: SymmetricState = SymmetricState.init(hsPattern) # We get all the Symmetric State field # cs : Cipher State # ck : chaining key # h : handshake hash var cs = getCipherState(symmetricState) ck = getChainingKey(symmetricState) h = getHandshakeHash(symmetricState) # When a Symmetric state is initialized, handshake hash and chaining key are (byte-wise) equal check: h.data.intoChaChaPolyKey == ck ######################################## # mixHash ######################################## # We generate a random byte sequence and execute a mixHash over it mixHash(symmetricState, randomSeqByte(rng[], rand(1 .. 128))) # mixHash changes only the handshake hash value of the Symmetric state check: cs == getCipherState(symmetricState) ck == getChainingKey(symmetricState) h != getHandshakeHash(symmetricState) # We update test values h = getHandshakeHash(symmetricState) ######################################## # mixKey ######################################## # We generate random input key material and we execute mixKey var inputKeyMaterial = randomSeqByte(rng[], rand(1 .. 128)) mixKey(symmetricState, inputKeyMaterial) # mixKey changes the Symmetric State's chaining key and encryption key of the embedded Cipher State # It further sets to 0 the nonce of the embedded Cipher State check: getKey(cs) != getKey(getCipherState(symmetricState)) getNonce(getCipherState(symmetricState)) == 0.uint64 cs != getCipherState(symmetricState) ck != getChainingKey(symmetricState) h == getHandshakeHash(symmetricState) # We update test values cs = getCipherState(symmetricState) ck = getChainingKey(symmetricState) ######################################## # mixKeyAndHash ######################################## # We generate random input key material and we execute mixKeyAndHash inputKeyMaterial = randomSeqByte(rng[], rand(1 .. 128)) mixKeyAndHash(symmetricState, inputKeyMaterial) # mixKeyAndHash executes a mixKey and a mixHash using the input key material # All Symmetric State's fields are updated check: cs != getCipherState(symmetricState) ck != getChainingKey(symmetricState) h != getHandshakeHash(symmetricState) # We update test values cs = getCipherState(symmetricState) ck = getChainingKey(symmetricState) h = getHandshakeHash(symmetricState) ######################################## # encryptAndHash and decryptAndHash ######################################## # We store the initial symmetricState in order to correctly perform decryption var initialSymmetricState = symmetricState # We generate random plaintext and we execute encryptAndHash var plaintext = randomChaChaPolyKey(rng[]) var nonce = getNonce(getCipherState(symmetricState)) var ciphertext = encryptAndHash(symmetricState, plaintext) # encryptAndHash combines encryptWithAd and mixHash over the ciphertext (encryption increases the nonce of the embedded Cipher State but does not change its key) # We check if only the handshake hash value and the Symmetric State changed accordingly check: cs != getCipherState(symmetricState) getKey(cs) == getKey(getCipherState(symmetricState)) getNonce(getCipherState(symmetricState)) == nonce + 1 ck == getChainingKey(symmetricState) h != getHandshakeHash(symmetricState) # We restore the symmetric State to its initial value to test decryption symmetricState = initialSymmetricState # We execute decryptAndHash over the ciphertext var decrypted = decryptAndHash(symmetricState, ciphertext) # decryptAndHash combines decryptWithAd and mixHash over the ciphertext (encryption increases the nonce of the embedded Cipher State but does not change its key) # We check if only the handshake hash value and the Symmetric State changed accordingly # We further check if decryption corresponds to the original plaintext check: cs != getCipherState(symmetricState) getKey(cs) == getKey(getCipherState(symmetricState)) getNonce(getCipherState(symmetricState)) == nonce + 1 ck == getChainingKey(symmetricState) h != getHandshakeHash(symmetricState) decrypted == plaintext ######################################## # split ######################################## # If at least one mixKey is executed (as above), ck is non-empty check: getChainingKey(symmetricState) != EmptyKey # When a Symmetric State's ck is non-empty, we can execute split, which creates two distinct Cipher States cs1 and cs2 # with non-empty encryption keys and nonce set to 0 var (cs1, cs2) = split(symmetricState) check: getKey(cs1) != EmptyKey getKey(cs2) != EmptyKey getNonce(cs1) == 0.uint64 getNonce(cs2) == 0.uint64 getKey(cs1) != getKey(cs2) test "Noise XX Handhshake and message encryption (extended test)": let hsPattern = NoiseHandshakePatterns["XX"] # We initialize Alice's and Bob's Handshake State let aliceStaticKey = genKeyPair(rng[]) var aliceHS = initialize(hsPattern = hsPattern, staticKey = aliceStaticKey, initiator = true) let bobStaticKey = genKeyPair(rng[]) var bobHS = initialize(hsPattern = hsPattern, staticKey = bobStaticKey) var sentTransportMessage: seq[byte] aliceStep, bobStep: HandshakeStepResult # Here the handshake starts # Write and read calls alternate between Alice and Bob: the handhshake progresses by alternatively calling stepHandshake for each user ############### # 1st step ############### # We generate a random transport message sentTransportMessage = randomSeqByte(rng[], 32) # By being the handshake initiator, Alice writes a Waku2 payload v2 containing her handshake message # and the (encrypted) transport message aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() # Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() check: bobStep.transportMessage == sentTransportMessage ############### # 2nd step ############### # We generate a random transport message sentTransportMessage = randomSeqByte(rng[], 32) # At this step, Bob writes and returns a payload bobStep = stepHandshake(rng[], bobHS, transportMessage = sentTransportMessage).get() # While Alice reads and returns the (decrypted) transport message aliceStep = stepHandshake(rng[], aliceHS, readPayloadV2 = bobStep.payload2).get() check: aliceStep.transportMessage == sentTransportMessage ############### # 3rd step ############### # We generate a random transport message sentTransportMessage = randomSeqByte(rng[], 32) # Similarly as in first step, Alice writes a Waku2 payload containing the handshake message and the (encrypted) transport message aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() # Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() check: bobStep.transportMessage == sentTransportMessage # Note that for this handshake pattern, no more message patterns are left for processing # Another call to stepHandshake would return an empty HandshakeStepResult # We test that extra calls to stepHandshake do not affect parties' handshake states # and that the intermediate HandshakeStepResult are empty let prevAliceHS = aliceHS let prevBobHS = bobHS let bobStep1 = stepHandshake(rng[], bobHS, transportMessage = sentTransportMessage).get() let aliceStep1 = stepHandshake(rng[], aliceHS, readPayloadV2 = bobStep1.payload2).get() let aliceStep2 = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() let bobStep2 = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep2.payload2).get() check: aliceStep1 == default(HandshakeStepResult) aliceStep2 == default(HandshakeStepResult) bobStep1 == default(HandshakeStepResult) bobStep2 == default(HandshakeStepResult) aliceHS == prevAliceHS bobHS == prevBobHS ######################### # After Handshake ######################### # 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 var payload2: PayloadV2 message: seq[byte] readMessage: seq[byte] defaultMessageNametagBuffer: MessageNametagBuffer for _ in 0 .. 10: # Alice writes to Bob message = randomSeqByte(rng[], 32) payload2 = writeMessage(aliceHSResult, message, defaultMessageNametagBuffer) readMessage = readMessage(bobHSResult, payload2, defaultMessageNametagBuffer).get() check: message == readMessage # Bob writes to Alice message = randomSeqByte(rng[], 32) payload2 = writeMessage(bobHSResult, message, defaultMessageNametagBuffer) readMessage = readMessage(aliceHSResult, payload2, defaultMessageNametagBuffer).get() check: message == readMessage test "Noise XXpsk0 Handhshake and message encryption (short test)": let hsPattern = NoiseHandshakePatterns["XXpsk0"] # We generate a random psk let psk = randomSeqByte(rng[], 32) # We initialize Alice's and Bob's Handshake State let aliceStaticKey = genKeyPair(rng[]) var aliceHS = initialize( hsPattern = hsPattern, staticKey = aliceStaticKey, psk = psk, initiator = true ) let bobStaticKey = genKeyPair(rng[]) var bobHS = initialize(hsPattern = hsPattern, staticKey = bobStaticKey, psk = psk) var sentTransportMessage: seq[byte] aliceStep, bobStep: HandshakeStepResult # Here the handshake starts # Write and read calls alternate between Alice and Bob: the handhshake progresses by alternatively calling stepHandshake for each user ############### # 1st step ############### # We generate a random transport message sentTransportMessage = randomSeqByte(rng[], 32) # By being the handshake initiator, Alice writes a Waku2 payload v2 containing her handshake message # and the (encrypted) transport message aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() # Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() check: bobStep.transportMessage == sentTransportMessage ############### # 2nd step ############### # We generate a random transport message sentTransportMessage = randomSeqByte(rng[], 32) # At this step, Bob writes and returns a payload bobStep = stepHandshake(rng[], bobHS, transportMessage = sentTransportMessage).get() # While Alice reads and returns the (decrypted) transport message aliceStep = stepHandshake(rng[], aliceHS, readPayloadV2 = bobStep.payload2).get() check: aliceStep.transportMessage == sentTransportMessage ############### # 3rd step ############### # We generate a random transport message sentTransportMessage = randomSeqByte(rng[], 32) # Similarly as in first step, Alice writes a Waku2 payload containing the handshake message and the (encrypted) transport message aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() # Bob reads Alice's payloads, and returns the (decrypted) transportMessage alice sent to him bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() check: bobStep.transportMessage == sentTransportMessage # Note that for this handshake pattern, no more message patterns are left for processing ######################### # After Handshake ######################### # 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 var payload2: PayloadV2 message: seq[byte] readMessage: seq[byte] defaultMessageNametagBuffer: MessageNametagBuffer for _ in 0 .. 10: # Alice writes to Bob message = randomSeqByte(rng[], 32) payload2 = writeMessage(aliceHSResult, message, defaultMessageNametagBuffer) readMessage = readMessage(bobHSResult, payload2, defaultMessageNametagBuffer).get() check: message == readMessage # Bob writes to Alice message = randomSeqByte(rng[], 32) payload2 = writeMessage(bobHSResult, message, defaultMessageNametagBuffer) readMessage = readMessage(aliceHSResult, payload2, defaultMessageNametagBuffer).get() check: message == readMessage test "Noise K1K1 Handhshake and message encryption (short test)": let hsPattern = NoiseHandshakePatterns["K1K1"] # We initialize Alice's and Bob's Handshake State let aliceStaticKey = genKeyPair(rng[]) let bobStaticKey = genKeyPair(rng[]) # This handshake has the following pre-message pattern: # -> s # <- s # ... # So we define accordingly the sequence of the pre-message public keys let preMessagePKs: seq[NoisePublicKey] = @[ toNoisePublicKey(getPublicKey(aliceStaticKey)), toNoisePublicKey(getPublicKey(bobStaticKey)), ] var aliceHS = initialize( hsPattern = hsPattern, staticKey = aliceStaticKey, preMessagePKs = preMessagePKs, initiator = true, ) var bobHS = initialize( hsPattern = hsPattern, staticKey = bobStaticKey, preMessagePKs = preMessagePKs ) var sentTransportMessage: seq[byte] aliceStep, bobStep: HandshakeStepResult # Here the handshake starts # Write and read calls alternate between Alice and Bob: the handhshake progresses by alternatively calling stepHandshake for each user ############### # 1st step ############### # We generate a random transport message sentTransportMessage = randomSeqByte(rng[], 32) # By being the handshake initiator, Alice writes a Waku2 payload v2 containing her handshake message # and the (encrypted) transport message aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() # Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() check: bobStep.transportMessage == sentTransportMessage ############### # 2nd step ############### # We generate a random transport message sentTransportMessage = randomSeqByte(rng[], 32) # At this step, Bob writes and returns a payload bobStep = stepHandshake(rng[], bobHS, transportMessage = sentTransportMessage).get() # While Alice reads and returns the (decrypted) transport message aliceStep = stepHandshake(rng[], aliceHS, readPayloadV2 = bobStep.payload2).get() check: aliceStep.transportMessage == sentTransportMessage ############### # 3rd step ############### # We generate a random transport message sentTransportMessage = randomSeqByte(rng[], 32) # Similarly as in first step, Alice writes a Waku2 payload containing the handshake_message and the (encrypted) transportMessage aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() # Bob reads Alice's payloads, and returns the (decrypted) transportMessage alice sent to him bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() check: bobStep.transportMessage == sentTransportMessage # Note that for this handshake pattern, no more message patterns are left for processing ######################### # After Handshake ######################### # 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 between Alice and Bob var payload2: PayloadV2 message: seq[byte] readMessage: seq[byte] defaultMessageNametagBuffer: MessageNametagBuffer for _ in 0 .. 10: # Alice writes to Bob message = randomSeqByte(rng[], 32) payload2 = writeMessage(aliceHSResult, message, defaultMessageNametagBuffer) readMessage = readMessage(bobHSResult, payload2, defaultMessageNametagBuffer).get() check: message == readMessage # Bob writes to Alice message = randomSeqByte(rng[], 32) payload2 = writeMessage(bobHSResult, message, defaultMessageNametagBuffer) readMessage = readMessage(aliceHSResult, payload2, defaultMessageNametagBuffer).get() check: message == readMessage test "Noise XK1 Handhshake and message encryption (short test)": let hsPattern = NoiseHandshakePatterns["XK1"] # We initialize Alice's and Bob's Handshake State let aliceStaticKey = genKeyPair(rng[]) let bobStaticKey = genKeyPair(rng[]) # This handshake has the following pre-message pattern: # <- s # ... # So we define accordingly the sequence of the pre-message public keys let preMessagePKs: seq[NoisePublicKey] = @[toNoisePublicKey(getPublicKey(bobStaticKey))] var aliceHS = initialize( hsPattern = hsPattern, staticKey = aliceStaticKey, preMessagePKs = preMessagePKs, initiator = true, ) var bobHS = initialize( hsPattern = hsPattern, staticKey = bobStaticKey, preMessagePKs = preMessagePKs ) var sentTransportMessage: seq[byte] aliceStep, bobStep: HandshakeStepResult # Here the handshake starts # Write and read calls alternate between Alice and Bob: the handhshake progresses by alternatively calling stepHandshake for each user ############### # 1st step ############### # We generate a random transport message sentTransportMessage = randomSeqByte(rng[], 32) # By being the handshake initiator, Alice writes a Waku2 payload v2 containing her handshake message # and the (encrypted) transport message aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() # Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() check: bobStep.transportMessage == sentTransportMessage ############### # 2nd step ############### # We generate a random transport message sentTransportMessage = randomSeqByte(rng[], 32) # At this step, Bob writes and returns a payload bobStep = stepHandshake(rng[], bobHS, transportMessage = sentTransportMessage).get() # While Alice reads and returns the (decrypted) transport message aliceStep = stepHandshake(rng[], aliceHS, readPayloadV2 = bobStep.payload2).get() check: aliceStep.transportMessage == sentTransportMessage ############### # 3rd step ############### # We generate a random transport message sentTransportMessage = randomSeqByte(rng[], 32) # Similarly as in first step, Alice writes a Waku2 payload containing the handshake message and the (encrypted) transport message aliceStep = stepHandshake(rng[], aliceHS, transportMessage = sentTransportMessage).get() # Bob reads Alice's payloads, and returns the (decrypted) transport message Alice sent to him bobStep = stepHandshake(rng[], bobHS, readPayloadV2 = aliceStep.payload2).get() check: bobStep.transportMessage == sentTransportMessage # Note that for this handshake pattern, no more message patterns are left for processing ######################### # After Handshake ######################### # 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 var payload2: PayloadV2 message: seq[byte] readMessage: seq[byte] defaultMessageNametagBuffer: MessageNametagBuffer for _ in 0 .. 10: # Alice writes to Bob message = randomSeqByte(rng[], 32) payload2 = writeMessage(aliceHSResult, message, defaultMessageNametagBuffer) readMessage = readMessage(bobHSResult, payload2, defaultMessageNametagBuffer).get() check: message == readMessage # Bob writes to Alice message = randomSeqByte(rng[], 32) payload2 = writeMessage(bobHSResult, message, defaultMessageNametagBuffer) readMessage = readMessage(aliceHSResult, payload2, defaultMessageNametagBuffer).get() check: message == readMessage