"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateStealthKeys = generateStealthKeys;
exports.generateStealthAddress = generateStealthAddress;
exports.checkStealthAddress = checkStealthAddress;
exports.deriveStealthPrivateKey = deriveStealthPrivateKey;
exports.parseMetaAddress = parseMetaAddress;
exports.createMetaAddress = createMetaAddress;
const secp256k1_1 = require("@noble/curves/secp256k1");
const sha3_1 = require("@noble/hashes/sha3");
const utils_1 = require("@noble/hashes/utils");
const constants_1 = require("./constants");
/**
 * Generate a new set of stealth keys for receiving private payments
 * @returns StealthKeys containing spending and viewing key pairs plus meta-address
 */
function generateStealthKeys() {
    // Generate spending key pair
    const spendingPrivateKey = secp256k1_1.secp256k1.utils.randomPrivateKey();
    const spendingPublicKey = secp256k1_1.secp256k1.getPublicKey(spendingPrivateKey, true); // compressed
    // Generate viewing key pair
    const viewingPrivateKey = secp256k1_1.secp256k1.utils.randomPrivateKey();
    const viewingPublicKey = secp256k1_1.secp256k1.getPublicKey(viewingPrivateKey, true); // compressed
    // Create meta-address: spending public key || viewing public key
    const stealthMetaAddress = (0, utils_1.concatBytes)(spendingPublicKey, viewingPublicKey);
    return {
        spendingPrivateKey: `0x${(0, utils_1.bytesToHex)(spendingPrivateKey)}`,
        spendingPublicKey: `0x${(0, utils_1.bytesToHex)(spendingPublicKey)}`,
        viewingPrivateKey: `0x${(0, utils_1.bytesToHex)(viewingPrivateKey)}`,
        viewingPublicKey: `0x${(0, utils_1.bytesToHex)(viewingPublicKey)}`,
        stealthMetaAddress: `0x${(0, utils_1.bytesToHex)(stealthMetaAddress)}`,
    };
}
/**
 * Generate a one-time stealth address for a recipient
 * @param stealthMetaAddress Recipient's stealth meta-address (66 bytes)
 * @returns Generated stealth address, ephemeral public key, and view tag
 */
function generateStealthAddress(stealthMetaAddress) {
    const metaBytes = (0, utils_1.hexToBytes)(stealthMetaAddress.slice(2));
    if (metaBytes.length !== constants_1.META_ADDRESS_LENGTH) {
        throw new Error(`Invalid meta-address length: ${metaBytes.length}, expected ${constants_1.META_ADDRESS_LENGTH}`);
    }
    // Extract spending and viewing public keys
    const spendingPubKey = metaBytes.slice(0, 33);
    const viewingPubKey = metaBytes.slice(33, 66);
    // Generate ephemeral key pair
    const ephemeralPrivateKey = secp256k1_1.secp256k1.utils.randomPrivateKey();
    const ephemeralPublicKey = secp256k1_1.secp256k1.getPublicKey(ephemeralPrivateKey, true);
    // Compute shared secret: S = ephemeralPrivate * viewingPubKey
    const sharedSecretPoint = secp256k1_1.secp256k1.getSharedSecret(ephemeralPrivateKey, viewingPubKey);
    const sharedSecret = (0, sha3_1.keccak_256)(sharedSecretPoint);
    // Compute stealth public key: P' = spendingPubKey + hash(S) * G
    const sharedSecretScalar = BigInt(`0x${(0, utils_1.bytesToHex)(sharedSecret)}`) % secp256k1_1.secp256k1.CURVE.n;
    const sharedSecretPoint2 = secp256k1_1.secp256k1.ProjectivePoint.BASE.multiply(sharedSecretScalar);
    // Add spending public key point
    const spendingPoint = secp256k1_1.secp256k1.ProjectivePoint.fromHex(spendingPubKey);
    const stealthPoint = spendingPoint.add(sharedSecretPoint2);
    const stealthPubKey = stealthPoint.toRawBytes(true);
    // Derive stealth address from public key
    const stealthAddress = publicKeyToAddress(stealthPubKey);
    // View tag: first byte of hash(sharedSecret)
    const viewTagHash = (0, sha3_1.keccak_256)(sharedSecret);
    const viewTag = viewTagHash[0];
    return {
        stealthAddress,
        ephemeralPubKey: `0x${(0, utils_1.bytesToHex)(ephemeralPublicKey)}`,
        viewTag,
    };
}
/**
 * Check if a stealth address belongs to you (using your viewing key)
 * @param ephemeralPubKey Ephemeral public key from announcement
 * @param viewingPrivateKey Your viewing private key
 * @param spendingPublicKey Your spending public key
 * @param announcedAddress The stealth address from announcement
 * @param viewTag Optional view tag for fast filtering
 * @returns true if the stealth address belongs to you
 */
