Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/common/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ export {
loadSubtleCrypto,
xor,
} from "./src/utils/misc.ts";

export { hmac } from "./src/hash/hmac.ts";
export { sha256, sha384, sha512 } from "./src/hash/sha2.ts";
110 changes: 110 additions & 0 deletions packages/common/src/hash/hmac.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// deno-lint-ignore-file no-explicit-any
/**
* This file is based on noble-hashes (https://github.com/paulmillr/noble-hashes).
*
* noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com)
*
* The original file is located at:
* https://github.com/paulmillr/noble-hashes/blob/2e0c00e1aa134082ba1380bf3afb8b1641f60fed/src/hmac.ts
*/
/**
* HMAC: RFC2104 message authentication code.
* @module
*/
import {
abytes,
aexists,
ahash,
type CHash,
clean,
type Hash,
} from "../utils/noble.ts";

export class _HMAC<T extends Hash<T>> implements Hash<_HMAC<T>> {
oHash: T;
iHash: T;
blockLen: number;
outputLen: number;
private finished = false;
private destroyed = false;

constructor(hash: CHash, key: Uint8Array) {
ahash(hash);
abytes(key, undefined, "key");
this.iHash = hash.create() as T;
if (typeof this.iHash.update !== "function") {
throw new Error("Expected instance of class which extends utils.Hash");
}
this.blockLen = this.iHash.blockLen;
this.outputLen = this.iHash.outputLen;
const blockLen = this.blockLen;
const pad = new Uint8Array(blockLen);
// blockLen can be bigger than outputLen
pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
for (let i = 0; i < pad.length; i++) pad[i] ^= 0x36;
this.iHash.update(pad);
// By doing update (processing of first block) of outer hash here we can re-use it between multiple calls via clone
this.oHash = hash.create() as T;
// Undo internal XOR && apply outer XOR
for (let i = 0; i < pad.length; i++) pad[i] ^= 0x36 ^ 0x5c;
this.oHash.update(pad);
clean(pad);
}
update(buf: Uint8Array): this {
aexists(this);
this.iHash.update(buf);
return this;
}
digestInto(out: Uint8Array): void {
aexists(this);
abytes(out, this.outputLen, "output");
this.finished = true;
this.iHash.digestInto(out);
this.oHash.update(out);
this.oHash.digestInto(out);
this.destroy();
}
digest(): Uint8Array {
const out = new Uint8Array(this.oHash.outputLen);
this.digestInto(out);
return out;
}
_cloneInto(to?: _HMAC<T>): _HMAC<T> {
// Create new instance without calling constructor since key already in state and we don't know it.
to ||= Object.create(Object.getPrototypeOf(this), {});
const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
to = to as this;
to.finished = finished;
to.destroyed = destroyed;
to.blockLen = blockLen;
to.outputLen = outputLen;
to.oHash = oHash._cloneInto(to.oHash);
to.iHash = iHash._cloneInto(to.iHash);
return to;
}
clone(): _HMAC<T> {
return this._cloneInto();
}
destroy(): void {
this.destroyed = true;
this.oHash.destroy();
this.iHash.destroy();
}
}

/**
* HMAC: RFC2104 message authentication code.
* @param hash - function that would be used e.g. sha256
* @param key - message key
* @param message - message data
* @example
* import { hmac } from '@noble/hashes/hmac';
* import { sha256 } from '@noble/hashes/sha2';
* const mac1 = hmac(sha256, 'key', 'message');
*/
export const hmac: {
(hash: CHash, key: Uint8Array, message: Uint8Array): Uint8Array;
create(hash: CHash, key: Uint8Array): _HMAC<any>;
} = (hash: CHash, key: Uint8Array, message: Uint8Array): Uint8Array =>
new _HMAC<any>(hash, key).update(message).digest();
hmac.create = (hash: CHash, key: Uint8Array) => new _HMAC<any>(hash, key);
211 changes: 211 additions & 0 deletions packages/common/src/hash/md.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// deno-lint-ignore-file no-explicit-any
/**
* This file is based on noble-hashes (https://github.com/paulmillr/noble-hashes).
*
* noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com)
*
* The original file is located at:
* https://github.com/paulmillr/noble-hashes/blob/2e0c00e1aa134082ba1380bf3afb8b1641f60fed/src/_md.ts
*/
/**
* Internal Merkle-Damgard hash utils.
* @module
*/
import {
abytes,
aexists,
aoutput,
clean,
createView,
type Hash,
} from "../utils/noble.ts";

/** Choice: a ? b : c */
export function Chi(a: number, b: number, c: number): number {
return (a & b) ^ (~a & c);
}

/** Majority function, true if any two inputs is true. */
export function Maj(a: number, b: number, c: number): number {
return (a & b) ^ (a & c) ^ (b & c);
}

