UNPKG

antelope-webauthn

Version:

A WebAuthn.io crypto utility for generating signatures, creating public keys, and verifying them, designed for Antelope-based blockchains such as Vaulta, WAX, and other related platforms. This package provides convenient tools to handle key pair generatio

130 lines (129 loc) 5.41 kB
import base58_to_binary from "base58-js/base58_to_binary.js"; import assertBrowserCompatibility from "./_utils/browser-compatability"; import decodeLEB128 from "./_utils/decodeLEB128"; import sha256 from "./_utils/sha256"; function calculateY(x, prefix) { const b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604bn; const p = 0xffffffff00000001000000000000000000000000ffffffffffffffffffffffffn; const a = (-3n + p) % p; function modPow(base, exponent, modulus) { let result = BigInt(1); while (exponent > 0n) { if (exponent % 2n === 1n) { result = (result * base) % modulus; } base = (base * base) % modulus; exponent = exponent >> 1n; } return result; } function tonelliShanksAlgorithm(ySquared, p) { function legendreSymbol(a, p) { // If a is divisible by p, return 0 if (a % p === 0n) { return 0; } const symbol = modPow(a, (p - 1n) / 2n, p); return symbol === 1n ? 1 : symbol === p - 1n ? -1 : 0; } if (legendreSymbol(ySquared, p) !== 1) throw new Error("No square root exists"); let q = p - 1n; let s = 0n; while (q % 2n === 0n) { q = q >> 1n; s += 1n; } let z = 2n; while (modPow(z, (p - 1n) >> 1n, p) !== p - 1n) { z += 1n; } let c = modPow(z, q, p); let r = modPow(ySquared, (q + 1n) >> 1n, p); let t = modPow(ySquared, q, p); let m = s; while (t !== 1n) { let temp = t; let i = 1n; while (temp !== 1n && i < m) { temp = (temp * temp) % p; i += 1n; } let b = modPow(c, BigInt(2n ** (m - i - 1n)), p); r = (r * b) % p; t = (t * b * b) % p; c = (b * b) % p; m = i; } return r; } function calculateYSquared(x) { const xCubed = (((x * x) % p) * x) % p; const ax = (a * x) % p; const ySquared = (xCubed + ax + b) % p; return ySquared; } const ySquared = calculateYSquared(x); const y = tonelliShanksAlgorithm(ySquared, p); if (prefix == 2 && y % 2n) return p - y; if (prefix == 3 && !(y % 2n)) return p - y; return y; } function base64UrlEncode(uint8Array) { const base64String = typeof btoa !== "undefined" ? // @ts-expect-error ts trying to enforce strict type which is unneccessary in this instance. btoa(String.fromCharCode.apply(null, uint8Array)) : Buffer.from(uint8Array).toString("base64"); return base64String .replace(/\+/g, "-") .replace(/\//g, "_") .replace(/=+$/, ""); } function bigintToUint8Array(bigint) { const length = Math.ceil(bigint.toString(2).length / 8); const buffer = new ArrayBuffer(length); const view = new DataView(buffer); for (let i = length - 1; i >= 0; i--) { view.setUint8(i, Number(bigint & 0xffn)); bigint >>= 8n; } return new Uint8Array(buffer); } export default async function verifyWebAuthnSignature(signature, public_key) { assertBrowserCompatibility(); const sig = base58_to_binary(signature.replace("SIG_WA_", "")).slice(0, -4); const r = sig.slice(1, 33); const s = sig.slice(33, 65); const authenticatorData_len_bytes = []; let authenticatorData_len_pos = 65; authenticatorData_len_bytes.push(sig[authenticatorData_len_pos]); do { authenticatorData_len_bytes.push(sig[authenticatorData_len_pos]); authenticatorData_len_pos += 1; } while (sig[authenticatorData_len_pos] & 0x80); const authenticatorData_len = decodeLEB128(authenticatorData_len_bytes); const authenticatorData = sig.slice(66, 66 + authenticatorData_len); const clientData_length_bytes = []; let clientData_length_pos = 66 + authenticatorData_len; clientData_length_bytes.push(sig[clientData_length_pos]); do { clientData_length_pos += 1; clientData_length_bytes.push(sig[clientData_length_pos]); } while (sig[clientData_length_pos] & 0x80); const clientData_length = decodeLEB128(clientData_length_bytes); const clientDataJSON = sig.slice(-clientData_length); const clientDataHash = new Uint8Array(await sha256(new Uint8Array(clientDataJSON))); const signedData = new Uint8Array(authenticatorData.length + clientDataHash.length); signedData.set(authenticatorData); signedData.set(clientDataHash, authenticatorData.length); const prefix_x_coordinate = base58_to_binary(public_key.replace("PUB_WA_", "")); const x_array = prefix_x_coordinate.slice(1, 33); const prefix = prefix_x_coordinate[0]; const y_array = bigintToUint8Array(calculateY(BigInt(x_array.reduce((acc, i) => (acc += String(i.toString(16)).padStart(2, "0")), "0x")), prefix)); const x = base64UrlEncode(x_array); const y = base64UrlEncode(y_array); const key = await crypto.subtle.importKey("jwk", { kty: "EC", crv: "P-256", x, y }, { name: "ECDSA", namedCurve: "P-256", hash: { name: "SHA-256" } }, false, ["verify"]); return await crypto.subtle.verify({ name: "ECDSA", hash: { name: "SHA-256" } }, key, Uint8Array.from([...r, ...s]), signedData.buffer); }