function checkStealthAddress(ephemeralPubKey, viewingPrivateKey, spendingPublicKey, announcedAddress, viewTag) {
    try {
        const ephemeralBytes = (0, utils_1.hexToBytes)(ephemeralPubKey.slice(2));
        const viewingPrivBytes = (0, utils_1.hexToBytes)(viewingPrivateKey.slice(2));
        const spendingPubBytes = (0, utils_1.hexToBytes)(spendingPublicKey.slice(2));
        // Compute shared secret: S = viewingPrivate * ephemeralPubKey
        const sharedSecretPoint = secp256k1_1.secp256k1.getSharedSecret(viewingPrivBytes, ephemeralBytes);
        const sharedSecret = (0, sha3_1.keccak_256)(sharedSecretPoint);
        // Fast check with view tag if provided
        if (viewTag !== undefined) {
            const viewTagHash = (0, sha3_1.keccak_256)(sharedSecret);
            if (viewTagHash[0] !== viewTag) {
                return false;
            }
        }
        // Compute expected stealth public key
        const sharedSecretScalar = BigInt(`0x${(0, utils_1.bytesToHex)(sharedSecret)}`) % secp256k1_1.secp256k1.CURVE.n;
        const sharedSecretPoint2 = secp256k1_1.secp256k1.ProjectivePoint.BASE.multiply(sharedSecretScalar);
        const spendingPoint = secp256k1_1.secp256k1.ProjectivePoint.fromHex(spendingPubBytes);
        const expectedStealthPoint = spendingPoint.add(sharedSecretPoint2);
        const expectedStealthPubKey = expectedStealthPoint.toRawBytes(true);
        // Derive expected address
        const expectedAddress = publicKeyToAddress(expectedStealthPubKey);
        return expectedAddress.toLowerCase() === announcedAddress.toLowerCase();
    }
    catch {
        return false;
    }
}
/**
 * Derive the private key for a stealth address you own
 * @param viewingPrivateKey Your viewing private key
 * @param spendingPrivateKey Your spending private key
 * @param ephemeralPubKey Ephemeral public key from announcement
 * @returns Private key for the stealth address
 */
function deriveStealthPrivateKey(viewingPrivateKey, spendingPrivateKey, ephemeralPubKey) {
    const viewingPrivBytes = (0, utils_1.hexToBytes)(viewingPrivateKey.slice(2));
    const spendingPrivBytes = (0, utils_1.hexToBytes)(spendingPrivateKey.slice(2));
    const ephemeralBytes = (0, utils_1.hexToBytes)(ephemeralPubKey.slice(2));
    // Compute shared secret: S = viewingPrivate * ephemeralPubKey
    const sharedSecretPoint = secp256k1_1.secp256k1.getSharedSecret(viewingPrivBytes, ephemeralBytes);
    const sharedSecret = (0, sha3_1.keccak_256)(sharedSecretPoint);
    // Stealth private key: spendingPrivate + hash(S)
    const spendingScalar = BigInt(`0x${(0, utils_1.bytesToHex)(spendingPrivBytes)}`);
    const sharedSecretScalar = BigInt(`0x${(0, utils_1.bytesToHex)(sharedSecret)}`);
    const stealthPrivateScalar = (spendingScalar + sharedSecretScalar) % secp256k1_1.secp256k1.CURVE.n;
    // Convert to 32-byte hex
    const stealthPrivateKey = stealthPrivateScalar.toString(16).padStart(64, '0');
    return `0x${stealthPrivateKey}`;
}
/**
 * Convert a compressed public key to an Ethereum address
 */
function publicKeyToAddress(publicKey) {
    // Get uncompressed public key (without prefix)
    const point = secp256k1_1.secp256k1.ProjectivePoint.fromHex(publicKey);
    const uncompressed = point.toRawBytes(false).slice(1); // Remove 0x04 prefix
    // Keccak256 hash and take last 20 bytes
    const hash = (0, sha3_1.keccak_256)(uncompressed);
    const addressBytes = hash.slice(-20);
    return `0x${(0, utils_1.bytesToHex)(addressBytes)}`;
}
/**
 * Parse a stealth meta-address into its components
 */
function parseMetaAddress(metaAddress) {
    const metaBytes = (0, utils_1.hexToBytes)(metaAddress.slice(2));
    if (metaBytes.length !== constants_1.META_ADDRESS_LENGTH) {
        throw new Error(`Invalid meta-address length: ${metaBytes.length}`);
    }
    return {
        spendingPublicKey: `0x${(0, utils_1.bytesToHex)(metaBytes.slice(0, 33))}`,
        viewingPublicKey: `0x${(0, utils_1.bytesToHex)(metaBytes.slice(33, 66))}`,
    };
}
/**
 * Create a stealth meta-address from spending and viewing public keys
 */
function createMetaAddress(spendingPublicKey, viewingPublicKey) {
    const spending = (0, utils_1.hexToBytes)(spendingPublicKey.slice(2));
    const viewing = (0, utils_1.hexToBytes)(viewingPublicKey.slice(2));
    if (spending.length !== 33 || viewing.length !== 33) {
        throw new Error('Public keys must be 33 bytes (compressed)');
    }
    return `0x${(0, utils_1.bytesToHex)((0, utils_1.concatBytes)(spending, viewing))}`;
}
//# sourceMappingURL=stealthAddress.js.map