@pact-toolbox/crypto
Version:
1,136 lines (1,114 loc) • 44.8 kB
JavaScript
import { getPublicKeyAsync, signAsync, utils, verifyAsync } from "@noble/ed25519";
import { blake2b } from "blakejs";
//#region ../../node_modules/.pnpm/uncrypto@0.1.3/node_modules/uncrypto/dist/crypto.web.mjs
const webCrypto = globalThis.crypto;
const subtle = webCrypto.subtle;
const randomUUID = () => {
return webCrypto.randomUUID();
};
const getRandomValues = (array) => {
return webCrypto.getRandomValues(array);
};
const _crypto = {
randomUUID,
getRandomValues,
subtle
};
//#endregion
//#region src/polyfill/secrets.ts
const PROHIBITED_KEY_USAGES = new Set([
"decrypt",
"deriveBits",
"deriveKey",
"encrypt",
"unwrapKey",
"wrapKey"
]);
const ED25519_PKCS8_HEADER = [
48,
46,
2,
1,
0,
48,
5,
6,
3,
43,
101,
112,
4,
34,
4,
32
];
function bufferSourceToUint8Array(data) {
return data instanceof Uint8Array ? data : new Uint8Array(ArrayBuffer.isView(data) ? data.buffer : data);
}
let storageKeyBySecretKey_INTERNAL_ONLY_DO_NOT_EXPORT;
let publicKeyBytesStore;
function createKeyPairFromBytes$1(bytes, extractable, keyUsages) {
const keyPair = createKeyPair_INTERNAL_ONLY_DO_NOT_EXPORT(extractable, keyUsages);
const cache = storageKeyBySecretKey_INTERNAL_ONLY_DO_NOT_EXPORT ||= /* @__PURE__ */ new WeakMap();
cache.set(keyPair.privateKey, bytes);
cache.set(keyPair.publicKey, bytes);
return keyPair;
}
function createKeyPair_INTERNAL_ONLY_DO_NOT_EXPORT(extractable, keyUsages) {
if (keyUsages.length === 0) throw new DOMException("Usages cannot be empty when creating a key.", "SyntaxError");
if (keyUsages.some((usage) => PROHIBITED_KEY_USAGES.has(usage))) throw new DOMException("Unsupported key usage for an Ed25519 key.", "SyntaxError");
const base = {
[Symbol.toStringTag]: "CryptoKey",
algorithm: Object.freeze({ name: "Ed25519" })
};
const privateKey = {
...base,
extractable,
type: "private",
usages: Object.freeze(keyUsages.filter((usage) => usage === "sign"))
};
const publicKey = {
...base,
extractable: true,
type: "public",
usages: Object.freeze(keyUsages.filter((usage) => usage === "verify"))
};
return Object.freeze({
privateKey: Object.freeze(privateKey),
publicKey: Object.freeze(publicKey)
});
}
function getSecretKeyBytes_INTERNAL_ONLY_DO_NOT_EXPORT(key) {
const secretKeyBytes = storageKeyBySecretKey_INTERNAL_ONLY_DO_NOT_EXPORT?.get(key);
if (secretKeyBytes === void 0) throw new Error("Could not find secret key material associated with this `CryptoKey`");
return secretKeyBytes;
}
async function getPublicKeyBytes(key) {
const publicKeyStore = publicKeyBytesStore ||= /* @__PURE__ */ new WeakMap();
const fromPublicStore = publicKeyStore.get(key);
if (fromPublicStore) return fromPublicStore;
const publicKeyBytes = await getPublicKeyAsync(getSecretKeyBytes_INTERNAL_ONLY_DO_NOT_EXPORT(key));
publicKeyStore.set(key, publicKeyBytes);
return publicKeyBytes;
}
function base64UrlEncode(bytes) {
return btoa(Array.from(bytes, (b) => String.fromCharCode(b)).join("")).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}
function base64UrlDecode(value) {
const m = value.length % 4;
const base64Value = value.replace(/-/g, "+").replace(/_/g, "/").padEnd(value.length + (m === 0 ? 0 : 4 - m), "=");
return Uint8Array.from(atob(base64Value), (c) => c.charCodeAt(0));
}
async function exportKeyPolyfill(format, key) {
if (key.extractable === false) throw new DOMException("key is not extractable", "InvalidAccessException");
switch (format) {
case "raw": {
if (key.type !== "public") throw new DOMException(`Unable to export a raw Ed25519 ${key.type} key`, "InvalidAccessError");
const publicKeyBytes = await getPublicKeyBytes(key);
return publicKeyBytes;
}
case "pkcs8": {
if (key.type !== "private") throw new DOMException(`Unable to export a pkcs8 Ed25519 ${key.type} key`, "InvalidAccessError");
const secretKeyBytes = getSecretKeyBytes_INTERNAL_ONLY_DO_NOT_EXPORT(key);
return new Uint8Array([...ED25519_PKCS8_HEADER, ...secretKeyBytes]);
}
case "jwk": {
const publicKeyBytes = await getPublicKeyBytes(key);
const base = {
crv: "Ed25519",
ext: key.extractable,
key_ops: key.usages,
kty: "OKP",
x: base64UrlEncode(publicKeyBytes)
};
if (key.type === "private") {
const secretKeyBytes = getSecretKeyBytes_INTERNAL_ONLY_DO_NOT_EXPORT(key);
return Object.freeze({
...base,
d: base64UrlEncode(secretKeyBytes)
});
}
return Object.freeze({ ...base });
}
default: throw new Error(`Exporting polyfilled Ed25519 keys in the "${format}" format is unimplemented`);
}
}
/**
* This function generates a key pair and stores the secret bytes associated with it in a
* module-private cache. Instead of vending the actual secret bytes, it returns a `CryptoKeyPair`
* that you can use with other methods in this package to produce signatures and derive public keys
* associated with the secret.
*/
function generateKeyPolyfill(extractable, keyUsages) {
const privateKeyBytes = utils.randomPrivateKey();
const keyPair = createKeyPairFromBytes$1(privateKeyBytes, extractable, keyUsages);
return keyPair;
}
function isPolyfilledKey(key) {
return !!storageKeyBySecretKey_INTERNAL_ONLY_DO_NOT_EXPORT?.has(key) || !!publicKeyBytesStore?.has(key);
}
async function signPolyfill(key, data) {
if (key.type !== "private" || !key.usages.includes("sign")) throw new DOMException("Unable to use this key to sign", "InvalidAccessError");
const privateKeyBytes = getSecretKeyBytes_INTERNAL_ONLY_DO_NOT_EXPORT(key);
const payload = bufferSourceToUint8Array(data);
const signature$1 = await signAsync(payload, privateKeyBytes);
return signature$1;
}
async function verifyPolyfill(key, signature$1, data) {
if (key.type !== "public" || !key.usages.includes("verify")) throw new DOMException("Unable to use this key to verify", "InvalidAccessError");
const publicKeyBytes = await getPublicKeyBytes(key);
try {
return await verifyAsync(bufferSourceToUint8Array(signature$1), bufferSourceToUint8Array(data), publicKeyBytes);
} catch {
return false;
}
}
function assertValidKeyUsages(keyUsages, type) {
const prohibitedKeyUses = new Set([...type === "private" ? ["verify"] : ["sign"], ...PROHIBITED_KEY_USAGES]);
if (keyUsages.some((usage) => prohibitedKeyUses.has(usage))) throw new DOMException("Unsupported key usage for a Ed25519 key", "SyntaxError");
}
function importKeyPolyfill(format, keyData, extractable, keyUsages) {
if (format === "raw") {
const bytes = bufferSourceToUint8Array(keyData);
assertValidKeyUsages(keyUsages, "public");
if (bytes.length !== 32) throw new DOMException("Ed25519 raw keys must be exactly 32-bytes", "DataError");
const publicKey = {
[Symbol.toStringTag]: "CryptoKey",
algorithm: Object.freeze({ name: "Ed25519" }),
extractable,
type: "public",
usages: Object.freeze(keyUsages.filter((usage) => usage === "verify"))
};
const cache = publicKeyBytesStore ||= /* @__PURE__ */ new WeakMap();
cache.set(publicKey, bytes);
return publicKey;
}
if (format === "pkcs8") {
const bytes = bufferSourceToUint8Array(keyData);
assertValidKeyUsages(keyUsages, "private");
if (bytes.length !== 48) throw new DOMException("Invalid keyData", "DataError");
const header = bytes.slice(0, 16);
if (!header.every((val, i) => val === ED25519_PKCS8_HEADER[i])) throw new DOMException("Invalid keyData", "DataError");
const secretKeyBytes = bytes.slice(16);
const privateKey = {
[Symbol.toStringTag]: "CryptoKey",
algorithm: Object.freeze({ name: "Ed25519" }),
extractable,
type: "private",
usages: Object.freeze(keyUsages.filter((usage) => usage === "sign"))
};
const cache = storageKeyBySecretKey_INTERNAL_ONLY_DO_NOT_EXPORT ||= /* @__PURE__ */ new WeakMap();
cache.set(privateKey, secretKeyBytes);
return privateKey;
}
if (format === "jwk") {
const jwk = keyData;
const type = "d" in jwk ? "private" : "public";
assertValidKeyUsages(keyUsages, type);
const keyOps = new Set(jwk.key_ops ?? []);
const sameKeyUsages = keyUsages.length === keyOps.size && [...keyUsages].every((x) => keyOps.has(x));
if (jwk.kty !== "OKP" || jwk.crv !== "Ed25519" || jwk.ext !== extractable || !sameKeyUsages) throw new DOMException("Invalid Ed25519 JWK", "DataError");
if (type === "public" && !jwk.x) throw new DOMException("Ed25519 JWK is missing public key coordinates", "DataError");
if (type === "private" && !jwk.d) throw new DOMException("Ed25519 JWK is missing private key coordinates", "DataError");
const usageToKeep = type === "public" ? "verify" : "sign";
const key = Object.freeze({
[Symbol.toStringTag]: "CryptoKey",
algorithm: Object.freeze({ name: "Ed25519" }),
extractable,
type,
usages: Object.freeze(keyUsages.filter((usage) => usage === usageToKeep))
});
if (type === "public") {
const cache = publicKeyBytesStore ||= /* @__PURE__ */ new WeakMap();
cache.set(key, base64UrlDecode(jwk.x));
} else {
const cache = storageKeyBySecretKey_INTERNAL_ONLY_DO_NOT_EXPORT ||= /* @__PURE__ */ new WeakMap();
cache.set(key, base64UrlDecode(jwk.d));
}
return key;
}
throw new Error(`Importing Ed25519 keys in the "${format}" format is unimplemented`);
}
//#endregion
//#region src/polyfill/install.ts
function install() {
{
/**
* Create `crypto.subtle` if it doesn't exist.
*/
const originalCryptoObject = globalThis.crypto ||= {};
const originalSubtleCrypto = originalCryptoObject.subtle ||= {};
/**
* Override `SubtleCrypto#exportKey`
*/
const originalExportKey = originalSubtleCrypto.exportKey;
originalSubtleCrypto.exportKey = async (...args) => {
const [_, key] = args;
if (isPolyfilledKey(key)) return await exportKeyPolyfill(...args);
else if (originalExportKey) return await originalExportKey.apply(originalSubtleCrypto, args);
else throw new TypeError("No native `exportKey` function exists to handle this call");
};
/**
* Override `SubtleCrypto#generateKey`
*/
const originalGenerateKey = originalSubtleCrypto.generateKey;
let originalGenerateKeySupportsEd25519;
originalSubtleCrypto.generateKey = async (...args) => {
const [algorithm] = args;
if (algorithm !== "Ed25519") if (originalGenerateKey) return await originalGenerateKey.apply(originalSubtleCrypto, args);
else throw new TypeError("No native `generateKey` function exists to handle this call");
let optimisticallyGeneratedKeyPair;
if (originalGenerateKeySupportsEd25519 === void 0) originalGenerateKeySupportsEd25519 = new Promise((resolve) => {
if (!originalGenerateKey) {
resolve(originalGenerateKeySupportsEd25519 = false);
return;
}
originalGenerateKey.apply(originalSubtleCrypto, args).then((keyPair) => {
if (process.env.NODE_ENV !== "production") console.warn("`webcrypto-ed25519-polyfill` was installed in an environment that supports Ed25519 key manipulation natively. Falling back to the native implementation. Consider installing this polyfill only in environments where Ed25519 is not supported.");
if (originalSubtleCrypto.generateKey !== originalGenerateKey) originalSubtleCrypto.generateKey = originalGenerateKey;
optimisticallyGeneratedKeyPair = keyPair;
resolve(originalGenerateKeySupportsEd25519 = true);
}).catch(() => {
resolve(originalGenerateKeySupportsEd25519 = false);
});
});
if (typeof originalGenerateKeySupportsEd25519 === "boolean" ? originalGenerateKeySupportsEd25519 : await originalGenerateKeySupportsEd25519) if (optimisticallyGeneratedKeyPair) return optimisticallyGeneratedKeyPair;
else if (originalGenerateKey) return await originalGenerateKey.apply(originalSubtleCrypto, args);
else throw new TypeError("No native `generateKey` function exists to handle this call");
else {
const [_, extractable, keyUsages] = args;
return generateKeyPolyfill(extractable, keyUsages);
}
};
/**
* Override `SubtleCrypto#sign`
*/
const originalSign = originalSubtleCrypto.sign;
originalSubtleCrypto.sign = async (...args) => {
const [_, key] = args;
if (isPolyfilledKey(key)) {
const [_$1, ...rest] = args;
return await signPolyfill(...rest);
} else if (originalSign) return await originalSign.apply(originalSubtleCrypto, args);
else throw new TypeError("No native `sign` function exists to handle this call");
};
/**
* Override `SubtleCrypto#verify`
*/
const originalVerify = originalSubtleCrypto.verify;
originalSubtleCrypto.verify = async (...args) => {
const [_, key] = args;
if (isPolyfilledKey(key)) {
const [_$1, ...rest] = args;
return await verifyPolyfill(...rest);
} else if (originalVerify) return await originalVerify.apply(originalSubtleCrypto, args);
else throw new TypeError("No native `verify` function exists to handle this call");
};
/**
* Override `SubtleCrypto#importKey`
*/
const originalImportKey = originalSubtleCrypto.importKey;
let originalImportKeySupportsEd25519;
originalSubtleCrypto.importKey = async (...args) => {
const [format, keyData, algorithm] = args;
if (algorithm !== "Ed25519") if (originalImportKey) return await originalImportKey.apply(originalSubtleCrypto, args);
else throw new TypeError("No native `importKey` function exists to handle this call");
let optimisticallyImportedKey;
if (originalImportKeySupportsEd25519 === void 0) originalImportKeySupportsEd25519 = new Promise((resolve) => {
if (!originalImportKey) {
resolve(originalImportKeySupportsEd25519 = false);
return;
}
originalImportKey.apply(originalSubtleCrypto, args).then((key) => {
if (process.env.NODE_ENV !== "production") console.warn("`webcrypto-ed25519-polyfill` was included in an environment that supports Ed25519 key manipulation natively. Falling back to the native implementation. Consider including this polyfill only in environments where Ed25519 is not supported.");
if (originalSubtleCrypto.importKey !== originalImportKey) originalSubtleCrypto.importKey = originalImportKey;
optimisticallyImportedKey = key;
resolve(originalImportKeySupportsEd25519 = true);
}).catch(() => {
resolve(originalImportKeySupportsEd25519 = false);
});
});
if (typeof originalImportKey === "boolean" ? originalImportKeySupportsEd25519 : await originalImportKeySupportsEd25519) if (optimisticallyImportedKey) return optimisticallyImportedKey;
else if (originalImportKey) return await originalImportKey.apply(originalSubtleCrypto, args);
else throw new TypeError("No native `importKey` function exists to handle this call");
else {
const [_format, _keyData, _algorithm, extractable, keyUsages] = args;
return importKeyPolyfill(format, keyData, extractable, keyUsages);
}
};
}
}
//#endregion
//#region src/assertions.ts
function assertIsSecureContext() {}
let cachedEd25519Decision;
async function isEd25519CurveSupported(subtle$1) {
if (cachedEd25519Decision === void 0) cachedEd25519Decision = new Promise((resolve) => {
subtle$1.generateKey("Ed25519", false, ["sign", "verify"]).catch(() => {
resolve(cachedEd25519Decision = false);
}).then(() => {
resolve(cachedEd25519Decision = true);
});
});
if (typeof cachedEd25519Decision === "boolean") return cachedEd25519Decision;
else return await cachedEd25519Decision;
}
function assertDigestCapabilityIsAvailable() {
assertIsSecureContext();
if (typeof _crypto === "undefined" || typeof _crypto.subtle?.digest !== "function") throw new Error("SubtleCrypto.digest is not available");
}
async function assertKeyGenerationIsAvailable() {
assertIsSecureContext();
if (typeof _crypto === "undefined" || typeof _crypto.subtle?.generateKey !== "function") throw new Error("SubtleCrypto.generateKey is not available");
if (!await isEd25519CurveSupported(_crypto.subtle)) throw new Error("Ed25519 curve is not supported");
}
function assertKeyExporterIsAvailable() {
assertIsSecureContext();
if (typeof _crypto === "undefined" || typeof _crypto.subtle?.exportKey !== "function") throw new Error("SubtleCrypto.exportKey is not available");
}
function assertSigningCapabilityIsAvailable() {
assertIsSecureContext();
if (typeof _crypto === "undefined" || typeof _crypto.subtle?.sign !== "function") throw new Error("SubtleCrypto.sign is not available");
}
function assertVerificationCapabilityIsAvailable() {
assertIsSecureContext();
if (typeof _crypto === "undefined" || typeof _crypto.subtle?.verify !== "function") throw new Error("SubtleCrypto.verify is not available");
}
function assertPRNGIsAvailable() {
if (typeof _crypto === "undefined" || typeof _crypto.getRandomValues !== "function") throw new Error("Crypto.getRandomValues is not available");
}
/**
* Asserts that a given byte array is not empty.
*/
function assertByteArrayIsNotEmptyForCodec(codecDescription, bytes, offset = 0) {
if (bytes.length - offset <= 0) throw new Error(`Empty byte array for ${codecDescription}`);
}
/**
* Asserts that a given byte array has enough bytes to decode.
*/
function assertByteArrayHasEnoughBytesForCodec(codecDescription, expected, bytes, offset = 0) {
const bytesLength = bytes.length - offset;
if (bytesLength < expected) throw new Error(`Not enough bytes to decode ${codecDescription}. Expected: ${expected}, Actual: ${bytesLength}`);
}
/**
* Asserts that a given offset is within the byte array bounds.
* This range is between 0 and the byte array length and is inclusive.
* An offset equals to the byte array length is considered a valid offset
* as it allows the post-offset of codecs to signal the end of the byte array.
*/
function assertByteArrayOffsetIsNotOutOfRange(codecDescription, offset, bytesLength) {
if (offset < 0 || offset > bytesLength) throw new Error(`Offset is out of range for ${codecDescription}: ${offset}`);
}
/**
* Asserts that a given string matches a given alphabet.
*/
function assertValidBaseString(alphabet$3, testValue, givenValue = testValue) {
if (!testValue.match(new RegExp(`^[${alphabet$3}]*$`))) throw new Error(`Invalid base${alphabet$3.length} string: ${givenValue}`);
}
//#endregion
//#region src/codecs/core.ts
/**
* Get the encoded size of a given value in bytes.
*/
function getEncodedSize(value, encoder) {
return "fixedSize" in encoder ? encoder.fixedSize : encoder.getSizeFromValue(value);
}
function createEncoder(encoder) {
return Object.freeze({
...encoder,
encode: (value) => {
const bytes = new Uint8Array(getEncodedSize(value, encoder));
encoder.write(value, bytes, 0);
return bytes;
}
});
}
function createDecoder(decoder) {
return Object.freeze({
...decoder,
decode: (bytes, offset = 0) => decoder.read(bytes, offset)[0]
});
}
function createCodec(codec) {
return Object.freeze({
...codec,
decode: (bytes, offset = 0) => codec.read(bytes, offset)[0],
encode: (value) => {
const bytes = new Uint8Array(getEncodedSize(value, codec));
codec.write(value, bytes, 0);
return bytes;
}
});
}
function isFixedSize(codec) {
return "fixedSize" in codec && typeof codec.fixedSize === "number";
}
function assertIsFixedSize(codec) {
if (!isFixedSize(codec)) throw new Error("expected a fixed size codec");
}
function isVariableSize(codec) {
return !isFixedSize(codec);
}
function assertIsVariableSize(codec) {
if (!isVariableSize(codec)) throw new Error("expected a variable size codec");
}
function transformEncoder(encoder, unmap) {
return createEncoder({
...isVariableSize(encoder) ? {
...encoder,
getSizeFromValue: (value) => encoder.getSizeFromValue(unmap(value))
} : encoder,
write: (value, bytes, offset) => encoder.write(unmap(value), bytes, offset)
});
}
function transformDecoder(decoder, map) {
return createDecoder({
...decoder,
read: (bytes, offset) => {
const [value, newOffset] = decoder.read(bytes, offset);
return [map(value, bytes, offset), newOffset];
}
});
}
function combineCodec(encoder, decoder) {
if (isFixedSize(encoder) !== isFixedSize(decoder)) throw new Error("encoder and decoder size compatibility mismatch");
if (isFixedSize(encoder) && isFixedSize(decoder) && encoder.fixedSize !== decoder.fixedSize) throw new Error(`encoder and decoder fixed size mismatch ${encoder.fixedSize} !== ${decoder.fixedSize}`);
if (!isFixedSize(encoder) && !isFixedSize(decoder) && encoder.maxSize !== decoder.maxSize) throw new Error(`encoder and decoder max size mismatch ${encoder.maxSize} !== ${decoder.maxSize}`);
return {
...decoder,
...encoder,
decode: decoder.decode,
encode: encoder.encode,
read: decoder.read,
write: encoder.write
};
}
function transformCodec(codec, unmap, map) {
return createCodec({
...transformEncoder(codec, unmap),
read: map ? transformDecoder(codec, map).read : codec.read
});
}
/**
* Concatenates an array of `Uint8Array`s into a single `Uint8Array`.
* Reuses the original byte array when applicable.
*/
const mergeBytes = (byteArrays) => {
const nonEmptyByteArrays = byteArrays.filter((arr) => arr.length);
if (nonEmptyByteArrays.length === 0) return byteArrays.length ? byteArrays[0] : new Uint8Array();
if (nonEmptyByteArrays.length === 1) return nonEmptyByteArrays[0];
const totalLength = nonEmptyByteArrays.reduce((total, arr) => total + arr.length, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
nonEmptyByteArrays.forEach((arr) => {
result.set(arr, offset);
offset += arr.length;
});
return result;
};
/**
* Pads a `Uint8Array` with zeroes to the specified length.
* If the array is longer than the specified length, it is returned as-is.
*/
const padBytes = (bytes, length) => {
if (bytes.length >= length) return bytes;
const paddedBytes = new Uint8Array(length).fill(0);
paddedBytes.set(bytes);
return paddedBytes;
};
/**
* Fixes a `Uint8Array` to the specified length.
* If the array is longer than the specified length, it is truncated.
* If the array is shorter than the specified length, it is padded with zeroes.
*/
const fixBytes = (bytes, length) => padBytes(bytes.length <= length ? bytes : bytes.slice(0, length), length);
/**
* Returns true if and only if the provided `data` byte array contains
* the provided `bytes` byte array at the specified `offset`.
*/
function containsBytes(data, bytes, offset) {
const slice = offset === 0 && data.length === bytes.length ? data : data.slice(offset, offset + bytes.length);
if (slice.length !== bytes.length) return false;
return bytes.every((b, i) => b === slice[i]);
}
/**
* Creates a fixed-size encoder from a given encoder.
*
* @param encoder - The encoder to wrap into a fixed-size encoder.
* @param fixedBytes - The fixed number of bytes to write.
*/
function fixEncoderSize(encoder, fixedBytes) {
return createEncoder({
fixedSize: fixedBytes,
write: (value, bytes, offset) => {
const variableByteArray = encoder.encode(value);
const fixedByteArray = variableByteArray.length > fixedBytes ? variableByteArray.slice(0, fixedBytes) : variableByteArray;
bytes.set(fixedByteArray, offset);
return offset + fixedBytes;
}
});
}
/**
* Creates a fixed-size decoder from a given decoder.
*
* @param decoder - The decoder to wrap into a fixed-size decoder.
* @param fixedBytes - The fixed number of bytes to read.
*/
function fixDecoderSize(decoder, fixedBytes) {
return createDecoder({
fixedSize: fixedBytes,
read: (bytes, offset) => {
assertByteArrayHasEnoughBytesForCodec("fixCodecSize", fixedBytes, bytes, offset);
if (offset > 0 || bytes.length > fixedBytes) bytes = bytes.slice(offset, offset + fixedBytes);
if (isFixedSize(decoder)) bytes = fixBytes(bytes, decoder.fixedSize);
const [value] = decoder.read(bytes, 0);
return [value, offset + fixedBytes];
}
});
}
/**
* Creates a fixed-size codec from a given codec.
*
* @param codec - The codec to wrap into a fixed-size codec.
* @param fixedBytes - The fixed number of bytes to read/write.
*/
function fixCodecSize(codec, fixedBytes) {
return combineCodec(fixEncoderSize(codec, fixedBytes), fixDecoderSize(codec, fixedBytes));
}
//#endregion
//#region src/codecs/strings/base16.ts
var HexC = /* @__PURE__ */ function(HexC$1) {
HexC$1[HexC$1["ZERO"] = 48] = "ZERO";
HexC$1[HexC$1["NINE"] = 57] = "NINE";
HexC$1[HexC$1["A_UP"] = 65] = "A_UP";
HexC$1[HexC$1["F_UP"] = 70] = "F_UP";
HexC$1[HexC$1["A_LO"] = 97] = "A_LO";
HexC$1[HexC$1["F_LO"] = 102] = "F_LO";
return HexC$1;
}(HexC || {});
function charCodeToBase16(char) {
if (char >= HexC.ZERO && char <= HexC.NINE) return char - HexC.ZERO;
if (char >= HexC.A_UP && char <= HexC.F_UP) return char - (HexC.A_UP - 10);
if (char >= HexC.A_LO && char <= HexC.F_LO) return char - (HexC.A_LO - 10);
}
/** Encodes strings in base16. */
function getBase16Encoder() {
return createEncoder({
getSizeFromValue: (value) => Math.ceil(value.length / 2),
write(value, bytes, offset) {
const len = value.length;
const al = len / 2;
if (len === 1) {
const c = value.charCodeAt(0);
const n = charCodeToBase16(c);
if (n === void 0) throw new Error(`Invalid string for base16: ${value}`);
bytes.set([n], offset);
return 1 + offset;
}
const hexBytes = new Uint8Array(al);
for (let i = 0, j = 0; i < al; i++) {
const c1 = value.charCodeAt(j++);
const c2 = value.charCodeAt(j++);
const n1 = charCodeToBase16(c1);
const n2 = charCodeToBase16(c2);
if (n1 === void 0 || n2 === void 0 && !Number.isNaN(c2)) throw new Error(`Invalid string for base16: ${value}`);
hexBytes[i] = !Number.isNaN(c2) ? n1 << 4 | (n2 ?? 0) : n1;
}
bytes.set(hexBytes, offset);
return hexBytes.length + offset;
}
});
}
/** Decodes strings in base16. */
function getBase16Decoder() {
return createDecoder({ read(bytes, offset) {
const value = bytes.slice(offset).reduce((str, byte) => str + byte.toString(16).padStart(2, "0"), "");
return [value, bytes.length];
} });
}
/** Encodes and decodes strings in base16. */
function getBase16Codec() {
return combineCodec(getBase16Encoder(), getBase16Decoder());
}
const base16 = getBase16Codec();
//#endregion
//#region src/keys/signatures.ts
let base16Encoder;
function assertIsSignature(putativeSignature) {
if (!base16Encoder) base16Encoder = getBase16Encoder();
if (putativeSignature.length < 64 || putativeSignature.length > 88) throw new Error(`Invalid signature length: ${putativeSignature.length} out of range`);
const bytes = base16Encoder.encode(putativeSignature);
const numBytes = bytes.byteLength;
if (numBytes !== 64) throw new Error(`Invalid signature length: ${numBytes} bytes`);
}
function isSignature(putativeSignature) {
if (!base16Encoder) base16Encoder = getBase16Encoder();
if (putativeSignature.length < 64 || putativeSignature.length > 88) return false;
const bytes = base16Encoder.encode(putativeSignature);
const numBytes = bytes.byteLength;
if (numBytes !== 64) return false;
return true;
}
async function signBytes(key, data) {
assertSigningCapabilityIsAvailable();
const signedData = await crypto.subtle.sign("Ed25519", key, data);
return new Uint8Array(signedData);
}
function signature(putativeSignature) {
assertIsSignature(putativeSignature);
return putativeSignature;
}
async function verifySignature(key, signature$1, data) {
assertVerificationCapabilityIsAvailable();
return await crypto.subtle.verify("Ed25519", key, signature$1, data);
}
//#endregion
//#region src/keys/keys.ts
function addPkcs8Header(bytes) {
return new Uint8Array([
48,
46,
2,
1,
0,
48,
5,
6,
3,
43,
101,
112,
4,
34,
4,
32,
...bytes
]);
}
async function createPrivateKeyFromBytes(bytes, extractable) {
const actualLength = bytes.byteLength;
if (actualLength !== 32) throw new Error(`Invalid private key length: ${actualLength}`);
const privateKeyBytesPkcs8 = addPkcs8Header(bytes);
return _crypto.subtle.importKey("pkcs8", privateKeyBytesPkcs8, "Ed25519", extractable ?? false, ["sign"]);
}
async function getPublicKeyFromPrivateKey(privateKey, extractable = false) {
assertKeyExporterIsAvailable();
if (privateKey.extractable === false) throw new Error(`Private key ${privateKey} is not extractable`);
const jwk = await _crypto.subtle.exportKey("jwk", privateKey);
return await _crypto.subtle.importKey("jwk", {
crv: "Ed25519",
ext: extractable,
key_ops: ["verify"],
kty: "OKP",
x: jwk.x
}, "Ed25519", extractable, ["verify"]);
}
async function generateKeyPair() {
await assertKeyGenerationIsAvailable();
const keyPair = await _crypto.subtle.generateKey("Ed25519", false, ["sign", "verify"]);
return keyPair;
}
async function generateExtractableKeyPair() {
await assertKeyGenerationIsAvailable();
const keyPair = await _crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
return keyPair;
}
async function createKeyPairFromBytes(bytes, extractable) {
assertPRNGIsAvailable();
if (bytes.byteLength !== 64) throw new Error(`invalid key pair length: ${bytes.byteLength}`);
const [publicKey, privateKey] = await Promise.all([_crypto.subtle.importKey("raw", bytes.slice(32), "Ed25519", true, ["verify"]), createPrivateKeyFromBytes(bytes.slice(0, 32), extractable)]);
const randomBytes = new Uint8Array(32);
_crypto.getRandomValues(randomBytes);
const signedData = await signBytes(privateKey, randomBytes);
const isValid = await verifySignature(publicKey, signedData, randomBytes);
if (!isValid) throw new Error("public key must match private key");
return {
privateKey,
publicKey
};
}
async function createKeyPairFromPrivateKeyBytes(bytes, extractable = false) {
const privateKeyPromise = createPrivateKeyFromBytes(bytes, extractable);
const [publicKey, privateKey] = await Promise.all([(extractable ? privateKeyPromise : createPrivateKeyFromBytes(bytes, true)).then(async (privateKey$1) => await getPublicKeyFromPrivateKey(privateKey$1, true)), privateKeyPromise]);
return {
privateKey,
publicKey
};
}
//#endregion
//#region src/address.ts
function isAddress(putativeAddress) {
if (putativeAddress.length < 32 || putativeAddress.length > 44) return false;
const bytes = base16.encode(putativeAddress);
const numBytes = bytes.byteLength;
if (numBytes !== 32) return false;
return true;
}
function assertIsAddress(putativeAddress) {
if (putativeAddress.length < 32 || putativeAddress.length > 44) throw new Error(`Invalid address length: ${putativeAddress.length} out of range`);
const bytes = base16.encode(putativeAddress);
const numBytes = bytes.byteLength;
if (numBytes !== 32) throw new Error(`Invalid address length: ${numBytes} bytes`);
}
function address(putativeAddress) {
assertIsAddress(putativeAddress);
return putativeAddress;
}
let addressEncoder;
let addressDecoder;
let addressCodec;
function getAddressEncoder() {
return addressEncoder ??= transformEncoder(fixEncoderSize(base16, 32), (putativeAddress) => address(putativeAddress));
}
function getAddressDecoder() {
return addressDecoder ??= fixDecoderSize(base16, 32);
}
function getAddressCodec() {
return addressCodec ??= combineCodec(getAddressEncoder(), getAddressDecoder());
}
function getAddressComparator() {
return new Intl.Collator("en", {
caseFirst: "lower",
ignorePunctuation: false,
localeMatcher: "best fit",
numeric: false,
sensitivity: "variant",
usage: "sort"
}).compare;
}
async function exportBase16Key(key) {
assertKeyExporterIsAvailable();
if (!key.extractable || key.algorithm.name !== "Ed25519") throw new Error(`Key is not extractable or has an invalid algorithm: ${key.algorithm.name}`);
const keyBytes = await crypto.subtle.exportKey("raw", key);
return getAddressDecoder().decode(new Uint8Array(keyBytes));
}
function isKAccount(putativeAccount) {
return putativeAccount.startsWith("k:") && isAddress(putativeAccount.slice(2));
}
function assertIsKAccount(putativeAccount) {
if (!isKAccount(putativeAccount)) throw new Error(`Invalid account: ${putativeAccount}`);
}
function kAccount(putativeAccount) {
assertIsKAccount(putativeAccount);
return putativeAccount;
}
async function getKAccountFromPublicKey(publicKey) {
const address$1 = await exportBase16Key(publicKey);
return kAccount(`k:${address$1}`);
}
async function genKeyPair() {
const keyPair = await generateExtractableKeyPair();
return {
publicKey: await exportBase16Key(keyPair.publicKey),
privateKey: await exportBase16Key(keyPair.privateKey)
};
}
//#endregion
//#region src/codecs/strings/baseX.ts
/**
* Encodes a string using a custom alphabet by dividing
* by the base and handling leading zeroes.
* @see {@link getBaseXCodec} for a more detailed description.
*/
const getBaseXEncoder = (alphabet$3) => {
return createEncoder({
getSizeFromValue: (value) => {
const [leadingZeroes, tailChars] = partitionLeadingZeroes(value, alphabet$3[0]);
if (!tailChars) return value.length;
const base10Number = getBigIntFromBaseX(tailChars, alphabet$3);
return leadingZeroes.length + Math.ceil(base10Number.toString(16).length / 2);
},
write(value, bytes, offset) {
assertValidBaseString(alphabet$3, value);
if (value === "") return offset;
const [leadingZeroes, tailChars] = partitionLeadingZeroes(value, alphabet$3[0]);
if (!tailChars) {
bytes.set(new Uint8Array(leadingZeroes.length).fill(0), offset);
return offset + leadingZeroes.length;
}
let base10Number = getBigIntFromBaseX(tailChars, alphabet$3);
const tailBytes = [];
while (base10Number > 0n) {
tailBytes.unshift(Number(base10Number % 256n));
base10Number /= 256n;
}
const bytesToAdd = [...Array(leadingZeroes.length).fill(0), ...tailBytes];
bytes.set(bytesToAdd, offset);
return offset + bytesToAdd.length;
}
});
};
/**
* Decodes a string using a custom alphabet by dividing
* by the base and handling leading zeroes.
* @see {@link getBaseXCodec} for a more detailed description.
*/
const getBaseXDecoder = (alphabet$3) => {
return createDecoder({ read(rawBytes, offset) {
const bytes = offset === 0 ? rawBytes : rawBytes.slice(offset);
if (bytes.length === 0) return ["", 0];
let trailIndex = bytes.findIndex((n) => n !== 0);
trailIndex = trailIndex === -1 ? bytes.length : trailIndex;
const leadingZeroes = alphabet$3[0].repeat(trailIndex);
if (trailIndex === bytes.length) return [leadingZeroes, rawBytes.length];
const base10Number = bytes.slice(trailIndex).reduce((sum, byte) => sum * 256n + BigInt(byte), 0n);
const tailChars = getBaseXFromBigInt(base10Number, alphabet$3);
return [leadingZeroes + tailChars, rawBytes.length];
} });
};
/**
* A string codec that requires a custom alphabet and uses
* the length of that alphabet as the base. It then divides
* the input by the base as many times as necessary to get
* the output. It also supports leading zeroes by using the
* first character of the alphabet as the zero character.
*
* This can be used to create codecs such as base10 or base58.
*/
const getBaseXCodec = (alphabet$3) => combineCodec(getBaseXEncoder(alphabet$3), getBaseXDecoder(alphabet$3));
function partitionLeadingZeroes(value, zeroCharacter) {
const [leadingZeros, tailChars] = value.split(new RegExp(`((?!${zeroCharacter}).*)`));
return [leadingZeros, tailChars];
}
function getBigIntFromBaseX(value, alphabet$3) {
const base = BigInt(alphabet$3.length);
let sum = 0n;
for (const char of value) {
sum *= base;
sum += BigInt(alphabet$3.indexOf(char));
}
return sum;
}
function getBaseXFromBigInt(value, alphabet$3) {
const base = BigInt(alphabet$3.length);
const tailChars = [];
while (value > 0n) {
tailChars.unshift(alphabet$3[Number(value % base)]);
value /= base;
}
return tailChars.join("");
}
//#endregion
//#region src/codecs/strings/base10.ts
const alphabet$2 = "0123456789";
/** Encodes strings in base10. */
function getBase10Encoder() {
return getBaseXEncoder(alphabet$2);
}
/** Decodes strings in base10. */
function getBase10Decoder() {
return getBaseXDecoder(alphabet$2);
}
/** Encodes and decodes strings in base10. */
function getBase10Codec() {
return getBaseXCodec(alphabet$2);
}
const base10 = getBase10Codec();
//#endregion
//#region src/codecs/strings/base58.ts
const alphabet$1 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
/** Encodes strings in base58. */
function getBase58Encoder() {
return getBaseXEncoder(alphabet$1);
}
/** Decodes strings in base58. */
function getBase58Decoder() {
return getBaseXDecoder(alphabet$1);
}
/** Encodes and decodes strings in base58. */
function getBase58Codec() {
return getBaseXCodec(alphabet$1);
}
const base58 = getBase58Codec();
//#endregion
//#region src/codecs/strings/baseX-reslice.ts
/**
* Encodes a string using a custom alphabet by reslicing the bits of the byte array.
* @see {@link getBaseXResliceCodec} for a more detailed description.
*/
function getBaseXResliceEncoder(alphabet$3, bits) {
return createEncoder({
getSizeFromValue: (value) => Math.floor(value.length * bits / 8),
write(value, bytes, offset) {
assertValidBaseString(alphabet$3, value);
if (value === "") return offset;
const charIndices = [...value].map((c) => alphabet$3.indexOf(c));
const reslicedBytes = reslice(charIndices, bits, 8, false);
bytes.set(reslicedBytes, offset);
return reslicedBytes.length + offset;
}
});
}
/**
* Decodes a string using a custom alphabet by reslicing the bits of the byte array.
* @see {@link getBaseXResliceCodec} for a more detailed description.
*/
function getBaseXResliceDecoder(alphabet$3, bits) {
return createDecoder({ read(rawBytes, offset = 0) {
const bytes = offset === 0 ? rawBytes : rawBytes.slice(offset);
if (bytes.length === 0) return ["", rawBytes.length];
const charIndices = reslice([...bytes], 8, bits, true);
return [charIndices.map((i) => alphabet$3[i]).join(""), rawBytes.length];
} });
}
/**
* A string serializer that reslices bytes into custom chunks
* of bits that are then mapped to a custom alphabet.
*
* This can be used to create serializers whose alphabet
* is a power of 2 such as base16 or base64.
*/
function getBaseXResliceCodec(alphabet$3, bits) {
return combineCodec(getBaseXResliceEncoder(alphabet$3, bits), getBaseXResliceDecoder(alphabet$3, bits));
}
/** Helper function to reslice the bits inside bytes. */
function reslice(input, inputBits, outputBits, useRemainder) {
const output = [];
let accumulator = 0;
let bitsInAccumulator = 0;
const mask = (1 << outputBits) - 1;
for (const value of input) {
accumulator = accumulator << inputBits | value;
bitsInAccumulator += inputBits;
while (bitsInAccumulator >= outputBits) {
bitsInAccumulator -= outputBits;
output.push(accumulator >> bitsInAccumulator & mask);
}
}
if (useRemainder && bitsInAccumulator > 0) output.push(accumulator << outputBits - bitsInAccumulator & mask);
return output;
}
//#endregion
//#region src/codecs/strings/base64.ts
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/** Encodes strings in base64. */
function getBase64Encoder() {
return transformEncoder(getBaseXResliceEncoder(alphabet, 6), (value) => value.replace(/=/g, ""));
}
/** Decodes strings in base64. */
function getBase64Decoder() {
return transformDecoder(getBaseXResliceDecoder(alphabet, 6), (value) => value.padEnd(Math.ceil(value.length / 4) * 4, "="));
}
/** Encodes and decodes strings in base64. */
function getBase64Codec() {
return combineCodec(getBase64Encoder(), getBase64Decoder());
}
//#endregion
//#region src/codecs/strings/base64-url.ts
const base64UrlAlphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
/** Encodes strings in base64url. */
function getBase64UrlEncoder() {
return transformEncoder(getBaseXResliceEncoder(base64UrlAlphabet, 6), (value) => value);
}
/** Decodes strings in base64url. */
function getBase64UrlDecoder() {
return transformDecoder(getBaseXResliceDecoder(base64UrlAlphabet, 6), (value) => value);
}
/** Encodes and decodes strings in base64url. */
function getBase64UrlCodec() {
return combineCodec(getBase64UrlEncoder(), getBase64UrlDecoder());
}
const base64Url = getBase64UrlCodec();
//#endregion
//#region src/codecs/strings/null.ts
/**Removes null characters from a string. */
function removeNullCharacters(value) {
return value.replace(/\u0000/g, "");
}
/** Pads a string with null characters at the end. */
function padNullCharacters(value, chars) {
return value.padEnd(chars, "\0");
}
//#endregion
//#region src/codecs/text.ts
const TextDecoder = globalThis.TextDecoder;
const TextEncoder = globalThis.TextEncoder;
//#endregion
//#region src/codecs/strings/utf-8.ts
/** Encodes UTF-8 strings using the native `TextEncoder` API. */
function getUtf8Encoder() {
let textEncoder;
return createEncoder({
getSizeFromValue: (value) => (textEncoder ||= new TextEncoder()).encode(value).length,
write: (value, bytes, offset) => {
const bytesToAdd = (textEncoder ||= new TextEncoder()).encode(value);
bytes.set(bytesToAdd, offset);
return offset + bytesToAdd.length;
}
});
}
/** Decodes UTF-8 strings using the native `TextDecoder` API. */
function getUtf8Decoder() {
let textDecoder;
return createDecoder({ read(bytes, offset) {
const value = (textDecoder ||= new TextDecoder()).decode(bytes.slice(offset));
return [removeNullCharacters(value), bytes.length];
} });
}
/** Encodes and decodes UTF-8 strings using the native `TextEncoder` and `TextDecoder` API. */
function getUtf8Codec() {
return combineCodec(getUtf8Encoder(), getUtf8Decoder());
}
const utf8 = getUtf8Codec();
//#endregion
//#region src/hash/base64-url-blake2b.ts
function blake2bBase64Url(input) {
return base64Url.decode(blake2b(input, void 0, 32));
}
//#endregion
//#region src/stringify.ts
const objToString = Object.prototype.toString;
const objKeys = Object.keys || function(obj) {
const keys = [];
for (const name in obj) keys.push(name);
return keys;
};
function stringify(val, isArrayProp) {
let i, max, str, keys, key, propVal, toStr;
if (val === true) return "true";
if (val === false) return "false";
switch (typeof val) {
case "object": if (val === null) return null;
else if ("toJSON" in val && typeof val.toJSON === "function") return stringify(val.toJSON(), isArrayProp);
else {
toStr = objToString.call(val);
if (toStr === "[object Array]") {
str = "[";
max = val.length - 1;
for (i = 0; i < max; i++) str += stringify(val[i], true) + ",";
if (max > -1) str += stringify(val[i], true);
return str + "]";
} else if (toStr === "[object Object]") {
keys = objKeys(val).sort();
max = keys.length;
str = "";
i = 0;
while (i < max) {
key = keys[i];
propVal = stringify(val[key], false);
if (propVal !== void 0) {
if (str) str += ",";
str += JSON.stringify(key) + ":" + propVal;
}
i++;
}
return "{" + str + "}";
} else return JSON.stringify(val);
}
case "function":
case "undefined": return isArrayProp ? null : void 0;
case "bigint": return `${val.toString()}n`;
case "string": return JSON.stringify(val);
default: return isFinite(val) ? val : null;
}
}
function fastStableStringify(val) {
const returnVal = stringify(val, false);
if (returnVal !== void 0) return "" + returnVal;
}
//#endregion
//#region src/index.ts
install();
//#endregion
export { TextDecoder, TextEncoder, address, assertByteArrayHasEnoughBytesForCodec, assertByteArrayIsNotEmptyForCodec, assertByteArrayOffsetIsNotOutOfRange, assertDigestCapabilityIsAvailable, assertIsAddress, assertIsFixedSize, assertIsKAccount, assertIsSignature, assertIsVariableSize, assertKeyExporterIsAvailable, assertKeyGenerationIsAvailable, assertPRNGIsAvailable, assertSigningCapabilityIsAvailable, assertValidBaseString, assertVerificationCapabilityIsAvailable, base10, base16, base58, base64Url, blake2b, blake2bBase64Url, combineCodec, containsBytes, createCodec, createDecoder, createEncoder, createKeyPairFromBytes, createKeyPairFromPrivateKeyBytes, createPrivateKeyFromBytes, exportBase16Key, fastStableStringify, fixBytes, fixCodecSize, fixDecoderSize, fixEncoderSize, genKeyPair, generateExtractableKeyPair, generateKeyPair, getAddressCodec, getAddressComparator, getAddressDecoder, getAddressEncoder, getBase10Codec, getBase10Decoder, getBase10Encoder, getBase16Codec, getBase16Decoder, getBase16Encoder, getBase58Codec, getBase58Decoder, getBase58Encoder, getBase64Codec, getBase64Decoder, getBase64Encoder, getBase64UrlCodec, getBase64UrlDecoder, getBase64UrlEncoder, getBaseXCodec, getBaseXDecoder, getBaseXEncoder, getBaseXResliceCodec, getBaseXResliceDecoder, getBaseXResliceEncoder, getEncodedSize, getKAccountFromPublicKey, getPublicKeyFromPrivateKey, getUtf8Codec, getUtf8Decoder, getUtf8Encoder, isAddress, isFixedSize, isKAccount, isSignature, isVariableSize, kAccount, mergeBytes, padBytes, padNullCharacters, removeNullCharacters, signBytes, signature, transformCodec, transformDecoder, transformEncoder, utf8, verifySignature };
//# sourceMappingURL=index.native.mjs.map