refactor: commitPublicKey receives a hashing function

This commit is contained in:
Richard Ramos 2023-11-20 16:40:40 -04:00 committed by richΛrd
parent 6baa061f0e
commit 3cd683c2f1
5 changed files with 59 additions and 31 deletions

View File

@ -2,23 +2,13 @@ import { ChaCha20Poly1305 } from "@stablelib/chacha20poly1305";
import { Hash } from "@stablelib/hash"; import { Hash } from "@stablelib/hash";
import { HKDF as hkdf } from "@stablelib/hkdf"; import { HKDF as hkdf } from "@stablelib/hkdf";
import { RandomSource } from "@stablelib/random"; import { RandomSource } from "@stablelib/random";
import { hash } from "@stablelib/sha256";
import { concat as uint8ArrayConcat } from "uint8arrays/concat"; import { concat as uint8ArrayConcat } from "uint8arrays/concat";
import type { bytes32 } from "./@types/basic.js"; import type { bytes32 } from "./@types/basic.js";
import type { KeyPair } from "./@types/keypair.js"; import type { KeyPair } from "./@types/keypair.js";
/** /**
* Generate hash using SHA2-256 * HKDF key derivation function
* @param data data to hash
* @returns hash digest
*/
export function hashSHA256(data: Uint8Array): Uint8Array {
return hash(data);
}
/**
* HKDF key derivation function using SHA256
* @param ck chaining key * @param ck chaining key
* @param ikm input key material * @param ikm input key material
* @param length length of each generated key * @param length length of each generated key
@ -79,14 +69,24 @@ export function chaCha20Poly1305Decrypt(
return ctx.open(nonce, ciphertext, ad); 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) * 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 publicKey x25519 public key
* @param r random fixed-length value * @param r random fixed-length value
* @returns 32 byte hash * @returns 32 byte hash
*/ */
export function commitPublicKey(publicKey: bytes32, r: Uint8Array): bytes32 { export function commitPublicKey(h: new () => Hash, publicKey: bytes32, r: Uint8Array): bytes32 {
return hashSHA256(uint8ArrayConcat([publicKey, r])); const data = uint8ArrayConcat([publicKey, r]);
return hash(h, data);
} }
/** /**

View File

@ -1,8 +1,8 @@
import { hash } from "@stablelib/sha256";
import { concat as uint8ArrayConcat } from "uint8arrays/concat"; import { concat as uint8ArrayConcat } from "uint8arrays/concat";
import { equals as uint8ArrayEquals } from "uint8arrays/equals"; import { equals as uint8ArrayEquals } from "uint8arrays/equals";
import { MessageNametag } from "./@types/handshake.js"; import { MessageNametag } from "./@types/handshake.js";
import { hashSHA256 } from "./crypto.js";
import { writeUIntLE } from "./utils.js"; import { writeUIntLE } from "./utils.js";
export const MessageNametagLength = 16; export const MessageNametagLength = 16;
@ -40,6 +40,7 @@ export class MessageNametagBuffer {
if (this.secret) { if (this.secret) {
for (let i = 0; i < this.buffer.length; i++) { for (let i = 0; i < this.buffer.length; i++) {
const counterBytesLE = writeUIntLE(new Uint8Array(8), this.counter, 0, 8); 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])); const d = hashSHA256(uint8ArrayConcat([this.secret, counterBytesLE]));
this.buffer[i] = toMessageNametag(d); this.buffer[i] = toMessageNametag(d);
this.counter++; 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);
}

View File

@ -1,10 +1,11 @@
import { Hash } from "@stablelib/hash";
import debug from "debug"; import debug from "debug";
import { fromString as uint8ArrayFromString } from "uint8arrays"; import { fromString as uint8ArrayFromString } from "uint8arrays";
import { concat as uint8ArrayConcat } from "uint8arrays/concat"; import { concat as uint8ArrayConcat } from "uint8arrays/concat";
import { equals as uint8ArrayEquals } from "uint8arrays/equals"; import { equals as uint8ArrayEquals } from "uint8arrays/equals";
import type { bytes32 } from "./@types/basic.js"; 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 { Nonce } from "./nonce.js";
import { HandshakePattern } from "./patterns.js"; import { HandshakePattern } from "./patterns.js";
@ -192,7 +193,7 @@ export class CipherState {
* @param name name of the noise handshake pattern to hash * @param name name of the noise handshake pattern to hash
* @returns sha256 digest of the protocol name * @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, // 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. // sets h equal to protocol_name with zero bytes appended to make HASHLEN bytes.
// Otherwise sets h = HASH(protocol_name). // Otherwise sets h = HASH(protocol_name).
@ -203,7 +204,7 @@ function hashProtocol(name: string): Uint8Array {
h.set(protocolName); h.set(protocolName);
return h; return h;
} else { } else {
return hashSHA256(protocolName); return hash(h, protocolName);
} }
} }
@ -217,7 +218,7 @@ export class SymmetricState {
private ck: bytes32; // chaining key private ck: bytes32; // chaining key
constructor(private readonly handshakePattern: HandshakePattern) { constructor(private readonly handshakePattern: HandshakePattern) {
this.h = hashProtocol(handshakePattern.name); this.h = hashProtocol(handshakePattern.hash, handshakePattern.name);
this.ck = this.h; this.ck = this.h;
this.cs = new CipherState(); this.cs = new CipherState();
} }
@ -269,7 +270,7 @@ export class SymmetricState {
*/ */
mixHash(data: Uint8Array): void { 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 // 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); log("mixHash", this.h);
} }

