diff --git a/src/crypto.ts b/src/crypto.ts index 73ef192..21df8af 100644 --- a/src/crypto.ts +++ b/src/crypto.ts @@ -2,23 +2,13 @@ import { ChaCha20Poly1305 } from "@stablelib/chacha20poly1305"; import { Hash } from "@stablelib/hash"; import { HKDF as hkdf } from "@stablelib/hkdf"; import { RandomSource } from "@stablelib/random"; -import { hash } from "@stablelib/sha256"; import { concat as uint8ArrayConcat } from "uint8arrays/concat"; import type { bytes32 } from "./@types/basic.js"; import type { KeyPair } from "./@types/keypair.js"; /** - * Generate hash using SHA2-256 - * @param data data to hash - * @returns hash digest - */ -export function hashSHA256(data: Uint8Array): Uint8Array { - return hash(data); -} - -/** - * HKDF key derivation function using SHA256 + * HKDF key derivation function * @param ck chaining key * @param ikm input key material * @param length length of each generated key @@ -79,14 +69,24 @@ export function chaCha20Poly1305Decrypt( return ctx.open(nonce, ciphertext, ad); } +export function hash(hash: new () => Hash, data: Uint8Array): bytes32 { + const h = new hash(); + h.update(data); + const digest = h.digest(); + h.clean(); + return digest; +} + /** * Generates a random static key commitment using a public key pk for randomness r as H(pk || s) + * @param h Hash function * @param publicKey x25519 public key * @param r random fixed-length value * @returns 32 byte hash */ -export function commitPublicKey(publicKey: bytes32, r: Uint8Array): bytes32 { - return hashSHA256(uint8ArrayConcat([publicKey, r])); +export function commitPublicKey(h: new () => Hash, publicKey: bytes32, r: Uint8Array): bytes32 { + const data = uint8ArrayConcat([publicKey, r]); + return hash(h, data); } /** diff --git a/src/messagenametag.ts b/src/messagenametag.ts index ee3ce46..27bbf1e 100644 --- a/src/messagenametag.ts +++ b/src/messagenametag.ts @@ -1,8 +1,8 @@ +import { hash } from "@stablelib/sha256"; import { concat as uint8ArrayConcat } from "uint8arrays/concat"; import { equals as uint8ArrayEquals } from "uint8arrays/equals"; import { MessageNametag } from "./@types/handshake.js"; -import { hashSHA256 } from "./crypto.js"; import { writeUIntLE } from "./utils.js"; export const MessageNametagLength = 16; @@ -40,6 +40,7 @@ export class MessageNametagBuffer { if (this.secret) { for (let i = 0; i < this.buffer.length; i++) { const counterBytesLE = writeUIntLE(new Uint8Array(8), this.counter, 0, 8); + // TODO: determine if this hash should be sha256, or if it should depend on the handshake pattern const d = hashSHA256(uint8ArrayConcat([this.secret, counterBytesLE])); this.buffer[i] = toMessageNametag(d); this.counter++; @@ -124,3 +125,12 @@ export class MessageNametagBuffer { } } } + +/** + * Generate hash using SHA2-256 + * @param data data to hash + * @returns hash digest + */ +function hashSHA256(data: Uint8Array): Uint8Array { + return hash(data); +} diff --git a/src/noise.ts b/src/noise.ts index 4ea1f72..f814082 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -1,10 +1,11 @@ +import { Hash } from "@stablelib/hash"; import debug from "debug"; import { fromString as uint8ArrayFromString } from "uint8arrays"; import { concat as uint8ArrayConcat } from "uint8arrays/concat"; import { equals as uint8ArrayEquals } from "uint8arrays/equals"; import type { bytes32 } from "./@types/basic.js"; -import { chaCha20Poly1305Decrypt, chaCha20Poly1305Encrypt, hashSHA256, HKDF } from "./crypto.js"; +import { chaCha20Poly1305Decrypt, chaCha20Poly1305Encrypt, hash, HKDF } from "./crypto.js"; import { Nonce } from "./nonce.js"; import { HandshakePattern } from "./patterns.js"; @@ -192,7 +193,7 @@ export class CipherState { * @param name name of the noise handshake pattern to hash * @returns sha256 digest of the protocol name */ -function hashProtocol(name: string): Uint8Array { +function hashProtocol(h: new () => Hash, name: string): Uint8Array { // If protocol_name is less than or equal to HASHLEN bytes in length, // sets h equal to protocol_name with zero bytes appended to make HASHLEN bytes. // Otherwise sets h = HASH(protocol_name). @@ -203,7 +204,7 @@ function hashProtocol(name: string): Uint8Array { h.set(protocolName); return h; } else { - return hashSHA256(protocolName); + return hash(h, protocolName); } } @@ -217,7 +218,7 @@ export class SymmetricState { private ck: bytes32; // chaining key constructor(private readonly handshakePattern: HandshakePattern) { - this.h = hashProtocol(handshakePattern.name); + this.h = hashProtocol(handshakePattern.hash, handshakePattern.name); this.ck = this.h; this.cs = new CipherState(); } @@ -269,7 +270,7 @@ export class SymmetricState { */ mixHash(data: Uint8Array): void { // We hash the previous handshake hash and input data and store the result in the Symmetric State's handshake hash value - this.h = hashSHA256(uint8ArrayConcat([this.h, data])); + this.h = hash(this.handshakePattern.hash, uint8ArrayConcat([this.h, data])); log("mixHash", this.h); } diff --git a/src/pairing.ts b/src/pairing.ts index b39a7bf..030d989 100644 --- a/src/pairing.ts +++ b/src/pairing.ts @@ -15,10 +15,9 @@ import { NoiseSecureTransferEncoder, } from "./codec.js"; import { commitPublicKey } from "./crypto.js"; -import { DH25519 } from "./dh25519.js"; import { Handshake, HandshakeResult, HandshakeStepResult, MessageNametagError } from "./handshake.js"; import { MessageNametagLength } from "./messagenametag.js"; -import { NoiseHandshakePatterns } from "./patterns.js"; +import { HandshakePattern, NoiseHandshakePatterns } from "./patterns.js"; import { NoisePublicKey } from "./publickey.js"; import { QR } from "./qr.js"; @@ -58,6 +57,7 @@ export class ResponderParameters { */ export class WakuPairing { public readonly contentTopic: string; + private readonly hsPattern: HandshakePattern; private initiator: boolean; private randomFixLenVal: Uint8Array; // r or s depending on who is sending the message @@ -95,11 +95,18 @@ export class WakuPairing { private responder: IReceiver, private myStaticKey: KeyPair, pairingParameters: InitiatorParameters | ResponderParameters, - private myEphemeralKey: KeyPair = new DH25519().generateKeyPair(), + private myEphemeralKey?: KeyPair, private readonly encoderParameters: EncoderParameters = {} ) { + this.hsPattern = NoiseHandshakePatterns.Noise_WakuPairing_25519_ChaChaPoly_SHA256; this.randomFixLenVal = randomBytes(32, rng); - this.myCommittedStaticKey = commitPublicKey(this.myStaticKey.publicKey, this.randomFixLenVal); + this.myCommittedStaticKey = commitPublicKey(this.hsPattern.hash, this.myStaticKey.publicKey, this.randomFixLenVal); + + if (!this.myEphemeralKey) { + this.myEphemeralKey = NoiseHandshakePatterns.Noise_WakuPairing_25519_ChaChaPoly_SHA256.dhKey.generateKeyPair(); + } + + console.log(this.myEphemeralKey.publicKey); if (pairingParameters instanceof InitiatorParameters) { this.initiator = true; @@ -125,8 +132,8 @@ export class WakuPairing { const preMessagePKs = [NoisePublicKey.fromPublicKey(this.qr.ephemeralKey)]; this.handshake = new Handshake({ - hsPattern: NoiseHandshakePatterns.Noise_WakuPairing_25519_ChaChaPoly_SHA256, - ephemeralKey: myEphemeralKey, + hsPattern: this.hsPattern, + ephemeralKey: this.myEphemeralKey, staticKey: myStaticKey, prologue: this.qr.toByteArray(), preMessagePKs, @@ -260,7 +267,11 @@ export class WakuPairing { if (!this.handshake.hs.rs) throw new Error("invalid handshake state"); // Initiator further checks if responder's commitment opens to responder's static key received - const expectedResponderCommittedStaticKey = commitPublicKey(this.handshake.hs.rs, hsStep.transportMessage); + const expectedResponderCommittedStaticKey = commitPublicKey( + this.hsPattern.hash, + this.handshake.hs.rs, + hsStep.transportMessage + ); if (!uint8ArrayEquals(expectedResponderCommittedStaticKey, this.qr.committedStaticKey)) { throw new Error("expected committed static key does not match the responder actual committed static key"); } @@ -333,7 +344,11 @@ export class WakuPairing { if (!this.handshake.hs.rs) throw new Error("invalid handshake state"); // The responder further checks if the initiator's commitment opens to the initiator's static key received - const expectedInitiatorCommittedStaticKey = commitPublicKey(this.handshake.hs.rs, hsStep.transportMessage); + const expectedInitiatorCommittedStaticKey = commitPublicKey( + this.hsPattern.hash, + this.handshake.hs.rs, + hsStep.transportMessage + ); if (!uint8ArrayEquals(expectedInitiatorCommittedStaticKey, initiatorCommittedStaticKey)) { throw new Error("expected committed static key does not match the initiator actual committed static key"); } diff --git a/src/waku-noise-pairing.spec.ts b/src/waku-noise-pairing.spec.ts index 298ce4e..e99d6b8 100644 --- a/src/waku-noise-pairing.spec.ts +++ b/src/waku-noise-pairing.spec.ts @@ -1,5 +1,6 @@ import { HMACDRBG } from "@stablelib/hmac-drbg"; import { randomBytes } from "@stablelib/random"; +import { SHA256 } from "@stablelib/sha256"; import { expect } from "chai"; import { equals as uint8ArrayEquals } from "uint8arrays/equals"; @@ -29,6 +30,7 @@ describe("Waku Noise Sessions", () => { // ========== const dhKey = new DH25519(); + const hash = SHA256; const hsPattern = NoiseHandshakePatterns.Noise_WakuPairing_25519_ChaChaPoly_SHA256; @@ -36,13 +38,13 @@ describe("Waku Noise Sessions", () => { const aliceStaticKey = dhKey.generateKeyPair(); const aliceEphemeralKey = dhKey.generateKeyPair(); const s = randomBytes(32, rng); - const aliceCommittedStaticKey = commitPublicKey(aliceStaticKey.publicKey, s); + const aliceCommittedStaticKey = commitPublicKey(hash, aliceStaticKey.publicKey, s); // Bob static/ephemeral key initialization and commitment const bobStaticKey = dhKey.generateKeyPair(); const bobEphemeralKey = dhKey.generateKeyPair(); const r = randomBytes(32, rng); - const bobCommittedStaticKey = commitPublicKey(bobStaticKey.publicKey, r); + const bobCommittedStaticKey = commitPublicKey(hash, bobStaticKey.publicKey, r); // Content topic information const applicationName = "waku-noise-sessions"; @@ -175,7 +177,7 @@ describe("Waku Noise Sessions", () => { expect(uint8ArrayEquals(aliceStep.transportMessage, sentTransportMessage)); // Alice further checks if Bob's commitment opens to Bob's static key she just received - const expectedBobCommittedStaticKey = commitPublicKey(aliceHS.hs.rs!, aliceStep.transportMessage); + const expectedBobCommittedStaticKey = commitPublicKey(hash, aliceHS.hs.rs!, aliceStep.transportMessage); expect(uint8ArrayEquals(expectedBobCommittedStaticKey, bobCommittedStaticKey)).to.be.true; @@ -212,7 +214,7 @@ describe("Waku Noise Sessions", () => { expect(uint8ArrayEquals(bobStep.transportMessage, sentTransportMessage)); // Bob further checks if Alice's commitment opens to Alice's static key he just received - const expectedAliceCommittedStaticKey = commitPublicKey(bobHS.hs.rs!, bobStep.transportMessage); + const expectedAliceCommittedStaticKey = commitPublicKey(hash, bobHS.hs.rs!, bobStep.transportMessage); expect(uint8ArrayEquals(expectedAliceCommittedStaticKey, aliceCommittedStaticKey)).to.be.true;