refactor: constructors and QR

This commit is contained in:
Richard Ramos 2022-12-03 09:37:39 -04:00
parent f2d2f891ab
commit c911333ef1
No known key found for this signature in database
GPG Key ID: BD36D48BC9FFC88C
11 changed files with 81 additions and 100 deletions

View File

@ -6,7 +6,10 @@
"Waku",
"keypair",
"nwaku",
"Nametag, ciphertext",
"wakunoise",
"HMACDRBG",
"Nametag",
"ciphertext",
"unpad",
"blocksize",
"Nametag",

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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 };

View File

@ -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();

View File

@ -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<NoiseTokens>;
constructor(direction: MessageDirection, tokens: Array<NoiseTokens>) {
this.direction = direction;
this.tokens = tokens;
}
constructor(public readonly direction: MessageDirection, public readonly tokens: Array<NoiseTokens>) {}
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<NoiseTokens>;
constructor(direction: MessageDirection, tokens: Array<NoiseTokens>) {
this.direction = direction;
this.tokens = tokens;
}
constructor(public readonly direction: MessageDirection, public readonly tokens: Array<NoiseTokens>) {}
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<PreMessagePattern>;
messagePatterns: Array<MessagePattern>;
constructor(name: string, preMessagePatterns: Array<PreMessagePattern>, messagePatterns: Array<MessagePattern>) {
this.name = name;
this.preMessagePatterns = preMessagePatterns;
this.messagePatterns = messagePatterns;
}
constructor(
public readonly name: string,
public readonly preMessagePatterns: Array<PreMessagePattern>,
public readonly messagePatterns: Array<MessagePattern>
) {}
equals(b: HandshakePattern): boolean {
if (this.preMessagePatterns.length != b.preMessagePatterns.length) return false;

View File

@ -19,8 +19,8 @@ export function toMessageNametag(input: Uint8Array): MessageNametag {
}
export class MessageNametagBuffer {
buffer: Array<MessageNametag> = new Array<MessageNametag>(MessageNametagBufferSize);
counter = 0;
private buffer: Array<MessageNametag> = new Array<MessageNametag>(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++) {

View File

@ -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));

44
src/qr.ts Normal file
View File

@ -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);
}
}

View File

@ -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 };
}

View File

@ -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,
});