/
mod.ts
182 lines (149 loc) · 5.26 KB
/
mod.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import {
encode,
decode
} from "./deps.ts";
import { Wasm, loadWasm } from "./loadWasm.ts";
function assert(sth: any): void {
if (!sth) {
throw new Error("sth is falsy")
}
}
export const BYTES_MIN: number = 1;
export const BYTES_MAX: number = 64;
export const INPUTBYTES_MIN: number = 0;
export const INPUTBYTES_MAX: bigint = 2n ** 128n - 1n;
export const KEYBYTES_MIN: number = 0;
export const KEYBYTES_MAX: number = 64;
export const SALTBYTES: number = 16;
export const PERSONALBYTES: number = 16;
/**
* @classdesc Class Blake2b implements BLAKE2b as specified in RFC 7693
* https://tools.ietf.org/html/rfc7693 and
* "BLAKE2: simpler, smaller, fast as MD5" https://blake2.net/blake2.pdf (the
* salting and personalization features are specified in the latter only).
* All BLAKE2b computations are carried out in WebAssembly.
*/
export class Blake2b {
protected static wasm: Wasm = loadWasm();
protected static freeList: number[] = [];
protected static head: number = 64;
public static readonly WASM: Uint8Array = Blake2b.wasm.buffer;
private finalized: boolean;
private pointer: number;
public readonly bytes: number;
/**
* Creates a new Blake2b instance computing the BLAKE2b checksum with a custom
* length. Providing a key turns the hash into a MAC. The key must be between
* zero and 64 bytes long. The digest size can be a value between 1 and 64 but
* it is highly recommended to use values equal or greater than:
* - 32 if BLAKE2b is used as a hash function (key is zero bytes long).
* - 16 if BLAKE2b is used as a MAC function (key is at least 16 bytes long).
* @constructor
* @param {number} bytes - Digest length. Must be inbetween
* Blake2b.BYTES_MIN and Blake2b.BYTES_MAX.
* @param {Uint8Array} [key] - Key length must be inbetween
* Blake2b.KEYBYTES_MIN and Blake2b.KEYBYTES_MAX
* @param {Uint8Array} [salt] - Must be Blake2b.SALTBYTES long.
* @param {Uint8Array} [personal] - Must be Blake2b.PERSONALBYTES long.
*/
public constructor(
bytes: number,
key?: string | Uint8Array,
salt?: string | Uint8Array,
personal?: string | Uint8Array,
inputEncoding?: string
) {
assert(bytes >= BYTES_MIN);
assert(bytes <= BYTES_MAX);
if (key) {
if (typeof key === "string") {
key = encode(key, inputEncoding) as Uint8Array;
}
assert(key.byteLength <= KEYBYTES_MAX);
}
if (salt) {
if (typeof salt === "string") {
salt = encode(salt, inputEncoding) as Uint8Array;
}
assert(salt.byteLength === SALTBYTES);
}
if (personal) {
if (typeof personal === "string") {
personal = encode(personal, inputEncoding) as Uint8Array;
}
assert(personal.byteLength === PERSONALBYTES);
}
if (!Blake2b.freeList.length) {
Blake2b.freeList.push(Blake2b.head);
Blake2b.head += 216;
}
this.bytes = bytes;
this.finalized = false;
this.pointer = Blake2b.freeList.pop() ?? 0;
Blake2b.wasm.memory.fill(0, 0, 64);
Blake2b.wasm.memory[0] = this.bytes;
Blake2b.wasm.memory[1] = key ? key.length : 0;
Blake2b.wasm.memory[2] = 1; // fanout
Blake2b.wasm.memory[3] = 1; // depth
if (salt) {
Blake2b.wasm.memory.set(salt as Uint8Array, 32);
}
if (personal) {
Blake2b.wasm.memory.set(personal as Uint8Array, 48);
}
if (this.pointer + 216 > Blake2b.wasm.memory.byteLength) {
Blake2b.wasm.realloc(this.pointer + 216); // we need 216 state bytes
}
Blake2b.wasm.exports.blake2b_init(this.pointer, this.bytes);
if (key) {
this.update(key);
Blake2b.wasm.memory.fill(0, Blake2b.head, Blake2b.head + key.length);
Blake2b.wasm.memory[this.pointer + 200] = 128;
}
}
/** Updates the hash with additional data. */
update(input: string | Uint8Array, inputEncoding?: string): Blake2b {
if (typeof input === "string") {
input = encode(input, inputEncoding) as Uint8Array;
}
if (Blake2b.head + input.byteLength > Blake2b.wasm.memory.byteLength) {
Blake2b.wasm.realloc(Blake2b.head + input.byteLength);
}
Blake2b.wasm.memory.set(input, Blake2b.head);
Blake2b.wasm.exports.blake2b_update(
this.pointer,
Blake2b.head,
Blake2b.head + input.byteLength
);
return this;
}
/** Obtains a digest of all fed-in data. */
digest(outputEncoding?: string): string | Uint8Array {
assert(!this.finalized);
const out: Uint8Array = new Uint8Array(this.bytes);
Blake2b.freeList.push(this.pointer);
Blake2b.wasm.exports.blake2b_final(this.pointer);
this.finalized = true;
for (let i: number = 0; i < this.bytes; i++) {
out[i] = Blake2b.wasm.memory[this.pointer + 128 + i];
}
return outputEncoding ? decode(out, outputEncoding) : out;
}
}
/**
* Convenience function for hashing of singular data. If any multiple of msg,
* key, salt or personal are passed as strings they must have the same encoding.
*/
export function blake2b(
msg: string | Uint8Array,
inputEncoding?: string,
outputEncoding?: string,
bytes: number = BYTES_MAX,
key?: string | Uint8Array,
salt?: string | Uint8Array,
personal?: string | Uint8Array
): string | Uint8Array {
return new Blake2b(bytes, key, salt, personal)
.update(msg, inputEncoding)
.digest(outputEncoding);
}