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", "Waku",
"keypair", "keypair",
"nwaku", "nwaku",
"Nametag, ciphertext", "wakunoise",
"HMACDRBG",
"Nametag",
"ciphertext",
"unpad", "unpad",
"blocksize", "blocksize",
"Nametag", "Nametag",

View File

@ -34,7 +34,8 @@ export class NoiseHandshakeEncoder implements Encoder {
export class NoiseHandshakeMessage extends MessageV0 implements Message { export class NoiseHandshakeMessage extends MessageV0 implements Message {
get payloadV2(): PayloadV2 { 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.). // 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 // However, are not required by Noise specifications and are thus optional
export class HandshakeResult { export class HandshakeResult {
csOutbound?: CipherState;
csInbound?: CipherState;
// Optional fields: // Optional fields:
nametagsInbound: MessageNametagBuffer = new MessageNametagBuffer(); nametagsInbound: MessageNametagBuffer = new MessageNametagBuffer();
nametagsOutbound: MessageNametagBuffer = new MessageNametagBuffer(); nametagsOutbound: MessageNametagBuffer = new MessageNametagBuffer();
rs: bytes32 = new Uint8Array(); rs: bytes32 = new Uint8Array();
h: bytes32 = new Uint8Array(); h: bytes32 = new Uint8Array();
constructor(private csOutbound: CipherState, private csInbound: CipherState) {}
// Noise specification, Section 5: // Noise specification, Section 5:
// Transport messages are then encrypted and decrypted by calling EncryptWithAd() // Transport messages are then encrypted and decrypted by calling EncryptWithAd()
// and DecryptWithAd() on the relevant CipherState with zero-length associated data. // and DecryptWithAd() on the relevant CipherState with zero-length associated data.
@ -70,7 +70,7 @@ export class HandshakeResult {
// We pad the transport message // We pad the transport message
const paddedTransportMessage = pkcs7.pad(transportMessage, NoisePaddingBlockSize); const paddedTransportMessage = pkcs7.pad(transportMessage, NoisePaddingBlockSize);
// Encryption is done with zero-length associated data as per specification // 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; return payload2;
} }
@ -97,7 +97,7 @@ export class HandshakeResult {
// On application level we decide to discard messages which fail decryption, without raising an error // On application level we decide to discard messages which fail decryption, without raising an error
try { try {
// Decryption is done with messageNametag as associated data // 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 // We unpad the decrypted message
message = pkcs7.unpad(paddedMessage); message = pkcs7.unpad(paddedMessage);
// The message successfully decrypted, we can delete the first element of the inbound Message Nametag Buffer // 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 // Finalizes the handshake by calling Split and assigning the proper Cipher States to users
finalizeHandshake(): HandshakeResult { finalizeHandshake(): HandshakeResult {
const hsResult = new HandshakeResult(); let hsResult: HandshakeResult;
// Noise specification, Section 5: // Noise specification, Section 5:
// Processing the final handshake message returns two CipherState objects, // Processing the final handshake message returns two CipherState objects,
@ -260,14 +260,12 @@ export class Handshake {
// We assign the proper Cipher States // We assign the proper Cipher States
if (this.hs.initiator) { if (this.hs.initiator) {
hsResult.csOutbound = cs1; hsResult = new HandshakeResult(cs1, cs2);
hsResult.csInbound = cs2;
// and nametags secrets // and nametags secrets
hsResult.nametagsInbound.secret = nms1; hsResult.nametagsInbound.secret = nms1;
hsResult.nametagsOutbound.secret = nms2; hsResult.nametagsOutbound.secret = nms2;
} else { } else {
hsResult.csOutbound = cs2; hsResult = new HandshakeResult(cs2, cs1);
hsResult.csInbound = cs1;
// and nametags secrets // and nametags secrets
hsResult.nametagsInbound.secret = nms2; hsResult.nametagsInbound.secret = nms2;
hsResult.nametagsOutbound.secret = nms1; hsResult.nametagsOutbound.secret = nms1;
@ -277,8 +275,10 @@ export class Handshake {
hsResult.nametagsInbound.initNametagsBuffer(); hsResult.nametagsInbound.initNametagsBuffer();
hsResult.nametagsOutbound.initNametagsBuffer(); hsResult.nametagsOutbound.initNametagsBuffer();
if (!this.hs.rs) throw new Error("invalid handshake state");
// We store the optional fields rs and h // 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); hsResult.h = new Uint8Array(this.hs.ss.h);
return hsResult; return hsResult;

View File

@ -24,7 +24,7 @@ import {
} from "./patterns"; } from "./patterns";
import { MessageNametagBuffer } from "./payload"; import { MessageNametagBuffer } from "./payload";
import { ChaChaPolyCipherState, NoisePublicKey } from "./publickey"; import { ChaChaPolyCipherState, NoisePublicKey } from "./publickey";
import { fromQr, toQr } from "./utils"; import { QR } from "./qr";
export { Handshake, HandshakeParameters, HandshakeResult, HandshakeStepResult, StepHandshakeParameters }; export { Handshake, HandshakeParameters, HandshakeResult, HandshakeStepResult, StepHandshakeParameters };
export { generateX25519KeyPair, generateX25519KeyPairFromSeed }; export { generateX25519KeyPair, generateX25519KeyPairFromSeed };
@ -41,4 +41,4 @@ export {
export { ChaChaPolyCipherState, NoisePublicKey }; export { ChaChaPolyCipherState, NoisePublicKey };
export { MessageNametagBuffer }; export { MessageNametagBuffer };
export { NoiseHandshakeDecoder, NoiseHandshakeEncoder, NoiseSecureTransferDecoder, NoiseSecureTransferEncoder }; 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 // Contains a Cipher State cs, the chaining key ck and the handshake hash value h
export class SymmetricState { export class SymmetricState {
cs: CipherState; cs: CipherState;
ck: bytes32; // chaining key
h: bytes32; // handshake hash 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.h = hashProtocol(hsPattern.name);
this.ck = this.h; this.ck = this.h;
this.cs = new CipherState(); 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. // 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) // (if non empty, only tokens e and s are allowed: http://www.noiseprotocol.org/noise.html#handshake-pattern-basics)
export class PreMessagePattern { export class PreMessagePattern {
direction: MessageDirection; constructor(public readonly direction: MessageDirection, public readonly tokens: Array<NoiseTokens>) {}
tokens: Array<NoiseTokens>;
constructor(direction: MessageDirection, tokens: Array<NoiseTokens>) {
this.direction = direction;
this.tokens = tokens;
}
equals(b: PreMessagePattern): boolean { equals(b: PreMessagePattern): boolean {
return ( return (
@ -41,13 +35,7 @@ export class PreMessagePattern {
// The message pattern consisting of a message direction and some Noise tokens // The message pattern consisting of a message direction and some Noise tokens
// All Noise tokens are allowed // All Noise tokens are allowed
export class MessagePattern { export class MessagePattern {
direction: MessageDirection; constructor(public readonly direction: MessageDirection, public readonly tokens: Array<NoiseTokens>) {}
tokens: Array<NoiseTokens>;
constructor(direction: MessageDirection, tokens: Array<NoiseTokens>) {
this.direction = direction;
this.tokens = tokens;
}
equals(b: MessagePattern): boolean { equals(b: MessagePattern): boolean {
return ( 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 // The handshake pattern object. It stores the handshake protocol name, the handshake pre message patterns and the handshake message patterns
export class HandshakePattern { export class HandshakePattern {
name: string; constructor(
preMessagePatterns: Array<PreMessagePattern>; public readonly name: string,
messagePatterns: Array<MessagePattern>; public readonly preMessagePatterns: Array<PreMessagePattern>,
public readonly messagePatterns: Array<MessagePattern>
constructor(name: string, preMessagePatterns: Array<PreMessagePattern>, messagePatterns: Array<MessagePattern>) { ) {}
this.name = name;
this.preMessagePatterns = preMessagePatterns;
this.messagePatterns = messagePatterns;
}
equals(b: HandshakePattern): boolean { equals(b: HandshakePattern): boolean {
if (this.preMessagePatterns.length != b.preMessagePatterns.length) return false; if (this.preMessagePatterns.length != b.preMessagePatterns.length) return false;

View File

@ -19,8 +19,8 @@ export function toMessageNametag(input: Uint8Array): MessageNametag {
} }
export class MessageNametagBuffer { export class MessageNametagBuffer {
buffer: Array<MessageNametag> = new Array<MessageNametag>(MessageNametagBufferSize); private buffer: Array<MessageNametag> = new Array<MessageNametag>(MessageNametagBufferSize);
counter = 0; private counter = 0;
secret?: Uint8Array; secret?: Uint8Array;
constructor() { constructor() {
@ -87,8 +87,8 @@ export class MessageNametagBuffer {
if (this.secret) { if (this.secret) {
// We rotate left the array by n // We rotate left the array by n
for (let i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
const first = this.buffer.shift()!; const first = this.buffer.shift();
this.buffer.push(first); if (first) this.buffer.push(first);
} }
for (let i = 0; i < n; i++) { 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) // 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 // Note: besides encryption, flag can be used to distinguish among multiple supported Elliptic Curves
export class NoisePublicKey { export class NoisePublicKey {
flag: number; constructor(public readonly flag: number, public readonly pk: Uint8Array) {}
pk: Uint8Array;
constructor(flag: number, pk: Uint8Array) {
this.flag = flag;
this.pk = pk;
}
clone(): NoisePublicKey { clone(): NoisePublicKey {
return new NoisePublicKey(this.flag, new Uint8Array(this.pk)); 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 // 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 { 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 (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"); 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; 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 { NoiseHandshakePatterns } from "./patterns";
import { MessageNametagBufferSize, MessageNametagLength } from "./payload"; import { MessageNametagBufferSize, MessageNametagLength } from "./payload";
import { NoisePublicKey } from "./publickey"; import { NoisePublicKey } from "./publickey";
import { fromQr, toQr } from "./utils"; import { QR } from "./qr";
describe("Waku Noise Sessions", () => { describe("Waku Noise Sessions", () => {
const rng = new HMACDRBG(); const rng = new HMACDRBG();
@ -48,12 +48,10 @@ describe("Waku Noise Sessions", () => {
// Out-of-band Communication // Out-of-band Communication
// Bob prepares the QR and sends it out-of-band to Alice // Bob prepares the QR and sends it out-of-band to Alice
const qr = toQr(applicationName, applicationVersion, shardId, bobEphemeralKey.publicKey, bobCommittedStaticKey); const qr = new QR(applicationName, applicationVersion, shardId, bobEphemeralKey.publicKey, bobCommittedStaticKey);
const enc = new TextEncoder();
const qrBytes = enc.encode(qr);
// Alice deserializes the QR code // Alice deserializes the QR code
const readQR = fromQr(qr); const readQR = QR.fromString(qr.toString());
// We check if QR serialization/deserialization works // We check if QR serialization/deserialization works
expect(readQR.applicationName).to.be.equals(applicationName); expect(readQR.applicationName).to.be.equals(applicationName);
@ -76,7 +74,7 @@ describe("Waku Noise Sessions", () => {
hsPattern, hsPattern,
ephemeralKey: aliceEphemeralKey, ephemeralKey: aliceEphemeralKey,
staticKey: aliceStaticKey, staticKey: aliceStaticKey,
prologue: qrBytes, prologue: qr.toByteArray(),
preMessagePKs, preMessagePKs,
initiator: true, initiator: true,
}); });
@ -84,7 +82,7 @@ describe("Waku Noise Sessions", () => {
hsPattern, hsPattern,
ephemeralKey: bobEphemeralKey, ephemeralKey: bobEphemeralKey,
staticKey: bobStaticKey, staticKey: bobStaticKey,
prologue: qrBytes, prologue: qr.toByteArray(),
preMessagePKs, preMessagePKs,
}); });