From 0f5bef78d1840577c883840d3710dc6b0db3a2c9 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Mon, 21 Nov 2022 13:09:25 +1100 Subject: [PATCH 01/14] feat: remove V0 suffix from Decoder/Encoder --- .../core/src/lib/waku_message/version_0.ts | 30 ++++++++++++++----- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/core/src/lib/waku_message/version_0.ts b/packages/core/src/lib/waku_message/version_0.ts index 056504a067..19d9ec586a 100644 --- a/packages/core/src/lib/waku_message/version_0.ts +++ b/packages/core/src/lib/waku_message/version_0.ts @@ -1,7 +1,7 @@ import type { - DecodedMessage, - Decoder, - Encoder, + DecodedMessage as IDecodedMessage, + Decoder as IDecoder, + Encoder as IEncoder, Message, ProtoMessage, RateLimitProof, @@ -16,7 +16,7 @@ const OneMillion = BigInt(1_000_000); export const Version = 0; export { proto }; -export class MessageV0 implements DecodedMessage { +export class DecodedMessage implements IDecodedMessage { constructor(protected proto: proto.WakuMessage) {} get _rawPayload(): Uint8Array | undefined { @@ -71,7 +71,7 @@ export class MessageV0 implements DecodedMessage { } } -export class EncoderV0 implements Encoder { +class Encoder implements IEncoder { constructor(public contentTopic: string, public ephemeral: boolean = false) {} async toWire(message: Partial): Promise { @@ -92,7 +92,14 @@ export class EncoderV0 implements Encoder { } } -export class DecoderV0 implements Decoder { +export function createEncoder( + contentTopic: string, + ephemeral = false +): Encoder { + return new Encoder(contentTopic, ephemeral); +} + +export class Decoder implements IDecoder { constructor(public contentTopic: string, public ephemeral: boolean = false) {} fromWireToProtoObj(bytes: Uint8Array): Promise { @@ -108,7 +115,7 @@ export class DecoderV0 implements Decoder { }); } - async fromProtoObj(proto: ProtoMessage): Promise { + async fromProtoObj(proto: ProtoMessage): Promise { // https://github.com/status-im/js-waku/issues/921 if (proto.version === undefined) { proto.version = 0; @@ -124,6 +131,13 @@ export class DecoderV0 implements Decoder { return Promise.resolve(undefined); } - return new MessageV0(proto); + return new DecodedMessage(proto); } } + +export function createDecoder( + contentTopic: string, + ephemeral = false +): Encoder { + return new Decoder(contentTopic, ephemeral); +} From ad15f861c38a88a5c08a20bb49731bfdb21821e9 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Wed, 23 Nov 2022 16:25:50 +1100 Subject: [PATCH 02/14] feat!: hide `Decoder`/`Encoder` classes to user Reasoning: by exposing the `Decoder` and `Encoder` classes to the user, the user may care about them, try to use the method etc. By "hiding" them away and providing `create*` help, the aim is for the user to just call a function instead of instantiating a class. Also, `V0` does not provide much information to the user so removing it. --- packages/core/src/lib/waku.ts | 4 +- .../src/lib/waku_message/version_0.spec.ts | 18 +++--- .../core/src/lib/waku_message/version_0.ts | 4 +- packages/message-encryption/src/index.spec.ts | 28 +++++---- packages/message-encryption/src/index.ts | 63 ++++++++++++++----- packages/tests/tests/ephemeral.node.spec.ts | 51 ++++++++------- packages/tests/tests/filter.node.spec.ts | 9 ++- packages/tests/tests/light_push.node.spec.ts | 4 +- packages/tests/tests/relay.node.spec.ts | 52 ++++++++------- packages/tests/tests/store.node.spec.ts | 27 ++++---- packages/tests/tests/waku.node.spec.ts | 8 +-- 11 files changed, 165 insertions(+), 103 deletions(-) diff --git a/packages/core/src/lib/waku.ts b/packages/core/src/lib/waku.ts index 279846b6df..ea54a64ed5 100644 --- a/packages/core/src/lib/waku.ts +++ b/packages/core/src/lib/waku.ts @@ -12,7 +12,7 @@ import { LightPushCodec, LightPushComponents, } from "./waku_light_push/index.js"; -import { EncoderV0 } from "./waku_message/version_0.js"; +import { createEncoder } from "./waku_message/version_0.js"; import * as relayConstants from "./waku_relay/constants.js"; import { RelayCodecs, RelayPingContentTopic } from "./waku_relay/constants.js"; import { StoreCodec, StoreComponents } from "./waku_store/index.js"; @@ -214,7 +214,7 @@ export class WakuNode implements Waku { const relay = this.relay; if (relay && relayPeriodSecs !== 0) { - const encoder = new EncoderV0(RelayPingContentTopic); + const encoder = createEncoder(RelayPingContentTopic); this.relayKeepAliveTimers[peerIdStr] = setInterval(() => { log("Sending Waku Relay ping message"); relay diff --git a/packages/core/src/lib/waku_message/version_0.spec.ts b/packages/core/src/lib/waku_message/version_0.spec.ts index 08a88001ef..25ca5d1afb 100644 --- a/packages/core/src/lib/waku_message/version_0.spec.ts +++ b/packages/core/src/lib/waku_message/version_0.spec.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import fc from "fast-check"; -import { DecoderV0, EncoderV0, MessageV0 } from "./version_0.js"; +import { createDecoder, createEncoder, DecodedMessage } from "./version_0.js"; const TestContentTopic = "/test/1/waku-message/utf8"; @@ -9,11 +9,13 @@ describe("Waku Message version 0", function () { it("Round trip binary serialization", async function () { await fc.assert( fc.asyncProperty(fc.uint8Array({ minLength: 1 }), async (payload) => { - const encoder = new EncoderV0(TestContentTopic); + const encoder = createEncoder(TestContentTopic); const bytes = await encoder.toWire({ payload }); - const decoder = new DecoderV0(TestContentTopic); + const decoder = createDecoder(TestContentTopic); const protoResult = await decoder.fromWireToProtoObj(bytes); - const result = (await decoder.fromProtoObj(protoResult!)) as MessageV0; + const result = (await decoder.fromProtoObj( + protoResult! + )) as DecodedMessage; expect(result.contentTopic).to.eq(TestContentTopic); expect(result.version).to.eq(0); @@ -27,11 +29,13 @@ describe("Waku Message version 0", function () { it("Ephemeral field set to true", async function () { await fc.assert( fc.asyncProperty(fc.uint8Array({ minLength: 1 }), async (payload) => { - const encoder = new EncoderV0(TestContentTopic, true); + const encoder = createEncoder(TestContentTopic, true); const bytes = await encoder.toWire({ payload }); - const decoder = new DecoderV0(TestContentTopic); + const decoder = createDecoder(TestContentTopic); const protoResult = await decoder.fromWireToProtoObj(bytes); - const result = (await decoder.fromProtoObj(protoResult!)) as MessageV0; + const result = (await decoder.fromProtoObj( + protoResult! + )) as DecodedMessage; expect(result.contentTopic).to.eq(TestContentTopic); expect(result.version).to.eq(0); diff --git a/packages/core/src/lib/waku_message/version_0.ts b/packages/core/src/lib/waku_message/version_0.ts index 19d9ec586a..38dbdb1fec 100644 --- a/packages/core/src/lib/waku_message/version_0.ts +++ b/packages/core/src/lib/waku_message/version_0.ts @@ -71,7 +71,7 @@ export class DecodedMessage implements IDecodedMessage { } } -class Encoder implements IEncoder { +export class Encoder implements IEncoder { constructor(public contentTopic: string, public ephemeral: boolean = false) {} async toWire(message: Partial): Promise { @@ -138,6 +138,6 @@ export class Decoder implements IDecoder { export function createDecoder( contentTopic: string, ephemeral = false -): Encoder { +): Decoder { return new Decoder(contentTopic, ephemeral); } diff --git a/packages/message-encryption/src/index.spec.ts b/packages/message-encryption/src/index.spec.ts index 2ecea93ddf..eebfbc4c80 100644 --- a/packages/message-encryption/src/index.spec.ts +++ b/packages/message-encryption/src/index.spec.ts @@ -4,16 +4,16 @@ import fc from "fast-check"; import { getPublicKey } from "./crypto.js"; import { - AsymDecoder, - AsymEncoder, + createAsymDecoder, + createAsymEncoder, + createSymDecoder, + createSymEncoder, decryptAsymmetric, decryptSymmetric, encryptAsymmetric, encryptSymmetric, postCipher, preCipher, - SymDecoder, - SymEncoder, } from "./index.js"; const TestContentTopic = "/test/1/waku-message/utf8"; @@ -27,10 +27,10 @@ describe("Waku Message version 1", function () { async (payload, privateKey) => { const publicKey = getPublicKey(privateKey); - const encoder = new AsymEncoder(TestContentTopic, publicKey); + const encoder = createAsymEncoder(TestContentTopic, publicKey); const bytes = await encoder.toWire({ payload }); - const decoder = new AsymDecoder(TestContentTopic, privateKey); + const decoder = createAsymDecoder(TestContentTopic, privateKey); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; const result = await decoder.fromProtoObj(protoResult); @@ -58,14 +58,14 @@ describe("Waku Message version 1", function () { const alicePublicKey = getPublicKey(alicePrivateKey); const bobPublicKey = getPublicKey(bobPrivateKey); - const encoder = new AsymEncoder( + const encoder = createAsymEncoder( TestContentTopic, bobPublicKey, alicePrivateKey ); const bytes = await encoder.toWire({ payload }); - const decoder = new AsymDecoder(TestContentTopic, bobPrivateKey); + const decoder = createAsymDecoder(TestContentTopic, bobPrivateKey); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; const result = await decoder.fromProtoObj(protoResult); @@ -87,10 +87,10 @@ describe("Waku Message version 1", function () { fc.uint8Array({ minLength: 1 }), fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), async (payload, symKey) => { - const encoder = new SymEncoder(TestContentTopic, symKey); + const encoder = createSymEncoder(TestContentTopic, symKey); const bytes = await encoder.toWire({ payload }); - const decoder = new SymDecoder(TestContentTopic, symKey); + const decoder = createSymDecoder(TestContentTopic, symKey); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; const result = await decoder.fromProtoObj(protoResult); @@ -115,10 +115,14 @@ describe("Waku Message version 1", function () { async (payload, sigPrivKey, symKey) => { const sigPubKey = getPublicKey(sigPrivKey); - const encoder = new SymEncoder(TestContentTopic, symKey, sigPrivKey); + const encoder = createSymEncoder( + TestContentTopic, + symKey, + sigPrivKey + ); const bytes = await encoder.toWire({ payload }); - const decoder = new SymDecoder(TestContentTopic, symKey); + const decoder = createSymDecoder(TestContentTopic, symKey); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; const result = await decoder.fromProtoObj(protoResult); diff --git a/packages/message-encryption/src/index.ts b/packages/message-encryption/src/index.ts index fc60c60a99..997d49a3d7 100644 --- a/packages/message-encryption/src/index.ts +++ b/packages/message-encryption/src/index.ts @@ -1,14 +1,14 @@ import * as secp from "@noble/secp256k1"; import { concat, hexToBytes } from "@waku/byte-utils"; import { - DecoderV0, - MessageV0, + DecodedMessage as DecodedMessageV0, + Decoder as DecoderV0, proto, } from "@waku/core/lib/waku_message/version_0"; import type { - DecodedMessage, - Decoder, - Encoder, + DecodedMessage as IDecodedMessage, + Decoder as IDecoder, + Encoder as IEncoder, Message, ProtoMessage, } from "@waku/interfaces"; @@ -44,7 +44,10 @@ export type Signature = { publicKey: Uint8Array | undefined; }; -export class MessageV1 extends MessageV0 implements DecodedMessage { +export class DecodedMessage + extends DecodedMessageV0 + implements IDecodedMessage +{ private readonly _decodedPayload: Uint8Array; constructor( @@ -62,7 +65,7 @@ export class MessageV1 extends MessageV0 implements DecodedMessage { } } -export class AsymEncoder implements Encoder { +class AsymEncoder implements IEncoder { constructor( public contentTopic: string, private publicKey: Uint8Array, @@ -100,7 +103,16 @@ export class AsymEncoder implements Encoder { } } -export class SymEncoder implements Encoder { +export function createAsymEncoder( + contentTopic: string, + publicKey: Uint8Array, + sigPrivKey?: Uint8Array, + ephemeral = false +): AsymEncoder { + return new AsymEncoder(contentTopic, publicKey, sigPrivKey, ephemeral); +} + +class SymEncoder implements IEncoder { constructor( public contentTopic: string, private symKey: Uint8Array, @@ -137,14 +149,23 @@ export class SymEncoder implements Encoder { } } -export class AsymDecoder extends DecoderV0 implements Decoder { +export function createSymEncoder( + contentTopic: string, + symKey: Uint8Array, + sigPrivKey?: Uint8Array, + ephemeral = false +): SymEncoder { + return new SymEncoder(contentTopic, symKey, sigPrivKey, ephemeral); +} + +class AsymDecoder extends DecoderV0 implements IDecoder { constructor(contentTopic: string, private privateKey: Uint8Array) { super(contentTopic); } async fromProtoObj( protoMessage: ProtoMessage - ): Promise { + ): Promise { const cipherPayload = protoMessage.payload; if (protoMessage.version !== Version) { @@ -186,7 +207,7 @@ export class AsymDecoder extends DecoderV0 implements Decoder { } log("Message decrypted", protoMessage); - return new MessageV1( + return new DecodedMessage( protoMessage, res.payload, res.sig?.signature, @@ -195,14 +216,21 @@ export class AsymDecoder extends DecoderV0 implements Decoder { } } -export class SymDecoder extends DecoderV0 implements Decoder { +export function createAsymDecoder( + contentTopic: string, + privateKey: Uint8Array +): AsymDecoder { + return new AsymDecoder(contentTopic, privateKey); +} + +class SymDecoder extends DecoderV0 implements IDecoder { constructor(contentTopic: string, private symKey: Uint8Array) { super(contentTopic); } async fromProtoObj( protoMessage: ProtoMessage - ): Promise { + ): Promise { const cipherPayload = protoMessage.payload; if (protoMessage.version !== Version) { @@ -244,7 +272,7 @@ export class SymDecoder extends DecoderV0 implements Decoder { } log("Message decrypted", protoMessage); - return new MessageV1( + return new DecodedMessage( protoMessage, res.payload, res.sig?.signature, @@ -253,6 +281,13 @@ export class SymDecoder extends DecoderV0 implements Decoder { } } +export function createSymDecoder( + contentTopic: string, + symKey: Uint8Array +): SymDecoder { + return new SymDecoder(contentTopic, symKey); +} + function getSizeOfPayloadSizeField(message: Uint8Array): number { const messageDataView = new DataView(message.buffer); return messageDataView.getUint8(0) & FlagMask; diff --git a/packages/tests/tests/ephemeral.node.spec.ts b/packages/tests/tests/ephemeral.node.spec.ts index 78c3ab80b7..48d933058b 100644 --- a/packages/tests/tests/ephemeral.node.spec.ts +++ b/packages/tests/tests/ephemeral.node.spec.ts @@ -1,16 +1,20 @@ import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; -import { DecoderV0, EncoderV0 } from "@waku/core/lib/waku_message/version_0"; -import { createLightNode } from "@waku/create"; -import { DecodedMessage, Protocols, WakuLight } from "@waku/interfaces"; import { - AsymDecoder, - AsymEncoder, + createDecoder, + createEncoder, + DecodedMessage, +} from "@waku/core/lib/waku_message/version_0"; +import { createLightNode } from "@waku/create"; +import { Protocols, WakuLight } from "@waku/interfaces"; +import { + createAsymDecoder, + createAsymEncoder, + createSymDecoder, + createSymEncoder, generatePrivateKey, generateSymmetricKey, getPublicKey, - SymDecoder, - SymEncoder, } from "@waku/message-encryption"; import { expect } from "chai"; import debug from "debug"; @@ -26,8 +30,8 @@ import { const log = debug("waku:test:ephemeral"); const TestContentTopic = "/test/1/ephemeral/utf8"; -const TestEncoder = new EncoderV0(TestContentTopic); -const TestDecoder = new DecoderV0(TestContentTopic); +const TestEncoder = createEncoder(TestContentTopic); +const TestDecoder = createDecoder(TestContentTopic); describe("Waku Message Ephemeral field", () => { let waku: WakuLight; @@ -75,17 +79,22 @@ describe("Waku Message Ephemeral field", () => { const AsymContentTopic = "/test/1/ephemeral-asym/utf8"; const SymContentTopic = "/test/1/ephemeral-sym/utf8"; - const asymEncoder = new AsymEncoder( + const asymEncoder = createAsymEncoder( AsymContentTopic, publicKey, undefined, true ); - const symEncoder = new SymEncoder(SymContentTopic, symKey, undefined, true); - const clearEncoder = new EncoderV0(TestContentTopic, true); + const symEncoder = createSymEncoder( + SymContentTopic, + symKey, + undefined, + true + ); + const clearEncoder = createEncoder(TestContentTopic, true); - const asymDecoder = new AsymDecoder(AsymContentTopic, privateKey); - const symDecoder = new SymDecoder(SymContentTopic, symKey); + const asymDecoder = createAsymDecoder(AsymContentTopic, privateKey); + const symDecoder = createSymDecoder(SymContentTopic, symKey); const [waku1, waku2, nimWakuMultiaddr] = await Promise.all([ createLightNode({ @@ -142,7 +151,7 @@ describe("Waku Message Ephemeral field", () => { it("Ephemeral field is preserved - encoder v0", async function () { this.timeout(10000); - const ephemeralEncoder = new EncoderV0(TestContentTopic, true); + const ephemeralEncoder = createEncoder(TestContentTopic, true); const messages: DecodedMessage[] = []; const callback = (msg: DecodedMessage): void => { @@ -182,14 +191,14 @@ describe("Waku Message Ephemeral field", () => { const symKey = generateSymmetricKey(); - const ephemeralEncoder = new SymEncoder( + const ephemeralEncoder = createSymEncoder( TestContentTopic, symKey, undefined, true ); - const encoder = new SymEncoder(TestContentTopic, symKey); - const decoder = new SymDecoder(TestContentTopic, symKey); + const encoder = createSymEncoder(TestContentTopic, symKey); + const decoder = createSymDecoder(TestContentTopic, symKey); const messages: DecodedMessage[] = []; const callback = (msg: DecodedMessage): void => { @@ -230,14 +239,14 @@ describe("Waku Message Ephemeral field", () => { const privKey = generatePrivateKey(); const pubKey = getPublicKey(privKey); - const ephemeralEncoder = new AsymEncoder( + const ephemeralEncoder = createAsymEncoder( TestContentTopic, pubKey, undefined, true ); - const encoder = new AsymEncoder(TestContentTopic, pubKey); - const decoder = new AsymDecoder(TestContentTopic, privKey); + const encoder = createAsymEncoder(TestContentTopic, pubKey); + const decoder = createAsymDecoder(TestContentTopic, privKey); const messages: DecodedMessage[] = []; const callback = (msg: DecodedMessage): void => { diff --git a/packages/tests/tests/filter.node.spec.ts b/packages/tests/tests/filter.node.spec.ts index 823d43a33d..f4d8fd5915 100644 --- a/packages/tests/tests/filter.node.spec.ts +++ b/packages/tests/tests/filter.node.spec.ts @@ -1,6 +1,9 @@ import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; -import { DecoderV0, EncoderV0 } from "@waku/core/lib/waku_message/version_0"; +import { + createDecoder, + createEncoder, +} from "@waku/core/lib/waku_message/version_0"; import { createLightNode } from "@waku/create"; import type { DecodedMessage, WakuLight } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; @@ -12,8 +15,8 @@ import { delay, makeLogFileName, NOISE_KEY_1, Nwaku } from "../src/index.js"; const log = debug("waku:test"); const TestContentTopic = "/test/1/waku-filter"; -const TestEncoder = new EncoderV0(TestContentTopic); -const TestDecoder = new DecoderV0(TestContentTopic); +const TestEncoder = createEncoder(TestContentTopic); +const TestDecoder = createDecoder(TestContentTopic); describe("Waku Filter", () => { let waku: WakuLight; diff --git a/packages/tests/tests/light_push.node.spec.ts b/packages/tests/tests/light_push.node.spec.ts index 38b8edca22..fc23be430f 100644 --- a/packages/tests/tests/light_push.node.spec.ts +++ b/packages/tests/tests/light_push.node.spec.ts @@ -1,6 +1,6 @@ import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; -import { EncoderV0 } from "@waku/core/lib/waku_message/version_0"; +import { createEncoder } from "@waku/core/lib/waku_message/version_0"; import { createLightNode } from "@waku/create"; import type { WakuLight } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; @@ -18,7 +18,7 @@ import { const log = debug("waku:test:lightpush"); const TestContentTopic = "/test/1/waku-light-push/utf8"; -const TestEncoder = new EncoderV0(TestContentTopic); +const TestEncoder = createEncoder(TestContentTopic); describe("Waku Light Push [node only]", () => { let waku: WakuLight; diff --git a/packages/tests/tests/relay.node.spec.ts b/packages/tests/tests/relay.node.spec.ts index e112941ea6..39f86fede3 100644 --- a/packages/tests/tests/relay.node.spec.ts +++ b/packages/tests/tests/relay.node.spec.ts @@ -3,21 +3,21 @@ import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; import { DefaultPubSubTopic } from "@waku/core"; import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; import { - DecoderV0, - EncoderV0, - MessageV0, + createDecoder, + createEncoder, + DecodedMessage, } from "@waku/core/lib/waku_message/version_0"; import { createPrivacyNode } from "@waku/create"; -import type { DecodedMessage, WakuPrivacy } from "@waku/interfaces"; +import type { WakuPrivacy } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; import { - AsymDecoder, - AsymEncoder, + createAsymDecoder, + createAsymEncoder, + createSymDecoder, + createSymEncoder, generatePrivateKey, generateSymmetricKey, getPublicKey, - SymDecoder, - SymEncoder, } from "@waku/message-encryption"; import { expect } from "chai"; import debug from "debug"; @@ -35,8 +35,8 @@ import { const log = debug("waku:test"); const TestContentTopic = "/test/1/waku-relay/utf8"; -const TestEncoder = new EncoderV0(TestContentTopic); -const TestDecoder = new DecoderV0(TestContentTopic); +const TestEncoder = createEncoder(TestContentTopic); +const TestDecoder = createDecoder(TestContentTopic); describe("Waku Relay [node only]", () => { // Node needed as we don't have a way to connect 2 js waku @@ -142,11 +142,11 @@ describe("Waku Relay [node only]", () => { const fooContentTopic = "foo"; const barContentTopic = "bar"; - const fooEncoder = new EncoderV0(fooContentTopic); - const barEncoder = new EncoderV0(barContentTopic); + const fooEncoder = createEncoder(fooContentTopic); + const barEncoder = createEncoder(barContentTopic); - const fooDecoder = new DecoderV0(fooContentTopic); - const barDecoder = new DecoderV0(barContentTopic); + const fooDecoder = createDecoder(fooContentTopic); + const barDecoder = createDecoder(barContentTopic); const fooMessages: DecodedMessage[] = []; waku2.relay.addObserver(fooDecoder, (msg) => { @@ -191,11 +191,11 @@ describe("Waku Relay [node only]", () => { const symKey = generateSymmetricKey(); const publicKey = getPublicKey(privateKey); - const asymEncoder = new AsymEncoder(asymTopic, publicKey); - const symEncoder = new SymEncoder(symTopic, symKey); + const asymEncoder = createAsymEncoder(asymTopic, publicKey); + const symEncoder = createSymEncoder(symTopic, symKey); - const asymDecoder = new AsymDecoder(asymTopic, privateKey); - const symDecoder = new SymDecoder(symTopic, symKey); + const asymDecoder = createAsymDecoder(asymTopic, privateKey); + const symDecoder = createSymDecoder(symTopic, symKey); const msgs: DecodedMessage[] = []; waku2.relay.addObserver(asymDecoder, (wakuMsg) => { @@ -231,14 +231,14 @@ describe("Waku Relay [node only]", () => { const receivedMsgPromise: Promise = new Promise( (resolve, reject) => { const deleteObserver = waku2.relay.addObserver( - new DecoderV0(contentTopic), + createDecoder(contentTopic), reject ); deleteObserver(); setTimeout(resolve, 500); } ); - await waku1.relay.send(new EncoderV0(contentTopic), { + await waku1.relay.send(createEncoder(contentTopic), { payload: utf8ToBytes(messageText), }); @@ -391,9 +391,13 @@ describe("Waku Relay [node only]", () => { const messageText = "Here is another message."; - const receivedMsgPromise: Promise = new Promise((resolve) => { - waku.relay.addObserver(TestDecoder, (msg) => resolve(msg)); - }); + const receivedMsgPromise: Promise = new Promise( + (resolve) => { + waku.relay.addObserver(TestDecoder, (msg) => + resolve(msg) + ); + } + ); await nwaku.sendMessage( Nwaku.toMessageRpcQuery({ @@ -405,7 +409,7 @@ describe("Waku Relay [node only]", () => { const receivedMsg = await receivedMsgPromise; expect(receivedMsg.contentTopic).to.eq(TestContentTopic); - expect(receivedMsg.version).to.eq(0); + expect(receivedMsg.version!).to.eq(0); expect(bytesToUtf8(receivedMsg.payload!)).to.eq(messageText); }); diff --git a/packages/tests/tests/store.node.spec.ts b/packages/tests/tests/store.node.spec.ts index 25b817fa23..9cb7496950 100644 --- a/packages/tests/tests/store.node.spec.ts +++ b/packages/tests/tests/store.node.spec.ts @@ -1,18 +1,21 @@ import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; import { createCursor, PageDirection } from "@waku/core"; import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; -import { DecoderV0, EncoderV0 } from "@waku/core/lib/waku_message/version_0"; +import { + createDecoder, + createEncoder, +} from "@waku/core/lib/waku_message/version_0"; import { createLightNode } from "@waku/create"; import { DecodedMessage, Message, WakuLight } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; import { - AsymDecoder, - AsymEncoder, + createAsymDecoder, + createAsymEncoder, + createSymDecoder, + createSymEncoder, generatePrivateKey, generateSymmetricKey, getPublicKey, - SymDecoder, - SymEncoder, } from "@waku/message-encryption"; import { expect } from "chai"; import debug from "debug"; @@ -28,8 +31,8 @@ import { const log = debug("waku:test:store"); const TestContentTopic = "/test/1/waku-store/utf8"; -const TestEncoder = new EncoderV0(TestContentTopic); -const TestDecoder = new DecoderV0(TestContentTopic); +const TestEncoder = createEncoder(TestContentTopic); +const TestDecoder = createDecoder(TestContentTopic); describe("Waku Store", () => { let waku: WakuLight; @@ -365,16 +368,16 @@ describe("Waku Store", () => { const symKey = generateSymmetricKey(); const publicKey = getPublicKey(privateKey); - const asymEncoder = new AsymEncoder(asymTopic, publicKey); - const symEncoder = new SymEncoder(symTopic, symKey); + const asymEncoder = createAsymEncoder(asymTopic, publicKey); + const symEncoder = createSymEncoder(symTopic, symKey); - const otherEncoder = new AsymEncoder( + const otherEncoder = createAsymEncoder( TestContentTopic, getPublicKey(generatePrivateKey()) ); - const asymDecoder = new AsymDecoder(asymTopic, privateKey); - const symDecoder = new SymDecoder(symTopic, symKey); + const asymDecoder = createAsymDecoder(asymTopic, privateKey); + const symDecoder = createSymDecoder(symTopic, symKey); const [waku1, waku2, nimWakuMultiaddr] = await Promise.all([ createLightNode({ diff --git a/packages/tests/tests/waku.node.spec.ts b/packages/tests/tests/waku.node.spec.ts index 9a85475b7a..fd4049a613 100644 --- a/packages/tests/tests/waku.node.spec.ts +++ b/packages/tests/tests/waku.node.spec.ts @@ -12,9 +12,9 @@ import type { } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; import { + createSymDecoder, + createSymEncoder, generateSymmetricKey, - SymDecoder, - SymEncoder, } from "@waku/message-encryption"; import { expect } from "chai"; @@ -167,9 +167,9 @@ describe("Decryption Keys", () => { this.timeout(10000); const symKey = generateSymmetricKey(); - const decoder = new SymDecoder(TestContentTopic, symKey); + const decoder = createSymDecoder(TestContentTopic, symKey); - const encoder = new SymEncoder(TestContentTopic, symKey); + const encoder = createSymEncoder(TestContentTopic, symKey); const messageText = "Message is encrypted"; const messageTimestamp = new Date("1995-12-17T03:24:00"); const message = { From e8efd5e96249a67df56ee7c5c5549b37688da7d3 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Wed, 23 Nov 2022 16:33:12 +1100 Subject: [PATCH 03/14] feat!: export user functions from package root `createEncoder`, `createDecoder` and `DecodedMessage` are function/types useful to the user so they should have easy access to it. We still export `Decoder` and `Encoder` but in a path so it cam be re-used by `@waku/message-encryption`. --- packages/core/src/index.ts | 6 ++++++ packages/tests/tests/ephemeral.node.spec.ts | 6 +----- packages/tests/tests/filter.node.spec.ts | 5 +---- packages/tests/tests/light_push.node.spec.ts | 2 +- packages/tests/tests/relay.node.spec.ts | 6 +----- packages/tests/tests/store.node.spec.ts | 5 +---- 6 files changed, 11 insertions(+), 19 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index dbafc4d4e2..be64f04783 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -4,6 +4,12 @@ export { DefaultUserAgent } from "./lib/waku.js"; export * as proto_message from "./proto/message.js"; export * as proto_topic_only_message from "./proto/topic_only_message.js"; +export { + createEncoder, + createDecoder, + DecodedMessage, +} from "./lib/waku_message/version_0.js"; + export * as waku from "./lib/waku.js"; export { WakuNode } from "./lib/waku.js"; diff --git a/packages/tests/tests/ephemeral.node.spec.ts b/packages/tests/tests/ephemeral.node.spec.ts index 48d933058b..a20e9421a5 100644 --- a/packages/tests/tests/ephemeral.node.spec.ts +++ b/packages/tests/tests/ephemeral.node.spec.ts @@ -1,10 +1,6 @@ import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; +import { createDecoder, createEncoder, DecodedMessage } from "@waku/core"; import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; -import { - createDecoder, - createEncoder, - DecodedMessage, -} from "@waku/core/lib/waku_message/version_0"; import { createLightNode } from "@waku/create"; import { Protocols, WakuLight } from "@waku/interfaces"; import { diff --git a/packages/tests/tests/filter.node.spec.ts b/packages/tests/tests/filter.node.spec.ts index f4d8fd5915..f1d456c906 100644 --- a/packages/tests/tests/filter.node.spec.ts +++ b/packages/tests/tests/filter.node.spec.ts @@ -1,9 +1,6 @@ import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; +import { createDecoder, createEncoder } from "@waku/core"; import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; -import { - createDecoder, - createEncoder, -} from "@waku/core/lib/waku_message/version_0"; import { createLightNode } from "@waku/create"; import type { DecodedMessage, WakuLight } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; diff --git a/packages/tests/tests/light_push.node.spec.ts b/packages/tests/tests/light_push.node.spec.ts index fc23be430f..d61f2cb63f 100644 --- a/packages/tests/tests/light_push.node.spec.ts +++ b/packages/tests/tests/light_push.node.spec.ts @@ -1,6 +1,6 @@ import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; +import { createEncoder } from "@waku/core"; import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; -import { createEncoder } from "@waku/core/lib/waku_message/version_0"; import { createLightNode } from "@waku/create"; import type { WakuLight } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; diff --git a/packages/tests/tests/relay.node.spec.ts b/packages/tests/tests/relay.node.spec.ts index 39f86fede3..d22c4588f8 100644 --- a/packages/tests/tests/relay.node.spec.ts +++ b/packages/tests/tests/relay.node.spec.ts @@ -1,12 +1,8 @@ import { PeerId } from "@libp2p/interface-peer-id"; import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; import { DefaultPubSubTopic } from "@waku/core"; +import { createDecoder, createEncoder, DecodedMessage } from "@waku/core"; import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; -import { - createDecoder, - createEncoder, - DecodedMessage, -} from "@waku/core/lib/waku_message/version_0"; import { createPrivacyNode } from "@waku/create"; import type { WakuPrivacy } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; diff --git a/packages/tests/tests/store.node.spec.ts b/packages/tests/tests/store.node.spec.ts index 9cb7496950..f1743169f6 100644 --- a/packages/tests/tests/store.node.spec.ts +++ b/packages/tests/tests/store.node.spec.ts @@ -1,10 +1,7 @@ import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; import { createCursor, PageDirection } from "@waku/core"; +import { createDecoder, createEncoder } from "@waku/core"; import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; -import { - createDecoder, - createEncoder, -} from "@waku/core/lib/waku_message/version_0"; import { createLightNode } from "@waku/create"; import { DecodedMessage, Message, WakuLight } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; From e65e0a0a80b19c58e7ac261faee796c8bdd92fbc Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Wed, 23 Nov 2022 16:38:19 +1100 Subject: [PATCH 04/14] refactor: move module that define cryptographic operations to crypto/ This is to prepare the split of encoder/decoder/message definition from index.ts. --- packages/message-encryption/src/{ => crypto}/ecies.ts | 2 +- .../message-encryption/src/{crypto.ts => crypto/index.ts} | 2 +- packages/message-encryption/src/{ => crypto}/symmetric.ts | 5 +++-- packages/message-encryption/src/index.ts | 6 +++--- 4 files changed, 8 insertions(+), 7 deletions(-) rename packages/message-encryption/src/{ => crypto}/ecies.ts (98%) rename packages/message-encryption/src/{crypto.ts => crypto/index.ts} (97%) rename packages/message-encryption/src/{ => crypto}/symmetric.ts (89%) diff --git a/packages/message-encryption/src/ecies.ts b/packages/message-encryption/src/crypto/ecies.ts similarity index 98% rename from packages/message-encryption/src/ecies.ts rename to packages/message-encryption/src/crypto/ecies.ts index b83d2a1d72..ae3bc460d0 100644 --- a/packages/message-encryption/src/ecies.ts +++ b/packages/message-encryption/src/crypto/ecies.ts @@ -1,7 +1,7 @@ import * as secp from "@noble/secp256k1"; import { concat, hexToBytes } from "@waku/byte-utils"; -import { getSubtle, randomBytes, sha256 } from "./crypto.js"; +import { getSubtle, randomBytes, sha256 } from "./index.js"; /** * HKDF as implemented in go-ethereum. */ diff --git a/packages/message-encryption/src/crypto.ts b/packages/message-encryption/src/crypto/index.ts similarity index 97% rename from packages/message-encryption/src/crypto.ts rename to packages/message-encryption/src/crypto/index.ts index 81344b02c1..91eaaca598 100644 --- a/packages/message-encryption/src/crypto.ts +++ b/packages/message-encryption/src/crypto/index.ts @@ -4,7 +4,7 @@ import * as secp from "@noble/secp256k1"; import { concat } from "@waku/byte-utils"; import sha3 from "js-sha3"; -import { Asymmetric, Symmetric } from "./constants.js"; +import { Asymmetric, Symmetric } from "../constants.js"; declare const self: Record | undefined; const crypto: { node?: any; web?: any } = { diff --git a/packages/message-encryption/src/symmetric.ts b/packages/message-encryption/src/crypto/symmetric.ts similarity index 89% rename from packages/message-encryption/src/symmetric.ts rename to packages/message-encryption/src/crypto/symmetric.ts index 8f289324cc..35f69f4558 100644 --- a/packages/message-encryption/src/symmetric.ts +++ b/packages/message-encryption/src/crypto/symmetric.ts @@ -1,5 +1,6 @@ -import { Symmetric } from "./constants.js"; -import { getSubtle, randomBytes } from "./crypto.js"; +import { Symmetric } from "../constants.js"; + +import { getSubtle, randomBytes } from "./index.js"; export async function encrypt( iv: Uint8Array, diff --git a/packages/message-encryption/src/index.ts b/packages/message-encryption/src/index.ts index 997d49a3d7..cd02baaa55 100644 --- a/packages/message-encryption/src/index.ts +++ b/packages/message-encryption/src/index.ts @@ -15,6 +15,7 @@ import type { import debug from "debug"; import { Symmetric } from "./constants.js"; +import * as ecies from "./crypto/ecies.js"; import { generatePrivateKey, generateSymmetricKey, @@ -22,9 +23,8 @@ import { keccak256, randomBytes, sign, -} from "./crypto.js"; -import * as ecies from "./ecies.js"; -import * as symmetric from "./symmetric.js"; +} from "./crypto/index.js"; +import * as symmetric from "./crypto/symmetric.js"; const log = debug("waku:message:version-1"); From 1d727b2bc087f5784239a77770c88e5cbe1b2640 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Wed, 23 Nov 2022 16:47:02 +1100 Subject: [PATCH 05/14] refactor: extract 26/WAKU-PAYLOAD related function to waku_payload.ts --- packages/message-encryption/src/index.ts | 245 +----------------- .../message-encryption/src/waku_payload.ts | 239 +++++++++++++++++ 2 files changed, 247 insertions(+), 237 deletions(-) create mode 100644 packages/message-encryption/src/waku_payload.ts diff --git a/packages/message-encryption/src/index.ts b/packages/message-encryption/src/index.ts index cd02baaa55..2f59584acc 100644 --- a/packages/message-encryption/src/index.ts +++ b/packages/message-encryption/src/index.ts @@ -1,5 +1,3 @@ -import * as secp from "@noble/secp256k1"; -import { concat, hexToBytes } from "@waku/byte-utils"; import { DecodedMessage as DecodedMessageV0, Decoder as DecoderV0, @@ -14,25 +12,22 @@ import type { } from "@waku/interfaces"; import debug from "debug"; -import { Symmetric } from "./constants.js"; -import * as ecies from "./crypto/ecies.js"; import { generatePrivateKey, generateSymmetricKey, getPublicKey, - keccak256, - randomBytes, - sign, } from "./crypto/index.js"; -import * as symmetric from "./crypto/symmetric.js"; +import { + decryptAsymmetric, + decryptSymmetric, + encryptAsymmetric, + encryptSymmetric, + postCipher, + preCipher, +} from "./waku_payload.js"; const log = debug("waku:message:version-1"); -const FlagsLength = 1; -const FlagMask = 3; // 0011 -const IsSignedMask = 4; // 0100 -const PaddingTarget = 256; -const SignatureLength = 65; const OneMillion = BigInt(1_000_000); export { generatePrivateKey, generateSymmetricKey, getPublicKey }; @@ -287,227 +282,3 @@ export function createSymDecoder( ): SymDecoder { return new SymDecoder(contentTopic, symKey); } - -function getSizeOfPayloadSizeField(message: Uint8Array): number { - const messageDataView = new DataView(message.buffer); - return messageDataView.getUint8(0) & FlagMask; -} - -function getPayloadSize( - message: Uint8Array, - sizeOfPayloadSizeField: number -): number { - let payloadSizeBytes = message.slice(1, 1 + sizeOfPayloadSizeField); - // int 32 == 4 bytes - if (sizeOfPayloadSizeField < 4) { - // If less than 4 bytes pad right (Little Endian). - payloadSizeBytes = concat( - [payloadSizeBytes, new Uint8Array(4 - sizeOfPayloadSizeField)], - 4 - ); - } - const payloadSizeDataView = new DataView(payloadSizeBytes.buffer); - return payloadSizeDataView.getInt32(0, true); -} - -function isMessageSigned(message: Uint8Array): boolean { - const messageDataView = new DataView(message.buffer); - return (messageDataView.getUint8(0) & IsSignedMask) == IsSignedMask; -} - -/** - * Proceed with Asymmetric encryption of the data as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/). - * The data MUST be flags | payload-length | payload | [signature]. - * The returned result can be set to `WakuMessage.payload`. - * - * @internal - */ -export async function encryptAsymmetric( - data: Uint8Array, - publicKey: Uint8Array | string -): Promise { - return ecies.encrypt(hexToBytes(publicKey), data); -} - -/** - * Proceed with Asymmetric decryption of the data as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/). - * The returned data is expected to be `flags | payload-length | payload | [signature]`. - * - * @internal - */ -export async function decryptAsymmetric( - payload: Uint8Array, - privKey: Uint8Array -): Promise { - return ecies.decrypt(privKey, payload); -} - -/** - * Proceed with Symmetric encryption of the data as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/). - * - * @param data The data to encrypt, expected to be `flags | payload-length | payload | [signature]`. - * @param key The key to use for encryption. - * @returns The decrypted data, `cipherText | tag | iv` and can be set to `WakuMessage.payload`. - * - * @internal - */ -export async function encryptSymmetric( - data: Uint8Array, - key: Uint8Array | string -): Promise { - const iv = symmetric.generateIv(); - - // Returns `cipher | tag` - const cipher = await symmetric.encrypt(iv, hexToBytes(key), data); - return concat([cipher, iv]); -} - -/** - * Proceed with Symmetric decryption of the data as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/). - * - * @param payload The cipher data, it is expected to be `cipherText | tag | iv`. - * @param key The key to use for decryption. - * @returns The decrypted data, expected to be `flags | payload-length | payload | [signature]`. - * - * @internal - */ -export async function decryptSymmetric( - payload: Uint8Array, - key: Uint8Array | string -): Promise { - const ivStart = payload.length - Symmetric.ivSize; - const cipher = payload.slice(0, ivStart); - const iv = payload.slice(ivStart); - - return symmetric.decrypt(iv, hexToBytes(key), cipher); -} - -/** - * Computes the flags & auxiliary-field as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/). - */ -function addPayloadSizeField(msg: Uint8Array, payload: Uint8Array): Uint8Array { - const fieldSize = computeSizeOfPayloadSizeField(payload); - let field = new Uint8Array(4); - const fieldDataView = new DataView(field.buffer); - fieldDataView.setUint32(0, payload.length, true); - field = field.slice(0, fieldSize); - msg = concat([msg, field]); - msg[0] |= fieldSize; - return msg; -} - -/** - * Returns the size of the auxiliary-field which in turns contains the payload size - */ -function computeSizeOfPayloadSizeField(payload: Uint8Array): number { - let s = 1; - for (let i = payload.length; i >= 256; i /= 256) { - s++; - } - return s; -} - -function validateDataIntegrity( - value: Uint8Array, - expectedSize: number -): boolean { - if (value.length !== expectedSize) { - return false; - } - - return expectedSize <= 3 || value.findIndex((i) => i !== 0) !== -1; -} - -function getSignature(message: Uint8Array): Uint8Array { - return message.slice(message.length - SignatureLength, message.length); -} - -function getHash(message: Uint8Array, isSigned: boolean): Uint8Array { - if (isSigned) { - return keccak256(message.slice(0, message.length - SignatureLength)); - } - return keccak256(message); -} - -function ecRecoverPubKey( - messageHash: Uint8Array, - signature: Uint8Array -): Uint8Array | undefined { - const recoveryDataView = new DataView(signature.slice(64).buffer); - const recovery = recoveryDataView.getUint8(0); - const _signature = secp.Signature.fromCompact(signature.slice(0, 64)); - - return secp.recoverPublicKey(messageHash, _signature, recovery, false); -} - -/** - * Prepare the payload pre-encryption. - * - * @internal - * @returns The encoded payload, ready for encryption using {@link encryptAsymmetric} - * or {@link encryptSymmetric}. - */ -export async function preCipher( - messagePayload: Uint8Array, - sigPrivKey?: Uint8Array -): Promise { - let envelope = new Uint8Array([0]); // No flags - envelope = addPayloadSizeField(envelope, messagePayload); - envelope = concat([envelope, messagePayload]); - - // Calculate padding: - let rawSize = - FlagsLength + - computeSizeOfPayloadSizeField(messagePayload) + - messagePayload.length; - - if (sigPrivKey) { - rawSize += SignatureLength; - } - - const remainder = rawSize % PaddingTarget; - const paddingSize = PaddingTarget - remainder; - const pad = randomBytes(paddingSize); - - if (!validateDataIntegrity(pad, paddingSize)) { - throw new Error("failed to generate random padding of size " + paddingSize); - } - - envelope = concat([envelope, pad]); - if (sigPrivKey) { - envelope[0] |= IsSignedMask; - const hash = keccak256(envelope); - const bytesSignature = await sign(hash, sigPrivKey); - envelope = concat([envelope, bytesSignature]); - } - - return envelope; -} - -/** - * Decode a decrypted payload. - * - * @internal - */ -export function postCipher( - message: Uint8Array -): { payload: Uint8Array; sig?: Signature } | undefined { - const sizeOfPayloadSizeField = getSizeOfPayloadSizeField(message); - if (sizeOfPayloadSizeField === 0) return; - - const payloadSize = getPayloadSize(message, sizeOfPayloadSizeField); - const payloadStart = 1 + sizeOfPayloadSizeField; - const payload = message.slice(payloadStart, payloadStart + payloadSize); - - const isSigned = isMessageSigned(message); - - let sig; - if (isSigned) { - const signature = getSignature(message); - const hash = getHash(message, isSigned); - const publicKey = ecRecoverPubKey(hash, signature); - sig = { signature, publicKey }; - } - - return { payload, sig }; -} diff --git a/packages/message-encryption/src/waku_payload.ts b/packages/message-encryption/src/waku_payload.ts new file mode 100644 index 0000000000..a62b3e835d --- /dev/null +++ b/packages/message-encryption/src/waku_payload.ts @@ -0,0 +1,239 @@ +import * as secp from "@noble/secp256k1"; +import { concat, hexToBytes } from "@waku/byte-utils"; + +import { Symmetric } from "./constants.js"; +import * as ecies from "./crypto/ecies.js"; +import { keccak256, randomBytes, sign } from "./crypto/index.js"; +import * as symmetric from "./crypto/symmetric.js"; + +import { Signature } from "./index"; + +const FlagsLength = 1; +const FlagMask = 3; // 0011 +const IsSignedMask = 4; // 0100 +const PaddingTarget = 256; +const SignatureLength = 65; + +function getSizeOfPayloadSizeField(message: Uint8Array): number { + const messageDataView = new DataView(message.buffer); + return messageDataView.getUint8(0) & FlagMask; +} + +function getPayloadSize( + message: Uint8Array, + sizeOfPayloadSizeField: number +): number { + let payloadSizeBytes = message.slice(1, 1 + sizeOfPayloadSizeField); + // int 32 == 4 bytes + if (sizeOfPayloadSizeField < 4) { + // If less than 4 bytes pad right (Little Endian). + payloadSizeBytes = concat( + [payloadSizeBytes, new Uint8Array(4 - sizeOfPayloadSizeField)], + 4 + ); + } + const payloadSizeDataView = new DataView(payloadSizeBytes.buffer); + return payloadSizeDataView.getInt32(0, true); +} + +function isMessageSigned(message: Uint8Array): boolean { + const messageDataView = new DataView(message.buffer); + return (messageDataView.getUint8(0) & IsSignedMask) == IsSignedMask; +} + +/** + * Proceed with Asymmetric encryption of the data as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/). + * The data MUST be flags | payload-length | payload | [signature]. + * The returned result can be set to `WakuMessage.payload`. + * + * @internal + */ +export async function encryptAsymmetric( + data: Uint8Array, + publicKey: Uint8Array | string +): Promise { + return ecies.encrypt(hexToBytes(publicKey), data); +} + +/** + * Proceed with Asymmetric decryption of the data as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/). + * The returned data is expected to be `flags | payload-length | payload | [signature]`. + * + * @internal + */ +export async function decryptAsymmetric( + payload: Uint8Array, + privKey: Uint8Array +): Promise { + return ecies.decrypt(privKey, payload); +} + +/** + * Proceed with Symmetric encryption of the data as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/). + * + * @param data The data to encrypt, expected to be `flags | payload-length | payload | [signature]`. + * @param key The key to use for encryption. + * @returns The decrypted data, `cipherText | tag | iv` and can be set to `WakuMessage.payload`. + * + * @internal + */ +export async function encryptSymmetric( + data: Uint8Array, + key: Uint8Array | string +): Promise { + const iv = symmetric.generateIv(); + + // Returns `cipher | tag` + const cipher = await symmetric.encrypt(iv, hexToBytes(key), data); + return concat([cipher, iv]); +} + +/** + * Proceed with Symmetric decryption of the data as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/). + * + * @param payload The cipher data, it is expected to be `cipherText | tag | iv`. + * @param key The key to use for decryption. + * @returns The decrypted data, expected to be `flags | payload-length | payload | [signature]`. + * + * @internal + */ +export async function decryptSymmetric( + payload: Uint8Array, + key: Uint8Array | string +): Promise { + const ivStart = payload.length - Symmetric.ivSize; + const cipher = payload.slice(0, ivStart); + const iv = payload.slice(ivStart); + + return symmetric.decrypt(iv, hexToBytes(key), cipher); +} + +/** + * Computes the flags & auxiliary-field as per [26/WAKU-PAYLOAD](https://rfc.vac.dev/spec/26/). + */ +function addPayloadSizeField(msg: Uint8Array, payload: Uint8Array): Uint8Array { + const fieldSize = computeSizeOfPayloadSizeField(payload); + let field = new Uint8Array(4); + const fieldDataView = new DataView(field.buffer); + fieldDataView.setUint32(0, payload.length, true); + field = field.slice(0, fieldSize); + msg = concat([msg, field]); + msg[0] |= fieldSize; + return msg; +} + +/** + * Returns the size of the auxiliary-field which in turns contains the payload size + */ +function computeSizeOfPayloadSizeField(payload: Uint8Array): number { + let s = 1; + for (let i = payload.length; i >= 256; i /= 256) { + s++; + } + return s; +} + +function validateDataIntegrity( + value: Uint8Array, + expectedSize: number +): boolean { + if (value.length !== expectedSize) { + return false; + } + + return expectedSize <= 3 || value.findIndex((i) => i !== 0) !== -1; +} + +function getSignature(message: Uint8Array): Uint8Array { + return message.slice(message.length - SignatureLength, message.length); +} + +function getHash(message: Uint8Array, isSigned: boolean): Uint8Array { + if (isSigned) { + return keccak256(message.slice(0, message.length - SignatureLength)); + } + return keccak256(message); +} + +function ecRecoverPubKey( + messageHash: Uint8Array, + signature: Uint8Array +): Uint8Array | undefined { + const recoveryDataView = new DataView(signature.slice(64).buffer); + const recovery = recoveryDataView.getUint8(0); + const _signature = secp.Signature.fromCompact(signature.slice(0, 64)); + + return secp.recoverPublicKey(messageHash, _signature, recovery, false); +} + +/** + * Prepare the payload pre-encryption. + * + * @internal + * @returns The encoded payload, ready for encryption using {@link encryptAsymmetric} + * or {@link encryptSymmetric}. + */ +export async function preCipher( + messagePayload: Uint8Array, + sigPrivKey?: Uint8Array +): Promise { + let envelope = new Uint8Array([0]); // No flags + envelope = addPayloadSizeField(envelope, messagePayload); + envelope = concat([envelope, messagePayload]); + + // Calculate padding: + let rawSize = + FlagsLength + + computeSizeOfPayloadSizeField(messagePayload) + + messagePayload.length; + + if (sigPrivKey) { + rawSize += SignatureLength; + } + + const remainder = rawSize % PaddingTarget; + const paddingSize = PaddingTarget - remainder; + const pad = randomBytes(paddingSize); + + if (!validateDataIntegrity(pad, paddingSize)) { + throw new Error("failed to generate random padding of size " + paddingSize); + } + + envelope = concat([envelope, pad]); + if (sigPrivKey) { + envelope[0] |= IsSignedMask; + const hash = keccak256(envelope); + const bytesSignature = await sign(hash, sigPrivKey); + envelope = concat([envelope, bytesSignature]); + } + + return envelope; +} + +/** + * Decode a decrypted payload. + * + * @internal + */ +export function postCipher( + message: Uint8Array +): { payload: Uint8Array; sig?: Signature } | undefined { + const sizeOfPayloadSizeField = getSizeOfPayloadSizeField(message); + if (sizeOfPayloadSizeField === 0) return; + + const payloadSize = getPayloadSize(message, sizeOfPayloadSizeField); + const payloadStart = 1 + sizeOfPayloadSizeField; + const payload = message.slice(payloadStart, payloadStart + payloadSize); + + const isSigned = isMessageSigned(message); + + let sig; + if (isSigned) { + const signature = getSignature(message); + const hash = getHash(message, isSigned); + const publicKey = ecRecoverPubKey(hash, signature); + sig = { signature, publicKey }; + } + + return { payload, sig }; +} From 563b66eab5806610f7a300e0232363db56c814bc Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Wed, 23 Nov 2022 16:59:15 +1100 Subject: [PATCH 06/14] refactor: separate symmetric and asymmetric encoders --- packages/message-encryption/src/ecies.spec.ts | 71 +++++ packages/message-encryption/src/ecies.ts | 134 ++++++++++ packages/message-encryption/src/index.spec.ts | 212 --------------- packages/message-encryption/src/index.ts | 245 +----------------- .../message-encryption/src/symmetric.spec.ts | 66 +++++ packages/message-encryption/src/symmetric.ts | 133 ++++++++++ .../src/waku_payload.spec.ts | 84 ++++++ 7 files changed, 490 insertions(+), 455 deletions(-) create mode 100644 packages/message-encryption/src/ecies.spec.ts create mode 100644 packages/message-encryption/src/ecies.ts delete mode 100644 packages/message-encryption/src/index.spec.ts create mode 100644 packages/message-encryption/src/symmetric.spec.ts create mode 100644 packages/message-encryption/src/symmetric.ts create mode 100644 packages/message-encryption/src/waku_payload.spec.ts diff --git a/packages/message-encryption/src/ecies.spec.ts b/packages/message-encryption/src/ecies.spec.ts new file mode 100644 index 0000000000..b9f75c03c5 --- /dev/null +++ b/packages/message-encryption/src/ecies.spec.ts @@ -0,0 +1,71 @@ +import { expect } from "chai"; +import fc from "fast-check"; + +import { getPublicKey } from "./crypto/index.js"; +import { createAsymDecoder, createAsymEncoder } from "./ecies.js"; + +const TestContentTopic = "/test/1/waku-message/utf8"; + +describe("Ecies Encryption", function () { + it("Round trip binary encryption [ecies, no signature]", async function () { + await fc.assert( + fc.asyncProperty( + fc.uint8Array({ minLength: 1 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + async (payload, privateKey) => { + const publicKey = getPublicKey(privateKey); + + const encoder = createAsymEncoder(TestContentTopic, publicKey); + const bytes = await encoder.toWire({ payload }); + + const decoder = createAsymDecoder(TestContentTopic, privateKey); + const protoResult = await decoder.fromWireToProtoObj(bytes!); + if (!protoResult) throw "Failed to proto decode"; + const result = await decoder.fromProtoObj(protoResult); + if (!result) throw "Failed to decode"; + + expect(result.contentTopic).to.equal(TestContentTopic); + expect(result.version).to.equal(1); + expect(result?.payload).to.deep.equal(payload); + expect(result.signature).to.be.undefined; + expect(result.signaturePublicKey).to.be.undefined; + } + ) + ); + }); + + it("R trip binary encryption [ecies, signature]", async function () { + this.timeout(4000); + + await fc.assert( + fc.asyncProperty( + fc.uint8Array({ minLength: 1 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + async (payload, alicePrivateKey, bobPrivateKey) => { + const alicePublicKey = getPublicKey(alicePrivateKey); + const bobPublicKey = getPublicKey(bobPrivateKey); + + const encoder = createAsymEncoder( + TestContentTopic, + bobPublicKey, + alicePrivateKey + ); + const bytes = await encoder.toWire({ payload }); + + const decoder = createAsymDecoder(TestContentTopic, bobPrivateKey); + const protoResult = await decoder.fromWireToProtoObj(bytes!); + if (!protoResult) throw "Failed to proto decode"; + const result = await decoder.fromProtoObj(protoResult); + if (!result) throw "Failed to decode"; + + expect(result.contentTopic).to.equal(TestContentTopic); + expect(result.version).to.equal(1); + expect(result?.payload).to.deep.equal(payload); + expect(result.signature).to.not.be.undefined; + expect(result.signaturePublicKey).to.deep.eq(alicePublicKey); + } + ) + ); + }); +}); diff --git a/packages/message-encryption/src/ecies.ts b/packages/message-encryption/src/ecies.ts new file mode 100644 index 0000000000..f0075f99ba --- /dev/null +++ b/packages/message-encryption/src/ecies.ts @@ -0,0 +1,134 @@ +import { + Decoder as DecoderV0, + proto, +} from "@waku/core/lib/waku_message/version_0"; +import type { + Decoder as IDecoder, + Encoder as IEncoder, + Message, + ProtoMessage, +} from "@waku/interfaces"; +import debug from "debug"; + +import { + decryptAsymmetric, + encryptAsymmetric, + postCipher, + preCipher, +} from "./waku_payload.js"; + +import { DecodedMessage, OneMillion, Version } from "./index.js"; + +const log = debug("waku:message-encryption:ecies"); + +class AsymEncoder implements IEncoder { + constructor( + public contentTopic: string, + private publicKey: Uint8Array, + private sigPrivKey?: Uint8Array, + public ephemeral: boolean = false + ) {} + + async toWire(message: Partial): Promise { + const protoMessage = await this.toProtoObj(message); + if (!protoMessage) return; + + return proto.WakuMessage.encode(protoMessage); + } + + async toProtoObj( + message: Partial + ): Promise { + const timestamp = message.timestamp ?? new Date(); + if (!message.payload) { + log("No payload to encrypt, skipping: ", message); + return; + } + const preparedPayload = await preCipher(message.payload, this.sigPrivKey); + + const payload = await encryptAsymmetric(preparedPayload, this.publicKey); + + return { + payload, + version: Version, + contentTopic: this.contentTopic, + timestamp: BigInt(timestamp.valueOf()) * OneMillion, + rateLimitProof: message.rateLimitProof, + ephemeral: this.ephemeral, + }; + } +} + +export function createAsymEncoder( + contentTopic: string, + publicKey: Uint8Array, + sigPrivKey?: Uint8Array, + ephemeral = false +): AsymEncoder { + return new AsymEncoder(contentTopic, publicKey, sigPrivKey, ephemeral); +} + +class AsymDecoder extends DecoderV0 implements IDecoder { + constructor(contentTopic: string, private privateKey: Uint8Array) { + super(contentTopic); + } + + async fromProtoObj( + protoMessage: ProtoMessage + ): Promise { + const cipherPayload = protoMessage.payload; + + if (protoMessage.version !== Version) { + log( + "Failed to decrypt due to incorrect version, expected:", + Version, + ", actual:", + protoMessage.version + ); + return; + } + + let payload; + if (!cipherPayload) { + log(`No payload to decrypt for contentTopic ${this.contentTopic}`); + return; + } + + try { + payload = await decryptAsymmetric(cipherPayload, this.privateKey); + } catch (e) { + log( + `Failed to decrypt message using asymmetric decryption for contentTopic: ${this.contentTopic}`, + e + ); + return; + } + + if (!payload) { + log(`Failed to decrypt payload for contentTopic ${this.contentTopic}`); + return; + } + + const res = await postCipher(payload); + + if (!res) { + log(`Failed to decode payload for contentTopic ${this.contentTopic}`); + return; + } + + log("Message decrypted", protoMessage); + return new DecodedMessage( + protoMessage, + res.payload, + res.sig?.signature, + res.sig?.publicKey + ); + } +} + +export function createAsymDecoder( + contentTopic: string, + privateKey: Uint8Array +): AsymDecoder { + return new AsymDecoder(contentTopic, privateKey); +} diff --git a/packages/message-encryption/src/index.spec.ts b/packages/message-encryption/src/index.spec.ts deleted file mode 100644 index eebfbc4c80..0000000000 --- a/packages/message-encryption/src/index.spec.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { expect } from "chai"; -import fc from "fast-check"; - -import { getPublicKey } from "./crypto.js"; - -import { - createAsymDecoder, - createAsymEncoder, - createSymDecoder, - createSymEncoder, - decryptAsymmetric, - decryptSymmetric, - encryptAsymmetric, - encryptSymmetric, - postCipher, - preCipher, -} from "./index.js"; - -const TestContentTopic = "/test/1/waku-message/utf8"; - -describe("Waku Message version 1", function () { - it("Round trip binary encryption [asymmetric, no signature]", async function () { - await fc.assert( - fc.asyncProperty( - fc.uint8Array({ minLength: 1 }), - fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), - async (payload, privateKey) => { - const publicKey = getPublicKey(privateKey); - - const encoder = createAsymEncoder(TestContentTopic, publicKey); - const bytes = await encoder.toWire({ payload }); - - const decoder = createAsymDecoder(TestContentTopic, privateKey); - const protoResult = await decoder.fromWireToProtoObj(bytes!); - if (!protoResult) throw "Failed to proto decode"; - const result = await decoder.fromProtoObj(protoResult); - if (!result) throw "Failed to decode"; - - expect(result.contentTopic).to.equal(TestContentTopic); - expect(result.version).to.equal(1); - expect(result?.payload).to.deep.equal(payload); - expect(result.signature).to.be.undefined; - expect(result.signaturePublicKey).to.be.undefined; - } - ) - ); - }); - - it("R trip binary encryption [asymmetric, signature]", async function () { - this.timeout(4000); - - await fc.assert( - fc.asyncProperty( - fc.uint8Array({ minLength: 1 }), - fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), - fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), - async (payload, alicePrivateKey, bobPrivateKey) => { - const alicePublicKey = getPublicKey(alicePrivateKey); - const bobPublicKey = getPublicKey(bobPrivateKey); - - const encoder = createAsymEncoder( - TestContentTopic, - bobPublicKey, - alicePrivateKey - ); - const bytes = await encoder.toWire({ payload }); - - const decoder = createAsymDecoder(TestContentTopic, bobPrivateKey); - const protoResult = await decoder.fromWireToProtoObj(bytes!); - if (!protoResult) throw "Failed to proto decode"; - const result = await decoder.fromProtoObj(protoResult); - if (!result) throw "Failed to decode"; - - expect(result.contentTopic).to.equal(TestContentTopic); - expect(result.version).to.equal(1); - expect(result?.payload).to.deep.equal(payload); - expect(result.signature).to.not.be.undefined; - expect(result.signaturePublicKey).to.deep.eq(alicePublicKey); - } - ) - ); - }); - - it("Round trip binary encryption [symmetric, no signature]", async function () { - await fc.assert( - fc.asyncProperty( - fc.uint8Array({ minLength: 1 }), - fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), - async (payload, symKey) => { - const encoder = createSymEncoder(TestContentTopic, symKey); - const bytes = await encoder.toWire({ payload }); - - const decoder = createSymDecoder(TestContentTopic, symKey); - const protoResult = await decoder.fromWireToProtoObj(bytes!); - if (!protoResult) throw "Failed to proto decode"; - const result = await decoder.fromProtoObj(protoResult); - if (!result) throw "Failed to decode"; - - expect(result.contentTopic).to.equal(TestContentTopic); - expect(result.version).to.equal(1); - expect(result?.payload).to.deep.equal(payload); - expect(result.signature).to.be.undefined; - expect(result.signaturePublicKey).to.be.undefined; - } - ) - ); - }); - - it("Round trip binary encryption [symmetric, signature]", async function () { - await fc.assert( - fc.asyncProperty( - fc.uint8Array({ minLength: 1 }), - fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), - fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), - async (payload, sigPrivKey, symKey) => { - const sigPubKey = getPublicKey(sigPrivKey); - - const encoder = createSymEncoder( - TestContentTopic, - symKey, - sigPrivKey - ); - const bytes = await encoder.toWire({ payload }); - - const decoder = createSymDecoder(TestContentTopic, symKey); - const protoResult = await decoder.fromWireToProtoObj(bytes!); - if (!protoResult) throw "Failed to proto decode"; - const result = await decoder.fromProtoObj(protoResult); - if (!result) throw "Failed to decode"; - - expect(result.contentTopic).to.equal(TestContentTopic); - expect(result.version).to.equal(1); - expect(result?.payload).to.deep.equal(payload); - expect(result.signature).to.not.be.undefined; - expect(result.signaturePublicKey).to.deep.eq(sigPubKey); - } - ) - ); - }); -}); - -describe("Encryption helpers", () => { - it("Asymmetric encrypt & decrypt", async function () { - await fc.assert( - fc.asyncProperty( - fc.uint8Array({ minLength: 1 }), - fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), - async (message, privKey) => { - const publicKey = getPublicKey(privKey); - - const enc = await encryptAsymmetric(message, publicKey); - const res = await decryptAsymmetric(enc, privKey); - - expect(res).deep.equal(message); - } - ) - ); - }); - - it("Symmetric encrypt & Decrypt", async function () { - await fc.assert( - fc.asyncProperty( - fc.uint8Array(), - fc.uint8Array({ minLength: 32, maxLength: 32 }), - async (message, key) => { - const enc = await encryptSymmetric(message, key); - const res = await decryptSymmetric(enc, key); - - expect(res).deep.equal(message); - } - ) - ); - }); - - it("pre and post cipher", async function () { - await fc.assert( - fc.asyncProperty(fc.uint8Array(), async (message) => { - const enc = await preCipher(message); - const res = postCipher(enc); - - expect(res?.payload).deep.equal( - message, - "Payload was not encrypted then decrypted correctly" - ); - }) - ); - }); - - it("Sign & Recover", async function () { - await fc.assert( - fc.asyncProperty( - fc.uint8Array(), - fc.uint8Array({ minLength: 32, maxLength: 32 }), - async (message, sigPrivKey) => { - const sigPubKey = getPublicKey(sigPrivKey); - - const enc = await preCipher(message, sigPrivKey); - const res = postCipher(enc); - - expect(res?.payload).deep.equal( - message, - "Payload was not encrypted then decrypted correctly" - ); - expect(res?.sig?.publicKey).deep.equal( - sigPubKey, - "signature Public key was not recovered from encrypted then decrypted signature" - ); - } - ) - ); - }); -}); diff --git a/packages/message-encryption/src/index.ts b/packages/message-encryption/src/index.ts index 2f59584acc..d10b2ab004 100644 --- a/packages/message-encryption/src/index.ts +++ b/packages/message-encryption/src/index.ts @@ -1,34 +1,16 @@ import { DecodedMessage as DecodedMessageV0, - Decoder as DecoderV0, proto, } from "@waku/core/lib/waku_message/version_0"; -import type { - DecodedMessage as IDecodedMessage, - Decoder as IDecoder, - Encoder as IEncoder, - Message, - ProtoMessage, -} from "@waku/interfaces"; -import debug from "debug"; +import type { DecodedMessage as IDecodedMessage } from "@waku/interfaces"; import { generatePrivateKey, generateSymmetricKey, getPublicKey, } from "./crypto/index.js"; -import { - decryptAsymmetric, - decryptSymmetric, - encryptAsymmetric, - encryptSymmetric, - postCipher, - preCipher, -} from "./waku_payload.js"; -const log = debug("waku:message:version-1"); - -const OneMillion = BigInt(1_000_000); +export const OneMillion = BigInt(1_000_000); export { generatePrivateKey, generateSymmetricKey, getPublicKey }; @@ -59,226 +41,3 @@ export class DecodedMessage return this._decodedPayload; } } - -class AsymEncoder implements IEncoder { - constructor( - public contentTopic: string, - private publicKey: Uint8Array, - private sigPrivKey?: Uint8Array, - public ephemeral: boolean = false - ) {} - - async toWire(message: Partial): Promise { - const protoMessage = await this.toProtoObj(message); - if (!protoMessage) return; - - return proto.WakuMessage.encode(protoMessage); - } - - async toProtoObj( - message: Partial - ): Promise { - const timestamp = message.timestamp ?? new Date(); - if (!message.payload) { - log("No payload to encrypt, skipping: ", message); - return; - } - const preparedPayload = await preCipher(message.payload, this.sigPrivKey); - - const payload = await encryptAsymmetric(preparedPayload, this.publicKey); - - return { - payload, - version: Version, - contentTopic: this.contentTopic, - timestamp: BigInt(timestamp.valueOf()) * OneMillion, - rateLimitProof: message.rateLimitProof, - ephemeral: this.ephemeral, - }; - } -} - -export function createAsymEncoder( - contentTopic: string, - publicKey: Uint8Array, - sigPrivKey?: Uint8Array, - ephemeral = false -): AsymEncoder { - return new AsymEncoder(contentTopic, publicKey, sigPrivKey, ephemeral); -} - -class SymEncoder implements IEncoder { - constructor( - public contentTopic: string, - private symKey: Uint8Array, - private sigPrivKey?: Uint8Array, - public ephemeral: boolean = false - ) {} - - async toWire(message: Partial): Promise { - const protoMessage = await this.toProtoObj(message); - if (!protoMessage) return; - - return proto.WakuMessage.encode(protoMessage); - } - - async toProtoObj( - message: Partial - ): Promise { - const timestamp = message.timestamp ?? new Date(); - if (!message.payload) { - log("No payload to encrypt, skipping: ", message); - return; - } - const preparedPayload = await preCipher(message.payload, this.sigPrivKey); - - const payload = await encryptSymmetric(preparedPayload, this.symKey); - return { - payload, - version: Version, - contentTopic: this.contentTopic, - timestamp: BigInt(timestamp.valueOf()) * OneMillion, - rateLimitProof: message.rateLimitProof, - ephemeral: this.ephemeral, - }; - } -} - -export function createSymEncoder( - contentTopic: string, - symKey: Uint8Array, - sigPrivKey?: Uint8Array, - ephemeral = false -): SymEncoder { - return new SymEncoder(contentTopic, symKey, sigPrivKey, ephemeral); -} - -class AsymDecoder extends DecoderV0 implements IDecoder { - constructor(contentTopic: string, private privateKey: Uint8Array) { - super(contentTopic); - } - - async fromProtoObj( - protoMessage: ProtoMessage - ): Promise { - const cipherPayload = protoMessage.payload; - - if (protoMessage.version !== Version) { - log( - "Failed to decrypt due to incorrect version, expected:", - Version, - ", actual:", - protoMessage.version - ); - return; - } - - let payload; - if (!cipherPayload) { - log(`No payload to decrypt for contentTopic ${this.contentTopic}`); - return; - } - - try { - payload = await decryptAsymmetric(cipherPayload, this.privateKey); - } catch (e) { - log( - `Failed to decrypt message using asymmetric decryption for contentTopic: ${this.contentTopic}`, - e - ); - return; - } - - if (!payload) { - log(`Failed to decrypt payload for contentTopic ${this.contentTopic}`); - return; - } - - const res = await postCipher(payload); - - if (!res) { - log(`Failed to decode payload for contentTopic ${this.contentTopic}`); - return; - } - - log("Message decrypted", protoMessage); - return new DecodedMessage( - protoMessage, - res.payload, - res.sig?.signature, - res.sig?.publicKey - ); - } -} - -export function createAsymDecoder( - contentTopic: string, - privateKey: Uint8Array -): AsymDecoder { - return new AsymDecoder(contentTopic, privateKey); -} - -class SymDecoder extends DecoderV0 implements IDecoder { - constructor(contentTopic: string, private symKey: Uint8Array) { - super(contentTopic); - } - - async fromProtoObj( - protoMessage: ProtoMessage - ): Promise { - const cipherPayload = protoMessage.payload; - - if (protoMessage.version !== Version) { - log( - "Failed to decrypt due to incorrect version, expected:", - Version, - ", actual:", - protoMessage.version - ); - return; - } - - let payload; - if (!cipherPayload) { - log(`No payload to decrypt for contentTopic ${this.contentTopic}`); - return; - } - - try { - payload = await decryptSymmetric(cipherPayload, this.symKey); - } catch (e) { - log( - `Failed to decrypt message using asymmetric decryption for contentTopic: ${this.contentTopic}`, - e - ); - return; - } - - if (!payload) { - log(`Failed to decrypt payload for contentTopic ${this.contentTopic}`); - return; - } - - const res = await postCipher(payload); - - if (!res) { - log(`Failed to decode payload for contentTopic ${this.contentTopic}`); - return; - } - - log("Message decrypted", protoMessage); - return new DecodedMessage( - protoMessage, - res.payload, - res.sig?.signature, - res.sig?.publicKey - ); - } -} - -export function createSymDecoder( - contentTopic: string, - symKey: Uint8Array -): SymDecoder { - return new SymDecoder(contentTopic, symKey); -} diff --git a/packages/message-encryption/src/symmetric.spec.ts b/packages/message-encryption/src/symmetric.spec.ts new file mode 100644 index 0000000000..94374a606a --- /dev/null +++ b/packages/message-encryption/src/symmetric.spec.ts @@ -0,0 +1,66 @@ +import { expect } from "chai"; +import fc from "fast-check"; + +import { getPublicKey } from "./crypto/index.js"; +import { createSymDecoder, createSymEncoder } from "./symmetric.js"; + +const TestContentTopic = "/test/1/waku-message/utf8"; + +describe("Symmetric Encryption", function () { + it("Round trip binary encryption [symmetric, no signature]", async function () { + await fc.assert( + fc.asyncProperty( + fc.uint8Array({ minLength: 1 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + async (payload, symKey) => { + const encoder = createSymEncoder(TestContentTopic, symKey); + const bytes = await encoder.toWire({ payload }); + + const decoder = createSymDecoder(TestContentTopic, symKey); + const protoResult = await decoder.fromWireToProtoObj(bytes!); + if (!protoResult) throw "Failed to proto decode"; + const result = await decoder.fromProtoObj(protoResult); + if (!result) throw "Failed to decode"; + + expect(result.contentTopic).to.equal(TestContentTopic); + expect(result.version).to.equal(1); + expect(result?.payload).to.deep.equal(payload); + expect(result.signature).to.be.undefined; + expect(result.signaturePublicKey).to.be.undefined; + } + ) + ); + }); + + it("Round trip binary encryption [symmetric, signature]", async function () { + await fc.assert( + fc.asyncProperty( + fc.uint8Array({ minLength: 1 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + async (payload, sigPrivKey, symKey) => { + const sigPubKey = getPublicKey(sigPrivKey); + + const encoder = createSymEncoder( + TestContentTopic, + symKey, + sigPrivKey + ); + const bytes = await encoder.toWire({ payload }); + + const decoder = createSymDecoder(TestContentTopic, symKey); + const protoResult = await decoder.fromWireToProtoObj(bytes!); + if (!protoResult) throw "Failed to proto decode"; + const result = await decoder.fromProtoObj(protoResult); + if (!result) throw "Failed to decode"; + + expect(result.contentTopic).to.equal(TestContentTopic); + expect(result.version).to.equal(1); + expect(result?.payload).to.deep.equal(payload); + expect(result.signature).to.not.be.undefined; + expect(result.signaturePublicKey).to.deep.eq(sigPubKey); + } + ) + ); + }); +}); diff --git a/packages/message-encryption/src/symmetric.ts b/packages/message-encryption/src/symmetric.ts new file mode 100644 index 0000000000..cf990ce669 --- /dev/null +++ b/packages/message-encryption/src/symmetric.ts @@ -0,0 +1,133 @@ +import { + Decoder as DecoderV0, + proto, +} from "@waku/core/lib/waku_message/version_0"; +import type { + Decoder as IDecoder, + Encoder as IEncoder, + Message, + ProtoMessage, +} from "@waku/interfaces"; +import debug from "debug"; + +import { + decryptSymmetric, + encryptSymmetric, + postCipher, + preCipher, +} from "./waku_payload.js"; + +import { DecodedMessage, OneMillion, Version } from "./index.js"; + +const log = debug("waku:message-encryption:symmetric"); + +class SymEncoder implements IEncoder { + constructor( + public contentTopic: string, + private symKey: Uint8Array, + private sigPrivKey?: Uint8Array, + public ephemeral: boolean = false + ) {} + + async toWire(message: Partial): Promise { + const protoMessage = await this.toProtoObj(message); + if (!protoMessage) return; + + return proto.WakuMessage.encode(protoMessage); + } + + async toProtoObj( + message: Partial + ): Promise { + const timestamp = message.timestamp ?? new Date(); + if (!message.payload) { + log("No payload to encrypt, skipping: ", message); + return; + } + const preparedPayload = await preCipher(message.payload, this.sigPrivKey); + + const payload = await encryptSymmetric(preparedPayload, this.symKey); + return { + payload, + version: Version, + contentTopic: this.contentTopic, + timestamp: BigInt(timestamp.valueOf()) * OneMillion, + rateLimitProof: message.rateLimitProof, + ephemeral: this.ephemeral, + }; + } +} + +export function createSymEncoder( + contentTopic: string, + symKey: Uint8Array, + sigPrivKey?: Uint8Array, + ephemeral = false +): SymEncoder { + return new SymEncoder(contentTopic, symKey, sigPrivKey, ephemeral); +} + +class SymDecoder extends DecoderV0 implements IDecoder { + constructor(contentTopic: string, private symKey: Uint8Array) { + super(contentTopic); + } + + async fromProtoObj( + protoMessage: ProtoMessage + ): Promise { + const cipherPayload = protoMessage.payload; + + if (protoMessage.version !== Version) { + log( + "Failed to decrypt due to incorrect version, expected:", + Version, + ", actual:", + protoMessage.version + ); + return; + } + + let payload; + if (!cipherPayload) { + log(`No payload to decrypt for contentTopic ${this.contentTopic}`); + return; + } + + try { + payload = await decryptSymmetric(cipherPayload, this.symKey); + } catch (e) { + log( + `Failed to decrypt message using asymmetric decryption for contentTopic: ${this.contentTopic}`, + e + ); + return; + } + + if (!payload) { + log(`Failed to decrypt payload for contentTopic ${this.contentTopic}`); + return; + } + + const res = await postCipher(payload); + + if (!res) { + log(`Failed to decode payload for contentTopic ${this.contentTopic}`); + return; + } + + log("Message decrypted", protoMessage); + return new DecodedMessage( + protoMessage, + res.payload, + res.sig?.signature, + res.sig?.publicKey + ); + } +} + +export function createSymDecoder( + contentTopic: string, + symKey: Uint8Array +): SymDecoder { + return new SymDecoder(contentTopic, symKey); +} diff --git a/packages/message-encryption/src/waku_payload.spec.ts b/packages/message-encryption/src/waku_payload.spec.ts new file mode 100644 index 0000000000..4fa1cd2e35 --- /dev/null +++ b/packages/message-encryption/src/waku_payload.spec.ts @@ -0,0 +1,84 @@ +import { expect } from "chai"; +import fc from "fast-check"; + +import { getPublicKey } from "./crypto/index.js"; +import { + decryptAsymmetric, + decryptSymmetric, + encryptAsymmetric, + encryptSymmetric, + postCipher, + preCipher, +} from "./waku_payload.js"; + +describe("Waku Payload", () => { + it("Asymmetric encrypt & decrypt", async function () { + await fc.assert( + fc.asyncProperty( + fc.uint8Array({ minLength: 1 }), + fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), + async (message, privKey) => { + const publicKey = getPublicKey(privKey); + + const enc = await encryptAsymmetric(message, publicKey); + const res = await decryptAsymmetric(enc, privKey); + + expect(res).deep.equal(message); + } + ) + ); + }); + + it("Symmetric encrypt & Decrypt", async function () { + await fc.assert( + fc.asyncProperty( + fc.uint8Array(), + fc.uint8Array({ minLength: 32, maxLength: 32 }), + async (message, key) => { + const enc = await encryptSymmetric(message, key); + const res = await decryptSymmetric(enc, key); + + expect(res).deep.equal(message); + } + ) + ); + }); + + it("pre and post cipher", async function () { + await fc.assert( + fc.asyncProperty(fc.uint8Array(), async (message) => { + const enc = await preCipher(message); + const res = postCipher(enc); + + expect(res?.payload).deep.equal( + message, + "Payload was not encrypted then decrypted correctly" + ); + }) + ); + }); + + it("Sign & Recover", async function () { + await fc.assert( + fc.asyncProperty( + fc.uint8Array(), + fc.uint8Array({ minLength: 32, maxLength: 32 }), + async (message, sigPrivKey) => { + const sigPubKey = getPublicKey(sigPrivKey); + + const enc = await preCipher(message, sigPrivKey); + const res = postCipher(enc); + + expect(res?.payload).deep.equal( + message, + "Payload was not encrypted then decrypted correctly" + ); + expect(res?.sig?.publicKey).deep.equal( + sigPubKey, + "signature Public key was not recovered from encrypted then decrypted signature" + ); + } + ) + ); + }); +}); From f7fabec8b36c9641d47d2a4235351959421401fd Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Mon, 5 Dec 2022 15:14:17 +1100 Subject: [PATCH 07/14] feat: easy import by having an export map for each type of encryption --- packages/message-encryption/package.json | 8 ++++ packages/message-encryption/rollup.config.js | 2 + packages/message-encryption/src/ecies.spec.ts | 10 ++--- packages/message-encryption/src/ecies.ts | 26 ++++++++----- .../message-encryption/src/symmetric.spec.ts | 14 +++---- packages/message-encryption/src/symmetric.ts | 25 +++++++----- .../message-encryption/src/waku_payload.ts | 2 +- packages/tests/tests/ephemeral.node.spec.ts | 39 +++++++++---------- packages/tests/tests/relay.node.spec.ts | 22 ++++++----- packages/tests/tests/store.node.spec.ts | 24 ++++++------ packages/tests/tests/waku.node.spec.ts | 10 ++--- 11 files changed, 102 insertions(+), 80 deletions(-) diff --git a/packages/message-encryption/package.json b/packages/message-encryption/package.json index 22ff21bd38..c7ebfbad4f 100644 --- a/packages/message-encryption/package.json +++ b/packages/message-encryption/package.json @@ -8,6 +8,14 @@ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" + }, + "./ecies": { + "types": "./dist/ecies.d.ts", + "import": "./dist/ecies.js" + }, + "./symmetric": { + "types": "./dist/symmetric.d.ts", + "import": "./dist/symmetric.js" } }, "type": "module", diff --git a/packages/message-encryption/rollup.config.js b/packages/message-encryption/rollup.config.js index d22d3d231e..f11e6b9c4c 100644 --- a/packages/message-encryption/rollup.config.js +++ b/packages/message-encryption/rollup.config.js @@ -5,6 +5,8 @@ import { nodeResolve } from "@rollup/plugin-node-resolve"; export default { input: { index: "dist/index.js", + ecies: "dist/ecies.js", + symmetric: "dist/symmetric.js", }, output: { dir: "bundle", diff --git a/packages/message-encryption/src/ecies.spec.ts b/packages/message-encryption/src/ecies.spec.ts index b9f75c03c5..0b1a30d25d 100644 --- a/packages/message-encryption/src/ecies.spec.ts +++ b/packages/message-encryption/src/ecies.spec.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import fc from "fast-check"; import { getPublicKey } from "./crypto/index.js"; -import { createAsymDecoder, createAsymEncoder } from "./ecies.js"; +import { createDecoder, createEncoder } from "./ecies.js"; const TestContentTopic = "/test/1/waku-message/utf8"; @@ -15,10 +15,10 @@ describe("Ecies Encryption", function () { async (payload, privateKey) => { const publicKey = getPublicKey(privateKey); - const encoder = createAsymEncoder(TestContentTopic, publicKey); + const encoder = createEncoder(TestContentTopic, publicKey); const bytes = await encoder.toWire({ payload }); - const decoder = createAsymDecoder(TestContentTopic, privateKey); + const decoder = createDecoder(TestContentTopic, privateKey); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; const result = await decoder.fromProtoObj(protoResult); @@ -46,14 +46,14 @@ describe("Ecies Encryption", function () { const alicePublicKey = getPublicKey(alicePrivateKey); const bobPublicKey = getPublicKey(bobPrivateKey); - const encoder = createAsymEncoder( + const encoder = createEncoder( TestContentTopic, bobPublicKey, alicePrivateKey ); const bytes = await encoder.toWire({ payload }); - const decoder = createAsymDecoder(TestContentTopic, bobPrivateKey); + const decoder = createDecoder(TestContentTopic, bobPrivateKey); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; const result = await decoder.fromProtoObj(protoResult); diff --git a/packages/message-encryption/src/ecies.ts b/packages/message-encryption/src/ecies.ts index f0075f99ba..65fee0f69d 100644 --- a/packages/message-encryption/src/ecies.ts +++ b/packages/message-encryption/src/ecies.ts @@ -17,11 +17,19 @@ import { preCipher, } from "./waku_payload.js"; -import { DecodedMessage, OneMillion, Version } from "./index.js"; +import { + DecodedMessage, + generatePrivateKey, + getPublicKey, + OneMillion, + Version, +} from "./index.js"; + +export { DecodedMessage, generatePrivateKey, getPublicKey }; const log = debug("waku:message-encryption:ecies"); -class AsymEncoder implements IEncoder { +class Encoder implements IEncoder { constructor( public contentTopic: string, private publicKey: Uint8Array, @@ -59,16 +67,16 @@ class AsymEncoder implements IEncoder { } } -export function createAsymEncoder( +export function createEncoder( contentTopic: string, publicKey: Uint8Array, sigPrivKey?: Uint8Array, ephemeral = false -): AsymEncoder { - return new AsymEncoder(contentTopic, publicKey, sigPrivKey, ephemeral); +): Encoder { + return new Encoder(contentTopic, publicKey, sigPrivKey, ephemeral); } -class AsymDecoder extends DecoderV0 implements IDecoder { +class Decoder extends DecoderV0 implements IDecoder { constructor(contentTopic: string, private privateKey: Uint8Array) { super(contentTopic); } @@ -126,9 +134,9 @@ class AsymDecoder extends DecoderV0 implements IDecoder { } } -export function createAsymDecoder( +export function createDecoder( contentTopic: string, privateKey: Uint8Array -): AsymDecoder { - return new AsymDecoder(contentTopic, privateKey); +): Decoder { + return new Decoder(contentTopic, privateKey); } diff --git a/packages/message-encryption/src/symmetric.spec.ts b/packages/message-encryption/src/symmetric.spec.ts index 94374a606a..47219109fa 100644 --- a/packages/message-encryption/src/symmetric.spec.ts +++ b/packages/message-encryption/src/symmetric.spec.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import fc from "fast-check"; import { getPublicKey } from "./crypto/index.js"; -import { createSymDecoder, createSymEncoder } from "./symmetric.js"; +import { createDecoder, createEncoder } from "./symmetric.js"; const TestContentTopic = "/test/1/waku-message/utf8"; @@ -13,10 +13,10 @@ describe("Symmetric Encryption", function () { fc.uint8Array({ minLength: 1 }), fc.uint8Array({ min: 1, minLength: 32, maxLength: 32 }), async (payload, symKey) => { - const encoder = createSymEncoder(TestContentTopic, symKey); + const encoder = createEncoder(TestContentTopic, symKey); const bytes = await encoder.toWire({ payload }); - const decoder = createSymDecoder(TestContentTopic, symKey); + const decoder = createDecoder(TestContentTopic, symKey); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; const result = await decoder.fromProtoObj(protoResult); @@ -41,14 +41,10 @@ describe("Symmetric Encryption", function () { async (payload, sigPrivKey, symKey) => { const sigPubKey = getPublicKey(sigPrivKey); - const encoder = createSymEncoder( - TestContentTopic, - symKey, - sigPrivKey - ); + const encoder = createEncoder(TestContentTopic, symKey, sigPrivKey); const bytes = await encoder.toWire({ payload }); - const decoder = createSymDecoder(TestContentTopic, symKey); + const decoder = createDecoder(TestContentTopic, symKey); const protoResult = await decoder.fromWireToProtoObj(bytes!); if (!protoResult) throw "Failed to proto decode"; const result = await decoder.fromProtoObj(protoResult); diff --git a/packages/message-encryption/src/symmetric.ts b/packages/message-encryption/src/symmetric.ts index cf990ce669..37cb42372f 100644 --- a/packages/message-encryption/src/symmetric.ts +++ b/packages/message-encryption/src/symmetric.ts @@ -17,11 +17,18 @@ import { preCipher, } from "./waku_payload.js"; -import { DecodedMessage, OneMillion, Version } from "./index.js"; +import { + DecodedMessage, + generateSymmetricKey, + OneMillion, + Version, +} from "./index.js"; + +export { DecodedMessage, generateSymmetricKey }; const log = debug("waku:message-encryption:symmetric"); -class SymEncoder implements IEncoder { +class Encoder implements IEncoder { constructor( public contentTopic: string, private symKey: Uint8Array, @@ -58,16 +65,16 @@ class SymEncoder implements IEncoder { } } -export function createSymEncoder( +export function createEncoder( contentTopic: string, symKey: Uint8Array, sigPrivKey?: Uint8Array, ephemeral = false -): SymEncoder { - return new SymEncoder(contentTopic, symKey, sigPrivKey, ephemeral); +): Encoder { + return new Encoder(contentTopic, symKey, sigPrivKey, ephemeral); } -class SymDecoder extends DecoderV0 implements IDecoder { +class Decoder extends DecoderV0 implements IDecoder { constructor(contentTopic: string, private symKey: Uint8Array) { super(contentTopic); } @@ -125,9 +132,9 @@ class SymDecoder extends DecoderV0 implements IDecoder { } } -export function createSymDecoder( +export function createDecoder( contentTopic: string, symKey: Uint8Array -): SymDecoder { - return new SymDecoder(contentTopic, symKey); +): Decoder { + return new Decoder(contentTopic, symKey); } diff --git a/packages/message-encryption/src/waku_payload.ts b/packages/message-encryption/src/waku_payload.ts index a62b3e835d..08846cc896 100644 --- a/packages/message-encryption/src/waku_payload.ts +++ b/packages/message-encryption/src/waku_payload.ts @@ -6,7 +6,7 @@ import * as ecies from "./crypto/ecies.js"; import { keccak256, randomBytes, sign } from "./crypto/index.js"; import * as symmetric from "./crypto/symmetric.js"; -import { Signature } from "./index"; +import { Signature } from "./index.js"; const FlagsLength = 1; const FlagMask = 3; // 0011 diff --git a/packages/tests/tests/ephemeral.node.spec.ts b/packages/tests/tests/ephemeral.node.spec.ts index a20e9421a5..73f631ba55 100644 --- a/packages/tests/tests/ephemeral.node.spec.ts +++ b/packages/tests/tests/ephemeral.node.spec.ts @@ -4,14 +4,16 @@ import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; import { createLightNode } from "@waku/create"; import { Protocols, WakuLight } from "@waku/interfaces"; import { - createAsymDecoder, - createAsymEncoder, - createSymDecoder, - createSymEncoder, + createDecoder as eciesDecoder, + createEncoder as eciesEncoder, generatePrivateKey, - generateSymmetricKey, getPublicKey, -} from "@waku/message-encryption"; +} from "@waku/message-encryption/ecies"; +import { + generateSymmetricKey, + createDecoder as symDecoder, + createEncoder as symEncoder, +} from "@waku/message-encryption/symmetric"; import { expect } from "chai"; import debug from "debug"; @@ -75,22 +77,17 @@ describe("Waku Message Ephemeral field", () => { const AsymContentTopic = "/test/1/ephemeral-asym/utf8"; const SymContentTopic = "/test/1/ephemeral-sym/utf8"; - const asymEncoder = createAsymEncoder( + const asymEncoder = eciesEncoder( AsymContentTopic, publicKey, undefined, true ); - const symEncoder = createSymEncoder( - SymContentTopic, - symKey, - undefined, - true - ); + const symEncoder = eciesEncoder(SymContentTopic, symKey, undefined, true); const clearEncoder = createEncoder(TestContentTopic, true); - const asymDecoder = createAsymDecoder(AsymContentTopic, privateKey); - const symDecoder = createSymDecoder(SymContentTopic, symKey); + const asymDecoder = eciesDecoder(AsymContentTopic, privateKey); + const symDecoder = eciesDecoder(SymContentTopic, symKey); const [waku1, waku2, nimWakuMultiaddr] = await Promise.all([ createLightNode({ @@ -187,14 +184,14 @@ describe("Waku Message Ephemeral field", () => { const symKey = generateSymmetricKey(); - const ephemeralEncoder = createSymEncoder( + const ephemeralEncoder = symEncoder( TestContentTopic, symKey, undefined, true ); - const encoder = createSymEncoder(TestContentTopic, symKey); - const decoder = createSymDecoder(TestContentTopic, symKey); + const encoder = symEncoder(TestContentTopic, symKey); + const decoder = symDecoder(TestContentTopic, symKey); const messages: DecodedMessage[] = []; const callback = (msg: DecodedMessage): void => { @@ -235,14 +232,14 @@ describe("Waku Message Ephemeral field", () => { const privKey = generatePrivateKey(); const pubKey = getPublicKey(privKey); - const ephemeralEncoder = createAsymEncoder( + const ephemeralEncoder = eciesEncoder( TestContentTopic, pubKey, undefined, true ); - const encoder = createAsymEncoder(TestContentTopic, pubKey); - const decoder = createAsymDecoder(TestContentTopic, privKey); + const encoder = eciesEncoder(TestContentTopic, pubKey); + const decoder = eciesDecoder(TestContentTopic, privKey); const messages: DecodedMessage[] = []; const callback = (msg: DecodedMessage): void => { diff --git a/packages/tests/tests/relay.node.spec.ts b/packages/tests/tests/relay.node.spec.ts index d22c4588f8..519c539f35 100644 --- a/packages/tests/tests/relay.node.spec.ts +++ b/packages/tests/tests/relay.node.spec.ts @@ -7,14 +7,16 @@ import { createPrivacyNode } from "@waku/create"; import type { WakuPrivacy } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; import { - createAsymDecoder, - createAsymEncoder, - createSymDecoder, - createSymEncoder, + createDecoder as createEciesDecoder, + createEncoder as createEciesEncoder, generatePrivateKey, - generateSymmetricKey, getPublicKey, -} from "@waku/message-encryption"; +} from "@waku/message-encryption/ecies"; +import { + createDecoder as createSymDecoder, + createEncoder as createSymEncoder, + generateSymmetricKey, +} from "@waku/message-encryption/symmetric"; import { expect } from "chai"; import debug from "debug"; @@ -187,21 +189,21 @@ describe("Waku Relay [node only]", () => { const symKey = generateSymmetricKey(); const publicKey = getPublicKey(privateKey); - const asymEncoder = createAsymEncoder(asymTopic, publicKey); + const eciesEncoder = createEciesEncoder(asymTopic, publicKey); const symEncoder = createSymEncoder(symTopic, symKey); - const asymDecoder = createAsymDecoder(asymTopic, privateKey); + const eciesDecoder = createEciesDecoder(asymTopic, privateKey); const symDecoder = createSymDecoder(symTopic, symKey); const msgs: DecodedMessage[] = []; - waku2.relay.addObserver(asymDecoder, (wakuMsg) => { + waku2.relay.addObserver(eciesDecoder, (wakuMsg) => { msgs.push(wakuMsg); }); waku2.relay.addObserver(symDecoder, (wakuMsg) => { msgs.push(wakuMsg); }); - await waku1.relay.send(asymEncoder, { payload: utf8ToBytes(asymText) }); + await waku1.relay.send(eciesEncoder, { payload: utf8ToBytes(asymText) }); await delay(200); await waku1.relay.send(symEncoder, { payload: utf8ToBytes(symText) }); diff --git a/packages/tests/tests/store.node.spec.ts b/packages/tests/tests/store.node.spec.ts index f1743169f6..9907d35b4c 100644 --- a/packages/tests/tests/store.node.spec.ts +++ b/packages/tests/tests/store.node.spec.ts @@ -6,14 +6,16 @@ import { createLightNode } from "@waku/create"; import { DecodedMessage, Message, WakuLight } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; import { - createAsymDecoder, - createAsymEncoder, - createSymDecoder, - createSymEncoder, + createDecoder as createEciesDecoder, + createEncoder as createEciesEncoder, generatePrivateKey, - generateSymmetricKey, getPublicKey, -} from "@waku/message-encryption"; +} from "@waku/message-encryption/ecies"; +import { + createDecoder as createSymDecoder, + createEncoder as createSymEncoder, + generateSymmetricKey, +} from "@waku/message-encryption/symmetric"; import { expect } from "chai"; import debug from "debug"; @@ -365,15 +367,15 @@ describe("Waku Store", () => { const symKey = generateSymmetricKey(); const publicKey = getPublicKey(privateKey); - const asymEncoder = createAsymEncoder(asymTopic, publicKey); + const eciesEncoder = createEciesEncoder(asymTopic, publicKey); const symEncoder = createSymEncoder(symTopic, symKey); - const otherEncoder = createAsymEncoder( + const otherEncoder = createEciesEncoder( TestContentTopic, getPublicKey(generatePrivateKey()) ); - const asymDecoder = createAsymDecoder(asymTopic, privateKey); + const eciesDecoder = createEciesDecoder(asymTopic, privateKey); const symDecoder = createSymDecoder(symTopic, symKey); const [waku1, waku2, nimWakuMultiaddr] = await Promise.all([ @@ -399,7 +401,7 @@ describe("Waku Store", () => { log("Sending messages using light push"); await Promise.all([ - waku1.lightPush.push(asymEncoder, asymMsg), + waku1.lightPush.push(eciesEncoder, asymMsg), waku1.lightPush.push(symEncoder, symMsg), waku1.lightPush.push(otherEncoder, otherMsg), waku1.lightPush.push(TestEncoder, clearMsg), @@ -411,7 +413,7 @@ describe("Waku Store", () => { log("Retrieve messages from store"); for await (const msgPromises of waku2.store.queryGenerator([ - asymDecoder, + eciesDecoder, symDecoder, TestDecoder, ])) { diff --git a/packages/tests/tests/waku.node.spec.ts b/packages/tests/tests/waku.node.spec.ts index fd4049a613..37e4e080c5 100644 --- a/packages/tests/tests/waku.node.spec.ts +++ b/packages/tests/tests/waku.node.spec.ts @@ -12,10 +12,10 @@ import type { } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; import { - createSymDecoder, - createSymEncoder, + createDecoder, + createEncoder, generateSymmetricKey, -} from "@waku/message-encryption"; +} from "@waku/message-encryption/symmetric"; import { expect } from "chai"; import { @@ -167,9 +167,9 @@ describe("Decryption Keys", () => { this.timeout(10000); const symKey = generateSymmetricKey(); - const decoder = createSymDecoder(TestContentTopic, symKey); + const decoder = createDecoder(TestContentTopic, symKey); - const encoder = createSymEncoder(TestContentTopic, symKey); + const encoder = createEncoder(TestContentTopic, symKey); const messageText = "Message is encrypted"; const messageTimestamp = new Date("1995-12-17T03:24:00"); const message = { From 33ed43c06839b22cc920e015d81e2fe38831e919 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 2 Dec 2022 16:38:30 +1100 Subject: [PATCH 08/14] feat: `waitForRemotePeer` can now be directly imported from `@waku/core` --- packages/core/CHANGELOG.md | 4 ++++ packages/core/package.json | 4 ---- packages/core/rollup.config.js | 3 +-- packages/core/src/index.ts | 2 ++ packages/tests/tests/enr.node.spec.ts | 2 +- packages/tests/tests/ephemeral.node.spec.ts | 11 ++++++++--- packages/tests/tests/filter.node.spec.ts | 3 +-- packages/tests/tests/light_push.node.spec.ts | 3 +-- packages/tests/tests/relay.node.spec.ts | 10 +++++++--- packages/tests/tests/store.node.spec.ts | 12 ++++++++---- .../tests/tests/wait_for_remote_peer.node.spec.ts | 2 +- packages/tests/tests/waku.node.spec.ts | 3 +-- 12 files changed, 35 insertions(+), 24 deletions(-) diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 3f3fe3e0e8..b8806f547f 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add `@multiformats/multiaddr` as peer dependency. +### Changed + +- `waitForRemotePeer` can now be directly imported from `@waku/core`. + ## @waku/core [0.0.6](https://github.com/waku-org/js-waku/compare/@waku/core@0.0.5...@waku/core@0.0.6) (2022-11-18) ### Added diff --git a/packages/core/package.json b/packages/core/package.json index 43d058a372..5c83f5f243 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,10 +13,6 @@ "types": "./dist/lib/predefined_bootstrap_nodes.d.ts", "import": "./dist/lib/predefined_bootstrap_nodes.js" }, - "./lib/wait_for_remote_peer": { - "types": "./dist/lib/wait_for_remote_peer.d.ts", - "import": "./dist/lib/wait_for_remote_peer.js" - }, "./lib/waku_message/version_0": { "types": "./dist/lib/waku_message/version_0.d.ts", "import": "./dist/lib/waku_message/version_0.js" diff --git a/packages/core/rollup.config.js b/packages/core/rollup.config.js index fd47526e18..1a9240bbe7 100644 --- a/packages/core/rollup.config.js +++ b/packages/core/rollup.config.js @@ -1,12 +1,11 @@ -import { nodeResolve } from "@rollup/plugin-node-resolve"; import commonjs from "@rollup/plugin-commonjs"; import json from "@rollup/plugin-json"; +import { nodeResolve } from "@rollup/plugin-node-resolve"; export default { input: { index: "dist/index.js", "lib/predefined_bootstrap_nodes": "dist/lib/predefined_bootstrap_nodes.js", - "lib/wait_for_remote_peer": "dist/lib/wait_for_remote_peer.js", "lib/waku_message/version_0": "dist/lib/waku_message/version_0.js", "lib/waku_message/topic_only_message": "dist/lib/waku_message/topic_only_message.js", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index be64f04783..8fc48aaa5e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -33,3 +33,5 @@ export { StoreCodec, createCursor, } from "./lib/waku_store/index.js"; + +export { waitForRemotePeer } from "./lib/wait_for_remote_peer.js"; diff --git a/packages/tests/tests/enr.node.spec.ts b/packages/tests/tests/enr.node.spec.ts index 3e85ce269c..517007d44b 100644 --- a/packages/tests/tests/enr.node.spec.ts +++ b/packages/tests/tests/enr.node.spec.ts @@ -1,4 +1,4 @@ -import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; +import { waitForRemotePeer } from "@waku/core"; import { createPrivacyNode } from "@waku/create"; import { ENR } from "@waku/enr"; import type { WakuPrivacy } from "@waku/interfaces"; diff --git a/packages/tests/tests/ephemeral.node.spec.ts b/packages/tests/tests/ephemeral.node.spec.ts index 73f631ba55..755820017f 100644 --- a/packages/tests/tests/ephemeral.node.spec.ts +++ b/packages/tests/tests/ephemeral.node.spec.ts @@ -1,8 +1,13 @@ import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; -import { createDecoder, createEncoder, DecodedMessage } from "@waku/core"; -import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; +import { + createDecoder, + createEncoder, + DecodedMessage, + waitForRemotePeer, +} from "@waku/core"; import { createLightNode } from "@waku/create"; -import { Protocols, WakuLight } from "@waku/interfaces"; +import { Protocols } from "@waku/interfaces"; +import type { WakuLight } from "@waku/interfaces"; import { createDecoder as eciesDecoder, createEncoder as eciesEncoder, diff --git a/packages/tests/tests/filter.node.spec.ts b/packages/tests/tests/filter.node.spec.ts index f1d456c906..0b97563264 100644 --- a/packages/tests/tests/filter.node.spec.ts +++ b/packages/tests/tests/filter.node.spec.ts @@ -1,6 +1,5 @@ import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; -import { createDecoder, createEncoder } from "@waku/core"; -import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; +import { createDecoder, createEncoder, waitForRemotePeer } from "@waku/core"; import { createLightNode } from "@waku/create"; import type { DecodedMessage, WakuLight } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; diff --git a/packages/tests/tests/light_push.node.spec.ts b/packages/tests/tests/light_push.node.spec.ts index d61f2cb63f..2a4f11c81a 100644 --- a/packages/tests/tests/light_push.node.spec.ts +++ b/packages/tests/tests/light_push.node.spec.ts @@ -1,6 +1,5 @@ import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; -import { createEncoder } from "@waku/core"; -import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; +import { createEncoder, waitForRemotePeer } from "@waku/core"; import { createLightNode } from "@waku/create"; import type { WakuLight } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; diff --git a/packages/tests/tests/relay.node.spec.ts b/packages/tests/tests/relay.node.spec.ts index 519c539f35..764813dceb 100644 --- a/packages/tests/tests/relay.node.spec.ts +++ b/packages/tests/tests/relay.node.spec.ts @@ -1,8 +1,12 @@ import { PeerId } from "@libp2p/interface-peer-id"; import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; -import { DefaultPubSubTopic } from "@waku/core"; -import { createDecoder, createEncoder, DecodedMessage } from "@waku/core"; -import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; +import { + createDecoder, + createEncoder, + DecodedMessage, + DefaultPubSubTopic, + waitForRemotePeer, +} from "@waku/core"; import { createPrivacyNode } from "@waku/create"; import type { WakuPrivacy } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; diff --git a/packages/tests/tests/store.node.spec.ts b/packages/tests/tests/store.node.spec.ts index 9907d35b4c..5a3ef35735 100644 --- a/packages/tests/tests/store.node.spec.ts +++ b/packages/tests/tests/store.node.spec.ts @@ -1,9 +1,13 @@ import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; -import { createCursor, PageDirection } from "@waku/core"; -import { createDecoder, createEncoder } from "@waku/core"; -import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; +import { + createCursor, + createDecoder, + createEncoder, + PageDirection, + waitForRemotePeer, +} from "@waku/core"; import { createLightNode } from "@waku/create"; -import { DecodedMessage, Message, WakuLight } from "@waku/interfaces"; +import type { DecodedMessage, Message, WakuLight } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; import { createDecoder as createEciesDecoder, diff --git a/packages/tests/tests/wait_for_remote_peer.node.spec.ts b/packages/tests/tests/wait_for_remote_peer.node.spec.ts index 8adf521525..0fa96d6fff 100644 --- a/packages/tests/tests/wait_for_remote_peer.node.spec.ts +++ b/packages/tests/tests/wait_for_remote_peer.node.spec.ts @@ -1,4 +1,4 @@ -import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; +import { waitForRemotePeer } from "@waku/core"; import { createLightNode, createPrivacyNode } from "@waku/create"; import type { WakuLight, WakuPrivacy } from "@waku/interfaces"; import { Protocols } from "@waku/interfaces"; diff --git a/packages/tests/tests/waku.node.spec.ts b/packages/tests/tests/waku.node.spec.ts index 37e4e080c5..5391297c15 100644 --- a/packages/tests/tests/waku.node.spec.ts +++ b/packages/tests/tests/waku.node.spec.ts @@ -1,8 +1,7 @@ import { bootstrap } from "@libp2p/bootstrap"; import type { PeerId } from "@libp2p/interface-peer-id"; import { bytesToUtf8, utf8ToBytes } from "@waku/byte-utils"; -import { DefaultUserAgent } from "@waku/core"; -import { waitForRemotePeer } from "@waku/core/lib/wait_for_remote_peer"; +import { DefaultUserAgent, waitForRemotePeer } from "@waku/core"; import { createLightNode, createPrivacyNode } from "@waku/create"; import type { DecodedMessage, From 6121aa78d73c0c35e7604ec47ac50a37bb330e26 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 2 Dec 2022 16:51:11 +1100 Subject: [PATCH 09/14] doc: add readme for @waku/message-encryption --- packages/message-encryption/README.md | 65 +++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 packages/message-encryption/README.md diff --git a/packages/message-encryption/README.md b/packages/message-encryption/README.md new file mode 100644 index 0000000000..68d3adb900 --- /dev/null +++ b/packages/message-encryption/README.md @@ -0,0 +1,65 @@ +# `@waku/message-encryption` + +Provide Waku Message Version 1 payload encryption as defined in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/). + +## Symmetric Encryption + +Symmetric encryption uses a unique key to encrypt and decrypt messages. + +```typescript +import { + createDecoder, + createEncoder, + generateSymmetricKey, +} from "@waku/message-encryption/symmetric"; + +// Generate a random key +const key = generateSymmetricKey(); + +// To send messages, create an encoder +const encoder = createEncoder(contentTopic, key); + +// For example +waku.lightPush.push(encoder, { payload }); + +// To receive messages, create a decoder +const decoder = createDecoder(contentTopic, key); + +// For example +await waku.store.queryOrderedCallback([decoder], (msg) => { + // ... +}); +``` + +## ECIES Encryption + +ECIES encryption enables encryption for a public key and decryption using a private key. + +```typescript +import { + createDecoder, + createEncoder, + generatePrivateKey, + getPublicKey, +} from "@waku/message-encryption/ecies"; + +// Generate a random private key +const privateKey = generatePrivateKey(); + +// Keep the private key secure, provide the public key to the sender +const publicKey = getPublicKey(privateKey); + +// To send messages, create an encoder +const encoder = createEncoder(contentTopic, publicKey); + +// For example +waku.lightPush.push(encoder, { payload }); + +// To receive messages, create a decoder +const decoder = createDecoder(contentTopic, privateKey); + +// For example +await waku.store.queryOrderedCallback([decoder], (msg) => { + // ... +}); +``` From f80c3ded0f9f5680370da033878608e9faada61a Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 2 Dec 2022 19:43:45 +1100 Subject: [PATCH 10/14] chore: fix size-limit --- .size-limit.cjs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/.size-limit.cjs b/.size-limit.cjs index 199a422e9d..6116b79fc7 100644 --- a/.size-limit.cjs +++ b/.size-limit.cjs @@ -6,22 +6,19 @@ module.exports = [ }, { name: "Waku default setup", - path: [ - "packages/create/bundle/index.js", - "packages/core/bundle/lib/wait_for_remote_peer.js" - ], - import: { - "./packages/create/bundle/index.js": "{ createLightNode }", - "./packages/core/bundle/lib/wait_for_remote_peer.js": - "{ waitForRemotePeer }", - "./packages/core/bundle/lib/waku_message/version_0.js": - "{ MessageV0, DecoderV0, EncoderV0 }", - }, + path: "packages/create/bundle/index.js", + import: + "{ createLightNode, waitForRemotePeer, createEncoder, createDecoder }", }, { - name: "Asymmetric, symmetric encryption and signature", - path: "packages/message-encryption/bundle/index.js", - import: "{ MessageV1, AsymEncoder, AsymDecoder, SymEncoder, SymDecoder }", + name: "ECIES encryption", + path: "packages/message-encryption/bundle/ecies.js", + import: "{ generatePrivateKey, createEncoder, createDecoder, DecodedMessage }", + }, + { + name: "Symmetric encryption", + path: "packages/message-encryption/bundle/symmetric.js", + import: "{ generateSymmetricKey, createEncoder, createDecoder, DecodedMessage }", }, { name: "DNS discovery", From a4ddb45af1ec9b91909b36c6cbf07d1176901299 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Fri, 2 Dec 2022 19:48:55 +1100 Subject: [PATCH 11/14] chore: do not use `Partial` when not needed --- packages/core/src/lib/waku_light_push/index.ts | 2 +- packages/core/src/lib/waku_message/version_0.ts | 4 ++-- packages/core/src/lib/waku_relay/index.ts | 5 +---- packages/message-encryption/src/ecies.ts | 6 ++---- packages/message-encryption/src/symmetric.ts | 6 ++---- 5 files changed, 8 insertions(+), 15 deletions(-) diff --git a/packages/core/src/lib/waku_light_push/index.ts b/packages/core/src/lib/waku_light_push/index.ts index 60f202aede..2a4eb7db29 100644 --- a/packages/core/src/lib/waku_light_push/index.ts +++ b/packages/core/src/lib/waku_light_push/index.ts @@ -60,7 +60,7 @@ class WakuLightPush implements LightPush { async push( encoder: Encoder, - message: Partial, + message: Message, opts?: ProtocolOptions ): Promise { const pubSubTopic = opts?.pubSubTopic ? opts.pubSubTopic : this.pubSubTopic; diff --git a/packages/core/src/lib/waku_message/version_0.ts b/packages/core/src/lib/waku_message/version_0.ts index 38dbdb1fec..68a56b8530 100644 --- a/packages/core/src/lib/waku_message/version_0.ts +++ b/packages/core/src/lib/waku_message/version_0.ts @@ -74,11 +74,11 @@ export class DecodedMessage implements IDecodedMessage { export class Encoder implements IEncoder { constructor(public contentTopic: string, public ephemeral: boolean = false) {} - async toWire(message: Partial): Promise { + async toWire(message: Message): Promise { return proto.WakuMessage.encode(await this.toProtoObj(message)); } - async toProtoObj(message: Partial): Promise { + async toProtoObj(message: Message): Promise { const timestamp = message.timestamp ?? new Date(); return { diff --git a/packages/core/src/lib/waku_relay/index.ts b/packages/core/src/lib/waku_relay/index.ts index edd2ecd501..3e8459e6c4 100644 --- a/packages/core/src/lib/waku_relay/index.ts +++ b/packages/core/src/lib/waku_relay/index.ts @@ -99,10 +99,7 @@ class WakuRelay extends GossipSub implements Relay { /** * Send Waku message. */ - public async send( - encoder: Encoder, - message: Partial - ): Promise { + public async send(encoder: Encoder, message: Message): Promise { const msg = await encoder.toWire(message); if (!msg) { log("Failed to encode message, aborting publish"); diff --git a/packages/message-encryption/src/ecies.ts b/packages/message-encryption/src/ecies.ts index 65fee0f69d..9fe01bd235 100644 --- a/packages/message-encryption/src/ecies.ts +++ b/packages/message-encryption/src/ecies.ts @@ -37,16 +37,14 @@ class Encoder implements IEncoder { public ephemeral: boolean = false ) {} - async toWire(message: Partial): Promise { + async toWire(message: Message): Promise { const protoMessage = await this.toProtoObj(message); if (!protoMessage) return; return proto.WakuMessage.encode(protoMessage); } - async toProtoObj( - message: Partial - ): Promise { + async toProtoObj(message: Message): Promise { const timestamp = message.timestamp ?? new Date(); if (!message.payload) { log("No payload to encrypt, skipping: ", message); diff --git a/packages/message-encryption/src/symmetric.ts b/packages/message-encryption/src/symmetric.ts index 37cb42372f..b3e09f7a66 100644 --- a/packages/message-encryption/src/symmetric.ts +++ b/packages/message-encryption/src/symmetric.ts @@ -36,16 +36,14 @@ class Encoder implements IEncoder { public ephemeral: boolean = false ) {} - async toWire(message: Partial): Promise { + async toWire(message: Message): Promise { const protoMessage = await this.toProtoObj(message); if (!protoMessage) return; return proto.WakuMessage.encode(protoMessage); } - async toProtoObj( - message: Partial - ): Promise { + async toProtoObj(message: Message): Promise { const timestamp = message.timestamp ?? new Date(); if (!message.payload) { log("No payload to encrypt, skipping: ", message); From 9cd1759a06dd2542e17dae6c85da4b23d1670950 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Mon, 5 Dec 2022 10:07:12 +1100 Subject: [PATCH 12/14] doc: update changelogs --- packages/core/CHANGELOG.md | 27 ++++++++++++++----- packages/message-encryption/CHANGELOG.md | 33 ++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 packages/message-encryption/CHANGELOG.md diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index b8806f547f..d664636735 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -10,12 +10,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `@multiformats/multiaddr` as peer dependency. +- New `createEncoder` and `createDecoder` functions so that the consumer does not deal with Encoder/Decoder classes. ### Changed -- `waitForRemotePeer` can now be directly imported from `@waku/core`. +- `waitForRemotePeer` must now be directly imported from `@waku/core`. +- `V0` suffix removed from the version 0 objects. +- `createEncoder`/`createDecoder`/`DecodedMessage` for Waku Message Version 0 (no Waku level encryption) can now be imported directly from `@waku/core`. -## @waku/core [0.0.6](https://github.com/waku-org/js-waku/compare/@waku/core@0.0.5...@waku/core@0.0.6) (2022-11-18) +## [@waku/core@0.0.6] - 2022-11-18 ### Added @@ -29,22 +32,29 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `PeerDiscoveryStaticPeer` has been removed, use `@libp2p/bootstrap` instead. -## @waku/core [0.0.5](https://github.com/waku-org/js-waku/compare/@waku/core@0.0.4...@waku/core@0.0.5) (2022-11-11) +## [@waku/core@0.0.5] - 2022-11-11 ### Changed - Bumped `libp2p` to 0.39.5. -## @waku/core [0.0.4](https://github.com/waku-org/js-waku/compare/@waku/core@0.0.3...@waku/core@0.0.4) (2022-11-09) +## [@waku/core@0.0.4] - 2022-11-09 ### Changed - Bumped `libp2p` to 0.39.2. -## @waku/core [0.0.2](https://github.com/waku-org/js-waku/compare/@waku/core@0.0.1...@waku/core@0.0.2) (2022-11-04) +## [@waku/core@0.0.3] - 2022-11-04 + +### Fixed + +- Missing `.js` extension. + +## [@waku/core@0.0.2] - 2022-11-04 ### Changed +- `js-waku` is deprecated, `@waku/core` and other `@waku/*` packages should be used instead. - extract version-1 from chore - extract utils from core - extract dns discovery and enr from core ([f7f28f0](https://github.com/waku-org/js-waku/commit/f7f28f03b01fa5bc89eaeb083b68981169b45c39)) @@ -614,7 +624,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [ReactJS Chat App example](./examples/web-chat). - [Typedoc Documentation](https://js-waku.wakuconnect.dev/). -[unreleased]: https://github.com/status-im/js-waku/compare/v0.30.0...HEAD +[unreleased]: https://github.com/status-im/js-waku/compare/@waku/core@0.0.5...HEAD +[@waku/core@0.0.5]: https://github.com/waku-org/js-waku/compare/@waku/core@0.0.4...@waku/core@0.0.5 +[@waku/core@0.0.4]: https://github.com/waku-org/js-waku/compare/@waku/core@0.0.3...@waku/core@0.0.4 +[@waku/core@0.0.3]: https://github.com/waku-org/js-waku/compare/@waku/core@0.0.2...@waku/core@0.0.3 +[@waku/core@0.0.2]: https://github.com/waku-org/js-waku/compare/@waku/core@0.0.1...@waku/core@0.0.2 +[@waku/core@0.0.1]: https://github.com/waku-org/js-waku/comparev0.30.0...@waku/core@0.0.1 [0.30.0]: https://github.com/status-im/js-waku/compare/v0.29.0...v0.30.0 [0.29.0]: https://github.com/status-im/js-waku/compare/v0.28.0...v0.29.0 [0.28.1]: https://github.com/status-im/js-waku/compare/v0.28.0...v0.28.1 diff --git a/packages/message-encryption/CHANGELOG.md b/packages/message-encryption/CHANGELOG.md new file mode 100644 index 0000000000..6b08476da8 --- /dev/null +++ b/packages/message-encryption/CHANGELOG.md @@ -0,0 +1,33 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +- Add `@multiformats/multiaddr` as peer dependency. +- New `createEncoder` and `createDecoder` functions so that the consumer does not deal with Encoder/Decoder classes. +- + +### Changed + +- `Asymmetric` renamed to `ECIES` to follow RFC terminology. +- Split `ECIES` and `symmetric` packages, all items are now export from two different paths: `@waku/message-encryption/ecies` and `@waku/message-encryption/symmetric`. +- remove `asym` and `sym` prefix from exported items as they are now differentiated from their export path: `createEncoder`, `createDecoder`, `DecodedMessage`. +- Remove usage for `Partial` with `Message` as `Message`'s field are all optional. + +## [0.0.4] - 2022-11-18 + +### Added + +- Alpha version of `@waku/message-encryption`. + +[unreleased]: https://github.com/waku-org/js-waku/compare/@waku/message-encryption@0.0.4...HEAD +[0.0.4]: https://github.com/waku-org/js-waku/compare/@waku/message-encryption@0.0.3...@waku/message-encryption@0.0.4 +[0.0.3]: https://github.com/waku-org/js-waku/compare/@waku/message-encryption@0.0.2...%40waku/message-encryption@0.0.3 +[0.0.2]: https://github.com/waku-org/js-waku/compare/@waku/message-encryption@0.0.1...%40waku/message-encryption@0.0.2 +[0.0.1]: https://github.com/status-im/js-waku/compare/a20b7809d61ff9a9732aba82b99bbe99f229b935...%40waku/message-encryption%400.0.2 From 84c477984f0812641a597c0f9edae746c1410f48 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Mon, 5 Dec 2022 15:54:12 +1100 Subject: [PATCH 13/14] fix: remove `ephemeral` argument for `Decoder` --- packages/core/src/lib/waku_message/version_0.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/core/src/lib/waku_message/version_0.ts b/packages/core/src/lib/waku_message/version_0.ts index 68a56b8530..2073019cbe 100644 --- a/packages/core/src/lib/waku_message/version_0.ts +++ b/packages/core/src/lib/waku_message/version_0.ts @@ -100,7 +100,7 @@ export function createEncoder( } export class Decoder implements IDecoder { - constructor(public contentTopic: string, public ephemeral: boolean = false) {} + constructor(public contentTopic: string) {} fromWireToProtoObj(bytes: Uint8Array): Promise { const protoMessage = proto.WakuMessage.decode(bytes); @@ -135,9 +135,6 @@ export class Decoder implements IDecoder { } } -export function createDecoder( - contentTopic: string, - ephemeral = false -): Decoder { - return new Decoder(contentTopic, ephemeral); +export function createDecoder(contentTopic: string): Decoder { + return new Decoder(contentTopic); } From 80d7215e15485e2f9ef60b52b8ae60a4099610c4 Mon Sep 17 00:00:00 2001 From: "fryorcraken.eth" Date: Mon, 5 Dec 2022 15:54:55 +1100 Subject: [PATCH 14/14] doc: `createEncoder` and `createDecoder` --- .../core/src/lib/waku_message/version_0.ts | 23 ++++++++++++++ packages/message-encryption/src/ecies.ts | 31 +++++++++++++++++++ packages/message-encryption/src/index.ts | 3 ++ packages/message-encryption/src/symmetric.ts | 31 +++++++++++++++++++ 4 files changed, 88 insertions(+) diff --git a/packages/core/src/lib/waku_message/version_0.ts b/packages/core/src/lib/waku_message/version_0.ts index 2073019cbe..b58251f7d5 100644 --- a/packages/core/src/lib/waku_message/version_0.ts +++ b/packages/core/src/lib/waku_message/version_0.ts @@ -92,6 +92,18 @@ export class Encoder implements IEncoder { } } +/** + * Creates an encoder that encode messages without Waku level encryption or signature. + * + * An encoder is used to encode messages in the [`14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/) + * format to be sent over the Waku network. The resulting encoder can then be + * pass to { @link @waku/interfaces.LightPush.push } or + * { @link @waku/interfaces.Relay.send } to automatically encode outgoing + * messages. + * + * @param contentTopic The content topic to set on outgoing messages. + * @param ephemeral An optional flag to mark message as ephemeral, ie, not to be stored by Waku Store nodes. + */ export function createEncoder( contentTopic: string, ephemeral = false @@ -135,6 +147,17 @@ export class Decoder implements IDecoder { } } +/** + * Creates an decoder that decode messages without Waku level encryption. + * + * A decoder is used to decode messages from the [14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/) + * format when received from the Waku network. The resulting decoder can then be + * pass to { @link @waku/interfaces.Filter.subscribe } or + * { @link @waku/interfaces.Relay.subscribe } to automatically decode incoming + * messages. + * + * @param contentTopic The resulting decoder will only decode messages with this content topic. + */ export function createDecoder(contentTopic: string): Decoder { return new Decoder(contentTopic); } diff --git a/packages/message-encryption/src/ecies.ts b/packages/message-encryption/src/ecies.ts index 9fe01bd235..6b73c5f38c 100644 --- a/packages/message-encryption/src/ecies.ts +++ b/packages/message-encryption/src/ecies.ts @@ -65,6 +65,24 @@ class Encoder implements IEncoder { } } +/** + * Creates an encoder that encrypts messages using ECIES for the given public, + * as defined in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/). + * + * An encoder is used to encode messages in the [`14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/) + * format to be sent over the Waku network. The resulting encoder can then be + * pass to { @link @waku/interfaces.LightPush.push } or + * { @link @waku/interfaces.Relay.send } to automatically encrypt + * and encode outgoing messages. + * + * The payload can optionally be signed with the given private key as defined + * in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/). + * + * @param contentTopic The content topic to set on outgoing messages. + * @param publicKey The public key to encrypt the payload for. + * @param sigPrivKey An optional private key to used to sign the payload before encryption. + * @param ephemeral An optional flag to mark message as ephemeral, ie, not to be stored by Waku Store nodes. + */ export function createEncoder( contentTopic: string, publicKey: Uint8Array, @@ -132,6 +150,19 @@ class Decoder extends DecoderV0 implements IDecoder { } } +/** + * Creates a decoder that decrypts messages using ECIES, using the given private + * key as defined in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/). + * + * A decoder is used to decode messages from the [14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/) + * format when received from the Waku network. The resulting decoder can then be + * pass to { @link @waku/interfaces.Filter.subscribe } or + * { @link @waku/interfaces.Relay.subscribe } to automatically decrypt and + * decode incoming messages. + * + * @param contentTopic The resulting decoder will only decode messages with this content topic. + * @param privateKey The private key used to decrypt the message. + */ export function createDecoder( contentTopic: string, privateKey: Uint8Array diff --git a/packages/message-encryption/src/index.ts b/packages/message-encryption/src/index.ts index d10b2ab004..7705df57c3 100644 --- a/packages/message-encryption/src/index.ts +++ b/packages/message-encryption/src/index.ts @@ -14,6 +14,9 @@ export const OneMillion = BigInt(1_000_000); export { generatePrivateKey, generateSymmetricKey, getPublicKey }; +export * as ecies from "./ecies.js"; +export * as symmetric from "./symmetric.js"; + export const Version = 1; export type Signature = { diff --git a/packages/message-encryption/src/symmetric.ts b/packages/message-encryption/src/symmetric.ts index b3e09f7a66..794cf47f4f 100644 --- a/packages/message-encryption/src/symmetric.ts +++ b/packages/message-encryption/src/symmetric.ts @@ -63,6 +63,24 @@ class Encoder implements IEncoder { } } +/** + * Creates an encoder that encrypts messages using symmetric encryption for the + * given key, as defined in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/). + * + * An encoder is used to encode messages in the [`14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/) + * format to be sent over the Waku network. The resulting encoder can then be + * pass to { @link @waku/interfaces.LightPush.push } or + * { @link @waku/interfaces.Relay.send } to automatically encrypt + * and encode outgoing messages. + * + * The payload can optionally be signed with the given private key as defined + * in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/). + * + * @param contentTopic The content topic to set on outgoing messages. + * @param symKey The symmetric key to encrypt the payload with. + * @param sigPrivKey An optional private key to used to sign the payload before encryption. + * @param ephemeral An optional flag to mark message as ephemeral, ie, not to be stored by Waku Store nodes. + */ export function createEncoder( contentTopic: string, symKey: Uint8Array, @@ -130,6 +148,19 @@ class Decoder extends DecoderV0 implements IDecoder { } } +/** + * Creates a decoder that decrypts messages using symmetric encryption, using + * the given key as defined in [26/WAKU2-PAYLOAD](https://rfc.vac.dev/spec/26/). + * + * A decoder is used to decode messages from the [14/WAKU2-MESSAGE](https://rfc.vac.dev/spec/14/) + * format when received from the Waku network. The resulting decoder can then be + * pass to { @link @waku/interfaces.Filter.subscribe } or + * { @link @waku/interfaces.Relay.subscribe } to automatically decrypt and + * decode incoming messages. + * + * @param contentTopic The resulting decoder will only decode messages with this content topic. + * @param symKey The symmetric key used to decrypt the message. + */ export function createDecoder( contentTopic: string, symKey: Uint8Array