UNPKG

shogun-core

Version:

SHOGUN CORE - Core library for Shogun Ecosystem

174 lines (173 loc) 7.25 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = default_1; const p256_1 = require("@noble/curves/p256"); const secp256k1_1 = require("@noble/curves/secp256k1"); const sha256_1 = require("@noble/hashes/sha256"); const sha3_1 = require("@noble/hashes/sha3"); const ripemd160_1 = require("@noble/hashes/ripemd160"); async function default_1(pwd, extra, options = {}) { const TEXT_ENCODER = new TextEncoder(); const pwdBytes = pwd ? typeof pwd === "string" ? TEXT_ENCODER.encode(normalizeString(pwd)) : pwd : crypto.getRandomValues(new Uint8Array(32)); const extras = extra ? (Array.isArray(extra) ? extra : [extra]).map((e) => normalizeString(e.toString())) : []; const extraBuf = TEXT_ENCODER.encode(extras.join("|")); const combinedInput = new Uint8Array(pwdBytes.length + extraBuf.length); combinedInput.set(pwdBytes); combinedInput.set(extraBuf, pwdBytes.length); if (combinedInput.length < 16) { throw new Error(`Insufficient input entropy (${combinedInput.length})`); } const version = "v1"; const result = {}; // Mantieni comportamento esistente (P-256) come default const { includeP256 = true, includeSecp256k1Bitcoin = false, includeSecp256k1Ethereum = false, } = options; if (includeP256) { const salts = [ { label: "signing", type: "pub/priv" }, { label: "encryption", type: "epub/epriv" }, ]; const [signingKeys, encryptionKeys] = await Promise.all(salts.map(async ({ label }) => { const salt = TEXT_ENCODER.encode(`${label}-${version}`); const privateKey = await stretchKey(combinedInput, salt); if (!p256_1.p256.utils.isValidPrivateKey(privateKey)) { throw new Error(`Invalid private key for ${label}`); } const publicKey = p256_1.p256.getPublicKey(privateKey, false); return { pub: keyBufferToJwk(publicKey), priv: arrayBufToBase64UrlEncode(privateKey), }; })); // Chiavi P-256 esistenti result.pub = signingKeys.pub; result.priv = signingKeys.priv; result.epub = encryptionKeys.pub; result.epriv = encryptionKeys.priv; } // Derivazione Bitcoin P2PKH (secp256k1 + SHA256 + RIPEMD160 + Base58) if (includeSecp256k1Bitcoin) { const bitcoinSalt = TEXT_ENCODER.encode(`secp256k1-bitcoin-${version}`); const bitcoinPrivateKey = await stretchKey(combinedInput, bitcoinSalt); if (!secp256k1_1.secp256k1.utils.isValidPrivateKey(bitcoinPrivateKey)) { throw new Error("Invalid secp256k1 private key for Bitcoin"); } const bitcoinPublicKey = secp256k1_1.secp256k1.getPublicKey(bitcoinPrivateKey, true); // Compressed result.secp256k1Bitcoin = { privateKey: bytesToHex(bitcoinPrivateKey), publicKey: bytesToHex(bitcoinPublicKey), address: deriveP2PKHAddress(bitcoinPublicKey), }; } // Derivazione Ethereum (secp256k1 + Keccak256) if (includeSecp256k1Ethereum) { const ethereumSalt = TEXT_ENCODER.encode(`secp256k1-ethereum-${version}`); const ethereumPrivateKey = await stretchKey(combinedInput, ethereumSalt); if (!secp256k1_1.secp256k1.utils.isValidPrivateKey(ethereumPrivateKey)) { throw new Error("Invalid secp256k1 private key for Ethereum"); } const ethereumPublicKey = secp256k1_1.secp256k1.getPublicKey(ethereumPrivateKey, false); // Uncompressed result.secp256k1Ethereum = { privateKey: "0x" + bytesToHex(ethereumPrivateKey), publicKey: "0x" + bytesToHex(ethereumPublicKey), address: deriveKeccak256Address(ethereumPublicKey), }; } return result; } function arrayBufToBase64UrlEncode(buf) { return btoa(String.fromCharCode(...buf)) .replace(/\//g, "_") .replace(/=/g, "") .replace(/\+/g, "-"); } function keyBufferToJwk(publicKeyBuffer) { if (publicKeyBuffer[0] !== 4) throw new Error("Invalid uncompressed public key format"); return [ arrayBufToBase64UrlEncode(publicKeyBuffer.slice(1, 33)), // x arrayBufToBase64UrlEncode(publicKeyBuffer.slice(33, 65)), // y ].join("."); } function normalizeString(str) { return str.normalize("NFC").trim(); } async function stretchKey(input, salt, iterations = 300_000) { const baseKey = await crypto.subtle.importKey("raw", input, { name: "PBKDF2" }, false, ["deriveBits"]); const keyBits = await crypto.subtle.deriveBits({ name: "PBKDF2", salt: salt, iterations, hash: "SHA-256" }, baseKey, 256); return new Uint8Array(keyBits); } function bytesToHex(bytes) { return Array.from(bytes) .map((b) => b.toString(16).padStart(2, "0")) .join(""); } // Base58 encoding per Bitcoin const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; function base58Encode(bytes) { if (bytes.length === 0) return ""; // Count leading zeros let zeros = 0; for (let i = 0; i < bytes.length && bytes[i] === 0; i++) { zeros++; } // Convert to base58 const digits = [0]; for (let i = zeros; i < bytes.length; i++) { let carry = bytes[i]; for (let j = 0; j < digits.length; j++) { carry += digits[j] << 8; digits[j] = carry % 58; carry = (carry / 58) | 0; } while (carry > 0) { digits.push(carry % 58); carry = (carry / 58) | 0; } } // Convert to string let result = ""; for (let i = 0; i < zeros; i++) { result += BASE58_ALPHABET[0]; } for (let i = digits.length - 1; i >= 0; i--) { result += BASE58_ALPHABET[digits[i]]; } return result; } function deriveP2PKHAddress(publicKey) { // Bitcoin P2PKH address derivation // 1. SHA256 hash del public key const sha256Hash = (0, sha256_1.sha256)(publicKey); // 2. RIPEMD160 hash del risultato const ripemd160Hash = (0, ripemd160_1.ripemd160)(sha256Hash); // 3. Aggiungi version byte (0x00 per mainnet P2PKH) const versionedHash = new Uint8Array(21); versionedHash[0] = 0x00; // Mainnet P2PKH version versionedHash.set(ripemd160Hash, 1); // 4. Double SHA256 per checksum const checksum = (0, sha256_1.sha256)((0, sha256_1.sha256)(versionedHash)); // 5. Aggiungi i primi 4 byte del checksum const addressBytes = new Uint8Array(25); addressBytes.set(versionedHash); addressBytes.set(checksum.slice(0, 4), 21); // 6. Base58 encode return base58Encode(addressBytes); } function deriveKeccak256Address(publicKey) { // Ethereum address derivation usando Keccak256 // 1. Rimuovi il prefix byte (0x04) dalla chiave pubblica non compressa const publicKeyWithoutPrefix = publicKey.slice(1); // 2. Calcola Keccak256 hash const hash = (0, sha3_1.keccak_256)(publicKeyWithoutPrefix); // 3. Prendi gli ultimi 20 byte const address = hash.slice(-20); // 4. Aggiungi '0x' prefix e converti in hex return "0x" + bytesToHex(address); }