shogun-core
Version:
SHOGUN CORE - Core library for Shogun Ecosystem
174 lines (173 loc) • 7.25 kB
JavaScript
;
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);
}