View File

@ -15,10 +15,9 @@ import {
NoiseSecureTransferEncoder, NoiseSecureTransferEncoder,
} from "./codec.js"; } from "./codec.js";
import { commitPublicKey } from "./crypto.js"; import { commitPublicKey } from "./crypto.js";
import { DH25519 } from "./dh25519.js";
import { Handshake, HandshakeResult, HandshakeStepResult, MessageNametagError } from "./handshake.js"; import { Handshake, HandshakeResult, HandshakeStepResult, MessageNametagError } from "./handshake.js";
import { MessageNametagLength } from "./messagenametag.js"; import { MessageNametagLength } from "./messagenametag.js";
import { NoiseHandshakePatterns } from "./patterns.js"; import { HandshakePattern, NoiseHandshakePatterns } from "./patterns.js";
import { NoisePublicKey } from "./publickey.js"; import { NoisePublicKey } from "./publickey.js";
import { QR } from "./qr.js"; import { QR } from "./qr.js";
@ -58,6 +57,7 @@ export class ResponderParameters {
*/ */
export class WakuPairing { export class WakuPairing {
public readonly contentTopic: string; public readonly contentTopic: string;
private readonly hsPattern: HandshakePattern;
private initiator: boolean; private initiator: boolean;
private randomFixLenVal: Uint8Array; // r or s depending on who is sending the message private randomFixLenVal: Uint8Array; // r or s depending on who is sending the message
@ -95,11 +95,18 @@ export class WakuPairing {
private responder: IReceiver, private responder: IReceiver,
private myStaticKey: KeyPair, private myStaticKey: KeyPair,
pairingParameters: InitiatorParameters | ResponderParameters, pairingParameters: InitiatorParameters | ResponderParameters,
private myEphemeralKey: KeyPair = new DH25519().generateKeyPair(), private myEphemeralKey?: KeyPair,
private readonly encoderParameters: EncoderParameters = {} private readonly encoderParameters: EncoderParameters = {}
) { ) {
this.hsPattern = NoiseHandshakePatterns.Noise_WakuPairing_25519_ChaChaPoly_SHA256;
this.randomFixLenVal = randomBytes(32, rng); 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) { if (pairingParameters instanceof InitiatorParameters) {
this.initiator = true; this.initiator = true;
@ -125,8 +132,8 @@ export class WakuPairing {
const preMessagePKs = [NoisePublicKey.fromPublicKey(this.qr.ephemeralKey)]; const preMessagePKs = [NoisePublicKey.fromPublicKey(this.qr.ephemeralKey)];
this.handshake = new Handshake({ this.handshake = new Handshake({
hsPattern: NoiseHandshakePatterns.Noise_WakuPairing_25519_ChaChaPoly_SHA256, hsPattern: this.hsPattern,
ephemeralKey: myEphemeralKey, ephemeralKey: this.myEphemeralKey,
staticKey: myStaticKey, staticKey: myStaticKey,
prologue: this.qr.toByteArray(), prologue: this.qr.toByteArray(),
preMessagePKs, preMessagePKs,
@ -260,7 +267,11 @@ export class WakuPairing {
if (!this.handshake.hs.rs) throw new Error("invalid handshake state"); 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 // 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)) { if (!uint8ArrayEquals(expectedResponderCommittedStaticKey, this.qr.committedStaticKey)) {
throw new Error("expected committed static key does not match the responder actual committed static key"); 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"); 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 // 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)) { if (!uint8ArrayEquals(expectedInitiatorCommittedStaticKey, initiatorCommittedStaticKey)) {
throw new Error("expected committed static key does not match the initiator actual committed static key"); throw new Error("expected committed static key does not match the initiator actual committed static key");
} }

View File

@ -1,5 +1,6 @@
import { HMACDRBG } from "@stablelib/hmac-drbg"; import { HMACDRBG } from "@stablelib/hmac-drbg";
import { randomBytes } from "@stablelib/random"; import { randomBytes } from "@stablelib/random";
import { SHA256 } from "@stablelib/sha256";
import { expect } from "chai"; import { expect } from "chai";
import { equals as uint8ArrayEquals } from "uint8arrays/equals"; import { equals as uint8ArrayEquals } from "uint8arrays/equals";
@ -29,6 +30,7 @@ describe("Waku Noise Sessions", () => {
// ========== // ==========
const dhKey = new DH25519(); const dhKey = new DH25519();
const hash = SHA256;
const hsPattern = NoiseHandshakePatterns.Noise_WakuPairing_25519_ChaChaPoly_SHA256; const hsPattern = NoiseHandshakePatterns.Noise_WakuPairing_25519_ChaChaPoly_SHA256;
@ -36,13 +38,13 @@ describe("Waku Noise Sessions", () => {
const aliceStaticKey = dhKey.generateKeyPair(); const aliceStaticKey = dhKey.generateKeyPair();
const aliceEphemeralKey = dhKey.generateKeyPair(); const aliceEphemeralKey = dhKey.generateKeyPair();
const s = randomBytes(32, rng); 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 // Bob static/ephemeral key initialization and commitment
const bobStaticKey = dhKey.generateKeyPair(); const bobStaticKey = dhKey.generateKeyPair();
const bobEphemeralKey = dhKey.generateKeyPair(); const bobEphemeralKey = dhKey.generateKeyPair();
const r = randomBytes(32, rng); const r = randomBytes(32, rng);
const bobCommittedStaticKey = commitPublicKey(bobStaticKey.publicKey, r); const bobCommittedStaticKey = commitPublicKey(hash, bobStaticKey.publicKey, r);
// Content topic information // Content topic information
const applicationName = "waku-noise-sessions"; const applicationName = "waku-noise-sessions";
@ -175,7 +177,7 @@ describe("Waku Noise Sessions", () => {
expect(uint8ArrayEquals(aliceStep.transportMessage, sentTransportMessage)); expect(uint8ArrayEquals(aliceStep.transportMessage, sentTransportMessage));
// Alice further checks if Bob's commitment opens to Bob's static key she just received // 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; expect(uint8ArrayEquals(expectedBobCommittedStaticKey, bobCommittedStaticKey)).to.be.true;
@ -212,7 +214,7 @@ describe("Waku Noise Sessions", () => {
expect(uint8ArrayEquals(bobStep.transportMessage, sentTransportMessage)); expect(uint8ArrayEquals(bobStep.transportMessage, sentTransportMessage));
// Bob further checks if Alice's commitment opens to Alice's static key he just received // 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; expect(uint8ArrayEquals(expectedAliceCommittedStaticKey, aliceCommittedStaticKey)).to.be.true;