src/main/generic/consensus/base/primitive/Hash.js
class Hash extends Serializable {
/**
* @param {?Uint8Array} arg
* @param {Hash.Algorithm} [algorithm]
* @private
*/
constructor(arg, algorithm = Hash.Algorithm.BLAKE2B) {
if (arg === null) {
arg = new Uint8Array(Hash.getSize(algorithm));
} else {
if (!(arg instanceof Uint8Array)) throw new Error('Primitive: Invalid type');
if (arg.length !== Hash.getSize(algorithm)) throw new Error('Primitive: Invalid length');
}
super();
this._obj = arg;
/** @type {Hash.Algorithm} */
this._algorithm = algorithm;
}
/**
* @deprecated
* @param {Uint8Array} arr
* @returns {Hash}
*/
static light(arr) {
return Hash.blake2b(arr);
}
/**
* @param {Uint8Array} arr
* @returns {Hash}
*/
static blake2b(arr) {
return new Hash(Hash.computeBlake2b(arr), Hash.Algorithm.BLAKE2B);
}
/**
* @param {Uint8Array} arr
* @deprecated
* @returns {Promise.<Hash>}
*/
static hard(arr) {
return Hash.argon2d(arr);
}
/**
* @param {Uint8Array} arr
* @returns {Promise.<Hash>}
*/
static async argon2d(arr) {
return new Hash(await (await CryptoWorker.getInstanceAsync()).computeArgon2d(arr), Hash.Algorithm.ARGON2D);
}
/**
* @param {Uint8Array} arr
* @returns {Hash}
*/
static sha256(arr) {
return new Hash(Hash.computeSha256(arr), Hash.Algorithm.SHA256);
}
/**
* @param {Uint8Array} arr
* @returns {Hash}
*/
static sha512(arr) {
return new Hash(Hash.computeSha512(arr), Hash.Algorithm.SHA512);
}
/**
* @param {Uint8Array} arr
* @param {Hash.Algorithm} algorithm
* @returns {Hash}
*/
static compute(arr, algorithm) {
// !! The algorithms supported by this function are the allowed hash algorithms for HTLCs !!
switch (algorithm) {
case Hash.Algorithm.BLAKE2B: return Hash.blake2b(arr);
case Hash.Algorithm.SHA256: return Hash.sha256(arr);
// Hash.Algorithm.SHA512 postponed until hard-fork
// Hash.Algorithm.ARGON2 intentionally omitted
default: throw new Error('Invalid hash algorithm');
}
}
/**
* @param {SerialBuffer} buf
* @param {Hash.Algorithm} [algorithm]
* @returns {Hash}
*/
static unserialize(buf, algorithm = Hash.Algorithm.BLAKE2B) {
return new Hash(buf.read(Hash.getSize(algorithm)), algorithm);
}
/**
* @param {SerialBuffer} [buf]
* @returns {SerialBuffer}
*/
serialize(buf) {
buf = buf || new SerialBuffer(this.serializedSize);
buf.write(this._obj);
return buf;
}
/**
* @param {number} begin
* @param {number} end
* @returns {Uint8Array}
*/
subarray(begin, end) {
return this._obj.subarray(begin, end);
}
/** @type {number} */
get serializedSize() {
return Hash.SIZE.get(this._algorithm);
}
/** @type {Uint8Array} */
get array() {
return this._obj;
}
/** @type {Hash.Algorithm} */
get algorithm() {
return this._algorithm;
}
/**
* @param {Serializable} o
* @returns {boolean}
*/
equals(o) {
return o instanceof Hash && o._algorithm === this._algorithm && super.equals(o);
}
/**
* @param {Hash|Uint8Array|string} hash
* @param {Hash.Algorithm} algorithm
* @return {Hash}
*/
static fromAny(hash, algorithm = Hash.Algorithm.BLAKE2B) {
if (hash instanceof Hash) return hash;
try {
return new Hash(BufferUtils.fromAny(hash, Hash.SIZE.get(algorithm)), algorithm);
} catch (e) {
throw new Error('Invalid hash format');
}
}
/**
* @returns {string}
*/
toPlain() {
return this.toHex();
}
/**
* @param {string} base64
* @returns {Hash}
*/
static fromBase64(base64) {
return new Hash(BufferUtils.fromBase64(base64));
}
/**
* @param {string} hex
* @returns {Hash}
*/
static fromHex(hex) {
return new Hash(BufferUtils.fromHex(hex));
}
/**
* @param {string} str
* @returns {Hash}
*/
static fromPlain(str) {
return Hash.fromString(str);
}
/**
* @param {string} str
* @returns {Hash}
*/
static fromString(str) {
try {
return Hash.fromHex(str);
} catch (e) {
// Ignore
}
try {
return Hash.fromBase64(str);
} catch (e) {
// Ignore
}
throw new Error('Invalid hash format');
}
/**
* @param {Hash} o
* @returns {boolean}
*/
static isHash(o) {
return o instanceof Hash;
}
/**
* @param {Hash.Algorithm} algorithm
* @returns {number}
*/
static getSize(algorithm) {
const size = Hash.SIZE.get(algorithm);
if (typeof size !== 'number') throw new Error('Invalid hash algorithm');
return size;
}
/**
* @param {Uint8Array} input
* @returns {Uint8Array}
*/
static computeBlake2b(input) {
if (PlatformUtils.isNodeJs()) {
const out = new Uint8Array(Hash.getSize(Hash.Algorithm.BLAKE2B));
NodeNative.node_blake2(out, new Uint8Array(input));
return out;
} else {
let stackPtr;
try {
stackPtr = Module.stackSave();
const hashSize = Hash.getSize(Hash.Algorithm.BLAKE2B);
const wasmOut = Module.stackAlloc(hashSize);
const wasmIn = Module.stackAlloc(input.length);
new Uint8Array(Module.HEAPU8.buffer, wasmIn, input.length).set(input);
const res = Module._nimiq_blake2(wasmOut, wasmIn, input.length);
if (res !== 0) {
throw res;
}
const hash = new Uint8Array(hashSize);
hash.set(new Uint8Array(Module.HEAPU8.buffer, wasmOut, hashSize));
return hash;
} catch (e) {
Log.w(Hash, e);
throw e;
} finally {
if (stackPtr !== undefined) Module.stackRestore(stackPtr);
}
}
}
/**
* @param {Uint8Array} input
* @returns {Uint8Array}
*/
static computeSha256(input) {
if (PlatformUtils.isNodeJs()) {
const out = new Uint8Array(Hash.getSize(Hash.Algorithm.SHA256));
NodeNative.node_sha256(out, new Uint8Array(input));
return out;
} else {
let stackPtr;
try {
stackPtr = Module.stackSave();
const hashSize = Hash.getSize(Hash.Algorithm.SHA256);
const wasmOut = Module.stackAlloc(hashSize);
const wasmIn = Module.stackAlloc(input.length);
new Uint8Array(Module.HEAPU8.buffer, wasmIn, input.length).set(input);
Module._nimiq_sha256(wasmOut, wasmIn, input.length);
const hash = new Uint8Array(hashSize);
hash.set(new Uint8Array(Module.HEAPU8.buffer, wasmOut, hashSize));
return hash;
} catch (e) {
Log.w(Hash, e);
throw e;
} finally {
if (stackPtr !== undefined) Module.stackRestore(stackPtr);
}
}
}
/**
* @param {Uint8Array} input
* @returns {Uint8Array}
*/
static computeSha512(input) {
if (PlatformUtils.isNodeJs()) {
const out = new Uint8Array(Hash.getSize(Hash.Algorithm.SHA512));
NodeNative.node_sha512(out, new Uint8Array(input));
return out;
} else {
let stackPtr;
try {
stackPtr = Module.stackSave();
const hashSize = Hash.getSize(Hash.Algorithm.SHA512);
const wasmOut = Module.stackAlloc(hashSize);
const wasmIn = Module.stackAlloc(input.length);
new Uint8Array(Module.HEAPU8.buffer, wasmIn, input.length).set(input);
Module._nimiq_sha512(wasmOut, wasmIn, input.length);
const hash = new Uint8Array(hashSize);
hash.set(new Uint8Array(Module.HEAPU8.buffer, wasmOut, hashSize));
return hash;
} catch (e) {
Log.w(Hash, e);
throw e;
} finally {
if (stackPtr !== undefined) Module.stackRestore(stackPtr);
}
}
}
}
/**
* @enum {number}
*/
Hash.Algorithm = {
BLAKE2B: 1,
ARGON2D: 2,
SHA256: 3,
SHA512: 4
};
/**
* @param {Hash.Algorithm} hashAlgorithm
* @return {string}
*/
Hash.Algorithm.toString = function(hashAlgorithm) {
switch (hashAlgorithm) {
case Hash.Algorithm.BLAKE2B: return 'blake2b';
case Hash.Algorithm.ARGON2D: return 'argon2d';
case Hash.Algorithm.SHA256: return 'sha256';
case Hash.Algorithm.SHA512: return 'sha512';
}
throw new Error('Invalid hash algorithm');
};
/**
* @type {Map<Hash.Algorithm, number>}
*/
Hash.SIZE = new Map();
Hash.SIZE.set(Hash.Algorithm.BLAKE2B, 32);
Hash.SIZE.set(Hash.Algorithm.ARGON2D, 32);
Hash.SIZE.set(Hash.Algorithm.SHA256, 32);
Hash.SIZE.set(Hash.Algorithm.SHA512, 64);
Hash.NULL = new Hash(new Uint8Array(32));
Class.register(Hash);