mirror of
https://github.com/waku-org/js-noise.git
synced 2025-02-23 08:28:17 +00:00
refactor: constructors and QR
This commit is contained in:
parent
f2d2f891ab
commit
c911333ef1
@ -6,7 +6,10 @@
|
||||
"Waku",
|
||||
"keypair",
|
||||
"nwaku",
|
||||
"Nametag, ciphertext",
|
||||
"wakunoise",
|
||||
"HMACDRBG",
|
||||
"Nametag",
|
||||
"ciphertext",
|
||||
"unpad",
|
||||
"blocksize",
|
||||
"Nametag",
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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 };
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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++) {
|
||||
|
@ -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
44
src/qr.ts
Normal 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);
|
||||
}
|
||||
}
|
42
src/utils.ts
42
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 };
|
||||
}
|
||||
|
@ -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,
|
||||
});
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user