/**
* Merkle-Damgard hash construction base class.
* Could be used to create MD5, RIPEMD, SHA1, SHA2.
*/
export abstract class HashMD<T extends HashMD<T>> implements Hash<T> {
protected abstract process(buf: DataView, offset: number): void;
protected abstract get(): number[];
protected abstract set(...args: number[]): void;
abstract destroy(): void;
protected abstract roundClean(): void;

readonly blockLen: number;
readonly outputLen: number;
readonly padOffset: number;
readonly isLE: boolean;

// For partial updates less than block size
protected buffer: Uint8Array;
protected view: DataView;
protected finished = false;
protected length = 0;
protected pos = 0;
protected destroyed = false;

constructor(
blockLen: number,
outputLen: number,
padOffset: number,
isLE: boolean,
) {
this.blockLen = blockLen;
this.outputLen = outputLen;
this.padOffset = padOffset;
this.isLE = isLE;
this.buffer = new Uint8Array(blockLen);
this.view = createView(this.buffer);
}
update(data: Uint8Array): this {
aexists(this);
abytes(data);
const { view, buffer, blockLen } = this;
const len = data.length;
for (let pos = 0; pos < len;) {
const take = Math.min(blockLen - this.pos, len - pos);
// Fast path: we have at least one block in input, cast it to view and process
if (take === blockLen) {
const dataView = createView(data);
for (; blockLen <= len - pos; pos += blockLen) {
this.process(dataView, pos);
}
continue;
}
buffer.set(data.subarray(pos, pos + take), this.pos);
this.pos += take;
pos += take;
if (this.pos === blockLen) {
this.process(view, 0);
this.pos = 0;
}
}
this.length += data.length;
this.roundClean();
return this;
}
digestInto(out: Uint8Array): void {
aexists(this);
aoutput(out, this);
this.finished = true;
// Padding
// We can avoid allocation of buffer for padding completely if it
// was previously not allocated here. But it won't change performance.
const { buffer, view, blockLen, isLE } = this;
let { pos } = this;
// append the bit '1' to the message
buffer[pos++] = 0b10000000;
clean(this.buffer.subarray(pos));
// we have less than padOffset left in buffer, so we cannot put length in
// current block, need process it and pad again
if (this.padOffset > blockLen - pos) {
this.process(view, 0);
pos = 0;
}
// Pad until full block byte with zeros
for (let i = pos; i < blockLen; i++) buffer[i] = 0;
// Note: sha512 requires length to be 128bit integer, but length in JS will overflow before that
// You need to write around 2 exabytes (u64_max / 8 / (1024**6)) for this to happen.
// So we just write lowest 64 bits of that value.
view.setBigUint64(blockLen - 8, BigInt(this.length * 8), isLE);
this.process(view, 0);
const oview = createView(out);
const len = this.outputLen;
// NOTE: we do division by 4 later, which must be fused in single op with modulo by JIT
if (len % 4) throw new Error("_sha2: outputLen must be aligned to 32bit");
const outLen = len / 4;
const state = this.get();
if (outLen > state.length) {
throw new Error("_sha2: outputLen bigger than state");
}
for (let i = 0; i < outLen; i++) oview.setUint32(4 * i, state[i], isLE);
}
digest(): Uint8Array {
const { buffer, outputLen } = this;
this.digestInto(buffer);
const res = buffer.slice(0, outputLen);
this.destroy();
return res;
}
_cloneInto(to?: T): T {
to ||= new (this.constructor as any)() as T;
to.set(...this.get());
const { blockLen, buffer, length, finished, destroyed, pos } = this;
to.destroyed = destroyed;
to.finished = finished;
to.length = length;
to.pos = pos;
if (length % blockLen) to.buffer.set(buffer);
return to as unknown as any;
}
clone(): T {
return this._cloneInto();
}
}

/**
* Initial SHA-2 state: fractional parts of square roots of first 16 primes 2..53.
* Check out `test/misc/sha2-gen-iv.js` for recomputation guide.
*/

/** Initial SHA256 state. Bits 0..32 of frac part of sqrt of primes 2..19 */
export const SHA256_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([
0x6a09e667,
0xbb67ae85,
0x3c6ef372,
0xa54ff53a,
0x510e527f,
0x9b05688c,
0x1f83d9ab,
0x5be0cd19,
]);

/** Initial SHA384 state. Bits 0..64 of frac part of sqrt of primes 23..53 */
export const SHA384_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([
0xcbbb9d5d,
0xc1059ed8,
0x629a292a,
0x367cd507,
0x9159015a,
0x3070dd17,
0x152fecd8,
0xf70e5939,
0x67332667,
0xffc00b31,
0x8eb44a87,
0x68581511,
0xdb0c2e0d,
0x64f98fa7,
0x47b5481d,
0xbefa4fa4,
]);

/** Initial SHA512 state. Bits 0..64 of frac part of sqrt of primes 2..19 */
export const SHA512_IV: Uint32Array = /* @__PURE__ */ Uint32Array.from([
0x6a09e667,
0xf3bcc908,
0xbb67ae85,
0x84caa73b,
0x3c6ef372,
0xfe94f82b,
0xa54ff53a,
0x5f1d36f1,
0x510e527f,
0xade682d1,
0x9b05688c,
0x2b3e6c1f,
0x1f83d9ab,
0xfb41bd6b,
0x5be0cd19,
0x137e2179,
]);
Loading
Loading