3619 lines
111 KiB
JavaScript
3619 lines
111 KiB
JavaScript
// src/types/roles.ts
|
|
var SPACE_ROLE_LEVEL = {
|
|
["viewer" /* VIEWER */]: 0,
|
|
["participant" /* PARTICIPANT */]: 1,
|
|
["moderator" /* MODERATOR */]: 2,
|
|
["admin" /* ADMIN */]: 3
|
|
};
|
|
|
|
// src/types/index.ts
|
|
var AuthLevel;
|
|
((AuthLevel2) => {
|
|
AuthLevel2[AuthLevel2["BASIC"] = 1] = "BASIC";
|
|
AuthLevel2[AuthLevel2["STANDARD"] = 2] = "STANDARD";
|
|
AuthLevel2[AuthLevel2["ELEVATED"] = 3] = "ELEVATED";
|
|
AuthLevel2[AuthLevel2["CRITICAL"] = 4] = "CRITICAL";
|
|
})(AuthLevel ||= {});
|
|
var GuardianType;
|
|
((GuardianType2) => {
|
|
GuardianType2["SECONDARY_PASSKEY"] = "secondary_passkey";
|
|
GuardianType2["TRUSTED_CONTACT"] = "trusted_contact";
|
|
GuardianType2["HARDWARE_KEY"] = "hardware_key";
|
|
GuardianType2["INSTITUTIONAL"] = "institutional";
|
|
GuardianType2["TIME_DELAYED_SELF"] = "time_delayed_self";
|
|
})(GuardianType ||= {});
|
|
// src/client/webauthn.ts
|
|
var DEFAULT_CONFIG = {
|
|
rpId: "jeffemmett.com",
|
|
rpName: "EncryptID",
|
|
origin: typeof window !== "undefined" ? window.location.origin : "",
|
|
userVerification: "required",
|
|
timeout: 60000
|
|
};
|
|
var conditionalUIAbortController = null;
|
|
function abortConditionalUI() {
|
|
if (conditionalUIAbortController) {
|
|
conditionalUIAbortController.abort();
|
|
conditionalUIAbortController = null;
|
|
}
|
|
}
|
|
function bufferToBase64url(buffer) {
|
|
const bytes = new Uint8Array(buffer);
|
|
let binary = "";
|
|
for (let i = 0;i < bytes.byteLength; i++) {
|
|
binary += String.fromCharCode(bytes[i]);
|
|
}
|
|
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
}
|
|
function base64urlToBuffer(base64url) {
|
|
const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
|
|
const padding = "=".repeat((4 - base64.length % 4) % 4);
|
|
const binary = atob(base64 + padding);
|
|
const bytes = new Uint8Array(binary.length);
|
|
for (let i = 0;i < binary.length; i++) {
|
|
bytes[i] = binary.charCodeAt(i);
|
|
}
|
|
return bytes.buffer;
|
|
}
|
|
function generateChallenge() {
|
|
return crypto.getRandomValues(new Uint8Array(32)).buffer;
|
|
}
|
|
async function generatePRFSalt(purpose) {
|
|
const encoder = new TextEncoder;
|
|
const data = encoder.encode(`encryptid-prf-salt-${purpose}-v1`);
|
|
return crypto.subtle.digest("SHA-256", data);
|
|
}
|
|
async function registerPasskey(username, displayName, config = {}) {
|
|
abortConditionalUI();
|
|
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
if (!window.PublicKeyCredential) {
|
|
throw new Error("WebAuthn is not supported in this browser");
|
|
}
|
|
const platformAvailable = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
|
|
const userId = crypto.getRandomValues(new Uint8Array(32));
|
|
const challenge = generateChallenge();
|
|
const prfSalt = await generatePRFSalt("master-key");
|
|
const createOptions = {
|
|
publicKey: {
|
|
challenge: new Uint8Array(challenge),
|
|
rp: { id: cfg.rpId, name: cfg.rpName },
|
|
user: { id: userId, name: username, displayName },
|
|
pubKeyCredParams: [
|
|
{ alg: -7, type: "public-key" },
|
|
{ alg: -257, type: "public-key" }
|
|
],
|
|
authenticatorSelection: {
|
|
residentKey: "required",
|
|
requireResidentKey: true,
|
|
userVerification: cfg.userVerification,
|
|
authenticatorAttachment: platformAvailable ? "platform" : undefined
|
|
},
|
|
attestation: "none",
|
|
timeout: cfg.timeout,
|
|
extensions: {
|
|
prf: { eval: { first: new Uint8Array(prfSalt) } },
|
|
credProps: true
|
|
}
|
|
}
|
|
};
|
|
const credential = await navigator.credentials.create(createOptions);
|
|
if (!credential)
|
|
throw new Error("Failed to create credential");
|
|
const response = credential.response;
|
|
const prfSupported = credential.getClientExtensionResults()?.prf?.enabled === true;
|
|
const publicKey = response.getPublicKey();
|
|
if (!publicKey)
|
|
throw new Error("Failed to get public key from credential");
|
|
return {
|
|
credentialId: bufferToBase64url(credential.rawId),
|
|
publicKey,
|
|
userId: bufferToBase64url(userId.buffer),
|
|
username,
|
|
createdAt: Date.now(),
|
|
prfSupported,
|
|
transports: response.getTransports?.()
|
|
};
|
|
}
|
|
async function authenticatePasskey(credentialId, config = {}) {
|
|
abortConditionalUI();
|
|
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
if (!window.PublicKeyCredential) {
|
|
throw new Error("WebAuthn is not supported in this browser");
|
|
}
|
|
const challenge = generateChallenge();
|
|
const prfSalt = await generatePRFSalt("master-key");
|
|
const allowCredentials = credentialId ? [{ type: "public-key", id: new Uint8Array(base64urlToBuffer(credentialId)) }] : undefined;
|
|
const getOptions = {
|
|
publicKey: {
|
|
challenge: new Uint8Array(challenge),
|
|
rpId: cfg.rpId,
|
|
allowCredentials,
|
|
userVerification: cfg.userVerification,
|
|
timeout: cfg.timeout,
|
|
extensions: {
|
|
prf: { eval: { first: new Uint8Array(prfSalt) } }
|
|
}
|
|
}
|
|
};
|
|
const credential = await navigator.credentials.get(getOptions);
|
|
if (!credential)
|
|
throw new Error("Authentication failed");
|
|
const response = credential.response;
|
|
const prfResults = credential.getClientExtensionResults()?.prf?.results;
|
|
return {
|
|
credentialId: bufferToBase64url(credential.rawId),
|
|
userId: response.userHandle ? bufferToBase64url(response.userHandle) : "",
|
|
prfOutput: prfResults?.first,
|
|
signature: response.signature,
|
|
authenticatorData: response.authenticatorData
|
|
};
|
|
}
|
|
async function isConditionalMediationAvailable() {
|
|
if (!window.PublicKeyCredential)
|
|
return false;
|
|
if (typeof PublicKeyCredential.isConditionalMediationAvailable === "function") {
|
|
return PublicKeyCredential.isConditionalMediationAvailable();
|
|
}
|
|
return false;
|
|
}
|
|
async function startConditionalUI(config = {}) {
|
|
const available = await isConditionalMediationAvailable();
|
|
if (!available)
|
|
return null;
|
|
abortConditionalUI();
|
|
conditionalUIAbortController = new AbortController;
|
|
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
const challenge = generateChallenge();
|
|
const prfSalt = await generatePRFSalt("master-key");
|
|
try {
|
|
const credential = await navigator.credentials.get({
|
|
publicKey: {
|
|
challenge: new Uint8Array(challenge),
|
|
rpId: cfg.rpId,
|
|
userVerification: cfg.userVerification,
|
|
timeout: cfg.timeout,
|
|
extensions: {
|
|
prf: { eval: { first: new Uint8Array(prfSalt) } }
|
|
}
|
|
},
|
|
mediation: "conditional",
|
|
signal: conditionalUIAbortController.signal
|
|
});
|
|
conditionalUIAbortController = null;
|
|
if (!credential)
|
|
return null;
|
|
const response = credential.response;
|
|
const prfResults = credential.getClientExtensionResults()?.prf?.results;
|
|
return {
|
|
credentialId: bufferToBase64url(credential.rawId),
|
|
userId: response.userHandle ? bufferToBase64url(response.userHandle) : "",
|
|
prfOutput: prfResults?.first,
|
|
signature: response.signature,
|
|
authenticatorData: response.authenticatorData
|
|
};
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
async function detectCapabilities() {
|
|
const capabilities = {
|
|
webauthn: false,
|
|
platformAuthenticator: false,
|
|
conditionalUI: false,
|
|
prfExtension: false
|
|
};
|
|
if (!window.PublicKeyCredential)
|
|
return capabilities;
|
|
capabilities.webauthn = true;
|
|
try {
|
|
capabilities.platformAuthenticator = await PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();
|
|
} catch {
|
|
capabilities.platformAuthenticator = false;
|
|
}
|
|
capabilities.conditionalUI = await isConditionalMediationAvailable();
|
|
capabilities.prfExtension = true;
|
|
return capabilities;
|
|
}
|
|
|
|
// src/client/api-client.ts
|
|
var DEFAULT_SERVER_URL = "https://encryptid.jeffemmett.com";
|
|
|
|
class EncryptIDClient {
|
|
serverUrl;
|
|
constructor(serverUrl = DEFAULT_SERVER_URL) {
|
|
this.serverUrl = serverUrl.replace(/\/$/, "");
|
|
}
|
|
async registerStart(username, displayName) {
|
|
const res = await fetch(`${this.serverUrl}/api/register/start`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ username, displayName: displayName || username })
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ error: "Registration start failed" }));
|
|
throw new Error(err.error || `HTTP ${res.status}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
async registerComplete(challenge, credential, userId, username) {
|
|
const response = credential.response;
|
|
const publicKey = response.getPublicKey();
|
|
const res = await fetch(`${this.serverUrl}/api/register/complete`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
challenge,
|
|
userId,
|
|
username,
|
|
credential: {
|
|
credentialId: bufferToBase64url(credential.rawId),
|
|
publicKey: publicKey ? bufferToBase64url(publicKey) : "",
|
|
transports: response.getTransports?.() || []
|
|
}
|
|
})
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ error: "Registration complete failed" }));
|
|
throw new Error(err.error || `HTTP ${res.status}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
async authStart(credentialId) {
|
|
const res = await fetch(`${this.serverUrl}/api/auth/start`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(credentialId ? { credentialId } : {})
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ error: "Auth start failed" }));
|
|
throw new Error(err.error || `HTTP ${res.status}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
async authComplete(challenge, credential) {
|
|
const response = credential.response;
|
|
const prfResults = credential.getClientExtensionResults()?.prf?.results;
|
|
const res = await fetch(`${this.serverUrl}/api/auth/complete`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({
|
|
challenge,
|
|
credential: {
|
|
credentialId: bufferToBase64url(credential.rawId),
|
|
signature: bufferToBase64url(response.signature),
|
|
authenticatorData: bufferToBase64url(response.authenticatorData),
|
|
prfOutput: prfResults?.first ? bufferToBase64url(prfResults.first) : null
|
|
}
|
|
})
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ error: "Auth complete failed" }));
|
|
throw new Error(err.error || `HTTP ${res.status}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
async verifySession(token) {
|
|
const res = await fetch(`${this.serverUrl}/api/session/verify`, {
|
|
headers: { Authorization: `Bearer ${token}` }
|
|
});
|
|
return res.json();
|
|
}
|
|
async refreshToken(token) {
|
|
const res = await fetch(`${this.serverUrl}/api/session/refresh`, {
|
|
method: "POST",
|
|
headers: { Authorization: `Bearer ${token}` }
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ error: "Token refresh failed" }));
|
|
throw new Error(err.error || `HTTP ${res.status}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
async listCredentials(token) {
|
|
const res = await fetch(`${this.serverUrl}/api/user/credentials`, {
|
|
headers: { Authorization: `Bearer ${token}` }
|
|
});
|
|
if (!res.ok) {
|
|
throw new Error("Failed to list credentials");
|
|
}
|
|
return res.json();
|
|
}
|
|
async setRecoveryEmail(token, email) {
|
|
const res = await fetch(`${this.serverUrl}/api/recovery/email/set`, {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`
|
|
},
|
|
body: JSON.stringify({ email })
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ error: "Failed to set recovery email" }));
|
|
throw new Error(err.error || `HTTP ${res.status}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
async requestEmailRecovery(email) {
|
|
const res = await fetch(`${this.serverUrl}/api/recovery/email/request`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ email })
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ error: "Recovery request failed" }));
|
|
throw new Error(err.error || `HTTP ${res.status}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
async verifyRecoveryToken(recoveryToken) {
|
|
const res = await fetch(`${this.serverUrl}/api/recovery/email/verify`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ token: recoveryToken })
|
|
});
|
|
if (!res.ok) {
|
|
const err = await res.json().catch(() => ({ error: "Recovery verification failed" }));
|
|
throw new Error(err.error || `HTTP ${res.status}`);
|
|
}
|
|
return res.json();
|
|
}
|
|
async register(username, displayName, config) {
|
|
const { options, userId } = await this.registerStart(username, displayName);
|
|
const createOptions = {
|
|
publicKey: {
|
|
...options,
|
|
challenge: base64urlToUint8Array(options.challenge),
|
|
user: {
|
|
...options.user,
|
|
id: base64urlToUint8Array(options.user.id)
|
|
},
|
|
pubKeyCredParams: options.pubKeyCredParams,
|
|
extensions: {
|
|
credProps: true,
|
|
prf: { eval: { first: new Uint8Array(32) } }
|
|
}
|
|
}
|
|
};
|
|
const credential = await navigator.credentials.create(createOptions);
|
|
if (!credential)
|
|
throw new Error("Failed to create credential");
|
|
return this.registerComplete(options.challenge, credential, userId, username);
|
|
}
|
|
async authenticate(credentialId, config) {
|
|
const { options } = await this.authStart(credentialId);
|
|
const getOptions = {
|
|
publicKey: {
|
|
challenge: base64urlToUint8Array(options.challenge),
|
|
rpId: options.rpId,
|
|
userVerification: options.userVerification,
|
|
timeout: options.timeout,
|
|
allowCredentials: options.allowCredentials?.map((c) => ({
|
|
type: c.type,
|
|
id: base64urlToUint8Array(c.id),
|
|
transports: c.transports
|
|
})),
|
|
extensions: {
|
|
prf: { eval: { first: new Uint8Array(32) } }
|
|
}
|
|
}
|
|
};
|
|
const credential = await navigator.credentials.get(getOptions);
|
|
if (!credential)
|
|
throw new Error("Authentication failed");
|
|
const result = await this.authComplete(options.challenge, credential);
|
|
const prfResults = credential.getClientExtensionResults()?.prf?.results;
|
|
return {
|
|
...result,
|
|
prfOutput: prfResults?.first
|
|
};
|
|
}
|
|
}
|
|
function base64urlToUint8Array(base64url) {
|
|
const base64 = base64url.replace(/-/g, "+").replace(/_/g, "/");
|
|
const padding = "=".repeat((4 - base64.length % 4) % 4);
|
|
const binary = atob(base64 + padding);
|
|
const bytes = new Uint8Array(binary.length);
|
|
for (let i = 0;i < binary.length; i++) {
|
|
bytes[i] = binary.charCodeAt(i);
|
|
}
|
|
return bytes;
|
|
}
|
|
// node_modules/@noble/hashes/utils.js
|
|
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
function isBytes(a) {
|
|
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array";
|
|
}
|
|
function anumber(n, title = "") {
|
|
if (!Number.isSafeInteger(n) || n < 0) {
|
|
const prefix = title && `"${title}" `;
|
|
throw new Error(`${prefix}expected integer >= 0, got ${n}`);
|
|
}
|
|
}
|
|
function abytes(value, length, title = "") {
|
|
const bytes = isBytes(value);
|
|
const len = value?.length;
|
|
const needsLen = length !== undefined;
|
|
if (!bytes || needsLen && len !== length) {
|
|
const prefix = title && `"${title}" `;
|
|
const ofLen = needsLen ? ` of length ${length}` : "";
|
|
const got = bytes ? `length=${len}` : `type=${typeof value}`;
|
|
throw new Error(prefix + "expected Uint8Array" + ofLen + ", got " + got);
|
|
}
|
|
return value;
|
|
}
|
|
function ahash(h) {
|
|
if (typeof h !== "function" || typeof h.create !== "function")
|
|
throw new Error("Hash must wrapped by utils.createHasher");
|
|
anumber(h.outputLen);
|
|
anumber(h.blockLen);
|
|
}
|
|
function aexists(instance, checkFinished = true) {
|
|
if (instance.destroyed)
|
|
throw new Error("Hash instance has been destroyed");
|
|
if (checkFinished && instance.finished)
|
|
throw new Error("Hash#digest() has already been called");
|
|
}
|
|
function aoutput(out, instance) {
|
|
abytes(out, undefined, "digestInto() output");
|
|
const min = instance.outputLen;
|
|
if (out.length < min) {
|
|
throw new Error('"digestInto() output" expected to be of length >=' + min);
|
|
}
|
|
}
|
|
function u32(arr) {
|
|
return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4));
|
|
}
|
|
function clean(...arrays) {
|
|
for (let i = 0;i < arrays.length; i++) {
|
|
arrays[i].fill(0);
|
|
}
|
|
}
|
|
function createView(arr) {
|
|
return new DataView(arr.buffer, arr.byteOffset, arr.byteLength);
|
|
}
|
|
function rotr(word, shift) {
|
|
return word << 32 - shift | word >>> shift;
|
|
}
|
|
var isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68)();
|
|
function byteSwap(word) {
|
|
return word << 24 & 4278190080 | word << 8 & 16711680 | word >>> 8 & 65280 | word >>> 24 & 255;
|
|
}
|
|
function byteSwap32(arr) {
|
|
for (let i = 0;i < arr.length; i++) {
|
|
arr[i] = byteSwap(arr[i]);
|
|
}
|
|
return arr;
|
|
}
|
|
var swap32IfBE = isLE ? (u) => u : byteSwap32;
|
|
var hasHexBuiltin = /* @__PURE__ */ (() => typeof Uint8Array.from([]).toHex === "function" && typeof Uint8Array.fromHex === "function")();
|
|
var hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
|
|
function bytesToHex(bytes) {
|
|
abytes(bytes);
|
|
if (hasHexBuiltin)
|
|
return bytes.toHex();
|
|
let hex = "";
|
|
for (let i = 0;i < bytes.length; i++) {
|
|
hex += hexes[bytes[i]];
|
|
}
|
|
return hex;
|
|
}
|
|
var asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 };
|
|
function asciiToBase16(ch) {
|
|
if (ch >= asciis._0 && ch <= asciis._9)
|
|
return ch - asciis._0;
|
|
if (ch >= asciis.A && ch <= asciis.F)
|
|
return ch - (asciis.A - 10);
|
|
if (ch >= asciis.a && ch <= asciis.f)
|
|
return ch - (asciis.a - 10);
|
|
return;
|
|
}
|
|
function hexToBytes(hex) {
|
|
if (typeof hex !== "string")
|
|
throw new Error("hex string expected, got " + typeof hex);
|
|
if (hasHexBuiltin)
|
|
return Uint8Array.fromHex(hex);
|
|
const hl = hex.length;
|
|
const al = hl / 2;
|
|
if (hl % 2)
|
|
throw new Error("hex string expected, got unpadded hex of length " + hl);
|
|
const array = new Uint8Array(al);
|
|
for (let ai = 0, hi = 0;ai < al; ai++, hi += 2) {
|
|
const n1 = asciiToBase16(hex.charCodeAt(hi));
|
|
const n2 = asciiToBase16(hex.charCodeAt(hi + 1));
|
|
if (n1 === undefined || n2 === undefined) {
|
|
const char = hex[hi] + hex[hi + 1];
|
|
throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi);
|
|
}
|
|
array[ai] = n1 * 16 + n2;
|
|
}
|
|
return array;
|
|
}
|
|
function concatBytes(...arrays) {
|
|
let sum = 0;
|
|
for (let i = 0;i < arrays.length; i++) {
|
|
const a = arrays[i];
|
|
abytes(a);
|
|
sum += a.length;
|
|
}
|
|
const res = new Uint8Array(sum);
|
|
for (let i = 0, pad = 0;i < arrays.length; i++) {
|
|
const a = arrays[i];
|
|
res.set(a, pad);
|
|
pad += a.length;
|
|
}
|
|
return res;
|
|
}
|
|
function createHasher(hashCons, info = {}) {
|
|
const hashC = (msg, opts) => hashCons(opts).update(msg).digest();
|
|
const tmp = hashCons(undefined);
|
|
hashC.outputLen = tmp.outputLen;
|
|
hashC.blockLen = tmp.blockLen;
|
|
hashC.create = (opts) => hashCons(opts);
|
|
Object.assign(hashC, info);
|
|
return Object.freeze(hashC);
|
|
}
|
|
function randomBytes(bytesLength = 32) {
|
|
const cr = typeof globalThis === "object" ? globalThis.crypto : null;
|
|
if (typeof cr?.getRandomValues !== "function")
|
|
throw new Error("crypto.getRandomValues must be defined");
|
|
return cr.getRandomValues(new Uint8Array(bytesLength));
|
|
}
|
|
var oidNist = (suffix) => ({
|
|
oid: Uint8Array.from([6, 9, 96, 134, 72, 1, 101, 3, 4, 2, suffix])
|
|
});
|
|
|
|
// node_modules/@noble/hashes/_md.js
|
|
function Chi(a, b, c) {
|
|
return a & b ^ ~a & c;
|
|
}
|
|
function Maj(a, b, c) {
|
|
return a & b ^ a & c ^ b & c;
|
|
}
|
|
|
|
class HashMD {
|
|
blockLen;
|
|
outputLen;
|
|
padOffset;
|
|
isLE;
|
|
buffer;
|
|
view;
|
|
finished = false;
|
|
length = 0;
|
|
pos = 0;
|
|
destroyed = false;
|
|
constructor(blockLen, outputLen, padOffset, isLE2) {
|
|
this.blockLen = blockLen;
|
|
this.outputLen = outputLen;
|
|
this.padOffset = padOffset;
|
|
this.isLE = isLE2;
|
|
this.buffer = new Uint8Array(blockLen);
|
|
this.view = createView(this.buffer);
|
|
}
|
|
update(data) {
|
|
aexists(this);
|
|
abytes(data);
|
|
const { view, buffer, blockLen } = this;
|
|
const len = data.length;
|
|
for (let pos = 0;pos < len; ) {
|
|
const take = Math.min(blockLen - this.pos, len - pos);
|
|
if (take === blockLen) {
|
|
const dataView = createView(data);
|
|
for (;blockLen <= len - pos; pos += blockLen)
|
|
this.process(dataView, pos);
|
|
continue;
|
|
}
|
|
buffer.set(data.subarray(pos, pos + take), this.pos);
|
|
this.pos += take;
|
|
pos += take;
|
|
if (this.pos === blockLen) {
|
|
this.process(view, 0);
|
|
this.pos = 0;
|
|
}
|
|
}
|
|
this.length += data.length;
|
|
this.roundClean();
|
|
return this;
|
|
}
|
|
digestInto(out) {
|
|
aexists(this);
|
|
aoutput(out, this);
|
|
this.finished = true;
|
|
const { buffer, view, blockLen, isLE: isLE2 } = this;
|
|
let { pos } = this;
|
|
buffer[pos++] = 128;
|
|
clean(this.buffer.subarray(pos));
|
|
if (this.padOffset > blockLen - pos) {
|
|
this.process(view, 0);
|
|
pos = 0;
|
|
}
|
|
for (let i = pos;i < blockLen; i++)
|
|
buffer[i] = 0;
|
|
view.setBigUint64(blockLen - 8, BigInt(this.length * 8), isLE2);
|
|
this.process(view, 0);
|
|
const oview = createView(out);
|
|
const len = this.outputLen;
|
|
if (len % 4)
|
|
throw new Error("_sha2: outputLen must be aligned to 32bit");
|
|
const outLen = len / 4;
|
|
const state = this.get();
|
|
if (outLen > state.length)
|
|
throw new Error("_sha2: outputLen bigger than state");
|
|
for (let i = 0;i < outLen; i++)
|
|
oview.setUint32(4 * i, state[i], isLE2);
|
|
}
|
|
digest() {
|
|
const { buffer, outputLen } = this;
|
|
this.digestInto(buffer);
|
|
const res = buffer.slice(0, outputLen);
|
|
this.destroy();
|
|
return res;
|
|
}
|
|
_cloneInto(to) {
|
|
to ||= new this.constructor;
|
|
to.set(...this.get());
|
|
const { blockLen, buffer, length, finished, destroyed, pos } = this;
|
|
to.destroyed = destroyed;
|
|
to.finished = finished;
|
|
to.length = length;
|
|
to.pos = pos;
|
|
if (length % blockLen)
|
|
to.buffer.set(buffer);
|
|
return to;
|
|
}
|
|
clone() {
|
|
return this._cloneInto();
|
|
}
|
|
}
|
|
var SHA256_IV = /* @__PURE__ */ Uint32Array.from([
|
|
1779033703,
|
|
3144134277,
|
|
1013904242,
|
|
2773480762,
|
|
1359893119,
|
|
2600822924,
|
|
528734635,
|
|
1541459225
|
|
]);
|
|
var SHA224_IV = /* @__PURE__ */ Uint32Array.from([
|
|
3238371032,
|
|
914150663,
|
|
812702999,
|
|
4144912697,
|
|
4290775857,
|
|
1750603025,
|
|
1694076839,
|
|
3204075428
|
|
]);
|
|
var SHA384_IV = /* @__PURE__ */ Uint32Array.from([
|
|
3418070365,
|
|
3238371032,
|
|
1654270250,
|
|
914150663,
|
|
2438529370,
|
|
812702999,
|
|
355462360,
|
|
4144912697,
|
|
1731405415,
|
|
4290775857,
|
|
2394180231,
|
|
1750603025,
|
|
3675008525,
|
|
1694076839,
|
|
1203062813,
|
|
3204075428
|
|
]);
|
|
var SHA512_IV = /* @__PURE__ */ Uint32Array.from([
|
|
1779033703,
|
|
4089235720,
|
|
3144134277,
|
|
2227873595,
|
|
1013904242,
|
|
4271175723,
|
|
2773480762,
|
|
1595750129,
|
|
1359893119,
|
|
2917565137,
|
|
2600822924,
|
|
725511199,
|
|
528734635,
|
|
4215389547,
|
|
1541459225,
|
|
327033209
|
|
]);
|
|
|
|
// node_modules/@noble/hashes/_u64.js
|
|
var U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1);
|
|
var _32n = /* @__PURE__ */ BigInt(32);
|
|
function fromBig(n, le = false) {
|
|
if (le)
|
|
return { h: Number(n & U32_MASK64), l: Number(n >> _32n & U32_MASK64) };
|
|
return { h: Number(n >> _32n & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 };
|
|
}
|
|
function split(lst, le = false) {
|
|
const len = lst.length;
|
|
let Ah = new Uint32Array(len);
|
|
let Al = new Uint32Array(len);
|
|
for (let i = 0;i < len; i++) {
|
|
const { h, l } = fromBig(lst[i], le);
|
|
[Ah[i], Al[i]] = [h, l];
|
|
}
|
|
return [Ah, Al];
|
|
}
|
|
var shrSH = (h, _l, s) => h >>> s;
|
|
var shrSL = (h, l, s) => h << 32 - s | l >>> s;
|
|
var rotrSH = (h, l, s) => h >>> s | l << 32 - s;
|
|
var rotrSL = (h, l, s) => h << 32 - s | l >>> s;
|
|
var rotrBH = (h, l, s) => h << 64 - s | l >>> s - 32;
|
|
var rotrBL = (h, l, s) => h >>> s - 32 | l << 64 - s;
|
|
var rotlSH = (h, l, s) => h << s | l >>> 32 - s;
|
|
var rotlSL = (h, l, s) => l << s | h >>> 32 - s;
|
|
var rotlBH = (h, l, s) => l << s - 32 | h >>> 64 - s;
|
|
var rotlBL = (h, l, s) => h << s - 32 | l >>> 64 - s;
|
|
function add(Ah, Al, Bh, Bl) {
|
|
const l = (Al >>> 0) + (Bl >>> 0);
|
|
return { h: Ah + Bh + (l / 2 ** 32 | 0) | 0, l: l | 0 };
|
|
}
|
|
var add3L = (Al, Bl, Cl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0);
|
|
var add3H = (low, Ah, Bh, Ch) => Ah + Bh + Ch + (low / 2 ** 32 | 0) | 0;
|
|
var add4L = (Al, Bl, Cl, Dl) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0);
|
|
var add4H = (low, Ah, Bh, Ch, Dh) => Ah + Bh + Ch + Dh + (low / 2 ** 32 | 0) | 0;
|
|
var add5L = (Al, Bl, Cl, Dl, El) => (Al >>> 0) + (Bl >>> 0) + (Cl >>> 0) + (Dl >>> 0) + (El >>> 0);
|
|
var add5H = (low, Ah, Bh, Ch, Dh, Eh) => Ah + Bh + Ch + Dh + Eh + (low / 2 ** 32 | 0) | 0;
|
|
|
|
// node_modules/@noble/hashes/sha2.js
|
|
var SHA256_K = /* @__PURE__ */ Uint32Array.from([
|
|
1116352408,
|
|
1899447441,
|
|
3049323471,
|
|
3921009573,
|
|
961987163,
|
|
1508970993,
|
|
2453635748,
|
|
2870763221,
|
|
3624381080,
|
|
310598401,
|
|
607225278,
|
|
1426881987,
|
|
1925078388,
|
|
2162078206,
|
|
2614888103,
|
|
3248222580,
|
|
3835390401,
|
|
4022224774,
|
|
264347078,
|
|
604807628,
|
|
770255983,
|
|
1249150122,
|
|
1555081692,
|
|
1996064986,
|
|
2554220882,
|
|
2821834349,
|
|
2952996808,
|
|
3210313671,
|
|
3336571891,
|
|
3584528711,
|
|
113926993,
|
|
338241895,
|
|
666307205,
|
|
773529912,
|
|
1294757372,
|
|
1396182291,
|
|
1695183700,
|
|
1986661051,
|
|
2177026350,
|
|
2456956037,
|
|
2730485921,
|
|
2820302411,
|
|
3259730800,
|
|
3345764771,
|
|
3516065817,
|
|
3600352804,
|
|
4094571909,
|
|
275423344,
|
|
430227734,
|
|
506948616,
|
|
659060556,
|
|
883997877,
|
|
958139571,
|
|
1322822218,
|
|
1537002063,
|
|
1747873779,
|
|
1955562222,
|
|
2024104815,
|
|
2227730452,
|
|
2361852424,
|
|
2428436474,
|
|
2756734187,
|
|
3204031479,
|
|
3329325298
|
|
]);
|
|
var SHA256_W = /* @__PURE__ */ new Uint32Array(64);
|
|
|
|
class SHA2_32B extends HashMD {
|
|
constructor(outputLen) {
|
|
super(64, outputLen, 8, false);
|
|
}
|
|
get() {
|
|
const { A, B, C, D, E, F, G, H } = this;
|
|
return [A, B, C, D, E, F, G, H];
|
|
}
|
|
set(A, B, C, D, E, F, G, H) {
|
|
this.A = A | 0;
|
|
this.B = B | 0;
|
|
this.C = C | 0;
|
|
this.D = D | 0;
|
|
this.E = E | 0;
|
|
this.F = F | 0;
|
|
this.G = G | 0;
|
|
this.H = H | 0;
|
|
}
|
|
process(view, offset) {
|
|
for (let i = 0;i < 16; i++, offset += 4)
|
|
SHA256_W[i] = view.getUint32(offset, false);
|
|
for (let i = 16;i < 64; i++) {
|
|
const W15 = SHA256_W[i - 15];
|
|
const W2 = SHA256_W[i - 2];
|
|
const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3;
|
|
const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10;
|
|
SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0;
|
|
}
|
|
let { A, B, C, D, E, F, G, H } = this;
|
|
for (let i = 0;i < 64; i++) {
|
|
const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25);
|
|
const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0;
|
|
const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22);
|
|
const T2 = sigma0 + Maj(A, B, C) | 0;
|
|
H = G;
|
|
G = F;
|
|
F = E;
|
|
E = D + T1 | 0;
|
|
D = C;
|
|
C = B;
|
|
B = A;
|
|
A = T1 + T2 | 0;
|
|
}
|
|
A = A + this.A | 0;
|
|
B = B + this.B | 0;
|
|
C = C + this.C | 0;
|
|
D = D + this.D | 0;
|
|
E = E + this.E | 0;
|
|
F = F + this.F | 0;
|
|
G = G + this.G | 0;
|
|
H = H + this.H | 0;
|
|
this.set(A, B, C, D, E, F, G, H);
|
|
}
|
|
roundClean() {
|
|
clean(SHA256_W);
|
|
}
|
|
destroy() {
|
|
this.set(0, 0, 0, 0, 0, 0, 0, 0);
|
|
clean(this.buffer);
|
|
}
|
|
}
|
|
|
|
class _SHA256 extends SHA2_32B {
|
|
A = SHA256_IV[0] | 0;
|
|
B = SHA256_IV[1] | 0;
|
|
C = SHA256_IV[2] | 0;
|
|
D = SHA256_IV[3] | 0;
|
|
E = SHA256_IV[4] | 0;
|
|
F = SHA256_IV[5] | 0;
|
|
G = SHA256_IV[6] | 0;
|
|
H = SHA256_IV[7] | 0;
|
|
constructor() {
|
|
super(32);
|
|
}
|
|
}
|
|
|
|
class _SHA224 extends SHA2_32B {
|
|
A = SHA224_IV[0] | 0;
|
|
B = SHA224_IV[1] | 0;
|
|
C = SHA224_IV[2] | 0;
|
|
D = SHA224_IV[3] | 0;
|
|
E = SHA224_IV[4] | 0;
|
|
F = SHA224_IV[5] | 0;
|
|
G = SHA224_IV[6] | 0;
|
|
H = SHA224_IV[7] | 0;
|
|
constructor() {
|
|
super(28);
|
|
}
|
|
}
|
|
var K512 = /* @__PURE__ */ (() => split([
|
|
"0x428a2f98d728ae22",
|
|
"0x7137449123ef65cd",
|
|
"0xb5c0fbcfec4d3b2f",
|
|
"0xe9b5dba58189dbbc",
|
|
"0x3956c25bf348b538",
|
|
"0x59f111f1b605d019",
|
|
"0x923f82a4af194f9b",
|
|
"0xab1c5ed5da6d8118",
|
|
"0xd807aa98a3030242",
|
|
"0x12835b0145706fbe",
|
|
"0x243185be4ee4b28c",
|
|
"0x550c7dc3d5ffb4e2",
|
|
"0x72be5d74f27b896f",
|
|
"0x80deb1fe3b1696b1",
|
|
"0x9bdc06a725c71235",
|
|
"0xc19bf174cf692694",
|
|
"0xe49b69c19ef14ad2",
|
|
"0xefbe4786384f25e3",
|
|
"0x0fc19dc68b8cd5b5",
|
|
"0x240ca1cc77ac9c65",
|
|
"0x2de92c6f592b0275",
|
|
"0x4a7484aa6ea6e483",
|
|
"0x5cb0a9dcbd41fbd4",
|
|
"0x76f988da831153b5",
|
|
"0x983e5152ee66dfab",
|
|
"0xa831c66d2db43210",
|
|
"0xb00327c898fb213f",
|
|
"0xbf597fc7beef0ee4",
|
|
"0xc6e00bf33da88fc2",
|
|
"0xd5a79147930aa725",
|
|
"0x06ca6351e003826f",
|
|
"0x142929670a0e6e70",
|
|
"0x27b70a8546d22ffc",
|
|
"0x2e1b21385c26c926",
|
|
"0x4d2c6dfc5ac42aed",
|
|
"0x53380d139d95b3df",
|
|
"0x650a73548baf63de",
|
|
"0x766a0abb3c77b2a8",
|
|
"0x81c2c92e47edaee6",
|
|
"0x92722c851482353b",
|
|
"0xa2bfe8a14cf10364",
|
|
"0xa81a664bbc423001",
|
|
"0xc24b8b70d0f89791",
|
|
"0xc76c51a30654be30",
|
|
"0xd192e819d6ef5218",
|
|
"0xd69906245565a910",
|
|
"0xf40e35855771202a",
|
|
"0x106aa07032bbd1b8",
|
|
"0x19a4c116b8d2d0c8",
|
|
"0x1e376c085141ab53",
|
|
"0x2748774cdf8eeb99",
|
|
"0x34b0bcb5e19b48a8",
|
|
"0x391c0cb3c5c95a63",
|
|
"0x4ed8aa4ae3418acb",
|
|
"0x5b9cca4f7763e373",
|
|
"0x682e6ff3d6b2b8a3",
|
|
"0x748f82ee5defb2fc",
|
|
"0x78a5636f43172f60",
|
|
"0x84c87814a1f0ab72",
|
|
"0x8cc702081a6439ec",
|
|
"0x90befffa23631e28",
|
|
"0xa4506cebde82bde9",
|
|
"0xbef9a3f7b2c67915",
|
|
"0xc67178f2e372532b",
|
|
"0xca273eceea26619c",
|
|
"0xd186b8c721c0c207",
|
|
"0xeada7dd6cde0eb1e",
|
|
"0xf57d4f7fee6ed178",
|
|
"0x06f067aa72176fba",
|
|
"0x0a637dc5a2c898a6",
|
|
"0x113f9804bef90dae",
|
|
"0x1b710b35131c471b",
|
|
"0x28db77f523047d84",
|
|
"0x32caab7b40c72493",
|
|
"0x3c9ebe0a15c9bebc",
|
|
"0x431d67c49c100d4c",
|
|
"0x4cc5d4becb3e42b6",
|
|
"0x597f299cfc657e2a",
|
|
"0x5fcb6fab3ad6faec",
|
|
"0x6c44198c4a475817"
|
|
].map((n) => BigInt(n))))();
|
|
var SHA512_Kh = /* @__PURE__ */ (() => K512[0])();
|
|
var SHA512_Kl = /* @__PURE__ */ (() => K512[1])();
|
|
var SHA512_W_H = /* @__PURE__ */ new Uint32Array(80);
|
|
var SHA512_W_L = /* @__PURE__ */ new Uint32Array(80);
|
|
|
|
class SHA2_64B extends HashMD {
|
|
constructor(outputLen) {
|
|
super(128, outputLen, 16, false);
|
|
}
|
|
get() {
|
|
const { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
|
|
return [Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl];
|
|
}
|
|
set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl) {
|
|
this.Ah = Ah | 0;
|
|
this.Al = Al | 0;
|
|
this.Bh = Bh | 0;
|
|
this.Bl = Bl | 0;
|
|
this.Ch = Ch | 0;
|
|
this.Cl = Cl | 0;
|
|
this.Dh = Dh | 0;
|
|
this.Dl = Dl | 0;
|
|
this.Eh = Eh | 0;
|
|
this.El = El | 0;
|
|
this.Fh = Fh | 0;
|
|
this.Fl = Fl | 0;
|
|
this.Gh = Gh | 0;
|
|
this.Gl = Gl | 0;
|
|
this.Hh = Hh | 0;
|
|
this.Hl = Hl | 0;
|
|
}
|
|
process(view, offset) {
|
|
for (let i = 0;i < 16; i++, offset += 4) {
|
|
SHA512_W_H[i] = view.getUint32(offset);
|
|
SHA512_W_L[i] = view.getUint32(offset += 4);
|
|
}
|
|
for (let i = 16;i < 80; i++) {
|
|
const W15h = SHA512_W_H[i - 15] | 0;
|
|
const W15l = SHA512_W_L[i - 15] | 0;
|
|
const s0h = rotrSH(W15h, W15l, 1) ^ rotrSH(W15h, W15l, 8) ^ shrSH(W15h, W15l, 7);
|
|
const s0l = rotrSL(W15h, W15l, 1) ^ rotrSL(W15h, W15l, 8) ^ shrSL(W15h, W15l, 7);
|
|
const W2h = SHA512_W_H[i - 2] | 0;
|
|
const W2l = SHA512_W_L[i - 2] | 0;
|
|
const s1h = rotrSH(W2h, W2l, 19) ^ rotrBH(W2h, W2l, 61) ^ shrSH(W2h, W2l, 6);
|
|
const s1l = rotrSL(W2h, W2l, 19) ^ rotrBL(W2h, W2l, 61) ^ shrSL(W2h, W2l, 6);
|
|
const SUMl = add4L(s0l, s1l, SHA512_W_L[i - 7], SHA512_W_L[i - 16]);
|
|
const SUMh = add4H(SUMl, s0h, s1h, SHA512_W_H[i - 7], SHA512_W_H[i - 16]);
|
|
SHA512_W_H[i] = SUMh | 0;
|
|
SHA512_W_L[i] = SUMl | 0;
|
|
}
|
|
let { Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl } = this;
|
|
for (let i = 0;i < 80; i++) {
|
|
const sigma1h = rotrSH(Eh, El, 14) ^ rotrSH(Eh, El, 18) ^ rotrBH(Eh, El, 41);
|
|
const sigma1l = rotrSL(Eh, El, 14) ^ rotrSL(Eh, El, 18) ^ rotrBL(Eh, El, 41);
|
|
const CHIh = Eh & Fh ^ ~Eh & Gh;
|
|
const CHIl = El & Fl ^ ~El & Gl;
|
|
const T1ll = add5L(Hl, sigma1l, CHIl, SHA512_Kl[i], SHA512_W_L[i]);
|
|
const T1h = add5H(T1ll, Hh, sigma1h, CHIh, SHA512_Kh[i], SHA512_W_H[i]);
|
|
const T1l = T1ll | 0;
|
|
const sigma0h = rotrSH(Ah, Al, 28) ^ rotrBH(Ah, Al, 34) ^ rotrBH(Ah, Al, 39);
|
|
const sigma0l = rotrSL(Ah, Al, 28) ^ rotrBL(Ah, Al, 34) ^ rotrBL(Ah, Al, 39);
|
|
const MAJh = Ah & Bh ^ Ah & Ch ^ Bh & Ch;
|
|
const MAJl = Al & Bl ^ Al & Cl ^ Bl & Cl;
|
|
Hh = Gh | 0;
|
|
Hl = Gl | 0;
|
|
Gh = Fh | 0;
|
|
Gl = Fl | 0;
|
|
Fh = Eh | 0;
|
|
Fl = El | 0;
|
|
({ h: Eh, l: El } = add(Dh | 0, Dl | 0, T1h | 0, T1l | 0));
|
|
Dh = Ch | 0;
|
|
Dl = Cl | 0;
|
|
Ch = Bh | 0;
|
|
Cl = Bl | 0;
|
|
Bh = Ah | 0;
|
|
Bl = Al | 0;
|
|
const All = add3L(T1l, sigma0l, MAJl);
|
|
Ah = add3H(All, T1h, sigma0h, MAJh);
|
|
Al = All | 0;
|
|
}
|
|
({ h: Ah, l: Al } = add(this.Ah | 0, this.Al | 0, Ah | 0, Al | 0));
|
|
({ h: Bh, l: Bl } = add(this.Bh | 0, this.Bl | 0, Bh | 0, Bl | 0));
|
|
({ h: Ch, l: Cl } = add(this.Ch | 0, this.Cl | 0, Ch | 0, Cl | 0));
|
|
({ h: Dh, l: Dl } = add(this.Dh | 0, this.Dl | 0, Dh | 0, Dl | 0));
|
|
({ h: Eh, l: El } = add(this.Eh | 0, this.El | 0, Eh | 0, El | 0));
|
|
({ h: Fh, l: Fl } = add(this.Fh | 0, this.Fl | 0, Fh | 0, Fl | 0));
|
|
({ h: Gh, l: Gl } = add(this.Gh | 0, this.Gl | 0, Gh | 0, Gl | 0));
|
|
({ h: Hh, l: Hl } = add(this.Hh | 0, this.Hl | 0, Hh | 0, Hl | 0));
|
|
this.set(Ah, Al, Bh, Bl, Ch, Cl, Dh, Dl, Eh, El, Fh, Fl, Gh, Gl, Hh, Hl);
|
|
}
|
|
roundClean() {
|
|
clean(SHA512_W_H, SHA512_W_L);
|
|
}
|
|
destroy() {
|
|
clean(this.buffer);
|
|
this.set(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
|
}
|
|
}
|
|
|
|
class _SHA512 extends SHA2_64B {
|
|
Ah = SHA512_IV[0] | 0;
|
|
Al = SHA512_IV[1] | 0;
|
|
Bh = SHA512_IV[2] | 0;
|
|
Bl = SHA512_IV[3] | 0;
|
|
Ch = SHA512_IV[4] | 0;
|
|
Cl = SHA512_IV[5] | 0;
|
|
Dh = SHA512_IV[6] | 0;
|
|
Dl = SHA512_IV[7] | 0;
|
|
Eh = SHA512_IV[8] | 0;
|
|
El = SHA512_IV[9] | 0;
|
|
Fh = SHA512_IV[10] | 0;
|
|
Fl = SHA512_IV[11] | 0;
|
|
Gh = SHA512_IV[12] | 0;
|
|
Gl = SHA512_IV[13] | 0;
|
|
Hh = SHA512_IV[14] | 0;
|
|
Hl = SHA512_IV[15] | 0;
|
|
constructor() {
|
|
super(64);
|
|
}
|
|
}
|
|
|
|
class _SHA384 extends SHA2_64B {
|
|
Ah = SHA384_IV[0] | 0;
|
|
Al = SHA384_IV[1] | 0;
|
|
Bh = SHA384_IV[2] | 0;
|
|
Bl = SHA384_IV[3] | 0;
|
|
Ch = SHA384_IV[4] | 0;
|
|
Cl = SHA384_IV[5] | 0;
|
|
Dh = SHA384_IV[6] | 0;
|
|
Dl = SHA384_IV[7] | 0;
|
|
Eh = SHA384_IV[8] | 0;
|
|
El = SHA384_IV[9] | 0;
|
|
Fh = SHA384_IV[10] | 0;
|
|
Fl = SHA384_IV[11] | 0;
|
|
Gh = SHA384_IV[12] | 0;
|
|
Gl = SHA384_IV[13] | 0;
|
|
Hh = SHA384_IV[14] | 0;
|
|
Hl = SHA384_IV[15] | 0;
|
|
constructor() {
|
|
super(48);
|
|
}
|
|
}
|
|
var T224_IV = /* @__PURE__ */ Uint32Array.from([
|
|
2352822216,
|
|
424955298,
|
|
1944164710,
|
|
2312950998,
|
|
502970286,
|
|
855612546,
|
|
1738396948,
|
|
1479516111,
|
|
258812777,
|
|
2077511080,
|
|
2011393907,
|
|
79989058,
|
|
1067287976,
|
|
1780299464,
|
|
286451373,
|
|
2446758561
|
|
]);
|
|
var T256_IV = /* @__PURE__ */ Uint32Array.from([
|
|
573645204,
|
|
4230739756,
|
|
2673172387,
|
|
3360449730,
|
|
596883563,
|
|
1867755857,
|
|
2520282905,
|
|
1497426621,
|
|
2519219938,
|
|
2827943907,
|
|
3193839141,
|
|
1401305490,
|
|
721525244,
|
|
746961066,
|
|
246885852,
|
|
2177182882
|
|
]);
|
|
|
|
class _SHA512_224 extends SHA2_64B {
|
|
Ah = T224_IV[0] | 0;
|
|
Al = T224_IV[1] | 0;
|
|
Bh = T224_IV[2] | 0;
|
|
Bl = T224_IV[3] | 0;
|
|
Ch = T224_IV[4] | 0;
|
|
Cl = T224_IV[5] | 0;
|
|
Dh = T224_IV[6] | 0;
|
|
Dl = T224_IV[7] | 0;
|
|
Eh = T224_IV[8] | 0;
|
|
El = T224_IV[9] | 0;
|
|
Fh = T224_IV[10] | 0;
|
|
Fl = T224_IV[11] | 0;
|
|
Gh = T224_IV[12] | 0;
|
|
Gl = T224_IV[13] | 0;
|
|
Hh = T224_IV[14] | 0;
|
|
Hl = T224_IV[15] | 0;
|
|
constructor() {
|
|
super(28);
|
|
}
|
|
}
|
|
|
|
class _SHA512_256 extends SHA2_64B {
|
|
Ah = T256_IV[0] | 0;
|
|
Al = T256_IV[1] | 0;
|
|
Bh = T256_IV[2] | 0;
|
|
Bl = T256_IV[3] | 0;
|
|
Ch = T256_IV[4] | 0;
|
|
Cl = T256_IV[5] | 0;
|
|
Dh = T256_IV[6] | 0;
|
|
Dl = T256_IV[7] | 0;
|
|
Eh = T256_IV[8] | 0;
|
|
El = T256_IV[9] | 0;
|
|
Fh = T256_IV[10] | 0;
|
|
Fl = T256_IV[11] | 0;
|
|
Gh = T256_IV[12] | 0;
|
|
Gl = T256_IV[13] | 0;
|
|
Hh = T256_IV[14] | 0;
|
|
Hl = T256_IV[15] | 0;
|
|
constructor() {
|
|
super(32);
|
|
}
|
|
}
|
|
var sha256 = /* @__PURE__ */ createHasher(() => new _SHA256, /* @__PURE__ */ oidNist(1));
|
|
|
|
// node_modules/@noble/curves/utils.js
|
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
var _0n = /* @__PURE__ */ BigInt(0);
|
|
var _1n = /* @__PURE__ */ BigInt(1);
|
|
function abool(value, title = "") {
|
|
if (typeof value !== "boolean") {
|
|
const prefix = title && `"${title}" `;
|
|
throw new Error(prefix + "expected boolean, got type=" + typeof value);
|
|
}
|
|
return value;
|
|
}
|
|
function abignumber(n) {
|
|
if (typeof n === "bigint") {
|
|
if (!isPosBig(n))
|
|
throw new Error("positive bigint expected, got " + n);
|
|
} else
|
|
anumber(n);
|
|
return n;
|
|
}
|
|
function numberToHexUnpadded(num) {
|
|
const hex = abignumber(num).toString(16);
|
|
return hex.length & 1 ? "0" + hex : hex;
|
|
}
|
|
function hexToNumber(hex) {
|
|
if (typeof hex !== "string")
|
|
throw new Error("hex string expected, got " + typeof hex);
|
|
return hex === "" ? _0n : BigInt("0x" + hex);
|
|
}
|
|
function bytesToNumberBE(bytes) {
|
|
return hexToNumber(bytesToHex(bytes));
|
|
}
|
|
function bytesToNumberLE(bytes) {
|
|
return hexToNumber(bytesToHex(copyBytes(abytes(bytes)).reverse()));
|
|
}
|
|
function numberToBytesBE(n, len) {
|
|
anumber(len);
|
|
n = abignumber(n);
|
|
const res = hexToBytes(n.toString(16).padStart(len * 2, "0"));
|
|
if (res.length !== len)
|
|
throw new Error("number too large");
|
|
return res;
|
|
}
|
|
function numberToBytesLE(n, len) {
|
|
return numberToBytesBE(n, len).reverse();
|
|
}
|
|
function copyBytes(bytes) {
|
|
return Uint8Array.from(bytes);
|
|
}
|
|
var isPosBig = (n) => typeof n === "bigint" && _0n <= n;
|
|
function inRange(n, min, max) {
|
|
return isPosBig(n) && isPosBig(min) && isPosBig(max) && min <= n && n < max;
|
|
}
|
|
function aInRange(title, n, min, max) {
|
|
if (!inRange(n, min, max))
|
|
throw new Error("expected valid " + title + ": " + min + " <= n < " + max + ", got " + n);
|
|
}
|
|
function bitLen(n) {
|
|
let len;
|
|
for (len = 0;n > _0n; n >>= _1n, len += 1)
|
|
;
|
|
return len;
|
|
}
|
|
var bitMask = (n) => (_1n << BigInt(n)) - _1n;
|
|
function createHmacDrbg(hashLen, qByteLen, hmacFn) {
|
|
anumber(hashLen, "hashLen");
|
|
anumber(qByteLen, "qByteLen");
|
|
if (typeof hmacFn !== "function")
|
|
throw new Error("hmacFn must be a function");
|
|
const u8n = (len) => new Uint8Array(len);
|
|
const NULL = Uint8Array.of();
|
|
const byte0 = Uint8Array.of(0);
|
|
const byte1 = Uint8Array.of(1);
|
|
const _maxDrbgIters = 1000;
|
|
let v = u8n(hashLen);
|
|
let k = u8n(hashLen);
|
|
let i = 0;
|
|
const reset = () => {
|
|
v.fill(1);
|
|
k.fill(0);
|
|
i = 0;
|
|
};
|
|
const h = (...msgs) => hmacFn(k, concatBytes(v, ...msgs));
|
|
const reseed = (seed = NULL) => {
|
|
k = h(byte0, seed);
|
|
v = h();
|
|
if (seed.length === 0)
|
|
return;
|
|
k = h(byte1, seed);
|
|
v = h();
|
|
};
|
|
const gen = () => {
|
|
if (i++ >= _maxDrbgIters)
|
|
throw new Error("drbg: tried max amount of iterations");
|
|
let len = 0;
|
|
const out = [];
|
|
while (len < qByteLen) {
|
|
v = h();
|
|
const sl = v.slice();
|
|
out.push(sl);
|
|
len += v.length;
|
|
}
|
|
return concatBytes(...out);
|
|
};
|
|
const genUntil = (seed, pred) => {
|
|
reset();
|
|
reseed(seed);
|
|
let res = undefined;
|
|
while (!(res = pred(gen())))
|
|
reseed();
|
|
reset();
|
|
return res;
|
|
};
|
|
return genUntil;
|
|
}
|
|
function validateObject(object, fields = {}, optFields = {}) {
|
|
if (!object || typeof object !== "object")
|
|
throw new Error("expected valid options object");
|
|
function checkField(fieldName, expectedType, isOpt) {
|
|
const val = object[fieldName];
|
|
if (isOpt && val === undefined)
|
|
return;
|
|
const current = typeof val;
|
|
if (current !== expectedType || val === null)
|
|
throw new Error(`param "${fieldName}" is invalid: expected ${expectedType}, got ${current}`);
|
|
}
|
|
const iter = (f, isOpt) => Object.entries(f).forEach(([k, v]) => checkField(k, v, isOpt));
|
|
iter(fields, false);
|
|
iter(optFields, true);
|
|
}
|
|
function memoized(fn) {
|
|
const map = new WeakMap;
|
|
return (arg, ...args) => {
|
|
const val = map.get(arg);
|
|
if (val !== undefined)
|
|
return val;
|
|
const computed = fn(arg, ...args);
|
|
map.set(arg, computed);
|
|
return computed;
|
|
};
|
|
}
|
|
|
|
// node_modules/@noble/curves/abstract/modular.js
|
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
var _0n2 = /* @__PURE__ */ BigInt(0);
|
|
var _1n2 = /* @__PURE__ */ BigInt(1);
|
|
var _2n = /* @__PURE__ */ BigInt(2);
|
|
var _3n = /* @__PURE__ */ BigInt(3);
|
|
var _4n = /* @__PURE__ */ BigInt(4);
|
|
var _5n = /* @__PURE__ */ BigInt(5);
|
|
var _7n = /* @__PURE__ */ BigInt(7);
|
|
var _8n = /* @__PURE__ */ BigInt(8);
|
|
var _9n = /* @__PURE__ */ BigInt(9);
|
|
var _16n = /* @__PURE__ */ BigInt(16);
|
|
function mod(a, b) {
|
|
const result = a % b;
|
|
return result >= _0n2 ? result : b + result;
|
|
}
|
|
function pow2(x, power, modulo) {
|
|
let res = x;
|
|
while (power-- > _0n2) {
|
|
res *= res;
|
|
res %= modulo;
|
|
}
|
|
return res;
|
|
}
|
|
function invert(number, modulo) {
|
|
if (number === _0n2)
|
|
throw new Error("invert: expected non-zero number");
|
|
if (modulo <= _0n2)
|
|
throw new Error("invert: expected positive modulus, got " + modulo);
|
|
let a = mod(number, modulo);
|
|
let b = modulo;
|
|
let x = _0n2, y = _1n2, u = _1n2, v = _0n2;
|
|
while (a !== _0n2) {
|
|
const q = b / a;
|
|
const r = b % a;
|
|
const m = x - u * q;
|
|
const n = y - v * q;
|
|
b = a, a = r, x = u, y = v, u = m, v = n;
|
|
}
|
|
const gcd = b;
|
|
if (gcd !== _1n2)
|
|
throw new Error("invert: does not exist");
|
|
return mod(x, modulo);
|
|
}
|
|
function assertIsSquare(Fp, root, n) {
|
|
if (!Fp.eql(Fp.sqr(root), n))
|
|
throw new Error("Cannot find square root");
|
|
}
|
|
function sqrt3mod4(Fp, n) {
|
|
const p1div4 = (Fp.ORDER + _1n2) / _4n;
|
|
const root = Fp.pow(n, p1div4);
|
|
assertIsSquare(Fp, root, n);
|
|
return root;
|
|
}
|
|
function sqrt5mod8(Fp, n) {
|
|
const p5div8 = (Fp.ORDER - _5n) / _8n;
|
|
const n2 = Fp.mul(n, _2n);
|
|
const v = Fp.pow(n2, p5div8);
|
|
const nv = Fp.mul(n, v);
|
|
const i = Fp.mul(Fp.mul(nv, _2n), v);
|
|
const root = Fp.mul(nv, Fp.sub(i, Fp.ONE));
|
|
assertIsSquare(Fp, root, n);
|
|
return root;
|
|
}
|
|
function sqrt9mod16(P) {
|
|
const Fp_ = Field(P);
|
|
const tn = tonelliShanks(P);
|
|
const c1 = tn(Fp_, Fp_.neg(Fp_.ONE));
|
|
const c2 = tn(Fp_, c1);
|
|
const c3 = tn(Fp_, Fp_.neg(c1));
|
|
const c4 = (P + _7n) / _16n;
|
|
return (Fp, n) => {
|
|
let tv1 = Fp.pow(n, c4);
|
|
let tv2 = Fp.mul(tv1, c1);
|
|
const tv3 = Fp.mul(tv1, c2);
|
|
const tv4 = Fp.mul(tv1, c3);
|
|
const e1 = Fp.eql(Fp.sqr(tv2), n);
|
|
const e2 = Fp.eql(Fp.sqr(tv3), n);
|
|
tv1 = Fp.cmov(tv1, tv2, e1);
|
|
tv2 = Fp.cmov(tv4, tv3, e2);
|
|
const e3 = Fp.eql(Fp.sqr(tv2), n);
|
|
const root = Fp.cmov(tv1, tv2, e3);
|
|
assertIsSquare(Fp, root, n);
|
|
return root;
|
|
};
|
|
}
|
|
function tonelliShanks(P) {
|
|
if (P < _3n)
|
|
throw new Error("sqrt is not defined for small field");
|
|
let Q = P - _1n2;
|
|
let S = 0;
|
|
while (Q % _2n === _0n2) {
|
|
Q /= _2n;
|
|
S++;
|
|
}
|
|
let Z = _2n;
|
|
const _Fp = Field(P);
|
|
while (FpLegendre(_Fp, Z) === 1) {
|
|
if (Z++ > 1000)
|
|
throw new Error("Cannot find square root: probably non-prime P");
|
|
}
|
|
if (S === 1)
|
|
return sqrt3mod4;
|
|
let cc = _Fp.pow(Z, Q);
|
|
const Q1div2 = (Q + _1n2) / _2n;
|
|
return function tonelliSlow(Fp, n) {
|
|
if (Fp.is0(n))
|
|
return n;
|
|
if (FpLegendre(Fp, n) !== 1)
|
|
throw new Error("Cannot find square root");
|
|
let M = S;
|
|
let c = Fp.mul(Fp.ONE, cc);
|
|
let t = Fp.pow(n, Q);
|
|
let R = Fp.pow(n, Q1div2);
|
|
while (!Fp.eql(t, Fp.ONE)) {
|
|
if (Fp.is0(t))
|
|
return Fp.ZERO;
|
|
let i = 1;
|
|
let t_tmp = Fp.sqr(t);
|
|
while (!Fp.eql(t_tmp, Fp.ONE)) {
|
|
i++;
|
|
t_tmp = Fp.sqr(t_tmp);
|
|
if (i === M)
|
|
throw new Error("Cannot find square root");
|
|
}
|
|
const exponent = _1n2 << BigInt(M - i - 1);
|
|
const b = Fp.pow(c, exponent);
|
|
M = i;
|
|
c = Fp.sqr(b);
|
|
t = Fp.mul(t, c);
|
|
R = Fp.mul(R, b);
|
|
}
|
|
return R;
|
|
};
|
|
}
|
|
function FpSqrt(P) {
|
|
if (P % _4n === _3n)
|
|
return sqrt3mod4;
|
|
if (P % _8n === _5n)
|
|
return sqrt5mod8;
|
|
if (P % _16n === _9n)
|
|
return sqrt9mod16(P);
|
|
return tonelliShanks(P);
|
|
}
|
|
var FIELD_FIELDS = [
|
|
"create",
|
|
"isValid",
|
|
"is0",
|
|
"neg",
|
|
"inv",
|
|
"sqrt",
|
|
"sqr",
|
|
"eql",
|
|
"add",
|
|
"sub",
|
|
"mul",
|
|
"pow",
|
|
"div",
|
|
"addN",
|
|
"subN",
|
|
"mulN",
|
|
"sqrN"
|
|
];
|
|
function validateField(field) {
|
|
const initial = {
|
|
ORDER: "bigint",
|
|
BYTES: "number",
|
|
BITS: "number"
|
|
};
|
|
const opts = FIELD_FIELDS.reduce((map, val) => {
|
|
map[val] = "function";
|
|
return map;
|
|
}, initial);
|
|
validateObject(field, opts);
|
|
return field;
|
|
}
|
|
function FpPow(Fp, num, power) {
|
|
if (power < _0n2)
|
|
throw new Error("invalid exponent, negatives unsupported");
|
|
if (power === _0n2)
|
|
return Fp.ONE;
|
|
if (power === _1n2)
|
|
return num;
|
|
let p = Fp.ONE;
|
|
let d = num;
|
|
while (power > _0n2) {
|
|
if (power & _1n2)
|
|
p = Fp.mul(p, d);
|
|
d = Fp.sqr(d);
|
|
power >>= _1n2;
|
|
}
|
|
return p;
|
|
}
|
|
function FpInvertBatch(Fp, nums, passZero = false) {
|
|
const inverted = new Array(nums.length).fill(passZero ? Fp.ZERO : undefined);
|
|
const multipliedAcc = nums.reduce((acc, num, i) => {
|
|
if (Fp.is0(num))
|
|
return acc;
|
|
inverted[i] = acc;
|
|
return Fp.mul(acc, num);
|
|
}, Fp.ONE);
|
|
const invertedAcc = Fp.inv(multipliedAcc);
|
|
nums.reduceRight((acc, num, i) => {
|
|
if (Fp.is0(num))
|
|
return acc;
|
|
inverted[i] = Fp.mul(acc, inverted[i]);
|
|
return Fp.mul(acc, num);
|
|
}, invertedAcc);
|
|
return inverted;
|
|
}
|
|
function FpLegendre(Fp, n) {
|
|
const p1mod2 = (Fp.ORDER - _1n2) / _2n;
|
|
const powered = Fp.pow(n, p1mod2);
|
|
const yes = Fp.eql(powered, Fp.ONE);
|
|
const zero = Fp.eql(powered, Fp.ZERO);
|
|
const no = Fp.eql(powered, Fp.neg(Fp.ONE));
|
|
if (!yes && !zero && !no)
|
|
throw new Error("invalid Legendre symbol result");
|
|
return yes ? 1 : zero ? 0 : -1;
|
|
}
|
|
function nLength(n, nBitLength) {
|
|
if (nBitLength !== undefined)
|
|
anumber(nBitLength);
|
|
const _nBitLength = nBitLength !== undefined ? nBitLength : n.toString(2).length;
|
|
const nByteLength = Math.ceil(_nBitLength / 8);
|
|
return { nBitLength: _nBitLength, nByteLength };
|
|
}
|
|
|
|
class _Field {
|
|
ORDER;
|
|
BITS;
|
|
BYTES;
|
|
isLE;
|
|
ZERO = _0n2;
|
|
ONE = _1n2;
|
|
_lengths;
|
|
_sqrt;
|
|
_mod;
|
|
constructor(ORDER, opts = {}) {
|
|
if (ORDER <= _0n2)
|
|
throw new Error("invalid field: expected ORDER > 0, got " + ORDER);
|
|
let _nbitLength = undefined;
|
|
this.isLE = false;
|
|
if (opts != null && typeof opts === "object") {
|
|
if (typeof opts.BITS === "number")
|
|
_nbitLength = opts.BITS;
|
|
if (typeof opts.sqrt === "function")
|
|
this.sqrt = opts.sqrt;
|
|
if (typeof opts.isLE === "boolean")
|
|
this.isLE = opts.isLE;
|
|
if (opts.allowedLengths)
|
|
this._lengths = opts.allowedLengths?.slice();
|
|
if (typeof opts.modFromBytes === "boolean")
|
|
this._mod = opts.modFromBytes;
|
|
}
|
|
const { nBitLength, nByteLength } = nLength(ORDER, _nbitLength);
|
|
if (nByteLength > 2048)
|
|
throw new Error("invalid field: expected ORDER of <= 2048 bytes");
|
|
this.ORDER = ORDER;
|
|
this.BITS = nBitLength;
|
|
this.BYTES = nByteLength;
|
|
this._sqrt = undefined;
|
|
Object.preventExtensions(this);
|
|
}
|
|
create(num) {
|
|
return mod(num, this.ORDER);
|
|
}
|
|
isValid(num) {
|
|
if (typeof num !== "bigint")
|
|
throw new Error("invalid field element: expected bigint, got " + typeof num);
|
|
return _0n2 <= num && num < this.ORDER;
|
|
}
|
|
is0(num) {
|
|
return num === _0n2;
|
|
}
|
|
isValidNot0(num) {
|
|
return !this.is0(num) && this.isValid(num);
|
|
}
|
|
isOdd(num) {
|
|
return (num & _1n2) === _1n2;
|
|
}
|
|
neg(num) {
|
|
return mod(-num, this.ORDER);
|
|
}
|
|
eql(lhs, rhs) {
|
|
return lhs === rhs;
|
|
}
|
|
sqr(num) {
|
|
return mod(num * num, this.ORDER);
|
|
}
|
|
add(lhs, rhs) {
|
|
return mod(lhs + rhs, this.ORDER);
|
|
}
|
|
sub(lhs, rhs) {
|
|
return mod(lhs - rhs, this.ORDER);
|
|
}
|
|
mul(lhs, rhs) {
|
|
return mod(lhs * rhs, this.ORDER);
|
|
}
|
|
pow(num, power) {
|
|
return FpPow(this, num, power);
|
|
}
|
|
div(lhs, rhs) {
|
|
return mod(lhs * invert(rhs, this.ORDER), this.ORDER);
|
|
}
|
|
sqrN(num) {
|
|
return num * num;
|
|
}
|
|
addN(lhs, rhs) {
|
|
return lhs + rhs;
|
|
}
|
|
subN(lhs, rhs) {
|
|
return lhs - rhs;
|
|
}
|
|
mulN(lhs, rhs) {
|
|
return lhs * rhs;
|
|
}
|
|
inv(num) {
|
|
return invert(num, this.ORDER);
|
|
}
|
|
sqrt(num) {
|
|
if (!this._sqrt)
|
|
this._sqrt = FpSqrt(this.ORDER);
|
|
return this._sqrt(this, num);
|
|
}
|
|
toBytes(num) {
|
|
return this.isLE ? numberToBytesLE(num, this.BYTES) : numberToBytesBE(num, this.BYTES);
|
|
}
|
|
fromBytes(bytes, skipValidation = false) {
|
|
abytes(bytes);
|
|
const { _lengths: allowedLengths, BYTES, isLE: isLE2, ORDER, _mod: modFromBytes } = this;
|
|
if (allowedLengths) {
|
|
if (!allowedLengths.includes(bytes.length) || bytes.length > BYTES) {
|
|
throw new Error("Field.fromBytes: expected " + allowedLengths + " bytes, got " + bytes.length);
|
|
}
|
|
const padded = new Uint8Array(BYTES);
|
|
padded.set(bytes, isLE2 ? 0 : padded.length - bytes.length);
|
|
bytes = padded;
|
|
}
|
|
if (bytes.length !== BYTES)
|
|
throw new Error("Field.fromBytes: expected " + BYTES + " bytes, got " + bytes.length);
|
|
let scalar = isLE2 ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes);
|
|
if (modFromBytes)
|
|
scalar = mod(scalar, ORDER);
|
|
if (!skipValidation) {
|
|
if (!this.isValid(scalar))
|
|
throw new Error("invalid field element: outside of range 0..ORDER");
|
|
}
|
|
return scalar;
|
|
}
|
|
invertBatch(lst) {
|
|
return FpInvertBatch(this, lst);
|
|
}
|
|
cmov(a, b, condition) {
|
|
return condition ? b : a;
|
|
}
|
|
}
|
|
function Field(ORDER, opts = {}) {
|
|
return new _Field(ORDER, opts);
|
|
}
|
|
function getFieldBytesLength(fieldOrder) {
|
|
if (typeof fieldOrder !== "bigint")
|
|
throw new Error("field order must be bigint");
|
|
const bitLength = fieldOrder.toString(2).length;
|
|
return Math.ceil(bitLength / 8);
|
|
}
|
|
function getMinHashLength(fieldOrder) {
|
|
const length = getFieldBytesLength(fieldOrder);
|
|
return length + Math.ceil(length / 2);
|
|
}
|
|
function mapHashToField(key, fieldOrder, isLE2 = false) {
|
|
abytes(key);
|
|
const len = key.length;
|
|
const fieldLen = getFieldBytesLength(fieldOrder);
|
|
const minLen = getMinHashLength(fieldOrder);
|
|
if (len < 16 || len < minLen || len > 1024)
|
|
throw new Error("expected " + minLen + "-1024 bytes of input, got " + len);
|
|
const num = isLE2 ? bytesToNumberLE(key) : bytesToNumberBE(key);
|
|
const reduced = mod(num, fieldOrder - _1n2) + _1n2;
|
|
return isLE2 ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen);
|
|
}
|
|
|
|
// node_modules/@noble/curves/abstract/curve.js
|
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
var _0n3 = /* @__PURE__ */ BigInt(0);
|
|
var _1n3 = /* @__PURE__ */ BigInt(1);
|
|
function negateCt(condition, item) {
|
|
const neg = item.negate();
|
|
return condition ? neg : item;
|
|
}
|
|
function normalizeZ(c, points) {
|
|
const invertedZs = FpInvertBatch(c.Fp, points.map((p) => p.Z));
|
|
return points.map((p, i) => c.fromAffine(p.toAffine(invertedZs[i])));
|
|
}
|
|
function validateW(W, bits) {
|
|
if (!Number.isSafeInteger(W) || W <= 0 || W > bits)
|
|
throw new Error("invalid window size, expected [1.." + bits + "], got W=" + W);
|
|
}
|
|
function calcWOpts(W, scalarBits) {
|
|
validateW(W, scalarBits);
|
|
const windows = Math.ceil(scalarBits / W) + 1;
|
|
const windowSize = 2 ** (W - 1);
|
|
const maxNumber = 2 ** W;
|
|
const mask = bitMask(W);
|
|
const shiftBy = BigInt(W);
|
|
return { windows, windowSize, mask, maxNumber, shiftBy };
|
|
}
|
|
function calcOffsets(n, window2, wOpts) {
|
|
const { windowSize, mask, maxNumber, shiftBy } = wOpts;
|
|
let wbits = Number(n & mask);
|
|
let nextN = n >> shiftBy;
|
|
if (wbits > windowSize) {
|
|
wbits -= maxNumber;
|
|
nextN += _1n3;
|
|
}
|
|
const offsetStart = window2 * windowSize;
|
|
const offset = offsetStart + Math.abs(wbits) - 1;
|
|
const isZero = wbits === 0;
|
|
const isNeg = wbits < 0;
|
|
const isNegF = window2 % 2 !== 0;
|
|
const offsetF = offsetStart;
|
|
return { nextN, offset, isZero, isNeg, isNegF, offsetF };
|
|
}
|
|
var pointPrecomputes = new WeakMap;
|
|
var pointWindowSizes = new WeakMap;
|
|
function getW(P) {
|
|
return pointWindowSizes.get(P) || 1;
|
|
}
|
|
function assert0(n) {
|
|
if (n !== _0n3)
|
|
throw new Error("invalid wNAF");
|
|
}
|
|
|
|
class wNAF {
|
|
BASE;
|
|
ZERO;
|
|
Fn;
|
|
bits;
|
|
constructor(Point, bits) {
|
|
this.BASE = Point.BASE;
|
|
this.ZERO = Point.ZERO;
|
|
this.Fn = Point.Fn;
|
|
this.bits = bits;
|
|
}
|
|
_unsafeLadder(elm, n, p = this.ZERO) {
|
|
let d = elm;
|
|
while (n > _0n3) {
|
|
if (n & _1n3)
|
|
p = p.add(d);
|
|
d = d.double();
|
|
n >>= _1n3;
|
|
}
|
|
return p;
|
|
}
|
|
precomputeWindow(point, W) {
|
|
const { windows, windowSize } = calcWOpts(W, this.bits);
|
|
const points = [];
|
|
let p = point;
|
|
let base = p;
|
|
for (let window2 = 0;window2 < windows; window2++) {
|
|
base = p;
|
|
points.push(base);
|
|
for (let i = 1;i < windowSize; i++) {
|
|
base = base.add(p);
|
|
points.push(base);
|
|
}
|
|
p = base.double();
|
|
}
|
|
return points;
|
|
}
|
|
wNAF(W, precomputes, n) {
|
|
if (!this.Fn.isValid(n))
|
|
throw new Error("invalid scalar");
|
|
let p = this.ZERO;
|
|
let f = this.BASE;
|
|
const wo = calcWOpts(W, this.bits);
|
|
for (let window2 = 0;window2 < wo.windows; window2++) {
|
|
const { nextN, offset, isZero, isNeg, isNegF, offsetF } = calcOffsets(n, window2, wo);
|
|
n = nextN;
|
|
if (isZero) {
|
|
f = f.add(negateCt(isNegF, precomputes[offsetF]));
|
|
} else {
|
|
p = p.add(negateCt(isNeg, precomputes[offset]));
|
|
}
|
|
}
|
|
assert0(n);
|
|
return { p, f };
|
|
}
|
|
wNAFUnsafe(W, precomputes, n, acc = this.ZERO) {
|
|
const wo = calcWOpts(W, this.bits);
|
|
for (let window2 = 0;window2 < wo.windows; window2++) {
|
|
if (n === _0n3)
|
|
break;
|
|
const { nextN, offset, isZero, isNeg } = calcOffsets(n, window2, wo);
|
|
n = nextN;
|
|
if (isZero) {
|
|
continue;
|
|
} else {
|
|
const item = precomputes[offset];
|
|
acc = acc.add(isNeg ? item.negate() : item);
|
|
}
|
|
}
|
|
assert0(n);
|
|
return acc;
|
|
}
|
|
getPrecomputes(W, point, transform) {
|
|
let comp = pointPrecomputes.get(point);
|
|
if (!comp) {
|
|
comp = this.precomputeWindow(point, W);
|
|
if (W !== 1) {
|
|
if (typeof transform === "function")
|
|
comp = transform(comp);
|
|
pointPrecomputes.set(point, comp);
|
|
}
|
|
}
|
|
return comp;
|
|
}
|
|
cached(point, scalar, transform) {
|
|
const W = getW(point);
|
|
return this.wNAF(W, this.getPrecomputes(W, point, transform), scalar);
|
|
}
|
|
unsafe(point, scalar, transform, prev) {
|
|
const W = getW(point);
|
|
if (W === 1)
|
|
return this._unsafeLadder(point, scalar, prev);
|
|
return this.wNAFUnsafe(W, this.getPrecomputes(W, point, transform), scalar, prev);
|
|
}
|
|
createCache(P, W) {
|
|
validateW(W, this.bits);
|
|
pointWindowSizes.set(P, W);
|
|
pointPrecomputes.delete(P);
|
|
}
|
|
hasCache(elm) {
|
|
return getW(elm) !== 1;
|
|
}
|
|
}
|
|
function mulEndoUnsafe(Point, point, k1, k2) {
|
|
let acc = point;
|
|
let p1 = Point.ZERO;
|
|
let p2 = Point.ZERO;
|
|
while (k1 > _0n3 || k2 > _0n3) {
|
|
if (k1 & _1n3)
|
|
p1 = p1.add(acc);
|
|
if (k2 & _1n3)
|
|
p2 = p2.add(acc);
|
|
acc = acc.double();
|
|
k1 >>= _1n3;
|
|
k2 >>= _1n3;
|
|
}
|
|
return { p1, p2 };
|
|
}
|
|
function createField(order, field, isLE2) {
|
|
if (field) {
|
|
if (field.ORDER !== order)
|
|
throw new Error("Field.ORDER must match order: Fp == p, Fn == n");
|
|
validateField(field);
|
|
return field;
|
|
} else {
|
|
return Field(order, { isLE: isLE2 });
|
|
}
|
|
}
|
|
function createCurveFields(type, CURVE, curveOpts = {}, FpFnLE) {
|
|
if (FpFnLE === undefined)
|
|
FpFnLE = type === "edwards";
|
|
if (!CURVE || typeof CURVE !== "object")
|
|
throw new Error(`expected valid ${type} CURVE object`);
|
|
for (const p of ["p", "n", "h"]) {
|
|
const val = CURVE[p];
|
|
if (!(typeof val === "bigint" && val > _0n3))
|
|
throw new Error(`CURVE.${p} must be positive bigint`);
|
|
}
|
|
const Fp = createField(CURVE.p, curveOpts.Fp, FpFnLE);
|
|
const Fn = createField(CURVE.n, curveOpts.Fn, FpFnLE);
|
|
const _b = type === "weierstrass" ? "b" : "d";
|
|
const params = ["Gx", "Gy", "a", _b];
|
|
for (const p of params) {
|
|
if (!Fp.isValid(CURVE[p]))
|
|
throw new Error(`CURVE.${p} must be valid field element of CURVE.Fp`);
|
|
}
|
|
CURVE = Object.freeze(Object.assign({}, CURVE));
|
|
return { CURVE, Fp, Fn };
|
|
}
|
|
function createKeygen(randomSecretKey, getPublicKey) {
|
|
return function keygen(seed) {
|
|
const secretKey = randomSecretKey(seed);
|
|
return { secretKey, publicKey: getPublicKey(secretKey) };
|
|
};
|
|
}
|
|
|
|
// node_modules/@noble/hashes/hmac.js
|
|
class _HMAC {
|
|
oHash;
|
|
iHash;
|
|
blockLen;
|
|
outputLen;
|
|
finished = false;
|
|
destroyed = false;
|
|
constructor(hash, key) {
|
|
ahash(hash);
|
|
abytes(key, undefined, "key");
|
|
this.iHash = hash.create();
|
|
if (typeof this.iHash.update !== "function")
|
|
throw new Error("Expected instance of class which extends utils.Hash");
|
|
this.blockLen = this.iHash.blockLen;
|
|
this.outputLen = this.iHash.outputLen;
|
|
const blockLen = this.blockLen;
|
|
const pad = new Uint8Array(blockLen);
|
|
pad.set(key.length > blockLen ? hash.create().update(key).digest() : key);
|
|
for (let i = 0;i < pad.length; i++)
|
|
pad[i] ^= 54;
|
|
this.iHash.update(pad);
|
|
this.oHash = hash.create();
|
|
for (let i = 0;i < pad.length; i++)
|
|
pad[i] ^= 54 ^ 92;
|
|
this.oHash.update(pad);
|
|
clean(pad);
|
|
}
|
|
update(buf) {
|
|
aexists(this);
|
|
this.iHash.update(buf);
|
|
return this;
|
|
}
|
|
digestInto(out) {
|
|
aexists(this);
|
|
abytes(out, this.outputLen, "output");
|
|
this.finished = true;
|
|
this.iHash.digestInto(out);
|
|
this.oHash.update(out);
|
|
this.oHash.digestInto(out);
|
|
this.destroy();
|
|
}
|
|
digest() {
|
|
const out = new Uint8Array(this.oHash.outputLen);
|
|
this.digestInto(out);
|
|
return out;
|
|
}
|
|
_cloneInto(to) {
|
|
to ||= Object.create(Object.getPrototypeOf(this), {});
|
|
const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this;
|
|
to = to;
|
|
to.finished = finished;
|
|
to.destroyed = destroyed;
|
|
to.blockLen = blockLen;
|
|
to.outputLen = outputLen;
|
|
to.oHash = oHash._cloneInto(to.oHash);
|
|
to.iHash = iHash._cloneInto(to.iHash);
|
|
return to;
|
|
}
|
|
clone() {
|
|
return this._cloneInto();
|
|
}
|
|
destroy() {
|
|
this.destroyed = true;
|
|
this.oHash.destroy();
|
|
this.iHash.destroy();
|
|
}
|
|
}
|
|
var hmac = (hash, key, message) => new _HMAC(hash, key).update(message).digest();
|
|
hmac.create = (hash, key) => new _HMAC(hash, key);
|
|
|
|
// node_modules/@noble/curves/abstract/weierstrass.js
|
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
var divNearest = (num, den) => (num + (num >= 0 ? den : -den) / _2n2) / den;
|
|
function _splitEndoScalar(k, basis, n) {
|
|
const [[a1, b1], [a2, b2]] = basis;
|
|
const c1 = divNearest(b2 * k, n);
|
|
const c2 = divNearest(-b1 * k, n);
|
|
let k1 = k - c1 * a1 - c2 * a2;
|
|
let k2 = -c1 * b1 - c2 * b2;
|
|
const k1neg = k1 < _0n4;
|
|
const k2neg = k2 < _0n4;
|
|
if (k1neg)
|
|
k1 = -k1;
|
|
if (k2neg)
|
|
k2 = -k2;
|
|
const MAX_NUM = bitMask(Math.ceil(bitLen(n) / 2)) + _1n4;
|
|
if (k1 < _0n4 || k1 >= MAX_NUM || k2 < _0n4 || k2 >= MAX_NUM) {
|
|
throw new Error("splitScalar (endomorphism): failed, k=" + k);
|
|
}
|
|
return { k1neg, k1, k2neg, k2 };
|
|
}
|
|
function validateSigFormat(format) {
|
|
if (!["compact", "recovered", "der"].includes(format))
|
|
throw new Error('Signature format must be "compact", "recovered", or "der"');
|
|
return format;
|
|
}
|
|
function validateSigOpts(opts, def) {
|
|
const optsn = {};
|
|
for (let optName of Object.keys(def)) {
|
|
optsn[optName] = opts[optName] === undefined ? def[optName] : opts[optName];
|
|
}
|
|
abool(optsn.lowS, "lowS");
|
|
abool(optsn.prehash, "prehash");
|
|
if (optsn.format !== undefined)
|
|
validateSigFormat(optsn.format);
|
|
return optsn;
|
|
}
|
|
|
|
class DERErr extends Error {
|
|
constructor(m = "") {
|
|
super(m);
|
|
}
|
|
}
|
|
var DER = {
|
|
Err: DERErr,
|
|
_tlv: {
|
|
encode: (tag, data) => {
|
|
const { Err: E } = DER;
|
|
if (tag < 0 || tag > 256)
|
|
throw new E("tlv.encode: wrong tag");
|
|
if (data.length & 1)
|
|
throw new E("tlv.encode: unpadded data");
|
|
const dataLen = data.length / 2;
|
|
const len = numberToHexUnpadded(dataLen);
|
|
if (len.length / 2 & 128)
|
|
throw new E("tlv.encode: long form length too big");
|
|
const lenLen = dataLen > 127 ? numberToHexUnpadded(len.length / 2 | 128) : "";
|
|
const t = numberToHexUnpadded(tag);
|
|
return t + lenLen + len + data;
|
|
},
|
|
decode(tag, data) {
|
|
const { Err: E } = DER;
|
|
let pos = 0;
|
|
if (tag < 0 || tag > 256)
|
|
throw new E("tlv.encode: wrong tag");
|
|
if (data.length < 2 || data[pos++] !== tag)
|
|
throw new E("tlv.decode: wrong tlv");
|
|
const first = data[pos++];
|
|
const isLong = !!(first & 128);
|
|
let length = 0;
|
|
if (!isLong)
|
|
length = first;
|
|
else {
|
|
const lenLen = first & 127;
|
|
if (!lenLen)
|
|
throw new E("tlv.decode(long): indefinite length not supported");
|
|
if (lenLen > 4)
|
|
throw new E("tlv.decode(long): byte length is too big");
|
|
const lengthBytes = data.subarray(pos, pos + lenLen);
|
|
if (lengthBytes.length !== lenLen)
|
|
throw new E("tlv.decode: length bytes not complete");
|
|
if (lengthBytes[0] === 0)
|
|
throw new E("tlv.decode(long): zero leftmost byte");
|
|
for (const b of lengthBytes)
|
|
length = length << 8 | b;
|
|
pos += lenLen;
|
|
if (length < 128)
|
|
throw new E("tlv.decode(long): not minimal encoding");
|
|
}
|
|
const v = data.subarray(pos, pos + length);
|
|
if (v.length !== length)
|
|
throw new E("tlv.decode: wrong value length");
|
|
return { v, l: data.subarray(pos + length) };
|
|
}
|
|
},
|
|
_int: {
|
|
encode(num) {
|
|
const { Err: E } = DER;
|
|
if (num < _0n4)
|
|
throw new E("integer: negative integers are not allowed");
|
|
let hex = numberToHexUnpadded(num);
|
|
if (Number.parseInt(hex[0], 16) & 8)
|
|
hex = "00" + hex;
|
|
if (hex.length & 1)
|
|
throw new E("unexpected DER parsing assertion: unpadded hex");
|
|
return hex;
|
|
},
|
|
decode(data) {
|
|
const { Err: E } = DER;
|
|
if (data[0] & 128)
|
|
throw new E("invalid signature integer: negative");
|
|
if (data[0] === 0 && !(data[1] & 128))
|
|
throw new E("invalid signature integer: unnecessary leading zero");
|
|
return bytesToNumberBE(data);
|
|
}
|
|
},
|
|
toSig(bytes) {
|
|
const { Err: E, _int: int, _tlv: tlv } = DER;
|
|
const data = abytes(bytes, undefined, "signature");
|
|
const { v: seqBytes, l: seqLeftBytes } = tlv.decode(48, data);
|
|
if (seqLeftBytes.length)
|
|
throw new E("invalid signature: left bytes after parsing");
|
|
const { v: rBytes, l: rLeftBytes } = tlv.decode(2, seqBytes);
|
|
const { v: sBytes, l: sLeftBytes } = tlv.decode(2, rLeftBytes);
|
|
if (sLeftBytes.length)
|
|
throw new E("invalid signature: left bytes after parsing");
|
|
return { r: int.decode(rBytes), s: int.decode(sBytes) };
|
|
},
|
|
hexFromSig(sig) {
|
|
const { _tlv: tlv, _int: int } = DER;
|
|
const rs = tlv.encode(2, int.encode(sig.r));
|
|
const ss = tlv.encode(2, int.encode(sig.s));
|
|
const seq = rs + ss;
|
|
return tlv.encode(48, seq);
|
|
}
|
|
};
|
|
var _0n4 = BigInt(0);
|
|
var _1n4 = BigInt(1);
|
|
var _2n2 = BigInt(2);
|
|
var _3n2 = BigInt(3);
|
|
var _4n2 = BigInt(4);
|
|
function weierstrass(params, extraOpts = {}) {
|
|
const validated = createCurveFields("weierstrass", params, extraOpts);
|
|
const { Fp, Fn } = validated;
|
|
let CURVE = validated.CURVE;
|
|
const { h: cofactor, n: CURVE_ORDER } = CURVE;
|
|
validateObject(extraOpts, {}, {
|
|
allowInfinityPoint: "boolean",
|
|
clearCofactor: "function",
|
|
isTorsionFree: "function",
|
|
fromBytes: "function",
|
|
toBytes: "function",
|
|
endo: "object"
|
|
});
|
|
const { endo } = extraOpts;
|
|
if (endo) {
|
|
if (!Fp.is0(CURVE.a) || typeof endo.beta !== "bigint" || !Array.isArray(endo.basises)) {
|
|
throw new Error('invalid endo: expected "beta": bigint and "basises": array');
|
|
}
|
|
}
|
|
const lengths = getWLengths(Fp, Fn);
|
|
function assertCompressionIsSupported() {
|
|
if (!Fp.isOdd)
|
|
throw new Error("compression is not supported: Field does not have .isOdd()");
|
|
}
|
|
function pointToBytes(_c, point, isCompressed) {
|
|
const { x, y } = point.toAffine();
|
|
const bx = Fp.toBytes(x);
|
|
abool(isCompressed, "isCompressed");
|
|
if (isCompressed) {
|
|
assertCompressionIsSupported();
|
|
const hasEvenY = !Fp.isOdd(y);
|
|
return concatBytes(pprefix(hasEvenY), bx);
|
|
} else {
|
|
return concatBytes(Uint8Array.of(4), bx, Fp.toBytes(y));
|
|
}
|
|
}
|
|
function pointFromBytes(bytes) {
|
|
abytes(bytes, undefined, "Point");
|
|
const { publicKey: comp, publicKeyUncompressed: uncomp } = lengths;
|
|
const length = bytes.length;
|
|
const head = bytes[0];
|
|
const tail = bytes.subarray(1);
|
|
if (length === comp && (head === 2 || head === 3)) {
|
|
const x = Fp.fromBytes(tail);
|
|
if (!Fp.isValid(x))
|
|
throw new Error("bad point: is not on curve, wrong x");
|
|
const y2 = weierstrassEquation(x);
|
|
let y;
|
|
try {
|
|
y = Fp.sqrt(y2);
|
|
} catch (sqrtError) {
|
|
const err = sqrtError instanceof Error ? ": " + sqrtError.message : "";
|
|
throw new Error("bad point: is not on curve, sqrt error" + err);
|
|
}
|
|
assertCompressionIsSupported();
|
|
const evenY = Fp.isOdd(y);
|
|
const evenH = (head & 1) === 1;
|
|
if (evenH !== evenY)
|
|
y = Fp.neg(y);
|
|
return { x, y };
|
|
} else if (length === uncomp && head === 4) {
|
|
const L = Fp.BYTES;
|
|
const x = Fp.fromBytes(tail.subarray(0, L));
|
|
const y = Fp.fromBytes(tail.subarray(L, L * 2));
|
|
if (!isValidXY(x, y))
|
|
throw new Error("bad point: is not on curve");
|
|
return { x, y };
|
|
} else {
|
|
throw new Error(`bad point: got length ${length}, expected compressed=${comp} or uncompressed=${uncomp}`);
|
|
}
|
|
}
|
|
const encodePoint = extraOpts.toBytes || pointToBytes;
|
|
const decodePoint = extraOpts.fromBytes || pointFromBytes;
|
|
function weierstrassEquation(x) {
|
|
const x2 = Fp.sqr(x);
|
|
const x3 = Fp.mul(x2, x);
|
|
return Fp.add(Fp.add(x3, Fp.mul(x, CURVE.a)), CURVE.b);
|
|
}
|
|
function isValidXY(x, y) {
|
|
const left = Fp.sqr(y);
|
|
const right = weierstrassEquation(x);
|
|
return Fp.eql(left, right);
|
|
}
|
|
if (!isValidXY(CURVE.Gx, CURVE.Gy))
|
|
throw new Error("bad curve params: generator point");
|
|
const _4a3 = Fp.mul(Fp.pow(CURVE.a, _3n2), _4n2);
|
|
const _27b2 = Fp.mul(Fp.sqr(CURVE.b), BigInt(27));
|
|
if (Fp.is0(Fp.add(_4a3, _27b2)))
|
|
throw new Error("bad curve params: a or b");
|
|
function acoord(title, n, banZero = false) {
|
|
if (!Fp.isValid(n) || banZero && Fp.is0(n))
|
|
throw new Error(`bad point coordinate ${title}`);
|
|
return n;
|
|
}
|
|
function aprjpoint(other) {
|
|
if (!(other instanceof Point))
|
|
throw new Error("Weierstrass Point expected");
|
|
}
|
|
function splitEndoScalarN(k) {
|
|
if (!endo || !endo.basises)
|
|
throw new Error("no endo");
|
|
return _splitEndoScalar(k, endo.basises, Fn.ORDER);
|
|
}
|
|
const toAffineMemo = memoized((p, iz) => {
|
|
const { X, Y, Z } = p;
|
|
if (Fp.eql(Z, Fp.ONE))
|
|
return { x: X, y: Y };
|
|
const is0 = p.is0();
|
|
if (iz == null)
|
|
iz = is0 ? Fp.ONE : Fp.inv(Z);
|
|
const x = Fp.mul(X, iz);
|
|
const y = Fp.mul(Y, iz);
|
|
const zz = Fp.mul(Z, iz);
|
|
if (is0)
|
|
return { x: Fp.ZERO, y: Fp.ZERO };
|
|
if (!Fp.eql(zz, Fp.ONE))
|
|
throw new Error("invZ was invalid");
|
|
return { x, y };
|
|
});
|
|
const assertValidMemo = memoized((p) => {
|
|
if (p.is0()) {
|
|
if (extraOpts.allowInfinityPoint && !Fp.is0(p.Y))
|
|
return;
|
|
throw new Error("bad point: ZERO");
|
|
}
|
|
const { x, y } = p.toAffine();
|
|
if (!Fp.isValid(x) || !Fp.isValid(y))
|
|
throw new Error("bad point: x or y not field elements");
|
|
if (!isValidXY(x, y))
|
|
throw new Error("bad point: equation left != right");
|
|
if (!p.isTorsionFree())
|
|
throw new Error("bad point: not in prime-order subgroup");
|
|
return true;
|
|
});
|
|
function finishEndo(endoBeta, k1p, k2p, k1neg, k2neg) {
|
|
k2p = new Point(Fp.mul(k2p.X, endoBeta), k2p.Y, k2p.Z);
|
|
k1p = negateCt(k1neg, k1p);
|
|
k2p = negateCt(k2neg, k2p);
|
|
return k1p.add(k2p);
|
|
}
|
|
|
|
class Point {
|
|
static BASE = new Point(CURVE.Gx, CURVE.Gy, Fp.ONE);
|
|
static ZERO = new Point(Fp.ZERO, Fp.ONE, Fp.ZERO);
|
|
static Fp = Fp;
|
|
static Fn = Fn;
|
|
X;
|
|
Y;
|
|
Z;
|
|
constructor(X, Y, Z) {
|
|
this.X = acoord("x", X);
|
|
this.Y = acoord("y", Y, true);
|
|
this.Z = acoord("z", Z);
|
|
Object.freeze(this);
|
|
}
|
|
static CURVE() {
|
|
return CURVE;
|
|
}
|
|
static fromAffine(p) {
|
|
const { x, y } = p || {};
|
|
if (!p || !Fp.isValid(x) || !Fp.isValid(y))
|
|
throw new Error("invalid affine point");
|
|
if (p instanceof Point)
|
|
throw new Error("projective point not allowed");
|
|
if (Fp.is0(x) && Fp.is0(y))
|
|
return Point.ZERO;
|
|
return new Point(x, y, Fp.ONE);
|
|
}
|
|
static fromBytes(bytes) {
|
|
const P = Point.fromAffine(decodePoint(abytes(bytes, undefined, "point")));
|
|
P.assertValidity();
|
|
return P;
|
|
}
|
|
static fromHex(hex) {
|
|
return Point.fromBytes(hexToBytes(hex));
|
|
}
|
|
get x() {
|
|
return this.toAffine().x;
|
|
}
|
|
get y() {
|
|
return this.toAffine().y;
|
|
}
|
|
precompute(windowSize = 8, isLazy = true) {
|
|
wnaf.createCache(this, windowSize);
|
|
if (!isLazy)
|
|
this.multiply(_3n2);
|
|
return this;
|
|
}
|
|
assertValidity() {
|
|
assertValidMemo(this);
|
|
}
|
|
hasEvenY() {
|
|
const { y } = this.toAffine();
|
|
if (!Fp.isOdd)
|
|
throw new Error("Field doesn't support isOdd");
|
|
return !Fp.isOdd(y);
|
|
}
|
|
equals(other) {
|
|
aprjpoint(other);
|
|
const { X: X1, Y: Y1, Z: Z1 } = this;
|
|
const { X: X2, Y: Y2, Z: Z2 } = other;
|
|
const U1 = Fp.eql(Fp.mul(X1, Z2), Fp.mul(X2, Z1));
|
|
const U2 = Fp.eql(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1));
|
|
return U1 && U2;
|
|
}
|
|
negate() {
|
|
return new Point(this.X, Fp.neg(this.Y), this.Z);
|
|
}
|
|
double() {
|
|
const { a, b } = CURVE;
|
|
const b3 = Fp.mul(b, _3n2);
|
|
const { X: X1, Y: Y1, Z: Z1 } = this;
|
|
let { ZERO: X3, ZERO: Y3, ZERO: Z3 } = Fp;
|
|
let t0 = Fp.mul(X1, X1);
|
|
let t1 = Fp.mul(Y1, Y1);
|
|
let t2 = Fp.mul(Z1, Z1);
|
|
let t3 = Fp.mul(X1, Y1);
|
|
t3 = Fp.add(t3, t3);
|
|
Z3 = Fp.mul(X1, Z1);
|
|
Z3 = Fp.add(Z3, Z3);
|
|
X3 = Fp.mul(a, Z3);
|
|
Y3 = Fp.mul(b3, t2);
|
|
Y3 = Fp.add(X3, Y3);
|
|
X3 = Fp.sub(t1, Y3);
|
|
Y3 = Fp.add(t1, Y3);
|
|
Y3 = Fp.mul(X3, Y3);
|
|
X3 = Fp.mul(t3, X3);
|
|
Z3 = Fp.mul(b3, Z3);
|
|
t2 = Fp.mul(a, t2);
|
|
t3 = Fp.sub(t0, t2);
|
|
t3 = Fp.mul(a, t3);
|
|
t3 = Fp.add(t3, Z3);
|
|
Z3 = Fp.add(t0, t0);
|
|
t0 = Fp.add(Z3, t0);
|
|
t0 = Fp.add(t0, t2);
|
|
t0 = Fp.mul(t0, t3);
|
|
Y3 = Fp.add(Y3, t0);
|
|
t2 = Fp.mul(Y1, Z1);
|
|
t2 = Fp.add(t2, t2);
|
|
t0 = Fp.mul(t2, t3);
|
|
X3 = Fp.sub(X3, t0);
|
|
Z3 = Fp.mul(t2, t1);
|
|
Z3 = Fp.add(Z3, Z3);
|
|
Z3 = Fp.add(Z3, Z3);
|
|
return new Point(X3, Y3, Z3);
|
|
}
|
|
add(other) {
|
|
aprjpoint(other);
|
|
const { X: X1, Y: Y1, Z: Z1 } = this;
|
|
const { X: X2, Y: Y2, Z: Z2 } = other;
|
|
let { ZERO: X3, ZERO: Y3, ZERO: Z3 } = Fp;
|
|
const a = CURVE.a;
|
|
const b3 = Fp.mul(CURVE.b, _3n2);
|
|
let t0 = Fp.mul(X1, X2);
|
|
let t1 = Fp.mul(Y1, Y2);
|
|
let t2 = Fp.mul(Z1, Z2);
|
|
let t3 = Fp.add(X1, Y1);
|
|
let t4 = Fp.add(X2, Y2);
|
|
t3 = Fp.mul(t3, t4);
|
|
t4 = Fp.add(t0, t1);
|
|
t3 = Fp.sub(t3, t4);
|
|
t4 = Fp.add(X1, Z1);
|
|
let t5 = Fp.add(X2, Z2);
|
|
t4 = Fp.mul(t4, t5);
|
|
t5 = Fp.add(t0, t2);
|
|
t4 = Fp.sub(t4, t5);
|
|
t5 = Fp.add(Y1, Z1);
|
|
X3 = Fp.add(Y2, Z2);
|
|
t5 = Fp.mul(t5, X3);
|
|
X3 = Fp.add(t1, t2);
|
|
t5 = Fp.sub(t5, X3);
|
|
Z3 = Fp.mul(a, t4);
|
|
X3 = Fp.mul(b3, t2);
|
|
Z3 = Fp.add(X3, Z3);
|
|
X3 = Fp.sub(t1, Z3);
|
|
Z3 = Fp.add(t1, Z3);
|
|
Y3 = Fp.mul(X3, Z3);
|
|
t1 = Fp.add(t0, t0);
|
|
t1 = Fp.add(t1, t0);
|
|
t2 = Fp.mul(a, t2);
|
|
t4 = Fp.mul(b3, t4);
|
|
t1 = Fp.add(t1, t2);
|
|
t2 = Fp.sub(t0, t2);
|
|
t2 = Fp.mul(a, t2);
|
|
t4 = Fp.add(t4, t2);
|
|
t0 = Fp.mul(t1, t4);
|
|
Y3 = Fp.add(Y3, t0);
|
|
t0 = Fp.mul(t5, t4);
|
|
X3 = Fp.mul(t3, X3);
|
|
X3 = Fp.sub(X3, t0);
|
|
t0 = Fp.mul(t3, t1);
|
|
Z3 = Fp.mul(t5, Z3);
|
|
Z3 = Fp.add(Z3, t0);
|
|
return new Point(X3, Y3, Z3);
|
|
}
|
|
subtract(other) {
|
|
return this.add(other.negate());
|
|
}
|
|
is0() {
|
|
return this.equals(Point.ZERO);
|
|
}
|
|
multiply(scalar) {
|
|
const { endo: endo2 } = extraOpts;
|
|
if (!Fn.isValidNot0(scalar))
|
|
throw new Error("invalid scalar: out of range");
|
|
let point, fake;
|
|
const mul = (n) => wnaf.cached(this, n, (p) => normalizeZ(Point, p));
|
|
if (endo2) {
|
|
const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(scalar);
|
|
const { p: k1p, f: k1f } = mul(k1);
|
|
const { p: k2p, f: k2f } = mul(k2);
|
|
fake = k1f.add(k2f);
|
|
point = finishEndo(endo2.beta, k1p, k2p, k1neg, k2neg);
|
|
} else {
|
|
const { p, f } = mul(scalar);
|
|
point = p;
|
|
fake = f;
|
|
}
|
|
return normalizeZ(Point, [point, fake])[0];
|
|
}
|
|
multiplyUnsafe(sc) {
|
|
const { endo: endo2 } = extraOpts;
|
|
const p = this;
|
|
if (!Fn.isValid(sc))
|
|
throw new Error("invalid scalar: out of range");
|
|
if (sc === _0n4 || p.is0())
|
|
return Point.ZERO;
|
|
if (sc === _1n4)
|
|
return p;
|
|
if (wnaf.hasCache(this))
|
|
return this.multiply(sc);
|
|
if (endo2) {
|
|
const { k1neg, k1, k2neg, k2 } = splitEndoScalarN(sc);
|
|
const { p1, p2 } = mulEndoUnsafe(Point, p, k1, k2);
|
|
return finishEndo(endo2.beta, p1, p2, k1neg, k2neg);
|
|
} else {
|
|
return wnaf.unsafe(p, sc);
|
|
}
|
|
}
|
|
toAffine(invertedZ) {
|
|
return toAffineMemo(this, invertedZ);
|
|
}
|
|
isTorsionFree() {
|
|
const { isTorsionFree } = extraOpts;
|
|
if (cofactor === _1n4)
|
|
return true;
|
|
if (isTorsionFree)
|
|
return isTorsionFree(Point, this);
|
|
return wnaf.unsafe(this, CURVE_ORDER).is0();
|
|
}
|
|
clearCofactor() {
|
|
const { clearCofactor } = extraOpts;
|
|
if (cofactor === _1n4)
|
|
return this;
|
|
if (clearCofactor)
|
|
return clearCofactor(Point, this);
|
|
return this.multiplyUnsafe(cofactor);
|
|
}
|
|
isSmallOrder() {
|
|
return this.multiplyUnsafe(cofactor).is0();
|
|
}
|
|
toBytes(isCompressed = true) {
|
|
abool(isCompressed, "isCompressed");
|
|
this.assertValidity();
|
|
return encodePoint(Point, this, isCompressed);
|
|
}
|
|
toHex(isCompressed = true) {
|
|
return bytesToHex(this.toBytes(isCompressed));
|
|
}
|
|
toString() {
|
|
return `<Point ${this.is0() ? "ZERO" : this.toHex()}>`;
|
|
}
|
|
}
|
|
const bits = Fn.BITS;
|
|
const wnaf = new wNAF(Point, extraOpts.endo ? Math.ceil(bits / 2) : bits);
|
|
Point.BASE.precompute(8);
|
|
return Point;
|
|
}
|
|
function pprefix(hasEvenY) {
|
|
return Uint8Array.of(hasEvenY ? 2 : 3);
|
|
}
|
|
function getWLengths(Fp, Fn) {
|
|
return {
|
|
secretKey: Fn.BYTES,
|
|
publicKey: 1 + Fp.BYTES,
|
|
publicKeyUncompressed: 1 + 2 * Fp.BYTES,
|
|
publicKeyHasPrefix: true,
|
|
signature: 2 * Fn.BYTES
|
|
};
|
|
}
|
|
function ecdh(Point, ecdhOpts = {}) {
|
|
const { Fn } = Point;
|
|
const randomBytes_ = ecdhOpts.randomBytes || randomBytes;
|
|
const lengths = Object.assign(getWLengths(Point.Fp, Fn), { seed: getMinHashLength(Fn.ORDER) });
|
|
function isValidSecretKey(secretKey) {
|
|
try {
|
|
const num = Fn.fromBytes(secretKey);
|
|
return Fn.isValidNot0(num);
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
function isValidPublicKey(publicKey, isCompressed) {
|
|
const { publicKey: comp, publicKeyUncompressed } = lengths;
|
|
try {
|
|
const l = publicKey.length;
|
|
if (isCompressed === true && l !== comp)
|
|
return false;
|
|
if (isCompressed === false && l !== publicKeyUncompressed)
|
|
return false;
|
|
return !!Point.fromBytes(publicKey);
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
}
|
|
function randomSecretKey(seed = randomBytes_(lengths.seed)) {
|
|
return mapHashToField(abytes(seed, lengths.seed, "seed"), Fn.ORDER);
|
|
}
|
|
function getPublicKey(secretKey, isCompressed = true) {
|
|
return Point.BASE.multiply(Fn.fromBytes(secretKey)).toBytes(isCompressed);
|
|
}
|
|
function isProbPub(item) {
|
|
const { secretKey, publicKey, publicKeyUncompressed } = lengths;
|
|
if (!isBytes(item))
|
|
return;
|
|
if ("_lengths" in Fn && Fn._lengths || secretKey === publicKey)
|
|
return;
|
|
const l = abytes(item, undefined, "key").length;
|
|
return l === publicKey || l === publicKeyUncompressed;
|
|
}
|
|
function getSharedSecret(secretKeyA, publicKeyB, isCompressed = true) {
|
|
if (isProbPub(secretKeyA) === true)
|
|
throw new Error("first arg must be private key");
|
|
if (isProbPub(publicKeyB) === false)
|
|
throw new Error("second arg must be public key");
|
|
const s = Fn.fromBytes(secretKeyA);
|
|
const b = Point.fromBytes(publicKeyB);
|
|
return b.multiply(s).toBytes(isCompressed);
|
|
}
|
|
const utils = {
|
|
isValidSecretKey,
|
|
isValidPublicKey,
|
|
randomSecretKey
|
|
};
|
|
const keygen = createKeygen(randomSecretKey, getPublicKey);
|
|
return Object.freeze({ getPublicKey, getSharedSecret, keygen, Point, utils, lengths });
|
|
}
|
|
function ecdsa(Point, hash, ecdsaOpts = {}) {
|
|
ahash(hash);
|
|
validateObject(ecdsaOpts, {}, {
|
|
hmac: "function",
|
|
lowS: "boolean",
|
|
randomBytes: "function",
|
|
bits2int: "function",
|
|
bits2int_modN: "function"
|
|
});
|
|
ecdsaOpts = Object.assign({}, ecdsaOpts);
|
|
const randomBytes2 = ecdsaOpts.randomBytes || randomBytes;
|
|
const hmac2 = ecdsaOpts.hmac || ((key, msg) => hmac(hash, key, msg));
|
|
const { Fp, Fn } = Point;
|
|
const { ORDER: CURVE_ORDER, BITS: fnBits } = Fn;
|
|
const { keygen, getPublicKey, getSharedSecret, utils, lengths } = ecdh(Point, ecdsaOpts);
|
|
const defaultSigOpts = {
|
|
prehash: true,
|
|
lowS: typeof ecdsaOpts.lowS === "boolean" ? ecdsaOpts.lowS : true,
|
|
format: "compact",
|
|
extraEntropy: false
|
|
};
|
|
const hasLargeCofactor = CURVE_ORDER * _2n2 < Fp.ORDER;
|
|
function isBiggerThanHalfOrder(number) {
|
|
const HALF = CURVE_ORDER >> _1n4;
|
|
return number > HALF;
|
|
}
|
|
function validateRS(title, num) {
|
|
if (!Fn.isValidNot0(num))
|
|
throw new Error(`invalid signature ${title}: out of range 1..Point.Fn.ORDER`);
|
|
return num;
|
|
}
|
|
function assertSmallCofactor() {
|
|
if (hasLargeCofactor)
|
|
throw new Error('"recovered" sig type is not supported for cofactor >2 curves');
|
|
}
|
|
function validateSigLength(bytes, format) {
|
|
validateSigFormat(format);
|
|
const size = lengths.signature;
|
|
const sizer = format === "compact" ? size : format === "recovered" ? size + 1 : undefined;
|
|
return abytes(bytes, sizer);
|
|
}
|
|
|
|
class Signature {
|
|
r;
|
|
s;
|
|
recovery;
|
|
constructor(r, s, recovery) {
|
|
this.r = validateRS("r", r);
|
|
this.s = validateRS("s", s);
|
|
if (recovery != null) {
|
|
assertSmallCofactor();
|
|
if (![0, 1, 2, 3].includes(recovery))
|
|
throw new Error("invalid recovery id");
|
|
this.recovery = recovery;
|
|
}
|
|
Object.freeze(this);
|
|
}
|
|
static fromBytes(bytes, format = defaultSigOpts.format) {
|
|
validateSigLength(bytes, format);
|
|
let recid;
|
|
if (format === "der") {
|
|
const { r: r2, s: s2 } = DER.toSig(abytes(bytes));
|
|
return new Signature(r2, s2);
|
|
}
|
|
if (format === "recovered") {
|
|
recid = bytes[0];
|
|
format = "compact";
|
|
bytes = bytes.subarray(1);
|
|
}
|
|
const L = lengths.signature / 2;
|
|
const r = bytes.subarray(0, L);
|
|
const s = bytes.subarray(L, L * 2);
|
|
return new Signature(Fn.fromBytes(r), Fn.fromBytes(s), recid);
|
|
}
|
|
static fromHex(hex, format) {
|
|
return this.fromBytes(hexToBytes(hex), format);
|
|
}
|
|
assertRecovery() {
|
|
const { recovery } = this;
|
|
if (recovery == null)
|
|
throw new Error("invalid recovery id: must be present");
|
|
return recovery;
|
|
}
|
|
addRecoveryBit(recovery) {
|
|
return new Signature(this.r, this.s, recovery);
|
|
}
|
|
recoverPublicKey(messageHash) {
|
|
const { r, s } = this;
|
|
const recovery = this.assertRecovery();
|
|
const radj = recovery === 2 || recovery === 3 ? r + CURVE_ORDER : r;
|
|
if (!Fp.isValid(radj))
|
|
throw new Error("invalid recovery id: sig.r+curve.n != R.x");
|
|
const x = Fp.toBytes(radj);
|
|
const R = Point.fromBytes(concatBytes(pprefix((recovery & 1) === 0), x));
|
|
const ir = Fn.inv(radj);
|
|
const h = bits2int_modN(abytes(messageHash, undefined, "msgHash"));
|
|
const u1 = Fn.create(-h * ir);
|
|
const u2 = Fn.create(s * ir);
|
|
const Q = Point.BASE.multiplyUnsafe(u1).add(R.multiplyUnsafe(u2));
|
|
if (Q.is0())
|
|
throw new Error("invalid recovery: point at infinify");
|
|
Q.assertValidity();
|
|
return Q;
|
|
}
|
|
hasHighS() {
|
|
return isBiggerThanHalfOrder(this.s);
|
|
}
|
|
toBytes(format = defaultSigOpts.format) {
|
|
validateSigFormat(format);
|
|
if (format === "der")
|
|
return hexToBytes(DER.hexFromSig(this));
|
|
const { r, s } = this;
|
|
const rb = Fn.toBytes(r);
|
|
const sb = Fn.toBytes(s);
|
|
if (format === "recovered") {
|
|
assertSmallCofactor();
|
|
return concatBytes(Uint8Array.of(this.assertRecovery()), rb, sb);
|
|
}
|
|
return concatBytes(rb, sb);
|
|
}
|
|
toHex(format) {
|
|
return bytesToHex(this.toBytes(format));
|
|
}
|
|
}
|
|
const bits2int = ecdsaOpts.bits2int || function bits2int_def(bytes) {
|
|
if (bytes.length > 8192)
|
|
throw new Error("input is too large");
|
|
const num = bytesToNumberBE(bytes);
|
|
const delta = bytes.length * 8 - fnBits;
|
|
return delta > 0 ? num >> BigInt(delta) : num;
|
|
};
|
|
const bits2int_modN = ecdsaOpts.bits2int_modN || function bits2int_modN_def(bytes) {
|
|
return Fn.create(bits2int(bytes));
|
|
};
|
|
const ORDER_MASK = bitMask(fnBits);
|
|
function int2octets(num) {
|
|
aInRange("num < 2^" + fnBits, num, _0n4, ORDER_MASK);
|
|
return Fn.toBytes(num);
|
|
}
|
|
function validateMsgAndHash(message, prehash) {
|
|
abytes(message, undefined, "message");
|
|
return prehash ? abytes(hash(message), undefined, "prehashed message") : message;
|
|
}
|
|
function prepSig(message, secretKey, opts) {
|
|
const { lowS, prehash, extraEntropy } = validateSigOpts(opts, defaultSigOpts);
|
|
message = validateMsgAndHash(message, prehash);
|
|
const h1int = bits2int_modN(message);
|
|
const d = Fn.fromBytes(secretKey);
|
|
if (!Fn.isValidNot0(d))
|
|
throw new Error("invalid private key");
|
|
const seedArgs = [int2octets(d), int2octets(h1int)];
|
|
if (extraEntropy != null && extraEntropy !== false) {
|
|
const e = extraEntropy === true ? randomBytes2(lengths.secretKey) : extraEntropy;
|
|
seedArgs.push(abytes(e, undefined, "extraEntropy"));
|
|
}
|
|
const seed = concatBytes(...seedArgs);
|
|
const m = h1int;
|
|
function k2sig(kBytes) {
|
|
const k = bits2int(kBytes);
|
|
if (!Fn.isValidNot0(k))
|
|
return;
|
|
const ik = Fn.inv(k);
|
|
const q = Point.BASE.multiply(k).toAffine();
|
|
const r = Fn.create(q.x);
|
|
if (r === _0n4)
|
|
return;
|
|
const s = Fn.create(ik * Fn.create(m + r * d));
|
|
if (s === _0n4)
|
|
return;
|
|
let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n4);
|
|
let normS = s;
|
|
if (lowS && isBiggerThanHalfOrder(s)) {
|
|
normS = Fn.neg(s);
|
|
recovery ^= 1;
|
|
}
|
|
return new Signature(r, normS, hasLargeCofactor ? undefined : recovery);
|
|
}
|
|
return { seed, k2sig };
|
|
}
|
|
function sign(message, secretKey, opts = {}) {
|
|
const { seed, k2sig } = prepSig(message, secretKey, opts);
|
|
const drbg = createHmacDrbg(hash.outputLen, Fn.BYTES, hmac2);
|
|
const sig = drbg(seed, k2sig);
|
|
return sig.toBytes(opts.format);
|
|
}
|
|
function verify(signature, message, publicKey, opts = {}) {
|
|
const { lowS, prehash, format } = validateSigOpts(opts, defaultSigOpts);
|
|
publicKey = abytes(publicKey, undefined, "publicKey");
|
|
message = validateMsgAndHash(message, prehash);
|
|
if (!isBytes(signature)) {
|
|
const end = signature instanceof Signature ? ", use sig.toBytes()" : "";
|
|
throw new Error("verify expects Uint8Array signature" + end);
|
|
}
|
|
validateSigLength(signature, format);
|
|
try {
|
|
const sig = Signature.fromBytes(signature, format);
|
|
const P = Point.fromBytes(publicKey);
|
|
if (lowS && sig.hasHighS())
|
|
return false;
|
|
const { r, s } = sig;
|
|
const h = bits2int_modN(message);
|
|
const is = Fn.inv(s);
|
|
const u1 = Fn.create(h * is);
|
|
const u2 = Fn.create(r * is);
|
|
const R = Point.BASE.multiplyUnsafe(u1).add(P.multiplyUnsafe(u2));
|
|
if (R.is0())
|
|
return false;
|
|
const v = Fn.create(R.x);
|
|
return v === r;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
function recoverPublicKey(signature, message, opts = {}) {
|
|
const { prehash } = validateSigOpts(opts, defaultSigOpts);
|
|
message = validateMsgAndHash(message, prehash);
|
|
return Signature.fromBytes(signature, "recovered").recoverPublicKey(message).toBytes();
|
|
}
|
|
return Object.freeze({
|
|
keygen,
|
|
getPublicKey,
|
|
getSharedSecret,
|
|
utils,
|
|
lengths,
|
|
Point,
|
|
sign,
|
|
verify,
|
|
recoverPublicKey,
|
|
Signature,
|
|
hash
|
|
});
|
|
}
|
|
|
|
// node_modules/@noble/curves/secp256k1.js
|
|
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
var secp256k1_CURVE = {
|
|
p: BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),
|
|
n: BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),
|
|
h: BigInt(1),
|
|
a: BigInt(0),
|
|
b: BigInt(7),
|
|
Gx: BigInt("0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"),
|
|
Gy: BigInt("0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8")
|
|
};
|
|
var secp256k1_ENDO = {
|
|
beta: BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),
|
|
basises: [
|
|
[BigInt("0x3086d221a7d46bcde86c90e49284eb15"), -BigInt("0xe4437ed6010e88286f547fa90abfe4c3")],
|
|
[BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"), BigInt("0x3086d221a7d46bcde86c90e49284eb15")]
|
|
]
|
|
};
|
|
var _2n3 = /* @__PURE__ */ BigInt(2);
|
|
function sqrtMod(y) {
|
|
const P = secp256k1_CURVE.p;
|
|
const _3n3 = BigInt(3), _6n = BigInt(6), _11n = BigInt(11), _22n = BigInt(22);
|
|
const _23n = BigInt(23), _44n = BigInt(44), _88n = BigInt(88);
|
|
const b2 = y * y * y % P;
|
|
const b3 = b2 * b2 * y % P;
|
|
const b6 = pow2(b3, _3n3, P) * b3 % P;
|
|
const b9 = pow2(b6, _3n3, P) * b3 % P;
|
|
const b11 = pow2(b9, _2n3, P) * b2 % P;
|
|
const b22 = pow2(b11, _11n, P) * b11 % P;
|
|
const b44 = pow2(b22, _22n, P) * b22 % P;
|
|
const b88 = pow2(b44, _44n, P) * b44 % P;
|
|
const b176 = pow2(b88, _88n, P) * b88 % P;
|
|
const b220 = pow2(b176, _44n, P) * b44 % P;
|
|
const b223 = pow2(b220, _3n3, P) * b3 % P;
|
|
const t1 = pow2(b223, _23n, P) * b22 % P;
|
|
const t2 = pow2(t1, _6n, P) * b2 % P;
|
|
const root = pow2(t2, _2n3, P);
|
|
if (!Fpk1.eql(Fpk1.sqr(root), y))
|
|
throw new Error("Cannot find square root");
|
|
return root;
|
|
}
|
|
var Fpk1 = Field(secp256k1_CURVE.p, { sqrt: sqrtMod });
|
|
var Pointk1 = /* @__PURE__ */ weierstrass(secp256k1_CURVE, {
|
|
Fp: Fpk1,
|
|
endo: secp256k1_ENDO
|
|
});
|
|
var secp256k1 = /* @__PURE__ */ ecdsa(Pointk1, sha256);
|
|
|
|
// node_modules/@noble/hashes/sha3.js
|
|
var _0n5 = BigInt(0);
|
|
var _1n5 = BigInt(1);
|
|
var _2n4 = BigInt(2);
|
|
var _7n2 = BigInt(7);
|
|
var _256n = BigInt(256);
|
|
var _0x71n = BigInt(113);
|
|
var SHA3_PI = [];
|
|
var SHA3_ROTL = [];
|
|
var _SHA3_IOTA = [];
|
|
for (let round = 0, R = _1n5, x = 1, y = 0;round < 24; round++) {
|
|
[x, y] = [y, (2 * x + 3 * y) % 5];
|
|
SHA3_PI.push(2 * (5 * y + x));
|
|
SHA3_ROTL.push((round + 1) * (round + 2) / 2 % 64);
|
|
let t = _0n5;
|
|
for (let j = 0;j < 7; j++) {
|
|
R = (R << _1n5 ^ (R >> _7n2) * _0x71n) % _256n;
|
|
if (R & _2n4)
|
|
t ^= _1n5 << (_1n5 << BigInt(j)) - _1n5;
|
|
}
|
|
_SHA3_IOTA.push(t);
|
|
}
|
|
var IOTAS = split(_SHA3_IOTA, true);
|
|
var SHA3_IOTA_H = IOTAS[0];
|
|
var SHA3_IOTA_L = IOTAS[1];
|
|
var rotlH = (h, l, s) => s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s);
|
|
var rotlL = (h, l, s) => s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s);
|
|
function keccakP(s, rounds = 24) {
|
|
const B = new Uint32Array(5 * 2);
|
|
for (let round = 24 - rounds;round < 24; round++) {
|
|
for (let x = 0;x < 10; x++)
|
|
B[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40];
|
|
for (let x = 0;x < 10; x += 2) {
|
|
const idx1 = (x + 8) % 10;
|
|
const idx0 = (x + 2) % 10;
|
|
const B0 = B[idx0];
|
|
const B1 = B[idx0 + 1];
|
|
const Th = rotlH(B0, B1, 1) ^ B[idx1];
|
|
const Tl = rotlL(B0, B1, 1) ^ B[idx1 + 1];
|
|
for (let y = 0;y < 50; y += 10) {
|
|
s[x + y] ^= Th;
|
|
s[x + y + 1] ^= Tl;
|
|
}
|
|
}
|
|
let curH = s[2];
|
|
let curL = s[3];
|
|
for (let t = 0;t < 24; t++) {
|
|
const shift = SHA3_ROTL[t];
|
|
const Th = rotlH(curH, curL, shift);
|
|
const Tl = rotlL(curH, curL, shift);
|
|
const PI = SHA3_PI[t];
|
|
curH = s[PI];
|
|
curL = s[PI + 1];
|
|
s[PI] = Th;
|
|
s[PI + 1] = Tl;
|
|
}
|
|
for (let y = 0;y < 50; y += 10) {
|
|
for (let x = 0;x < 10; x++)
|
|
B[x] = s[y + x];
|
|
for (let x = 0;x < 10; x++)
|
|
s[y + x] ^= ~B[(x + 2) % 10] & B[(x + 4) % 10];
|
|
}
|
|
s[0] ^= SHA3_IOTA_H[round];
|
|
s[1] ^= SHA3_IOTA_L[round];
|
|
}
|
|
clean(B);
|
|
}
|
|
|
|
class Keccak {
|
|
state;
|
|
pos = 0;
|
|
posOut = 0;
|
|
finished = false;
|
|
state32;
|
|
destroyed = false;
|
|
blockLen;
|
|
suffix;
|
|
outputLen;
|
|
enableXOF = false;
|
|
rounds;
|
|
constructor(blockLen, suffix, outputLen, enableXOF = false, rounds = 24) {
|
|
this.blockLen = blockLen;
|
|
this.suffix = suffix;
|
|
this.outputLen = outputLen;
|
|
this.enableXOF = enableXOF;
|
|
this.rounds = rounds;
|
|
anumber(outputLen, "outputLen");
|
|
if (!(0 < blockLen && blockLen < 200))
|
|
throw new Error("only keccak-f1600 function is supported");
|
|
this.state = new Uint8Array(200);
|
|
this.state32 = u32(this.state);
|
|
}
|
|
clone() {
|
|
return this._cloneInto();
|
|
}
|
|
keccak() {
|
|
swap32IfBE(this.state32);
|
|
keccakP(this.state32, this.rounds);
|
|
swap32IfBE(this.state32);
|
|
this.posOut = 0;
|
|
this.pos = 0;
|
|
}
|
|
update(data) {
|
|
aexists(this);
|
|
abytes(data);
|
|
const { blockLen, state } = this;
|
|
const len = data.length;
|
|
for (let pos = 0;pos < len; ) {
|
|
const take = Math.min(blockLen - this.pos, len - pos);
|
|
for (let i = 0;i < take; i++)
|
|
state[this.pos++] ^= data[pos++];
|
|
if (this.pos === blockLen)
|
|
this.keccak();
|
|
}
|
|
return this;
|
|
}
|
|
finish() {
|
|
if (this.finished)
|
|
return;
|
|
this.finished = true;
|
|
const { state, suffix, pos, blockLen } = this;
|
|
state[pos] ^= suffix;
|
|
if ((suffix & 128) !== 0 && pos === blockLen - 1)
|
|
this.keccak();
|
|
state[blockLen - 1] ^= 128;
|
|
this.keccak();
|
|
}
|
|
writeInto(out) {
|
|
aexists(this, false);
|
|
abytes(out);
|
|
this.finish();
|
|
const bufferOut = this.state;
|
|
const { blockLen } = this;
|
|
for (let pos = 0, len = out.length;pos < len; ) {
|
|
if (this.posOut >= blockLen)
|
|
this.keccak();
|
|
const take = Math.min(blockLen - this.posOut, len - pos);
|
|
out.set(bufferOut.subarray(this.posOut, this.posOut + take), pos);
|
|
this.posOut += take;
|
|
pos += take;
|
|
}
|
|
return out;
|
|
}
|
|
xofInto(out) {
|
|
if (!this.enableXOF)
|
|
throw new Error("XOF is not possible for this instance");
|
|
return this.writeInto(out);
|
|
}
|
|
xof(bytes) {
|
|
anumber(bytes);
|
|
return this.xofInto(new Uint8Array(bytes));
|
|
}
|
|
digestInto(out) {
|
|
aoutput(out, this);
|
|
if (this.finished)
|
|
throw new Error("digest() was already called");
|
|
this.writeInto(out);
|
|
this.destroy();
|
|
return out;
|
|
}
|
|
digest() {
|
|
return this.digestInto(new Uint8Array(this.outputLen));
|
|
}
|
|
destroy() {
|
|
this.destroyed = true;
|
|
clean(this.state);
|
|
}
|
|
_cloneInto(to) {
|
|
const { blockLen, suffix, outputLen, rounds, enableXOF } = this;
|
|
to ||= new Keccak(blockLen, suffix, outputLen, enableXOF, rounds);
|
|
to.state32.set(this.state32);
|
|
to.pos = this.pos;
|
|
to.posOut = this.posOut;
|
|
to.finished = this.finished;
|
|
to.rounds = rounds;
|
|
to.suffix = suffix;
|
|
to.outputLen = outputLen;
|
|
to.enableXOF = enableXOF;
|
|
to.destroyed = this.destroyed;
|
|
return to;
|
|
}
|
|
}
|
|
var genKeccak = (suffix, blockLen, outputLen, info = {}) => createHasher(() => new Keccak(blockLen, suffix, outputLen), info);
|
|
var keccak_256 = /* @__PURE__ */ genKeccak(1, 136, 32);
|
|
|
|
// src/client/key-derivation.ts
|
|
class EncryptIDKeyManager {
|
|
masterKey = null;
|
|
derivedKeys = null;
|
|
fromPRF = false;
|
|
async initFromPRF(prfOutput) {
|
|
this.masterKey = await crypto.subtle.importKey("raw", prfOutput, { name: "HKDF" }, false, ["deriveKey", "deriveBits"]);
|
|
this.fromPRF = true;
|
|
this.derivedKeys = null;
|
|
}
|
|
async initFromPassphrase(passphrase, salt) {
|
|
const encoder = new TextEncoder;
|
|
const passphraseKey = await crypto.subtle.importKey("raw", encoder.encode(passphrase), { name: "PBKDF2" }, false, ["deriveBits"]);
|
|
const masterKeyMaterial = await crypto.subtle.deriveBits({ name: "PBKDF2", salt, iterations: 600000, hash: "SHA-256" }, passphraseKey, 256);
|
|
this.masterKey = await crypto.subtle.importKey("raw", masterKeyMaterial, { name: "HKDF" }, false, ["deriveKey", "deriveBits"]);
|
|
this.fromPRF = false;
|
|
this.derivedKeys = null;
|
|
}
|
|
static generateSalt() {
|
|
return crypto.getRandomValues(new Uint8Array(32));
|
|
}
|
|
isInitialized() {
|
|
return this.masterKey !== null;
|
|
}
|
|
async getKeys() {
|
|
if (!this.masterKey)
|
|
throw new Error("Key manager not initialized");
|
|
if (this.derivedKeys)
|
|
return this.derivedKeys;
|
|
const [encryptionKey, signingKeyPair, didSeed, ethereum] = await Promise.all([
|
|
this.deriveEncryptionKey(),
|
|
this.deriveSigningKeyPair(),
|
|
this.deriveDIDSeed(),
|
|
this.deriveEthereumKeys()
|
|
]);
|
|
const did = await this.generateDID(didSeed);
|
|
this.derivedKeys = { encryptionKey, signingKeyPair, didSeed, did, fromPRF: this.fromPRF, ethereum };
|
|
return this.derivedKeys;
|
|
}
|
|
async deriveEncryptionKey() {
|
|
const encoder = new TextEncoder;
|
|
return crypto.subtle.deriveKey({ name: "HKDF", hash: "SHA-256", salt: encoder.encode("encryptid-encryption-key-v1"), info: encoder.encode("AES-256-GCM") }, this.masterKey, { name: "AES-GCM", length: 256 }, false, ["encrypt", "decrypt", "wrapKey", "unwrapKey"]);
|
|
}
|
|
async deriveSigningKeyPair() {
|
|
return crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, false, ["sign", "verify"]);
|
|
}
|
|
async deriveEthereumKeys() {
|
|
const encoder = new TextEncoder;
|
|
const privateKeyBits = await crypto.subtle.deriveBits({
|
|
name: "HKDF",
|
|
hash: "SHA-256",
|
|
salt: encoder.encode("encryptid-ethereum-secp256k1-v1"),
|
|
info: encoder.encode("secp256k1-private-key")
|
|
}, this.masterKey, 256);
|
|
const privateKey = new Uint8Array(privateKeyBits);
|
|
const publicKey = secp256k1.getPublicKey(privateKey, true);
|
|
const uncompressedPubKey = secp256k1.getPublicKey(privateKey, false);
|
|
const pubKeyHash = keccak_256(uncompressedPubKey.slice(1));
|
|
const addressBytes = pubKeyHash.slice(-20);
|
|
const address = toChecksumAddress(addressBytes);
|
|
return { address, publicKey, privateKey };
|
|
}
|
|
async deriveDIDSeed() {
|
|
const encoder = new TextEncoder;
|
|
const seed = await crypto.subtle.deriveBits({ name: "HKDF", hash: "SHA-256", salt: encoder.encode("encryptid-did-key-v1"), info: encoder.encode("Ed25519-seed") }, this.masterKey, 256);
|
|
return new Uint8Array(seed);
|
|
}
|
|
async generateDID(seed) {
|
|
const publicKeyHash = await crypto.subtle.digest("SHA-256", seed);
|
|
const publicKeyBytes = new Uint8Array(publicKeyHash).slice(0, 32);
|
|
const multicodecPrefix = new Uint8Array([237, 1]);
|
|
const multicodecKey = new Uint8Array(34);
|
|
multicodecKey.set(multicodecPrefix);
|
|
multicodecKey.set(publicKeyBytes, 2);
|
|
const base58Encoded = bufferToBase64url(multicodecKey.buffer).replace(/-/g, "").replace(/_/g, "");
|
|
return `did:key:z${base58Encoded}`;
|
|
}
|
|
clear() {
|
|
this.masterKey = null;
|
|
this.derivedKeys = null;
|
|
this.fromPRF = false;
|
|
}
|
|
}
|
|
function toChecksumAddress(addressBytes) {
|
|
const hex = Array.from(addressBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
const hash = keccak_256(new TextEncoder().encode(hex));
|
|
let checksummed = "0x";
|
|
for (let i = 0;i < 40; i++) {
|
|
const hashNibble = hash[Math.floor(i / 2)] >> (i % 2 === 0 ? 4 : 0) & 15;
|
|
checksummed += hashNibble >= 8 ? hex[i].toUpperCase() : hex[i];
|
|
}
|
|
return checksummed;
|
|
}
|
|
function signEthHash(hash, privateKey) {
|
|
const sigBytes = secp256k1.sign(hash, privateKey, { prehash: false, format: "recovered" });
|
|
const sig = secp256k1.Signature.fromBytes(sigBytes, "recovered");
|
|
return {
|
|
r: sig.r.toString(16).padStart(64, "0"),
|
|
s: sig.s.toString(16).padStart(64, "0"),
|
|
v: (sig.recovery ?? 0) + 27,
|
|
signature: sig.toBytes("compact")
|
|
};
|
|
}
|
|
var keyManagerInstance = null;
|
|
function getKeyManager() {
|
|
if (!keyManagerInstance)
|
|
keyManagerInstance = new EncryptIDKeyManager;
|
|
return keyManagerInstance;
|
|
}
|
|
// src/client/session.ts
|
|
var OPERATION_PERMISSIONS = {
|
|
"rspace:view-public": { minAuthLevel: 1 /* BASIC */ },
|
|
"rspace:view-private": { minAuthLevel: 2 /* STANDARD */ },
|
|
"rspace:edit-board": { minAuthLevel: 2 /* STANDARD */ },
|
|
"rspace:create-board": { minAuthLevel: 2 /* STANDARD */ },
|
|
"rspace:delete-board": { minAuthLevel: 3 /* ELEVATED */, maxAgeSeconds: 300 },
|
|
"rspace:encrypt-board": { minAuthLevel: 3 /* ELEVATED */, requiresCapability: "encrypt" },
|
|
"rwallet:view-balance": { minAuthLevel: 1 /* BASIC */ },
|
|
"rwallet:view-history": { minAuthLevel: 2 /* STANDARD */ },
|
|
"rwallet:send-small": { minAuthLevel: 2 /* STANDARD */, requiresCapability: "wallet" },
|
|
"rwallet:send-large": { minAuthLevel: 3 /* ELEVATED */, requiresCapability: "wallet", maxAgeSeconds: 60 },
|
|
"rwallet:add-guardian": { minAuthLevel: 4 /* CRITICAL */, maxAgeSeconds: 60 },
|
|
"rwallet:remove-guardian": { minAuthLevel: 4 /* CRITICAL */, maxAgeSeconds: 60 },
|
|
"rvote:view-proposals": { minAuthLevel: 1 /* BASIC */ },
|
|
"rvote:cast-vote": { minAuthLevel: 3 /* ELEVATED */, requiresCapability: "sign", maxAgeSeconds: 300 },
|
|
"rvote:delegate": { minAuthLevel: 3 /* ELEVATED */, requiresCapability: "wallet" },
|
|
"rfiles:list-files": { minAuthLevel: 2 /* STANDARD */ },
|
|
"rfiles:download-own": { minAuthLevel: 2 /* STANDARD */, requiresCapability: "encrypt" },
|
|
"rfiles:upload": { minAuthLevel: 2 /* STANDARD */, requiresCapability: "encrypt" },
|
|
"rfiles:share": { minAuthLevel: 3 /* ELEVATED */, requiresCapability: "encrypt" },
|
|
"rfiles:delete": { minAuthLevel: 3 /* ELEVATED */, maxAgeSeconds: 300 },
|
|
"rfiles:export-keys": { minAuthLevel: 4 /* CRITICAL */, maxAgeSeconds: 60 },
|
|
"rmaps:view-public": { minAuthLevel: 1 /* BASIC */ },
|
|
"rmaps:add-location": { minAuthLevel: 2 /* STANDARD */ },
|
|
"rmaps:edit-location": { minAuthLevel: 2 /* STANDARD */, requiresCapability: "sign" },
|
|
"account:view-profile": { minAuthLevel: 2 /* STANDARD */ },
|
|
"account:edit-profile": { minAuthLevel: 3 /* ELEVATED */ },
|
|
"account:export-data": { minAuthLevel: 4 /* CRITICAL */, maxAgeSeconds: 60 },
|
|
"account:delete": { minAuthLevel: 4 /* CRITICAL */, maxAgeSeconds: 60 },
|
|
"rspace:create-space": { minAuthLevel: 2 /* STANDARD */ },
|
|
"rspace:configure-space": { minAuthLevel: 3 /* ELEVATED */, maxAgeSeconds: 300 },
|
|
"rspace:delete-space": { minAuthLevel: 4 /* CRITICAL */, maxAgeSeconds: 60 },
|
|
"rspace:invite-member": { minAuthLevel: 2 /* STANDARD */ },
|
|
"rspace:remove-member": { minAuthLevel: 3 /* ELEVATED */, maxAgeSeconds: 300 },
|
|
"rspace:change-visibility": { minAuthLevel: 3 /* ELEVATED */, maxAgeSeconds: 300 },
|
|
"rfunds:create-space": { minAuthLevel: 2 /* STANDARD */ },
|
|
"rfunds:edit-flows": { minAuthLevel: 2 /* STANDARD */ },
|
|
"rfunds:share-space": { minAuthLevel: 2 /* STANDARD */ }
|
|
};
|
|
var SESSION_STORAGE_KEY = "encryptid_session";
|
|
var TOKEN_REFRESH_THRESHOLD = 5 * 60 * 1000;
|
|
|
|
class SessionManager {
|
|
session = null;
|
|
refreshTimer = null;
|
|
constructor() {
|
|
this.restoreSession();
|
|
}
|
|
async createSession(authResult, did, capabilities, walletAddress, username) {
|
|
const now = Math.floor(Date.now() / 1000);
|
|
const claims = {
|
|
iss: "https://encryptid.jeffemmett.com",
|
|
sub: did,
|
|
aud: ["rspace.online", "rwallet.online", "rvote.online", "rfiles.online", "rmaps.online", "rmail.online"],
|
|
iat: now,
|
|
exp: now + 15 * 60,
|
|
jti: bufferToBase64url(crypto.getRandomValues(new Uint8Array(16)).buffer),
|
|
username: username || "",
|
|
eid: {
|
|
walletAddress,
|
|
credentialId: authResult.credentialId,
|
|
authLevel: 3 /* ELEVATED */,
|
|
authTime: now,
|
|
capabilities,
|
|
recoveryConfigured: false
|
|
}
|
|
};
|
|
const accessToken = this.createUnsignedToken(claims);
|
|
const refreshToken = this.createRefreshToken(did);
|
|
this.session = { accessToken, refreshToken, claims, lastAuthTime: Date.now() };
|
|
this.persistSession();
|
|
this.scheduleRefresh();
|
|
return this.session;
|
|
}
|
|
getSession() {
|
|
return this.session;
|
|
}
|
|
getDID() {
|
|
return this.session?.claims.sub ?? null;
|
|
}
|
|
getAccessToken() {
|
|
return this.session?.accessToken ?? null;
|
|
}
|
|
getAuthLevel() {
|
|
if (!this.session)
|
|
return 1 /* BASIC */;
|
|
const now = Math.floor(Date.now() / 1000);
|
|
if (now >= this.session.claims.exp)
|
|
return 1 /* BASIC */;
|
|
const authAge = now - this.session.claims.eid.authTime;
|
|
if (authAge < 60)
|
|
return 3 /* ELEVATED */;
|
|
if (authAge < 15 * 60)
|
|
return 2 /* STANDARD */;
|
|
return 1 /* BASIC */;
|
|
}
|
|
canPerform(operation) {
|
|
const permission = OPERATION_PERMISSIONS[operation];
|
|
if (!permission)
|
|
return { allowed: false, reason: "Unknown operation" };
|
|
if (!this.session)
|
|
return { allowed: false, reason: "Not authenticated" };
|
|
const currentLevel = this.getAuthLevel();
|
|
if (currentLevel < permission.minAuthLevel) {
|
|
return { allowed: false, reason: `Requires ${AuthLevel[permission.minAuthLevel]} auth level (current: ${AuthLevel[currentLevel]})` };
|
|
}
|
|
if (permission.requiresCapability) {
|
|
if (!this.session.claims.eid.capabilities[permission.requiresCapability]) {
|
|
return { allowed: false, reason: `Requires ${permission.requiresCapability} capability` };
|
|
}
|
|
}
|
|
if (permission.maxAgeSeconds) {
|
|
const authAge = Math.floor(Date.now() / 1000) - this.session.claims.eid.authTime;
|
|
if (authAge > permission.maxAgeSeconds) {
|
|
return { allowed: false, reason: `Authentication too old (${authAge}s > ${permission.maxAgeSeconds}s)` };
|
|
}
|
|
}
|
|
return { allowed: true };
|
|
}
|
|
requiresFreshAuth(operation) {
|
|
const permission = OPERATION_PERMISSIONS[operation];
|
|
if (!permission)
|
|
return true;
|
|
if (permission.minAuthLevel >= 4 /* CRITICAL */)
|
|
return true;
|
|
if (permission.maxAgeSeconds && permission.maxAgeSeconds <= 60)
|
|
return true;
|
|
return false;
|
|
}
|
|
upgradeAuthLevel(level = 3 /* ELEVATED */) {
|
|
if (!this.session)
|
|
return;
|
|
this.session.claims.eid.authLevel = level;
|
|
this.session.claims.eid.authTime = Math.floor(Date.now() / 1000);
|
|
this.session.lastAuthTime = Date.now();
|
|
this.persistSession();
|
|
}
|
|
clearSession() {
|
|
this.session = null;
|
|
if (this.refreshTimer) {
|
|
clearTimeout(this.refreshTimer);
|
|
this.refreshTimer = null;
|
|
}
|
|
try {
|
|
localStorage.removeItem(SESSION_STORAGE_KEY);
|
|
} catch {}
|
|
}
|
|
isValid() {
|
|
if (!this.session)
|
|
return false;
|
|
return Math.floor(Date.now() / 1000) < this.session.claims.exp;
|
|
}
|
|
createUnsignedToken(claims) {
|
|
const header = { alg: "none", typ: "JWT" };
|
|
return `${btoa(JSON.stringify(header))}.${btoa(JSON.stringify(claims))}.`;
|
|
}
|
|
createRefreshToken(did) {
|
|
return btoa(JSON.stringify({
|
|
sub: did,
|
|
iat: Math.floor(Date.now() / 1000),
|
|
exp: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60,
|
|
jti: bufferToBase64url(crypto.getRandomValues(new Uint8Array(16)).buffer)
|
|
}));
|
|
}
|
|
persistSession() {
|
|
if (!this.session)
|
|
return;
|
|
try {
|
|
localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(this.session));
|
|
} catch {}
|
|
}
|
|
restoreSession() {
|
|
try {
|
|
const stored = localStorage.getItem(SESSION_STORAGE_KEY);
|
|
if (stored) {
|
|
const session = JSON.parse(stored);
|
|
if (Math.floor(Date.now() / 1000) < session.claims.exp) {
|
|
this.session = session;
|
|
this.scheduleRefresh();
|
|
} else {
|
|
localStorage.removeItem(SESSION_STORAGE_KEY);
|
|
}
|
|
}
|
|
} catch {}
|
|
}
|
|
scheduleRefresh() {
|
|
if (!this.session)
|
|
return;
|
|
if (this.refreshTimer)
|
|
clearTimeout(this.refreshTimer);
|
|
const expiresAt = this.session.claims.exp * 1000;
|
|
const refreshAt = expiresAt - TOKEN_REFRESH_THRESHOLD;
|
|
const delay = Math.max(refreshAt - Date.now(), 0);
|
|
this.refreshTimer = setTimeout(() => this.refreshTokens(), delay);
|
|
}
|
|
async refreshTokens() {
|
|
if (!this.session)
|
|
return;
|
|
const now = Math.floor(Date.now() / 1000);
|
|
this.session.claims.eid.authLevel = Math.min(this.session.claims.eid.authLevel, 2 /* STANDARD */);
|
|
this.session.claims.iat = now;
|
|
this.session.claims.exp = now + 15 * 60;
|
|
this.session.claims.jti = bufferToBase64url(crypto.getRandomValues(new Uint8Array(16)).buffer);
|
|
this.session.accessToken = this.createUnsignedToken(this.session.claims);
|
|
this.persistSession();
|
|
this.scheduleRefresh();
|
|
}
|
|
}
|
|
var sessionManagerInstance = null;
|
|
function getSessionManager() {
|
|
if (!sessionManagerInstance)
|
|
sessionManagerInstance = new SessionManager;
|
|
return sessionManagerInstance;
|
|
}
|
|
// src/client/recovery.ts
|
|
class RecoveryManager {
|
|
config = null;
|
|
activeRequest = null;
|
|
constructor() {
|
|
this.loadConfig();
|
|
}
|
|
async initializeRecovery(threshold = 3) {
|
|
this.config = {
|
|
threshold,
|
|
delaySeconds: 48 * 60 * 60,
|
|
guardians: [],
|
|
guardianListHash: "",
|
|
updatedAt: Date.now()
|
|
};
|
|
await this.saveConfig();
|
|
return this.config;
|
|
}
|
|
async addGuardian(guardian) {
|
|
if (!this.config)
|
|
throw new Error("Recovery not initialized");
|
|
if (this.config.guardians.length >= 7)
|
|
throw new Error("Maximum of 7 guardians allowed");
|
|
const newGuardian = {
|
|
...guardian,
|
|
id: bufferToBase64url(crypto.getRandomValues(new Uint8Array(16)).buffer),
|
|
addedAt: Date.now()
|
|
};
|
|
this.config.guardians.push(newGuardian);
|
|
this.config.guardianListHash = await this.hashGuardianList();
|
|
this.config.updatedAt = Date.now();
|
|
await this.saveConfig();
|
|
return newGuardian;
|
|
}
|
|
async removeGuardian(guardianId) {
|
|
if (!this.config)
|
|
throw new Error("Recovery not initialized");
|
|
const index = this.config.guardians.findIndex((g) => g.id === guardianId);
|
|
if (index === -1)
|
|
throw new Error("Guardian not found");
|
|
const remainingWeight = this.config.guardians.filter((g) => g.id !== guardianId).reduce((sum, g) => sum + g.weight, 0);
|
|
if (remainingWeight < this.config.threshold)
|
|
throw new Error("Cannot remove guardian: would make recovery impossible");
|
|
this.config.guardians.splice(index, 1);
|
|
this.config.guardianListHash = await this.hashGuardianList();
|
|
this.config.updatedAt = Date.now();
|
|
await this.saveConfig();
|
|
}
|
|
async setThreshold(threshold) {
|
|
if (!this.config)
|
|
throw new Error("Recovery not initialized");
|
|
const totalWeight = this.config.guardians.reduce((sum, g) => sum + g.weight, 0);
|
|
if (threshold > totalWeight)
|
|
throw new Error("Threshold cannot exceed total guardian weight");
|
|
if (threshold < 1)
|
|
throw new Error("Threshold must be at least 1");
|
|
this.config.threshold = threshold;
|
|
this.config.updatedAt = Date.now();
|
|
await this.saveConfig();
|
|
}
|
|
async setDelay(delaySeconds) {
|
|
if (!this.config)
|
|
throw new Error("Recovery not initialized");
|
|
if (delaySeconds < 3600 || delaySeconds > 7 * 24 * 3600)
|
|
throw new Error("Delay must be between 1 hour and 7 days");
|
|
this.config.delaySeconds = delaySeconds;
|
|
this.config.updatedAt = Date.now();
|
|
await this.saveConfig();
|
|
}
|
|
getConfig() {
|
|
return this.config;
|
|
}
|
|
isConfigured() {
|
|
if (!this.config)
|
|
return false;
|
|
return this.config.guardians.reduce((sum, g) => sum + g.weight, 0) >= this.config.threshold;
|
|
}
|
|
async verifyGuardian(guardianId) {
|
|
if (!this.config)
|
|
throw new Error("Recovery not initialized");
|
|
const guardian = this.config.guardians.find((g) => g.id === guardianId);
|
|
if (!guardian)
|
|
throw new Error("Guardian not found");
|
|
guardian.lastVerified = Date.now();
|
|
await this.saveConfig();
|
|
return true;
|
|
}
|
|
async initiateRecovery(newCredentialId) {
|
|
if (!this.config)
|
|
throw new Error("Recovery not configured");
|
|
if (this.activeRequest?.status === "pending")
|
|
throw new Error("Recovery already in progress");
|
|
const now = Date.now();
|
|
this.activeRequest = {
|
|
id: bufferToBase64url(crypto.getRandomValues(new Uint8Array(16)).buffer),
|
|
accountDID: "",
|
|
newCredentialId,
|
|
initiatedAt: now,
|
|
completesAt: now + this.config.delaySeconds * 1000,
|
|
status: "pending",
|
|
approvals: [],
|
|
approvalWeight: 0
|
|
};
|
|
return this.activeRequest;
|
|
}
|
|
async approveRecovery(guardianId, signature) {
|
|
if (!this.activeRequest || this.activeRequest.status !== "pending")
|
|
throw new Error("No pending recovery request");
|
|
if (!this.config)
|
|
throw new Error("Recovery not configured");
|
|
const guardian = this.config.guardians.find((g) => g.id === guardianId);
|
|
if (!guardian)
|
|
throw new Error("Guardian not found");
|
|
if (this.activeRequest.approvals.some((a) => a.guardianId === guardianId))
|
|
throw new Error("Guardian already approved");
|
|
this.activeRequest.approvals.push({ guardianId, approvedAt: Date.now(), signature });
|
|
this.activeRequest.approvalWeight += guardian.weight;
|
|
if (this.activeRequest.approvalWeight >= this.config.threshold) {
|
|
this.activeRequest.status = "approved";
|
|
}
|
|
return this.activeRequest;
|
|
}
|
|
async cancelRecovery() {
|
|
if (!this.activeRequest || this.activeRequest.status !== "pending")
|
|
throw new Error("No pending recovery request to cancel");
|
|
this.activeRequest.status = "cancelled";
|
|
this.activeRequest = null;
|
|
}
|
|
async completeRecovery() {
|
|
if (!this.activeRequest)
|
|
throw new Error("No recovery request");
|
|
if (this.activeRequest.status !== "approved")
|
|
throw new Error("Recovery not approved");
|
|
if (Date.now() < this.activeRequest.completesAt) {
|
|
const remaining = this.activeRequest.completesAt - Date.now();
|
|
throw new Error(`Time-lock not expired. ${Math.ceil(remaining / 1000 / 60)} minutes remaining.`);
|
|
}
|
|
this.activeRequest.status = "completed";
|
|
this.activeRequest = null;
|
|
}
|
|
getActiveRequest() {
|
|
return this.activeRequest;
|
|
}
|
|
async hashGuardianList() {
|
|
if (!this.config)
|
|
return "";
|
|
const sortedIds = this.config.guardians.map((g) => g.id).sort().join(",");
|
|
const hash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(sortedIds));
|
|
return bufferToBase64url(hash);
|
|
}
|
|
async saveConfig() {
|
|
if (!this.config)
|
|
return;
|
|
try {
|
|
localStorage.setItem("encryptid_recovery", JSON.stringify(this.config));
|
|
} catch {}
|
|
}
|
|
loadConfig() {
|
|
try {
|
|
const stored = localStorage.getItem("encryptid_recovery");
|
|
if (stored)
|
|
this.config = JSON.parse(stored);
|
|
} catch {}
|
|
}
|
|
}
|
|
var recoveryManagerInstance = null;
|
|
function getRecoveryManager() {
|
|
if (!recoveryManagerInstance)
|
|
recoveryManagerInstance = new RecoveryManager;
|
|
return recoveryManagerInstance;
|
|
}
|
|
function getGuardianTypeInfo(type) {
|
|
switch (type) {
|
|
case "secondary_passkey" /* SECONDARY_PASSKEY */:
|
|
return { name: "Backup Passkey", description: "Another device you own (phone, YubiKey, etc.)", icon: "key", setupInstructions: "Register a passkey on a second device you control." };
|
|
case "trusted_contact" /* TRUSTED_CONTACT */:
|
|
return { name: "Trusted Contact", description: "A friend or family member with their own EncryptID", icon: "user", setupInstructions: "Ask a trusted person to create an EncryptID account." };
|
|
case "hardware_key" /* HARDWARE_KEY */:
|
|
return { name: "Hardware Security Key", description: "A YubiKey or similar device stored offline", icon: "shield", setupInstructions: "Register a hardware security key and store it safely." };
|
|
case "institutional" /* INSTITUTIONAL */:
|
|
return { name: "Recovery Service", description: "A professional recovery service provider", icon: "building", setupInstructions: "Connect with a trusted recovery service." };
|
|
case "time_delayed_self" /* TIME_DELAYED_SELF */:
|
|
return { name: "Time-Delayed Self", description: "Recover yourself after a waiting period", icon: "clock", setupInstructions: "Set up a recovery option that requires waiting before completing." };
|
|
default:
|
|
return { name: "Unknown", description: "Unknown guardian type", icon: "question", setupInstructions: "" };
|
|
}
|
|
}
|
|
|
|
// src/index.ts
|
|
var VERSION = "0.1.0";
|
|
var SPEC_VERSION = "2026-02";
|
|
export {
|
|
startConditionalUI,
|
|
signEthHash,
|
|
registerPasskey,
|
|
getSessionManager,
|
|
getRecoveryManager,
|
|
getKeyManager,
|
|
getGuardianTypeInfo,
|
|
detectCapabilities,
|
|
bufferToBase64url,
|
|
base64urlToBuffer,
|
|
authenticatePasskey,
|
|
VERSION,
|
|
SessionManager,
|
|
SPEC_VERSION,
|
|
RecoveryManager,
|
|
OPERATION_PERMISSIONS,
|
|
GuardianType,
|
|
EncryptIDKeyManager,
|
|
EncryptIDClient,
|
|
AuthLevel
|
|
};
|