trustmark.js / dist /index.cjs
pdbq's picture
Force square for model P
ca020a1 verified
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
TrustMark: () => TrustMark
});
module.exports = __toCommonJS(src_exports);
// src/trustmark.ts
var import_node_fs = require("fs");
var import_node_crypto = require("crypto");
var ort = __toESM(require("onnxruntime-node"), 1);
var tf = __toESM(require("@tensorflow/tfjs-node"), 1);
// src/bchecc.ts
var BCH = class {
ECCstate;
/**
* Initializes the ECC state with given parameters.
* @param {number} t - Number of error correctable bits, max number of bit flips we can account for, increasing this increase the ecc length
* @param {number} poly - The polynomial used for ECC.
*/
constructor(t, poly) {
let tmp = poly;
let m = 0;
while (tmp >> 1) {
tmp = tmp >> 1;
m += 1;
}
this.ECCstate = {
m,
t,
poly
};
this.ECCstate.n = Math.pow(2, m) - 1;
const words = Math.ceil(m * t / 32);
this.ECCstate.ecc_bytes = Math.ceil(m * t / 8);
this.ECCstate.cyclic_tab = new Array(words * 1024).fill(BigInt(0));
this.ECCstate.syn = new Array(2 * t).fill(0);
this.ECCstate.elp = new Array(t + 1).fill(0);
this.ECCstate.errloc = new Array(t).fill(0);
let x = 1;
const k = Math.pow(2, this.deg(poly));
if (k !== Math.pow(2, this.ECCstate.m)) {
return;
}
this.ECCstate.exponents = new Array(1 + this.ECCstate.n).fill(0);
this.ECCstate.logarithms = new Array(1 + this.ECCstate.n).fill(0);
this.ECCstate.elp_pre = new Array(1 + this.ECCstate.m).fill(0);
for (let i2 = 0; i2 < this.ECCstate.n; i2++) {
this.ECCstate.exponents[i2] = x;
this.ECCstate.logarithms[x] = i2;
if (i2 && x === 1) {
return;
}
x *= 2;
if (x & k) {
x ^= poly;
}
}
this.ECCstate.logarithms[0] = 0;
this.ECCstate.exponents[this.ECCstate.n] = 1;
let n = 0;
const g = { deg: 0, c: new Array(m * t + 1).fill(BigInt(0)) };
const roots = new Array(this.ECCstate.n + 1).fill(0);
const genpoly = new Array(Math.ceil(m * t + 1 / 32)).fill(BigInt(0));
for (let i2 = 0; i2 < t; i2++) {
let r = 2 * i2 + 1;
for (let j = 0; j < m; j++) {
roots[r] = 1;
r = this.mod(this, 2 * r);
}
}
g.deg = 0;
g.c[0] = BigInt(1);
for (let i2 = 0; i2 < this.ECCstate.n; i2++) {
if (roots[i2]) {
const r = this.ECCstate.exponents[i2];
g.c[g.deg + 1] = BigInt(1);
for (let j = g.deg; j > 0; j--) {
g.c[j] = this.g_mul(this, g.c[j], r) ^ g.c[j - 1];
}
g.c[0] = this.g_mul(this, g.c[0], r);
g.deg += 1;
}
}
n = g.deg + 1;
let i = 0;
while (n > 0) {
const nbits = n > 32 ? 32 : n;
let word = BigInt(0);
for (let j = 0; j < nbits; j++) {
if (g.c[n - 1 - j]) {
word |= BigInt(Math.pow(2, 31 - j));
}
}
genpoly[i] = word;
i += 1;
n -= nbits;
}
this.ECCstate.ecc_bits = g.deg;
this.buildCyclic(genpoly);
let sum = 0;
let aexp = 0;
for (let i2 = 0; i2 < m; i2++) {
for (let j = 0; j < m; j++) {
sum ^= this.g_pow(this, i2 * Math.pow(2, j));
}
if (sum) {
aexp = this.ECCstate.exponents[i2];
break;
}
}
x = 0;
const precomp = new Array(31).fill(0);
let remaining = m;
while (x <= this.ECCstate.n && remaining) {
let y = this.g_sqrt(this, x) ^ x;
for (let i2 = 0; i2 < 2; i2++) {
const r = this.g_log(this, y);
if (y && r < m && !precomp[r]) {
this.ECCstate.elp_pre[r] = x;
precomp[r] = 1;
remaining -= 1;
break;
}
y ^= aexp;
}
x += 1;
}
}
/**
* Encodes the data and generates ECC bytes.
* @param {number[]} data - The input data array.
* @returns {Uint8Array} - The generated ECC bytes.
*/
encode(data) {
let bigIntData = this.convertAllBitsToBigInts(data, 8);
const datalen = bigIntData.length;
const l = this.ceilop(this.ECCstate.m * this.ECCstate.t, 32) - 1;
let ecc = new Array(this.getEccBytes()).fill(0);
const ecc_max_words = this.ceilop(31 * 64, 32);
const r = new Array(ecc_max_words).fill(BigInt(0));
const tab0idx = 0;
const tab1idx = tab0idx + 256 * (l + 1);
const tab2idx = tab1idx + 256 * (l + 1);
const tab3idx = tab2idx + 256 * (l + 1);
let mlen = Math.floor(datalen / 4);
let offset = 0;
while (mlen > 0) {
let w = this.convertBytesToBigInt(bigIntData.slice(offset, offset + 4));
w ^= r[0];
const p0 = tab0idx + (l + 1) * Number(w >> BigInt(0) & BigInt(255));
const p1 = tab1idx + (l + 1) * Number(w >> BigInt(8) & BigInt(255));
const p2 = tab2idx + (l + 1) * Number(w >> BigInt(16) & BigInt(255));
const p3 = tab3idx + (l + 1) * Number(w >> BigInt(24) & BigInt(255));
for (let i = 0; i < l; i++) {
r[i] = r[i + 1] ^ this.ECCstate.cyclic_tab[Number(p0) + i] ^ this.ECCstate.cyclic_tab[Number(p1) + i] ^ this.ECCstate.cyclic_tab[Number(p2) + i] ^ this.ECCstate.cyclic_tab[Number(p3) + i];
}
r[l] = this.ECCstate.cyclic_tab[Number(p0) + l] ^ this.ECCstate.cyclic_tab[Number(p1) + l] ^ this.ECCstate.cyclic_tab[Number(p2) + l] ^ this.ECCstate.cyclic_tab[Number(p3) + l];
mlen--;
offset += 4;
}
bigIntData = bigIntData.slice(offset);
let leftdata = bigIntData.length;
ecc = r;
let posn = 0;
while (leftdata) {
const tmp = bigIntData[posn];
posn++;
let pidx = (l + 1) * Number(ecc[0] >> BigInt(24) ^ tmp & BigInt(255));
for (let i = 0; i < l; i++) {
ecc[i] = (ecc[i] << BigInt(8) & BigInt(4294967295) | ecc[i + 1] >> BigInt(24)) ^ this.ECCstate.cyclic_tab[Number(pidx)];
pidx++;
}
ecc[l] = ecc[l] << BigInt(8) & BigInt(4294967295) ^ this.ECCstate.cyclic_tab[Number(pidx)];
leftdata--;
}
this.ECCstate.ecc_buf = ecc;
let eccout = [];
for (const e of r) {
eccout.push(Number(e >> BigInt(24)) & 255);
eccout.push(Number(e >> BigInt(16)) & 255);
eccout.push(Number(e >> BigInt(8)) & 255);
eccout.push(Number(e >> BigInt(0)) & 255);
}
eccout = eccout.slice(0, this.getEccBytes());
const eccbytes = new Uint8Array(eccout);
return eccbytes;
}
/**
* Decodes the data and corrects errors using ECC.
* @param {number[]} data - The input data array.
* @param {Uint8Array} recvecc - The received ECC data.
* @returns {any} - The corrected data and status.
*/
decode(data, recvecc) {
this.encode(data);
const eccbuf = this.convertAllBitsToBigInts(Array.from(recvecc), 32);
const eccwords = this.ceilop(this.ECCstate.m * this.ECCstate.t, 32);
let sum = BigInt(0);
for (let i = 0; i < eccwords; i++) {
this.ECCstate.ecc_buf[i] = this.ECCstate.ecc_buf[i] ^ eccbuf[i];
sum = sum | this.ECCstate.ecc_buf[i];
}
const dataout = this.convertAllBitsToBigInts(data, 8);
if (sum === BigInt(0)) {
return {
bitflips: 0,
valid: true,
binary: this.toBinString(dataout, data.length),
hex: this.toHexString(dataout, data.length),
ascii: this.toAsciiString(dataout)
};
}
let s = this.ECCstate.ecc_bits;
let t = this.ECCstate.t;
const syn = new Array(2 * t).fill(0);
const m = s & 31;
const synbuf = this.ECCstate.ecc_buf;
if (m) {
synbuf[Math.floor(s / 32)] = synbuf[Math.floor(s / 32)] & ~BigInt(Math.pow(2, Number(32 - m)) - 1);
}
let synptr = 0;
while (s > 0 || synptr === 0) {
let poly = synbuf[synptr];
synptr += 1;
s -= 32;
while (poly) {
const i = this.degBigInt(poly);
for (let j = 0; j < 2 * t; j += 2) {
syn[j] = syn[j] ^ this.g_pow(this, (j + 1) * (i + s));
}
poly = poly ^ BigInt(Math.pow(2, i));
}
}
for (let i = 0; i < t; i++) {
syn[2 * i + 1] = this.g_sqrt(this, syn[i]);
}
const n = this.ECCstate.n;
t = this.ECCstate.t;
let pp = -1;
let pd = 1;
let pelp = { deg: 0, c: new Array(2 * t).fill(0) };
pelp.c[0] = 1;
const elp = { deg: 0, c: new Array(2 * t).fill(0) };
elp.c[0] = 1;
let d = syn[0];
let elp_copy;
for (let i = 0; i < t; i++) {
if (elp.deg > t) {
break;
}
if (d) {
const k = 2 * i - pp;
elp_copy = JSON.parse(JSON.stringify(elp));
let tmp = this.g_log(this, d) + n - this.g_log(this, pd);
for (let j = 0; j <= pelp.deg; j++) {
if (pelp.c[j] !== BigInt(0)) {
const l = this.g_log(this, pelp.c[j]);
elp.c[j + k] = elp.c[j + k] ^ this.g_pow(this, tmp + l);
}
}
tmp = pelp.deg + k;
if (tmp > elp.deg) {
elp.deg = tmp;
pelp = JSON.parse(JSON.stringify(elp_copy));
pd = d;
pp = 2 * i;
}
}
if (i < t - 1) {
d = syn[2 * i + 2];
for (let j = 1; j <= elp.deg; j++) {
d = d ^ this.g_mul(this, elp.c[j], syn[2 * i + 2 - j]);
}
}
}
this.ECCstate.elp = elp;
const nroots = this.getRoots(this, dataout.length, this.ECCstate.elp);
const datalen = dataout.length;
const nbits = datalen * 8 + this.ECCstate.ecc_bits;
if (nroots === -1) {
return { valid: false };
}
for (let i = 0; i < nroots; i++) {
if (this.ECCstate.errloc[i] >= nbits) {
return -1;
}
this.ECCstate.errloc[i] = nbits - 1 - this.ECCstate.errloc[i];
this.ECCstate.errloc[i] = this.ECCstate.errloc[i] & ~7 | 7 - (this.ECCstate.errloc[i] & 7);
}
for (const bitflip of this.ECCstate.errloc) {
const byte = Math.floor(bitflip / 8);
const bit = Math.pow(2, bitflip & 7);
if (bitflip < (dataout.length + recvecc.length) * 8) {
if (byte < dataout.length) {
dataout[byte] = dataout[byte] ^ BigInt(bit);
} else {
recvecc[byte - dataout.length] = recvecc[byte - dataout.length] ^ bit;
}
}
}
return {
bitflips: nroots,
valid: true,
binary: this.toBinString(dataout, data.length),
hex: this.toHexString(dataout, data.length),
ascii: this.toAsciiString(dataout)
};
}
/**
* Finds the roots of a polynomial.
* @param {any} instance - The instance of the ECC state.
* @param {number} k - The degree of the polynomial.
* @param {any} poly - The polynomial.
* @returns {number} - The number of roots found.
*/
getRoots(instance, k, poly) {
const roots = [];
if (poly.deg > 2) {
k = k * 8 + instance.ECCstate.ecc_bits;
const rep = new Array(instance.ECCstate.t * 2).fill(0);
const d = poly.deg;
const l = instance.ECCstate.n - this.g_log(instance, poly.c[poly.deg]);
for (let i = 0; i < d; i++) {
if (poly.c[i]) {
rep[i] = this.mod(instance, this.g_log(instance, poly.c[i]) + l);
} else {
rep[i] = -1;
}
}
rep[poly.deg] = 0;
const syn0 = this.g_div(instance, poly.c[0], poly.c[poly.deg]);
for (let i = instance.ECCstate.n - k + 1; i < instance.ECCstate.n + 1; i++) {
let syn = syn0;
for (let j = 1; j < poly.deg + 1; j++) {
const m = rep[j];
if (m >= 0) {
syn = syn ^ this.g_pow(instance, m + j * i);
}
}
if (syn === 0) {
roots.push(instance.ECCstate.n - i);
if (roots.length === poly.deg) {
break;
}
}
}
if (roots.length < poly.deg) {
instance.ECCstate.errloc = [];
return -1;
}
}
if (poly.deg === 1) {
if (poly.c[0]) {
roots.push(
this.mod(
instance,
instance.ECCstate.n - instance.ECCstate.logarithms[poly.c[0]] + instance.ECCstate.logarithms[poly.c[1]]
)
);
}
}
if (poly.deg === 2) {
if (poly.c[0] && poly.c[1]) {
const l0 = instance.ECCstate.logarithms[poly.c[0]];
const l1 = instance.ECCstate.logarithms[poly.c[1]];
const l2 = instance.ECCstate.logarithms[poly.c[2]];
const u = this.g_pow(instance, l0 + l2 + 2 * (instance.ECCstate.n - l1));
let r = 0;
let v = u;
while (v) {
const i = this.deg(v);
r = r ^ instance.ECCstate.elp_pre[i];
v = v ^ Math.pow(2, i);
}
if (this.g_sqrt(instance, r) ^ Number(r === u)) {
roots.push(this.modn(instance, 2 * instance.ECCstate.n - l1 - instance.ECCstate.logarithms[r] + l2));
roots.push(this.modn(instance, 2 * instance.ECCstate.n - l1 - instance.ECCstate.logarithms[r ^ 1] + l2));
}
}
}
instance.ECCstate.errloc = roots;
return roots.length;
}
/**
* Gets the number of ECC bits.
* @returns {number} - The number of ECC bits.
*/
getEccBits() {
return this.ECCstate.ecc_bits;
}
/**
* Gets the number of ECC bytes.
* @returns {number} - The number of ECC bytes.
*/
getEccBytes() {
return Math.ceil(this.ECCstate.m * this.ECCstate.t / 8);
}
/**
* Builds a cyclic table for error correction.
* @param {bigint[]} g - The generator polynomial.
*/
buildCyclic(g) {
const l = Math.ceil(this.ECCstate.m * this.ECCstate.t / 32);
const plen = Math.ceil((this.ECCstate.ecc_bits + 1) / 32);
const ecclen = Math.ceil(this.ECCstate.ecc_bits / 32);
this.ECCstate.cyclic_tab = new Array(4 * 256 * l).fill(BigInt(0));
for (let i = 0; i < 256; i++) {
for (let b = 0; b < 4; b++) {
const offset = (b * 256 + i) * l;
let data = BigInt(i) << BigInt(8 * b);
while (data) {
const d = this.degBigInt(data);
data ^= g[0] >> BigInt(31 - d);
for (let j = 0; j < ecclen; j++) {
let hi, lo;
if (d < 31) {
hi = BigInt(g[j] << BigInt(d + 1)) & BigInt(4294967295);
} else {
hi = BigInt(0);
}
if (j + 1 < plen) {
lo = g[j + 1] >> BigInt(31 - d);
} else {
lo = BigInt(0);
}
if (this.ECCstate.cyclic_tab[j + offset] === BigInt(0)) {
this.ECCstate.cyclic_tab[j + offset] = BigInt(0);
}
this.ECCstate.cyclic_tab[j + offset] ^= hi | lo;
}
}
}
}
}
/** GALOIS OPERATIONS */
/**
* Computes the power of a value in a Galois field.
* @param instance - The current context containing Galois field parameters.
* @param i - The exponent value.
* @returns The result of raising a value to the power i in the Galois field.
*/
g_pow(instance, i) {
return instance.ECCstate.exponents[this.modn(instance, i)];
}
/**
* Computes the square root of a value in a Galois field.
* @param instance - The current context containing Galois field parameters.
* @param a - The value whose square root is to be computed.
* @returns The square root of the value in the Galois field.
*/
g_sqrt(instance, a) {
if (a) {
return instance.ECCstate.exponents[this.mod(instance, 2 * instance.ECCstate.logarithms[a])];
} else {
return 0;
}
}
/**
* Computes the logarithm of a value in a Galois field.
* @param instance - The current context containing Galois field parameters.
* @param x - The value whose logarithm is to be computed.
* @returns The logarithm of the value in the Galois field.
*/
g_log(instance, x) {
return instance.ECCstate.logarithms[x];
}
/**
* Multiplies two values in a Galois field.
* @param instance - The current context containing Galois field parameters.
* @param a - The first value to be multiplied.
* @param b - The second value to be multiplied.
* @returns The product of the two values in the Galois field.
*/
g_mul(instance, a, b) {
if (a > 0 && b > 0) {
const res = this.mod(instance, instance.ECCstate.logarithms[a] + instance.ECCstate.logarithms[b]);
return instance.ECCstate.exponents[res];
} else {
return 0;
}
}
/**
* Divides two values in a Galois field.
* @param instance - The current context containing Galois field parameters.
* @param a - The dividend.
* @param b - The divisor.
* @returns The quotient of the division in the Galois field.
*/
g_div(instance, a, b) {
if (a) {
return instance.ECCstate.exponents[this.mod(instance, instance.ECCstate.logarithms[a] + instance.ECCstate.n - instance.ECCstate.logarithms[b])];
} else {
return 0;
}
}
/**
* Reduces a value modulo the Galois field size.
* @param instance - The current context containing Galois field parameters.
* @param v - The value to be reduced.
* @returns The value reduced modulo the Galois field size.
*/
mod(instance, v) {
if (v < instance.ECCstate.n) {
return v;
} else {
return v - instance.ECCstate.n;
}
}
/**
* Reduces a value modulo the Galois field size.
* @param instance - The current context containing Galois field parameters.
* @param v - The value to be reduced.
* @returns The value reduced modulo the Galois field size.
*/
modn(instance, v) {
const n = instance.ECCstate.n;
while (v >= n) {
v -= n;
v = (v & n) + (v >> instance.ECCstate.m);
}
return v;
}
/**
* Computes the degree of a polynomial represented as an integer.
* @param x - The polynomial represented as an integer.
* @returns The degree of the polynomial.
*/
deg(x) {
let count = 0;
while (x >> 1) {
x = x >> 1;
count += 1;
}
return count;
}
/**
* Computes the ceiling of the division of two integers.
* @param a - The dividend.
* @param b - The divisor.
* @returns The ceiling of the division of a by b.
*/
ceilop(a, b) {
return Math.floor((a + b - 1) / b);
}
/**
* Computes the degree of a polynomial represented as a BigInt.
* @param x - The polynomial represented as a BigInt.
* @returns The degree of the polynomial.
*/
degBigInt(x) {
let count = 0;
while (x >> BigInt(1)) {
x = x >> BigInt(1);
count += 1;
}
return count;
}
/**
* Converts an array of bits into a single BigInt value.
* @param {number[]} bitArray - The array of bits to convert.
* @param {number} bitLimit - The maximum number of bits to process.
* @returns {BigInt} - The combined value of all bits in the array.
*/
convertBitsToBigInt(bitArray, bitLimit) {
let result = BigInt(0);
if (bitLimit < bitArray.length) {
bitLimit = bitArray.length;
}
let pos = bitLimit - 1;
for (let b = 0; b < bitLimit; b++) {
if (bitArray[b]) {
result += BigInt(1) << BigInt(pos);
}
pos--;
}
return result;
}
/**
* Processes an array of bits in chunks, converting each chunk into a BigInt.
* @param {number[]} bitArray - The array of bits to process.
* @param {number} chunkSize - The size of each chunk of bits to process.
* @returns {BigInt[]} - An array of BigInt values representing chunks of the original bit array.
*/
convertAllBitsToBigInts(bitArray, chunkSize) {
const dataLength = bitArray.length;
let numChunks = Math.floor(dataLength / chunkSize);
const resultArray = [];
let offset = 0;
while (numChunks > 0) {
const chunk = bitArray.slice(offset, offset + chunkSize);
const bigInt = this.convertBitsToBigInt(chunk, chunkSize);
resultArray.push(bigInt);
offset += chunkSize;
numChunks--;
}
const remainingBitsArray = bitArray.slice(offset);
if (remainingBitsArray.length > 0) {
const bigInt = this.convertBitsToBigInt(remainingBitsArray, chunkSize);
resultArray.push(bigInt);
}
return resultArray;
}
/**
* Converts an array of up to 4 bytes into a single BigInt value.
* @param {bigint[]} byteArray - The array of bytes to convert.
* @returns {BigInt} - The combined value of the bytes as a BigInt.
*/
convertBytesToBigInt(byteArray) {
let result = BigInt(0);
if (byteArray.length > 0) result += byteArray[0] << BigInt(24);
if (byteArray.length > 1) result += byteArray[1] << BigInt(16);
if (byteArray.length > 2) result += byteArray[2] << BigInt(8);
if (byteArray.length > 3) result += byteArray[3];
return result;
}
/**
* Generates a binary string from data.
* @param {any[]} dataout - The data output array.
* @param {number} datalen - The desired length of the binary string.
* @returns {string} - The binary string representation of the data.
*/
toBinString(dataout, datalen) {
let out = "";
for (const byte of dataout) {
out += this.numberToBinaryString(byte, 8);
}
out = out.slice(0, datalen);
return out;
}
/**
* Converts a number to a binary string of a given length.
* @param {number} num - The number to convert.
* @param {number} length - The desired length of the binary string.
* @returns {string} - The binary string representation of the number.
*/
numberToBinaryString(num, length) {
let binaryString = num.toString(2);
while (binaryString.length < length) {
binaryString = "0" + binaryString;
}
return binaryString;
}
/**
* Decodes a Uint8Array to a string using 7-bit ASCII encoding.
* @param {Uint8Array} data - The input byte array.
* @returns {string} - The decoded string.
*/
toAsciiString(data) {
const textBitStr = data.map((byte) => byte.toString(2).padStart(8, "0")).join("");
const textInt7 = [];
for (let i = 0; i < textBitStr.length; i += 7) {
const bitSegment = textBitStr.slice(i, i + 7);
textInt7.push(parseInt(bitSegment, 2));
}
const textBytes = new Uint8Array(textInt7);
const decodedText = new TextDecoder("utf-8").decode(textBytes).replace(/\0/g, "");
return decodedText;
}
/**
* Converts an array of numbers to a hexadecimal string.
* @param {any[]} data - The array of numbers to convert.
* @returns {string} - The hexadecimal string representation of the numbers.
*/
toHexString(data, datalen) {
if (data.length > datalen / 8) {
data.pop();
}
return data.map(function(byte) {
byte = Number(byte);
if (byte > 15) return (byte & 255).toString(16);
else return "0" + (byte & 255).toString(16);
}).join("");
}
};
// src/datalayer.ts
var BCH_POLYNOMIAL = 137;
var DataLayer = class {
payload_len;
// Length of the payload in bits
encoding_mode;
// Encoding mode to be used
versionbits;
// Number of bits for the schema version
bch_encoder;
// BCH encoder instance
bch_decoders;
// Dictionary of BCH decoders for different schemas
/**
* Initializes the DataLayer with specified parameters.
* @param {number} payload_len - The length of the payload in bits.
* @param {boolean} verbose - Flag to indicate if messages should be logged.
* @param {number} encoding_mode - The encoding mode to be used (default is 0).
*/
constructor(payload_len, verbose, encoding_mode) {
this.bch_encoder = this.buildBCH(encoding_mode);
this.encoding_mode = encoding_mode;
this.versionbits = 4;
this.bch_decoders = {};
for (let i = 0; i < 4; i++) {
this.bch_decoders[i] = this.buildBCH(i);
}
this.payload_len = payload_len;
}
/**
* Builds and returns a BCH instance based on the given encoding mode.
*
* @param encoding_mode The encoding mode.
* @returns A BCH instance configured for the specified encoding mode.
*/
buildBCH(encoding_mode) {
switch (encoding_mode) {
case 1:
return new BCH(5, BCH_POLYNOMIAL);
case 2:
return new BCH(4, BCH_POLYNOMIAL);
case 3:
return new BCH(3, BCH_POLYNOMIAL);
default:
return new BCH(8, BCH_POLYNOMIAL);
}
}
/**
* Encodes a text string into a Float32Array with the ECC encoding.
* @param {string} text - The input text string.
* @returns {Float32Array} - The encoded Float32Array.
*/
encodeText(text) {
const data = this.encodeAscii(text);
const packet_d = Array.from(data).map((x) => x.toString(2).padStart(8, "0")).join("");
return this.encodePacket(packet_d);
}
/**
* Encodes a binary string into a Float32Array with the ECC encoding.
* @param {string} strbin - The input binary string.
* @returns {Float32Array} - The encoded Float32Array with the ECC encoding.
*/
encodeBinary(strbin) {
return this.encodePacket(String(strbin));
}
/**
* Processes and encodes the packet data.
* @param {string} packet_d - The binary string representation of the packet data.
* @returns {Float32Array} - The encoded Float32Array.
*/
encodePacket(packet_d) {
const data_bitcount = this.payload_len - this.bch_encoder.getEccBits() - this.versionbits;
const ecc_bitcount = this.bch_encoder.getEccBits();
packet_d = packet_d.substring(0, data_bitcount);
packet_d = packet_d.padEnd(data_bitcount, "0");
const pad_d = packet_d.length % 8 === 0 ? 0 : 8 - packet_d.length % 8;
const paddedpacket_d = packet_d + "0".repeat(pad_d);
const padded_data = Array.from(paddedpacket_d.split("").map(Number));
const ecc = this.bch_encoder.encode(padded_data);
let packet_e = Array.from(ecc).map((x) => x.toString(2).padStart(8, "0")).join("");
packet_e = packet_e.substring(0, ecc_bitcount);
const pad_e = packet_e.length % 8 === 0 || this.encoding_mode !== 0 ? 0 : 8 - packet_e.length % 8;
packet_e = packet_e.padEnd(packet_e.length + pad_e, "0");
const version = this.encoding_mode;
const packet_v = version.toString(2).padStart(4, "0");
let packet = packet_d + packet_e + packet_v;
packet = packet.split("").map((x) => parseInt(x, 10)).join("");
if (this.payload_len !== packet.length) {
throw new Error("Error! Could not form complete packet");
}
return new Float32Array(packet.split("").map(Number));
}
/**
* Encodes a string to a Float32Array using 7-bit ASCII encoding.
* @param {string} text - The input text string.
* @returns {Float32Array} - The encoded Float32Array.
*/
encodeAscii(text) {
const textInt7 = Array.from(text).map((t) => t.charCodeAt(0) & 127);
let textBitStr = textInt7.map((t) => t.toString(2).padStart(7, "0")).join("");
if (textBitStr.length % 8 !== 0) {
textBitStr = textBitStr.padEnd(textBitStr.length + (8 - textBitStr.length % 8), "0");
}
const byteArray = [];
for (let i = 0; i < textBitStr.length; i += 8) {
byteArray.push(parseInt(textBitStr.slice(i, i + 8), 2));
}
return new Float32Array(byteArray);
}
};
function getSchemaCapacity(schema_version) {
switch (schema_version) {
case 0:
return 40;
case 1:
return 61;
case 2:
return 68;
case 3:
return 75;
default:
throw new Error("Invalid schema version");
}
}
function getSchemaVersion(binary_array) {
const last_two_bits = binary_array.slice(-2);
const version = last_two_bits[0] * 2 + last_two_bits[1];
return version;
}
// src/ONNX_HUB_MANIFEST.json
var ONNX_HUB_MANIFEST_default = [
{
model: "Trustmark variant Q encoder",
model_name: "encoder_Q.onnx",
model_remote_host: "https://cc-assets.netlify.app",
model_path: "/watermarking/trustmark-models/encoder_Q.onnx",
onnx_version: "1.9.0",
opset_version: 17,
metadata: {
model_sha: "19b3d1b25836130ffd78775a8f61539f993375d1823ef0e59ba5b8dffb4f892d",
model_bytes: 17312208,
tags: ["watermarking"],
io_ports: {
inputs: [
{
name: " onnx::Concat_0",
shape: [1, 3, 256, 256],
type: "tensor(float)"
},
{
name: "onnx::Gemm_1",
shape: [1, 100],
type: "tensor(float)"
}
],
outputs: [
{
name: "image",
shape: [1, 3, 256, 256],
type: "tensor(float)"
}
]
}
}
},
{
model: "Trustmark variant P encoder",
model_name: "encoder_P.onnx",
model_remote_host: "https://cc-assets.netlify.app",
model_path: "/watermarking/trustmark-models/encoder_P.onnx",
onnx_version: "1.9.0",
opset_version: 17,
metadata: {
model_sha: "053441c9c9f05fc158ccba71c610d9d58fcd2c82d1912bf0ffcee988cf2f74c8",
model_bytes: 17312208,
tags: ["watermarking"],
io_ports: {
inputs: [
{
name: " onnx::Concat_0",
shape: [1, 3, 256, 256],
type: "tensor(float)"
},
{
name: "onnx::Gemm_1",
shape: [1, 100],
type: "tensor(float)"
}
],
outputs: [
{
name: "image",
shape: [1, 3, 256, 256],
type: "tensor(float)"
}
]
}
}
},
{
model: "Trustmark variant Q decoder",
model_name: "decoder_Q.onnx",
model_remote_host: "https://cc-assets.netlify.app",
model_path: "/watermarking/trustmark-models/decoder_Q.onnx",
onnx_version: "1.9.0",
opset_version: 17,
metadata: {
model_sha: "ee3268f057c9dabef680e169302f5973d0589feea86189ed229a896cc3aa88df",
model_bytes: 47401222,
tags: ["watermarking"],
io_ports: {
inputs: [
{
name: "image",
shape: [1, 3, 256, 256],
type: "tensor(float)"
}
],
outputs: [
{
name: "output",
shape: [1, 100],
type: "tensor(float)"
}
]
}
}
},
{
model: "Trustmark variant P decoder",
model_name: "decoder_P.onnx",
model_remote_host: "https://cc-assets.netlify.app",
model_path: "/watermarking/trustmark-models/decoder_P.onnx",
onnx_version: "1.9.0",
opset_version: 17,
metadata: {
model_sha: "be6d7c33f8a7b376f179e75f3f7c58ff816a9ac7bb6d37fd0a729a635f624c35",
model_bytes: 47400467,
tags: ["watermarking"],
io_ports: {
inputs: [
{
name: "image",
shape: [1, 3, 224, 224],
type: "tensor(float)"
}
],
outputs: [
{
name: "output",
shape: [1, 100],
type: "tensor(float)"
}
]
}
}
}
];
// src/trustmark.ts
var MODELS_PATH = "models/";
var ASPECT_RATIO_LIM = 2;
var IS_BROWSER = false;
var IS_NODE = false;
if (typeof window === "undefined") {
IS_NODE = true;
} else {
IS_BROWSER = true;
}
var VERBOSE = true;
var TrustMark = class _TrustMark {
/** * Static encoding mapping for different BCH modes. */
static encoding = {
undefined: -1,
BCH_SUPER: 0,
BCH_3: 3,
BCH_4: 2,
BCH_5: 1
};
use_ecc;
secret_len;
ecc;
decoder_session;
encoder_session;
preprocess_224_session;
preprocess_256_session;
model_type;
/**
* Constructs a new TrustMark instance.
* @param {boolean} [use_ecc=true] - use BCH error correction on the payload, reducing payload size (default)
* @param {number} [secret_len=100] - The length of the secret.
* @param {number} [encoding_mode=TrustMark.encoding.BCH_4] - The data schema encoding mode to use.
*/
constructor(use_ecc = true, secret_len = 100, encoding_mode = _TrustMark.encoding.BCH_4) {
this.use_ecc = use_ecc;
this.secret_len = secret_len;
this.ecc = new DataLayer(secret_len, VERBOSE, encoding_mode);
}
/**
* Decodes the watermark of an image from a given URL.
*
* @param image_url The URL of the image to decode.
* @returns A promise that resolves to the decoded watermnark data.
*/
async decode(image_url) {
tf.engine().startScope();
const stego_image = await this.loadImage(image_url, "decode");
await sleep(0);
tf.engine().endScope();
const input_feeds = { image: stego_image.onnx };
const start_time = /* @__PURE__ */ new Date();
const model_output = await this.decoder_session.run(input_feeds);
const time_elapsed = (/* @__PURE__ */ new Date()).getTime() - start_time.getTime();
tsLog(`Prediction: ${time_elapsed}ms`);
await sleep(0);
const output_data = model_output.output.cpuData;
const binary_array = output_data.map((value) => value >= 0 ? 1 : 0);
const schema = getSchemaVersion(binary_array);
let data_bits = getSchemaCapacity(schema);
let data = binary_array.slice(0, data_bits);
let ecc = binary_array.slice(data_bits, 96);
let decoded_data = this.ecc.bch_decoders[schema].decode(data, ecc);
decoded_data.schema = schema;
if (!decoded_data.valid) {
for (let alt_schema = 0; alt_schema < 3; alt_schema++) {
if (alt_schema === schema) continue;
data_bits = getSchemaCapacity(alt_schema);
data = binary_array.slice(0, data_bits);
ecc = binary_array.slice(data_bits, 96);
decoded_data = this.ecc.bch_decoders[alt_schema].decode(data, ecc);
decoded_data.schema = alt_schema;
if (decoded_data.valid) {
break;
}
}
}
decoded_data.raw = binary_array;
return decoded_data;
}
/**
* Encodes a secret into an image and returns the stego image and the residual image.
*
* @param {string} image_url The cover image data.
* @param {string} string_secret The secret string to encode.
* @param {number} wm_strength The watermark strength. Default is 0.4.
* @param {boolean} maculate Whether to overwrite an existing watermark with random values. Default is false.
* @param {string} output The output format. Default is 'bytes'.
* @returns A promise that resolves with the encoded data or rejects with an error.
*/
async encode(image_url, string_secret, wm_strength = 0.4, maculate = false, output = "bytes") {
tf.engine().startScope();
const cover_image = await this.loadImage(image_url, "encode");
let mode;
let secret = new Float32Array(100);
if (maculate === true) {
mode = "binary";
secret.set(
Float32Array.from({ length: 96 }, () => Math.round(Math.random())),
0
);
secret.set([0, 0, 0, 0], 96);
} else {
const binary_count = string_secret.match(/[01]/g);
if (binary_count && binary_count.length == string_secret.length) {
mode = "binary";
} else {
mode = "text";
}
if (!this.use_ecc) {
if (mode === "binary") {
secret = new Float32Array(Array.from(string_secret).map(Number));
} else {
secret = this.ecc.encodeAscii(string_secret);
secret = new Float32Array(Array.from(secret).map(Number));
}
} else {
if (mode === "binary") {
secret = this.ecc.encodeBinary(string_secret);
} else {
secret = this.ecc.encodeText(string_secret);
}
}
}
cover_image.onnx_secret = new ort.Tensor("float32", secret, [1, 100]);
const input_feeds = { "onnx::Concat_0": cover_image.onnx, "onnx::Gemm_1": cover_image.onnx_secret };
let start_time = /* @__PURE__ */ new Date();
const model_output = await this.encoder_session.run(input_feeds);
let time_elapsed = (/* @__PURE__ */ new Date()).getTime() - start_time.getTime();
tsLog(`Inference: ${time_elapsed}ms`);
await sleep(0);
start_time = /* @__PURE__ */ new Date();
const tf_cover = tf.tensor(cover_image.onnx.cpuData, [1, 3, 256, 256]);
const tf_stego = tf.tensor(model_output.image.cpuData, [1, 3, 256, 256]);
let tf_residual = tf.clipByValue(tf_stego, -1, 1).sub(tf_cover).squeeze().transpose([1, 2, 0]);
tf_cover.dispose();
tf_stego.dispose();
if (IS_NODE && VERBOSE || IS_BROWSER) {
const residual_display = tf_residual.mul(10).clipByValue(0, 1);
if (IS_NODE) {
if (output == "png") {
cover_image.residual = await tf.node.encodePng(residual_display.mul(255));
} else {
cover_image.residual = await tf.browser.toPixels(residual_display);
}
} else {
cover_image.residual = await tf.browser.toPixels(residual_display);
}
residual_display.dispose();
}
tf_residual = tf.image.resizeBilinear(tf_residual, [cover_image.crop_height, cover_image.crop_width]);
time_elapsed = (/* @__PURE__ */ new Date()).getTime() - start_time.getTime();
tsLog(`Residual Interpolation: ${time_elapsed}ms`);
await sleep(0);
start_time = /* @__PURE__ */ new Date();
let tf_merge = tf.clipByValue(tf.add(tf_residual.mul(wm_strength), cover_image.tf_crop), 0, 1);
if (cover_image.aspect_ratio > 2 || this.model_type == "P") {
if (cover_image.orientation == "landscape") {
const axe_length = Math.floor((cover_image.width - cover_image.crop_axe) / 2);
const part_a = cover_image.tf_source.slice([0, 0, 0], [cover_image.crop_axe, axe_length, 3]);
const part_b = cover_image.tf_source.slice(
[0, axe_length + cover_image.crop_axe, 0],
[cover_image.crop_axe, cover_image.width - axe_length - cover_image.crop_axe, 3]
);
tf_merge = tf.concat([part_a, tf_merge, part_b], 1);
}
if (cover_image.orientation == "portrait") {
const axe_length = Math.floor((cover_image.height - cover_image.crop_axe) / 2);
const part_a = cover_image.tf_source.slice([0, 0, 0], [axe_length, cover_image.crop_axe, 3]);
const part_b = cover_image.tf_source.slice(
[axe_length + cover_image.crop_axe, 0, 0],
[cover_image.height - axe_length - cover_image.crop_axe, cover_image.crop_axe, 3]
);
tf_merge = tf.concat([part_a, tf_merge, part_b], 0);
}
}
cover_image.tf_crop.dispose();
tf_residual.dispose();
time_elapsed = (/* @__PURE__ */ new Date()).getTime() - start_time.getTime();
tsLog(`Compositing: ${time_elapsed}ms`);
await sleep(0);
start_time = /* @__PURE__ */ new Date();
if (IS_NODE) {
if (output == "png") {
cover_image.stego = await tf.node.encodePng(tf_merge.mul(255));
} else {
cover_image.stego = await tf.browser.toPixels(tf_merge);
}
} else {
cover_image.stego = await tf.browser.toPixels(tf_merge);
}
time_elapsed = (/* @__PURE__ */ new Date()).getTime() - start_time.getTime();
tsLog(`Encoding: ${time_elapsed}ms`);
await sleep(0);
tf.engine().endScope();
return {
stego: cover_image.stego,
residual: cover_image.residual ? cover_image.residual : new Uint8Array(),
height: cover_image.height,
width: cover_image.width
};
}
/**
* Processes the input image based on the specified processing type.
*
* @param {any} image - The image object containing the tensor source and other properties.
* @param {string} process_type - The type of processing to be applied to the image ('decode' or other types).
* @returns {Promise<any>} A promise that resolves with the processed image.
* @throws {Error} Throws an error if there is an issue processing the image.
*/
async processImage(image2, process_type) {
const start_time = /* @__PURE__ */ new Date();
image2.width = image2.tf_source.shape[2];
image2.height = image2.tf_source.shape[1];
if (image2.width > image2.height) {
image2.orientation = "landscape";
image2.aspect_ratio = image2.width / image2.height;
} else {
image2.orientation = "portrait";
image2.aspect_ratio = image2.height / image2.width;
}
if (image2.aspect_ratio > ASPECT_RATIO_LIM || this.model_type == "P") {
const size = Math.min(image2.width, image2.height);
const left = (image2.width - size) / 2;
const top = (image2.height - size) / 2;
image2.tf_crop = tf.image.cropAndResize(
image2.tf_source,
[[top / image2.height, left / image2.width, (top + size) / image2.height, (left + size) / image2.width]],
[0],
[size, size],
"nearest"
);
image2.crop_axe = image2.crop_width = image2.crop_height = size;
} else {
image2.tf_crop = image2.tf_source;
image2.crop_width = image2.width;
image2.crop_height = image2.height;
}
image2.tf_source = image2.tf_source.squeeze();
image2.tf_crop = image2.tf_crop.transpose([0, 3, 1, 2]);
const data = image2.tf_crop.dataSync();
const onnxTensor = new ort.Tensor("float32", data, image2.tf_crop.shape);
image2.tf_crop = image2.tf_crop.transpose([0, 2, 3, 1]);
image2.tf_crop = image2.tf_crop.squeeze();
if (this.model_type == "P" && process_type == "decode") {
image2.onnx = (await this.preprocess_224_session.run({ input: onnxTensor })).output;
} else {
image2.onnx = (await this.preprocess_256_session.run({ input: onnxTensor })).output;
}
await sleep(0);
const time_elapsed = (/* @__PURE__ */ new Date()).getTime() - start_time.getTime();
tsLog(`Processing: ${image2.width}x${image2.height}: ${time_elapsed}ms`);
return image2;
}
/**
* Loads an image from a URL or filesystem and processes it based on the specified type.
*
* @param {string} image_url - The URL or filesystem path of the image to be loaded.
* @param {string} process_type - The type of processing to be applied to the image.
* @returns {Promise<any>} A promise that resolves with the processed image.
* @throws {Error} Throws an error if there is an issue loading or processing the image.
*/
async loadImage(image_url, process_type) {
return new Promise(async (resolve) => {
const start_time = /* @__PURE__ */ new Date();
const image2 = { url: image_url };
if (IS_NODE) {
const image_buffer = (0, import_node_fs.readFileSync)(image2.url);
image2.tf_source = tf.node.decodeImage(image_buffer).expandDims(0).div(255);
} else {
const img = new Image();
img.onload = async () => {
image2.tf_source = tf.browser.fromPixels(img).expandDims(0).div(255);
const time_elapsed = (/* @__PURE__ */ new Date()).getTime() - start_time.getTime();
tsLog(`Loading: ${time_elapsed}ms`);
resolve(await this.processImage(image2, process_type));
};
img.src = image2.url;
}
if (IS_NODE) {
const time_elapsed = (/* @__PURE__ */ new Date()).getTime() - start_time.getTime();
tsLog(`Loading: ${time_elapsed}ms`);
resolve(await this.processImage(image2, process_type));
}
});
}
/**
* Loads the necessary models based on the specified type.
*
* @param {string} [type='Q'] - The type of models to load ('Q' or 'P').
* @throws {Error} Throws an error if there is an issue loading any of the models.
*/
async loadModels(type = "Q") {
const models = await getModels();
let decoder_model_url;
let encoder_model_url;
this.model_type = type;
if (type == "Q") {
decoder_model_url = models["decoder_Q.onnx"];
encoder_model_url = models["encoder_Q.onnx"];
}
if (type == "P") {
decoder_model_url = models["decoder_P.onnx"];
encoder_model_url = models["encoder_P.onnx"];
this.preprocess_224_session = await ort.InferenceSession.create("models/preprocess_224.onnx").catch(
(error) => {
throw new Error(`Error loading preprocessing ONNX model: ${error}`);
}
);
}
const session_option = { executionProviders: ["cpu"] };
this.preprocess_256_session = await ort.InferenceSession.create("models/preprocess_256.onnx").catch(
(error) => {
throw new Error(`Error loading preprocessing ONNX model: ${error}`);
}
);
this.decoder_session = await ort.InferenceSession.create(decoder_model_url, session_option).catch((error) => {
throw new Error(`Error loading decoder ONNX model: ${error}`);
});
this.encoder_session = await ort.InferenceSession.create(encoder_model_url, session_option).catch((error) => {
throw new Error(`Error loading encoder ONNX model: ${error}`);
});
}
};
async function getModels() {
return new Promise(async (resolve, reject) => {
const fetchs = [];
const models = {};
for (const model of ONNX_HUB_MANIFEST_default) {
const model_url = model.model_remote_host + model.model_path;
const model_path = MODELS_PATH + model.model_name;
const model_bytes = model.metadata.model_bytes;
if (IS_NODE) {
if ((0, import_node_fs.existsSync)(model_path)) {
models[model.model_name] = model_path;
} else {
tsLog(`'${model_path}' needs to be fetched and cached from remote repository.`);
fetchs.push(fetchModel(model_url, model_path, model.model_name, model.metadata.model_sha, model_bytes));
}
} else {
await restoreFileFromCache(model.model_name).then((file) => {
models[model.model_name] = file;
}).catch((e) => {
tsLog(model.model_name + " needs to be fetched and cached from remote repository.");
fetchs.push(fetchModel(model_url, model_path, model.model_name, model.metadata.model_sha, model_bytes));
});
}
}
await Promise.all(fetchs).then((fmodels) => {
fmodels.forEach(function(fmodel) {
models[fmodel.model_name] = fmodel.path;
});
}).catch((err) => reject(err));
resolve(models);
});
}
async function fetchModel(url, file_path, model_name, checksum, model_bytes) {
return new Promise(async (resolve, reject) => {
fetch(url).then((response) => {
return response.body;
}).then((body) => {
const reader = body.getReader();
let charsReceived = 0;
return new ReadableStream({
async start(controller) {
return pump();
function pump() {
return reader.read().then(({ done, value }) => {
if (done) {
controller.close();
return;
}
charsReceived += value.length;
const progress_percentage = Math.floor(charsReceived / model_bytes * 100);
if (IS_NODE) {
process.stdout.clearLine(0);
process.stdout.cursorTo(0);
process.stdout.write(`Progress: ${drawProgressBar(progress_percentage)} of ${model_name}`);
} else {
tsLog(`Loading model: ${progress_percentage}% of ${file_path}`, true);
}
controller.enqueue(value);
return pump();
});
}
}
});
}).then((stream) => new Response(stream)).then((response) => response.arrayBuffer()).then(async (a_buffer) => {
const file_chacksum = await sha(new Uint8Array(a_buffer));
if (file_chacksum == checksum) {
const model = {
model_name,
path: a_buffer
};
if (IS_NODE) {
(0, import_node_fs.writeFile)(file_path, new Uint8Array(a_buffer), (err) => {
if (err) {
reject(err);
} else {
resolve(model);
}
});
} else {
await storeFileInCache(model_name, new Blob([a_buffer], { type: "application/octet-stream" }));
resolve(model);
}
}
}).catch((err) => reject(err));
});
}
async function restoreFileFromCache(model_name) {
const modelCache = await caches.open("models");
const response = await modelCache.match(model_name);
if (!response) {
throw new Error(`${model_name} not found in cache.`);
}
const file = await response.arrayBuffer();
tsLog(`${model_name} found in cache.`);
return file;
}
async function storeFileInCache(model_name, blob) {
try {
const modelCache = await caches.open("models");
await modelCache.put(model_name, new Response(blob));
tsLog(`${model_name} cached`);
} catch (err) {
throw new Error(err);
}
}
function drawProgressBar(progress) {
const barWidth = 30;
const filledWidth = Math.floor(progress / 100 * barWidth);
const emptyWidth = barWidth - filledWidth;
const progressBar = "\u2588".repeat(filledWidth) + "\u2592".repeat(emptyWidth);
return `[${progressBar}] ${progress}%`;
}
function sha(content) {
if (IS_NODE) {
return (0, import_node_crypto.createHash)("sha256").update(content).digest("hex");
} else {
return hash(content);
}
}
function sleep(m) {
if (IS_BROWSER) {
return new Promise((resolve) => setTimeout(resolve, m));
}
}
function tsLog(str, browser_only = false) {
if (IS_BROWSER) {
const payloadevt = new CustomEvent("status", { detail: str });
window.dispatchEvent(payloadevt);
}
if (IS_NODE && browser_only === false && VERBOSE) {
console.log(str);
}
}
async function hash(content) {
const hashBuffer = await crypto.subtle.digest("SHA-256", content);
const hashArray = Array.from(new Uint8Array(hashBuffer));
const hashHex = hashArray.map((bytes) => bytes.toString(16).padStart(2, "0")).join("");
return hashHex;
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
TrustMark
});