From c911333ef1a2935683ed1cdd56a014ee677218d7 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Sat, 3 Dec 2022 09:37:39 -0400 Subject: [PATCH] refactor: constructors and QR --- .cspell.json | 5 +++- src/codec.ts | 3 ++- src/handshake.ts | 20 ++++++++-------- src/index.ts | 4 ++-- src/noise.ts | 5 ++-- src/patterns.ts | 30 ++++++----------------- src/payload.ts | 8 +++---- src/publickey.ts | 8 +------ src/qr.ts | 44 ++++++++++++++++++++++++++++++++++ src/utils.ts | 42 -------------------------------- src/waku-noise-pairing.spec.ts | 12 ++++------ 11 files changed, 81 insertions(+), 100 deletions(-) create mode 100644 src/qr.ts diff --git a/.cspell.json b/.cspell.json index 929a905..6223d3a 100644 --- a/.cspell.json +++ b/.cspell.json @@ -6,7 +6,10 @@ "Waku", "keypair", "nwaku", - "Nametag, ciphertext", + "wakunoise", + "HMACDRBG", + "Nametag", + "ciphertext", "unpad", "blocksize", "Nametag", diff --git a/src/codec.ts b/src/codec.ts index 3a71021..64dac76 100644 --- a/src/codec.ts +++ b/src/codec.ts @@ -34,7 +34,8 @@ export class NoiseHandshakeEncoder implements Encoder { export class NoiseHandshakeMessage extends MessageV0 implements Message { get payloadV2(): PayloadV2 { - return PayloadV2.deserialize(this.payload!); + if (!this.payload) throw new Error("no payload available"); + return PayloadV2.deserialize(this.payload); } } diff --git a/src/handshake.ts b/src/handshake.ts index 3cfd11a..ec61975 100644 --- a/src/handshake.ts +++ b/src/handshake.ts @@ -38,14 +38,14 @@ export class HandshakeStepResult { // The recipient static key rs and handshake hash values h are stored to address some possible future applications (channel-binding, session management, etc.). // However, are not required by Noise specifications and are thus optional export class HandshakeResult { - csOutbound?: CipherState; - csInbound?: CipherState; // Optional fields: nametagsInbound: MessageNametagBuffer = new MessageNametagBuffer(); nametagsOutbound: MessageNametagBuffer = new MessageNametagBuffer(); rs: bytes32 = new Uint8Array(); h: bytes32 = new Uint8Array(); + constructor(private csOutbound: CipherState, private csInbound: CipherState) {} + // Noise specification, Section 5: // Transport messages are then encrypted and decrypted by calling EncryptWithAd() // and DecryptWithAd() on the relevant CipherState with zero-length associated data. @@ -70,7 +70,7 @@ export class HandshakeResult { // We pad the transport message const paddedTransportMessage = pkcs7.pad(transportMessage, NoisePaddingBlockSize); // Encryption is done with zero-length associated data as per specification - payload2.transportMessage = this.csOutbound!.encryptWithAd(payload2.messageNametag, paddedTransportMessage); + payload2.transportMessage = this.csOutbound.encryptWithAd(payload2.messageNametag, paddedTransportMessage); return payload2; } @@ -97,7 +97,7 @@ export class HandshakeResult { // On application level we decide to discard messages which fail decryption, without raising an error try { // Decryption is done with messageNametag as associated data - const paddedMessage = this.csInbound!.decryptWithAd(readPayload2.messageNametag, readPayload2.transportMessage); + const paddedMessage = this.csInbound.decryptWithAd(readPayload2.messageNametag, readPayload2.transportMessage); // We unpad the decrypted message message = pkcs7.unpad(paddedMessage); // The message successfully decrypted, we can delete the first element of the inbound Message Nametag Buffer @@ -245,7 +245,7 @@ export class Handshake { // Finalizes the handshake by calling Split and assigning the proper Cipher States to users finalizeHandshake(): HandshakeResult { - const hsResult = new HandshakeResult(); + let hsResult: HandshakeResult; // Noise specification, Section 5: // Processing the final handshake message returns two CipherState objects, @@ -260,14 +260,12 @@ export class Handshake { // We assign the proper Cipher States if (this.hs.initiator) { - hsResult.csOutbound = cs1; - hsResult.csInbound = cs2; + hsResult = new HandshakeResult(cs1, cs2); // and nametags secrets hsResult.nametagsInbound.secret = nms1; hsResult.nametagsOutbound.secret = nms2; } else { - hsResult.csOutbound = cs2; - hsResult.csInbound = cs1; + hsResult = new HandshakeResult(cs2, cs1); // and nametags secrets hsResult.nametagsInbound.secret = nms2; hsResult.nametagsOutbound.secret = nms1; @@ -277,8 +275,10 @@ export class Handshake { hsResult.nametagsInbound.initNametagsBuffer(); hsResult.nametagsOutbound.initNametagsBuffer(); + if (!this.hs.rs) throw new Error("invalid handshake state"); + // We store the optional fields rs and h - hsResult.rs = new Uint8Array(this.hs.rs!); + hsResult.rs = new Uint8Array(this.hs.rs); hsResult.h = new Uint8Array(this.hs.ss.h); return hsResult; diff --git a/src/index.ts b/src/index.ts index d6e9c2d..5ed925e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -24,7 +24,7 @@ import { } from "./patterns"; import { MessageNametagBuffer } from "./payload"; import { ChaChaPolyCipherState, NoisePublicKey } from "./publickey"; -import { fromQr, toQr } from "./utils"; +import { QR } from "./qr"; export { Handshake, HandshakeParameters, HandshakeResult, HandshakeStepResult, StepHandshakeParameters }; export { generateX25519KeyPair, generateX25519KeyPairFromSeed }; @@ -41,4 +41,4 @@ export { export { ChaChaPolyCipherState, NoisePublicKey }; export { MessageNametagBuffer }; export { NoiseHandshakeDecoder, NoiseHandshakeEncoder, NoiseSecureTransferDecoder, NoiseSecureTransferEncoder }; -export { fromQr, toQr }; +export { QR }; diff --git a/src/noise.ts b/src/noise.ts index 58443d5..d1dc7c0 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -156,11 +156,10 @@ function hashProtocol(name: string): Uint8Array { // Contains a Cipher State cs, the chaining key ck and the handshake hash value h export class SymmetricState { cs: CipherState; - ck: bytes32; // chaining key h: bytes32; // handshake hash - hsPattern: HandshakePattern; + private ck: bytes32; // chaining key - constructor(hsPattern: HandshakePattern) { + constructor(private readonly hsPattern: HandshakePattern) { this.h = hashProtocol(hsPattern.name); this.ck = this.h; this.cs = new CipherState(); diff --git a/src/patterns.ts b/src/patterns.ts index a3cd76a..44b8d44 100644 --- a/src/patterns.ts +++ b/src/patterns.ts @@ -21,13 +21,7 @@ export enum MessageDirection { // The pre message pattern consisting of a message direction and some Noise tokens, if any. // (if non empty, only tokens e and s are allowed: http://www.noiseprotocol.org/noise.html#handshake-pattern-basics) export class PreMessagePattern { - direction: MessageDirection; - tokens: Array; - - constructor(direction: MessageDirection, tokens: Array) { - this.direction = direction; - this.tokens = tokens; - } + constructor(public readonly direction: MessageDirection, public readonly tokens: Array) {} equals(b: PreMessagePattern): boolean { return ( @@ -41,13 +35,7 @@ export class PreMessagePattern { // The message pattern consisting of a message direction and some Noise tokens // All Noise tokens are allowed export class MessagePattern { - direction: MessageDirection; - tokens: Array; - - constructor(direction: MessageDirection, tokens: Array) { - this.direction = direction; - this.tokens = tokens; - } + constructor(public readonly direction: MessageDirection, public readonly tokens: Array) {} equals(b: MessagePattern): boolean { return ( @@ -60,15 +48,11 @@ export class MessagePattern { // The handshake pattern object. It stores the handshake protocol name, the handshake pre message patterns and the handshake message patterns export class HandshakePattern { - name: string; - preMessagePatterns: Array; - messagePatterns: Array; - - constructor(name: string, preMessagePatterns: Array, messagePatterns: Array) { - this.name = name; - this.preMessagePatterns = preMessagePatterns; - this.messagePatterns = messagePatterns; - } + constructor( + public readonly name: string, + public readonly preMessagePatterns: Array, + public readonly messagePatterns: Array + ) {} equals(b: HandshakePattern): boolean { if (this.preMessagePatterns.length != b.preMessagePatterns.length) return false; diff --git a/src/payload.ts b/src/payload.ts index ee94676..34f30d4 100644 --- a/src/payload.ts +++ b/src/payload.ts @@ -19,8 +19,8 @@ export function toMessageNametag(input: Uint8Array): MessageNametag { } export class MessageNametagBuffer { - buffer: Array = new Array(MessageNametagBufferSize); - counter = 0; + private buffer: Array = new Array(MessageNametagBufferSize); + private counter = 0; secret?: Uint8Array; constructor() { @@ -87,8 +87,8 @@ export class MessageNametagBuffer { if (this.secret) { // We rotate left the array by n for (let i = 0; i < n; i++) { - const first = this.buffer.shift()!; - this.buffer.push(first); + const first = this.buffer.shift(); + if (first) this.buffer.push(first); } for (let i = 0; i < n; i++) { diff --git a/src/publickey.ts b/src/publickey.ts index e95267e..cb72bc7 100644 --- a/src/publickey.ts +++ b/src/publickey.ts @@ -50,13 +50,7 @@ export class ChaChaPolyCipherState { // or the encryption of the X coordinate concatenated with the authorization tag, if encrypted (this implies flag = 1) // Note: besides encryption, flag can be used to distinguish among multiple supported Elliptic Curves export class NoisePublicKey { - flag: number; - pk: Uint8Array; - - constructor(flag: number, pk: Uint8Array) { - this.flag = flag; - this.pk = pk; - } + constructor(public readonly flag: number, public readonly pk: Uint8Array) {} clone(): NoisePublicKey { return new NoisePublicKey(this.flag, new Uint8Array(this.pk)); diff --git a/src/qr.ts b/src/qr.ts new file mode 100644 index 0000000..bbf5a8e --- /dev/null +++ b/src/qr.ts @@ -0,0 +1,44 @@ +import { decode, encode, fromUint8Array, toUint8Array } from "js-base64"; + +import { bytes32 } from "./@types/basic"; + +export class QR { + constructor( + public readonly applicationName: string, + public readonly applicationVersion: string, + public readonly shardId: string, + public readonly ephemeralKey: bytes32, + public readonly committedStaticKey: bytes32 + ) {} + + // Serializes input parameters to a base64 string for exposure through QR code (used by WakuPairing) + toString(): string { + let qr = encode(this.applicationName) + ":"; + qr += encode(this.applicationVersion) + ":"; + qr += encode(this.shardId) + ":"; + qr += fromUint8Array(this.ephemeralKey) + ":"; + qr += fromUint8Array(this.committedStaticKey); + + return qr; + } + + toByteArray(): Uint8Array { + const enc = new TextEncoder(); + return enc.encode(this.toString()); + } + + // Deserializes input string in base64 to the corresponding (applicationName, applicationVersion, shardId, ephemeralKey, committedStaticKey) + static fromString(qrString: string): QR { + const values = qrString.split(":"); + + if (values.length != 5) throw new Error("invalid qr string"); + + const applicationName = decode(values[0]); + const applicationVersion = decode(values[1]); + const shardId = decode(values[2]); + const ephemeralKey = toUint8Array(values[3]); + const committedStaticKey = toUint8Array(values[4]); + + return new QR(applicationName, applicationVersion, shardId, ephemeralKey, committedStaticKey); + } +} diff --git a/src/utils.ts b/src/utils.ts index 44b88b5..052818c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,5 @@ // Adapted from https://github.com/feross/buffer -import { decode, encode, fromUint8Array, toUint8Array } from "js-base64"; - -import { bytes32 } from "./@types/basic"; - function checkInt(buf: Uint8Array, value: number, offset: number, ext: number, max: number, min: number): void { if (value > max || value < min) throw new RangeError('"value" argument is out of bounds'); if (offset + ext > buf.length) throw new RangeError("Index out of range"); @@ -53,41 +49,3 @@ export function readUIntLE(buf: Uint8Array, offset: number, byteLength: number, return val; } - -// Serializes input parameters to a base64 string for exposure through QR code (used by WakuPairing) -export function toQr( - applicationName: string, - applicationVersion: string, - shardId: string, - ephemeralKey: bytes32, - committedStaticKey: bytes32 -): string { - let qr = encode(applicationName) + ":"; - qr += encode(applicationVersion) + ":"; - qr += encode(shardId) + ":"; - qr += fromUint8Array(ephemeralKey) + ":"; - qr += fromUint8Array(committedStaticKey); - - return qr; -} - -// Deserializes input string in base64 to the corresponding (applicationName, applicationVersion, shardId, ephemeralKey, committedStaticKey) -export function fromQr(qr: string): { - applicationName: string; - applicationVersion: string; - shardId: string; - ephemeralKey: bytes32; - committedStaticKey: bytes32; -} { - const values = qr.split(":"); - - if (values.length != 5) throw new Error("invalid qr string"); - - const applicationName = decode(values[0]); - const applicationVersion = decode(values[1]); - const shardId = decode(values[2]); - const ephemeralKey = toUint8Array(values[3]); - const committedStaticKey = toUint8Array(values[4]); - - return { applicationName, applicationVersion, shardId, ephemeralKey, committedStaticKey }; -} diff --git a/src/waku-noise-pairing.spec.ts b/src/waku-noise-pairing.spec.ts index 88e48ce..1787462 100644 --- a/src/waku-noise-pairing.spec.ts +++ b/src/waku-noise-pairing.spec.ts @@ -14,7 +14,7 @@ import { Handshake } from "./handshake"; import { NoiseHandshakePatterns } from "./patterns"; import { MessageNametagBufferSize, MessageNametagLength } from "./payload"; import { NoisePublicKey } from "./publickey"; -import { fromQr, toQr } from "./utils"; +import { QR } from "./qr"; describe("Waku Noise Sessions", () => { const rng = new HMACDRBG(); @@ -48,12 +48,10 @@ describe("Waku Noise Sessions", () => { // Out-of-band Communication // Bob prepares the QR and sends it out-of-band to Alice - const qr = toQr(applicationName, applicationVersion, shardId, bobEphemeralKey.publicKey, bobCommittedStaticKey); - const enc = new TextEncoder(); - const qrBytes = enc.encode(qr); + const qr = new QR(applicationName, applicationVersion, shardId, bobEphemeralKey.publicKey, bobCommittedStaticKey); // Alice deserializes the QR code - const readQR = fromQr(qr); + const readQR = QR.fromString(qr.toString()); // We check if QR serialization/deserialization works expect(readQR.applicationName).to.be.equals(applicationName); @@ -76,7 +74,7 @@ describe("Waku Noise Sessions", () => { hsPattern, ephemeralKey: aliceEphemeralKey, staticKey: aliceStaticKey, - prologue: qrBytes, + prologue: qr.toByteArray(), preMessagePKs, initiator: true, }); @@ -84,7 +82,7 @@ describe("Waku Noise Sessions", () => { hsPattern, ephemeralKey: bobEphemeralKey, staticKey: bobStaticKey, - prologue: qrBytes, + prologue: qr.toByteArray(), preMessagePKs, });