From 7f4df9628b6abe4d5d3cd7a80b355c370dc881fc Mon Sep 17 00:00:00 2001 From: s1fr0 <28568419+s1fr0@users.noreply.github.com> Date: Wed, 30 Mar 2022 23:51:36 +0200 Subject: [PATCH] feat(noise): added support to Waku Payload V2 --- tests/v2/test_waku_noise.nim | 43 +++++++++- waku/v2/node/waku_payload.nim | 37 ++++++++- waku/v2/protocol/waku_noise/noise.nim | 112 +++++++++++++++++++++++++- 3 files changed, 187 insertions(+), 5 deletions(-) diff --git a/tests/v2/test_waku_noise.nim b/tests/v2/test_waku_noise.nim index bd58bb7d0..ad29b59fb 100644 --- a/tests/v2/test_waku_noise.nim +++ b/tests/v2/test_waku_noise.nim @@ -2,7 +2,9 @@ import testutils/unittests, + ../../waku/v2/protocol/waku_message, ../../waku/v2/protocol/waku_noise/noise, + ../../waku/v2/node/waku_payload, ../test_helpers, std/tables @@ -79,4 +81,43 @@ procSuite "Waku Noise": dec_pk: NoisePublicKey = decryptNoisePublicKey(cs, deserializedNoisePublicKey) check: - noisePublicKey == dec_pk \ No newline at end of file + noisePublicKey == dec_pk + + test "Encode/decode PayloadV2 to byte sequence": + + let + payload2 = randomPayloadV2(rng[]) + encoded_payload = encodeV2(payload2) + decoded_payload = decodeV2(encoded_payload.get()) + + check: + payload2 == decoded_payload.get() + + + test "Encode/Decode Waku2 payload (version 2) - ChaChaPoly Keyinfo": + # Encoding + let + version = 2'u32 + payload = randomPayloadV2(rng[]) + encodedPayload = encodePayloadV2(payload) + + check encodedPayload.isOk() + + let + msg = WakuMessage(payload: encodedPayload.get(), version: version) + pb = msg.encode() + + # Decoding + let msgDecoded = WakuMessage.init(pb.buffer) + check msgDecoded.isOk() + + let + cipherState = randomChaChaPolyCipherState(rng[]) + keyInfo = KeyInfo(kind: ChaChaPolyEncryption, cs: cipherState) + decoded = decodePayloadV2(msgDecoded.get(), keyInfo) + + check: + decoded.isOk() + decoded.get() == payload + + #TODO: add encrypt payload with ChaChaPoly diff --git a/waku/v2/node/waku_payload.nim b/waku/v2/node/waku_payload.nim index c936bd2a6..be1362765 100644 --- a/waku/v2/node/waku_payload.nim +++ b/waku/v2/node/waku_payload.nim @@ -4,7 +4,11 @@ import std/options, eth/keys, ../../whisper/whisper_types, - ../protocol/waku_message + ../protocol/waku_message, + ../protocol/waku_noise/noise + +import libp2p/crypto/[chacha20poly1305, curve25519] + export whisper_types, keys, options @@ -12,6 +16,7 @@ type KeyKind* = enum Symmetric Asymmetric + ChaChaPolyEncryption None KeyInfo* = object @@ -20,6 +25,8 @@ type symKey*: SymKey of Asymmetric: privKey*: PrivateKey + of ChaChaPolyEncryption: + cs*: ChaChaPolyCipherState of None: discard @@ -56,7 +63,7 @@ proc decodePayload*(message: WakuMessage, keyInfo: KeyInfo): return ok(decoded.get()) else: return err("Couldn't decrypt using asymmetric key") - of None: + else: discard else: return err("Unsupported WakuMessage version") @@ -77,3 +84,29 @@ proc encode*(payload: Payload, version: uint32, rng: var BrHmacDrbgContext): return err("Couldn't encode the payload") else: return err("Unsupported WakuMessage version") + + +proc decodePayloadV2*(message: WakuMessage, keyInfo: KeyInfo): + WakuResult[PayloadV2] = + case message.version + of 2: + case keyInfo.kind + of ChaChaPolyEncryption: + let decoded = decodeV2(message.payload)#, keyInfo.cs) + if decoded.isSome(): + return ok(decoded.get()) + else: + return err("Couldn't decrypt using ChaChaPoly Cipher State") + else: + discard + else: + return err("Key info doesn't match v2 payloads") + + +proc encodePayloadV2*(payload: PayloadV2): + WakuResult[seq[byte]] = + let encoded = encodeV2(payload) + if encoded.isSome(): + return ok(encoded.get()) + else: + return err("Couldn't encode the payload") \ No newline at end of file diff --git a/waku/v2/protocol/waku_noise/noise.nim b/waku/v2/protocol/waku_noise/noise.nim index d1965f906..301690a00 100644 --- a/waku/v2/protocol/waku_noise/noise.nim +++ b/waku/v2/protocol/waku_noise/noise.nim @@ -132,7 +132,6 @@ proc serializeNoisePublicKey*(noisePublicKey: NoisePublicKey): seq[byte] = result.add noisePublicKey.flag result.add noisePublicKey.pk -#TODO: strip pk_auth if pk not encrypted proc intoNoisePublicKey*(serializedNoisePublicKey: seq[byte]): NoisePublicKey = result.flag = serializedNoisePublicKey[0] assert result.flag == 0 or result.flag == 1 @@ -167,4 +166,113 @@ proc decryptNoisePublicKey*(cs: ChaChaPolyCipherState, noisePublicKey: NoisePubl if noisePublicKey.flag == 0: debug "Public key is not encrypted." debug "Public key is left unchanged" - result = noisePublicKey \ No newline at end of file + result = noisePublicKey + + + + + +# Payload functions +type + PayloadV2* = object + protocol_id: uint8 + handshake_message: seq[NoisePublicKey] + transport_message: seq[byte] + + +proc `==`(p1, p2: PayloadV2): bool = + result = (p1.protocol_id == p2.protocol_id) and + (p1.handshake_message == p2.handshake_message) and + (p1.transport_message == p2.transport_message) + + + +proc randomPayloadV2*(rng: var BrHmacDrbgContext): PayloadV2 = + var protocol_id = newSeq[byte](1) + brHmacDrbgGenerate(rng, protocol_id) + result.protocol_id = protocol_id[0].uint8 + result.handshake_message = @[genNoisePublicKey(rng), genNoisePublicKey(rng), genNoisePublicKey(rng)] + result.transport_message = newSeq[byte](128) + brHmacDrbgGenerate(rng, result.transport_message) + + +proc encodeV2*(self: PayloadV2): Option[seq[byte]] = + + #We collect public keys contained in the handshake message + var + ser_handshake_message_len: int = 0 + ser_handshake_message = newSeqOfCap[byte](256) + ser_pk: seq[byte] + for pk in self.handshake_message: + ser_pk = serializeNoisePublicKey(pk) + ser_handshake_message_len += ser_pk.len + ser_handshake_message.add ser_pk + + + #RFC: handshake-message-len is 1 byte + if ser_handshake_message_len > 256: + debug "Payload malformed: too many public keys contained in the handshake message" + return none(seq[byte]) + + let transport_message_len = self.transport_message.len + #let transport_message_len_len = ceil(log(transport_message_len, 8)).int + + var payload = newSeqOfCap[byte](1 + #self.protocol_id.len + + 1 + #ser_handshake_message_len + ser_handshake_message_len + + 8 + #transport_message_len + transport_message_len #self.transport_message + ) + + + payload.add self.protocol_id.byte + payload.add ser_handshake_message_len.byte + payload.add ser_handshake_message + payload.add toBytesLE(transport_message_len.uint64) + payload.add self.transport_message + + return some(payload) + + + +#Decode Noise handshake payload +proc decodeV2*(payload: seq[byte]): Option[PayloadV2] = + var res: PayloadV2 + + var i: uint64 = 0 + res.protocol_id = payload[i].uint8 + i+=1 + + var handshake_message_len = payload[i].uint64 + i+=1 + + var handshake_message: seq[NoisePublicKey] + + var + flag: byte + pk_len: uint64 + written: uint64 = 0 + + while written != handshake_message_len: + #Note that flag can be used to add support to multiple Elliptic Curve arithmetics.. + flag = payload[i] + if flag == 0: + pk_len = 1 + Curve25519Key.len + handshake_message.add intoNoisePublicKey(payload[i..