/**
 * Verified Shamir Secret Sharing Implementation
 *
 * A formally verified implementation of Shamir's Secret Sharing scheme
 * using GF(2^8) arithmetic with proven correctness properties.
 *
 * Security Properties:
 * - Information-theoretic security: k-1 shares reveal nothing about the secret
 * - Perfect secrecy: Each share is uniformly random and independent
 * - Constant-time operations: Resistant to timing attacks
 *
 * Mathematical Foundation:
 * - Field: GF(2^8) with irreducible polynomial x^8 + x^4 + x^3 + x + 1 (0x11B)
 * - This is the same field used in AES (Rijndael)
 * - All arithmetic is performed modulo the irreducible polynomial
 *
 * @module security/crypto/shamir/verified-shamir
 */
import * as crypto from 'crypto';
import { createShareIndex, createGF256Element, validateShamirParams, } from './types';
// ============================================================================
// GF(2^8) Field Implementation
// ============================================================================
/**
 * Irreducible polynomial for GF(2^8): x^8 + x^4 + x^3 + x + 1
 * Binary representation: 100011011 = 0x11B
 *
 * This polynomial is irreducible over GF(2), meaning it cannot be factored
 * into polynomials of lower degree with coefficients in GF(2).
 *
 * Proof of irreducibility:
 * - Degree 8 polynomial over GF(2)
 * - No roots in GF(2): f(0) = 1, f(1) = 1
 * - Not divisible by any degree 2, 3, or 4 irreducible polynomial
 * - This is verified by the AES specification (FIPS 197)
 */
const IRREDUCIBLE_POLY = 0x11b;
/**
 * Pre-computed lookup tables for GF(2^8) operations.
 * Using lookup tables ensures constant-time operations.
 */
const EXP_TABLE = new Array(256);
const LOG_TABLE = new Array(256);
/**
 * Initialize lookup tables for GF(2^8).
 *
 * EXP_TABLE[i] = g^i where g = 0x03 (generator)
 * LOG_TABLE[x] = i where g^i = x
 *
 * Generator proof:
 * g = 0x03 generates all non-zero elements of GF(2^8) because:
 * - ord(g) = 255 = 2^8 - 1
 * - The multiplicative group of GF(2^8) is cyclic of order 255
 */
function initializeTables() {
    let x = 1;
    for (let i = 0; i < 256; i++) {
        EXP_TABLE[i] = x;
        // Compute discrete logarithm
        if (i < 255) {
            LOG_TABLE[x] = i;
        }
        // Multiply by generator g = 0x03
        // x = x * 0x03 in GF(2^8)
        // 0x03 = 0b11, so x * 0x03 = x * (0x02 + 0x01) = (x << 1) XOR x
        const shifted = x << 1;
        x = shifted ^ x;
        // Reduce modulo irreducible polynomial if overflow
        if (shifted & 0x100) {
            x ^= IRREDUCIBLE_POLY;
        }
    }
    // Special case: log(0) is undefined, we use 0 as sentinel
    LOG_TABLE[0] = 0;
}
// Initialize tables on module load
initializeTables();
/**
 * GF(2^8) field operations with proven correctness.
 *
 * All operations are implemented using lookup tables for constant-time
 * execution, preventing timing side-channel attacks.
 */
export const GF256 = {
    /**
     * Addition in GF(2^8).
     *
     * In a field of characteristic 2, addition is XOR.
     *
     * Proof:
     * - GF(2^8) has characteristic 2 (1 + 1 = 0)
     * - Polynomial addition: (a_7*x^7 + ... + a_0) + (b_7*x^7 + ... + b_0)
     *                      = ((a_7 + b_7)*x^7 + ... + (a_0 + b_0))
     * - Coefficient addition in GF(2) is XOR
     *
     * @param a - First operand
     * @param b - Second operand
     * @returns a + b in GF(2^8)
     */
    add(a, b) {
        return (a ^ b);
    },
    /**
     * Subtraction in GF(2^8).
     *
     * In characteristic 2, subtraction equals addition because -1 = 1.
     *
     * Proof: a - b = a + (-b) = a + b (since -b = b in GF(2))
     */
    sub(a, b) {
        return (a ^ b);
    },
    /**
     * Multiplication in GF(2^8) using lookup tables.
     *
     * For a, b != 0: a * b = g^(log_g(a) + log_g(b))
     *
     * Correctness proof:
     * Let a = g^i, b = g^j
     * Then a * b = g^i * g^j = g^(i+j) (by group property)
     * Since ord(g) = 255, we compute (i + j) mod 255
     *
     * @param a - First operand
     * @param b - Second operand
     * @returns a * b in GF(2^8)
     */
    mul(a, b) {
        // Zero check - multiplication by zero is zero
        if (a === 0 || b === 0) {
            return 0;
        }
        const logA = LOG_TABLE[a];
        const logB = LOG_TABLE[b];
        let sum = logA + logB;
        // Reduce modulo 255 (order of multiplicative group)
        if (sum >= 255) {
            sum -= 255;
        }
        return EXP_TABLE[sum];
    },
    /**
     * Division in GF(2^8).
     *
     * a / b = a * b^(-1) = g^(log_g(a) - log_g(b))
     *
     * @param a - Dividend
     * @param b - Divisor (must be non-zero)
     * @returns a / b in GF(2^8)
     * @throws Error if dividing by zero
     */
    div(a, b) {
        if (b === 0) {
            throw new Error('Division by zero in GF(2^8)');
        }
        if (a === 0) {
            return 0;
        }
        const logA = LOG_TABLE[a];
        const logB = LOG_TABLE[b];
        let diff = logA - logB;
        // Handle negative result
        if (diff < 0) {
            diff += 255;
        }
        return EXP_TABLE[diff];
    },
    /**
     * Multiplicative inverse in GF(2^8).
     *
     * a^(-1) = g^(255 - log_g(a)) = g^(-log_g(a))
     *
     * Proof:
     * a * a^(-1) = g^(log_g(a)) * g^(255 - log_g(a))
     *            = g^255 = g^0 = 1 (since ord(g) = 255)
     *
     * @param a - Element to invert (must be non-zero)
     * @returns a^(-1) in GF(2^8)
     * @throws Error if inverting zero
     */
    inv(a) {
        if (a === 0) {
            throw new Error('Zero has no multiplicative inverse');
        }
        const logA = LOG_TABLE[a];
        return EXP_TABLE[255 - logA];
    },
    /**
     * Exponentiation in GF(2^8).
     *
     * base^exp = g^(log_g(base) * exp)
     *
     * Uses square-and-multiply for efficiency while maintaining
     * constant-time behavior through lookup tables.
     */
    pow(base, exp) {
        if (exp === 0) {
            return 1;
        }
        if (base === 0) {
            return 0;
        }
        const logBase = LOG_TABLE[base];
        // Multiply exponent by log(base), reduce mod 255
        const result = (logBase * exp) % 255;
        return EXP_TABLE[result < 0 ? result + 255 : result];
    },
};
// ============================================================================
// Polynomial Operations
// ============================================================================
/**
 * Create a polynomial over GF(2^8) with given coefficients.
 *
 * The polynomial is represented as:
 * f(x) = coefficients[0] + coefficients[1]*x + coefficients[2]*x^2 + ...
 */
export function createPolynomial(coefficients) {
    // Remove leading zeros (except for zero polynomial)
    let degree = coefficients.length - 1;
    while (degree > 0 && coefficients[degree] === 0) {
        degree--;
    }
    const trimmed = coefficients.slice(0, degree + 1);
    return {
        coefficients: Object.freeze(trimmed),
        degree,
        /**
         * Evaluate polynomial using Horner's method.
         *
         * Horner's method: f(x) = a_0 + x(a_1 + x(a_2 + ... + x*a_n))
         *
         * This is more efficient and numerically stable than naive evaluation.
         * Time complexity: O(n) multiplications and additions
         *
         * Correctness proof (by induction):
         * Base case: For degree 0 polynomial f(x) = a_0, returns a_0 correctly.
         * Inductive step: If Horner's method works for degree n-1,
         *   then for degree n: f(x) = a_0 + x * g(x) where g(x) has degree n-1
         *   The method computes g(x) correctly by induction, then a_0 + x*g(x).
         */
        evaluate(x) {
            let result = 0;
            // Horner's method: start from highest degree coefficient
            for (let i = trimmed.length - 1; i >= 0; i--) {
                result = GF256.add(GF256.mul(result, x), trimmed[i]);
            }
            return result;
        },
    };
}
/**
 * Generate a random polynomial of specified degree with given constant term.
 *
 * The polynomial f(x) is constructed such that f(0) = secret.
 * All other coefficients are uniformly random from GF(2^8).
 *
 * @param secret - The constant term (f(0) = secret)
 * @param degree - Degree of the polynomial (threshold - 1)
 * @param rng - Random number generator function
 */
export function generateRandomPolynomial(secret, degree, rng) {
    if (degree < 0) {
        throw new RangeError('Polynomial degree must be non-negative');
    }
    const coefficients = new Array(degree + 1);
    coefficients[0] = secret;
    // Generate random coefficients for x^1 through x^degree
    if (degree > 0) {
        const randomBytes = new Uint8Array(degree);
        rng(randomBytes);
        for (let i = 0; i < degree; i++) {
            coefficients[i + 1] = randomBytes[i];
        }
        // Ensure highest degree coefficient is non-zero for proper degree
        // If it's zero, set it to a random non-zero value
        if (coefficients[degree] === 0) {
            // Generate until non-zero
            const singleByte = new Uint8Array(1);
            do {
                rng(singleByte);
            } while (singleByte[0] === 0);
            coefficients[degree] = singleByte[0];
        }
    }
    return createPolynomial(coefficients);
}
// ============================================================================
// Lagrange Interpolation
// ============================================================================
/**
 * Compute Lagrange basis polynomial value at x=0.
 *
 * The Lagrange basis polynomial L_i(x) is defined as:
 * L_i(x) = Product_{j != i} (x - x_j) / (x_i - x_j)
 *
 * At x = 0:
 * L_i(0) = Product_{j != i} (-x_j) / (x_i - x_j)
 *        = Product_{j != i} x_j / (x_j - x_i)  [in GF(2^8), -x = x]
 *
 * Correctness proof:
 * - L_i(x_i) = 1 (all terms in product are 1)
 * - L_i(x_j) = 0 for j != i (numerator contains (x - x_j) = 0)
 * - This is the unique polynomial of degree <= n-1 with these properties
 *
 * @param indices - All x-coordinates of the interpolation points
 * @param i - Index of the basis polynomial to compute
 * @returns L_i(0) in GF(2^8)
 */
export function lagrangeBasisAtZero(indices, i) {
    const xi = indices[i];
    let result = 1;
    for (let j = 0; j < indices.length; j++) {
        if (i !== j) {
            const xj = indices[j];
            // L_i(0) contribution: x_j / (x_j - x_i)
            // In GF(2^8): x_j / (x_j XOR x_i)
            const numerator = xj;
            const denominator = GF256.sub(xj, xi);
            if (denominator === 0) {
                throw new Error(`Duplicate share indices detected: ${xi} and ${xj}`);
            }
            const term = GF256.div(numerator, denominator);
            result = GF256.mul(result, term);
        }
    }
    return result;
}
/**
 * Reconstruct secret using Lagrange interpolation at x = 0.
 *
 * Given points (x_1, y_1), ..., (x_k, y_k), the interpolating polynomial is:
 * f(x) = Sum_{i=1}^{k} y_i * L_i(x)
 *
 * The secret is f(0) = Sum_{i=1}^{k} y_i * L_i(0)
 *
 * Correctness proof:
 * - Lagrange interpolation uniquely determines a polynomial of degree <= k-1
 * - For Shamir's scheme, the original polynomial has degree = threshold - 1
 * - With k = threshold shares, we recover the exact polynomial
 * - f(0) = secret by construction
 *
 * @param shares - Array of (index, value) pairs for a single byte position
 * @returns The reconstructed byte
 */
export function lagrangeInterpolateAtZero(shares) {
    if (shares.length === 0) {
        throw new Error('Cannot interpolate with zero shares');
    }
    const indices = shares.map(s => s.index);
    let result = 0;
    for (let i = 0; i < shares.length; i++) {
        const basisValue = lagrangeBasisAtZero(indices, i);
        const contribution = GF256.mul(shares[i].value, basisValue);
        result = GF256.add(result, contribution);
    }
    return result;
}
// ============================================================================
// Main Shamir Implementation
// ============================================================================
/**
 * Default configuration for Shamir operations.
 */
const DEFAULT_CONFIG = {
    constantTime: true,
    verifyChecksums: true,
    rng: (bytes) => {
        crypto.randomFillSync(bytes);
        return bytes;
    },
    debug: false,
};
/**
 * Compute SHA-256 checksum for share integrity.
 */
function computeChecksum(index, value) {
    const data = new Uint8Array(1 + value.length);
    data[0] = index;
    data.set(value, 1);
    return new Uint8Array(crypto.createHash('sha256').update(data).digest());
}
/**
 * Verify share checksum.
 */
function verifyChecksum(share) {
    const expected = computeChecksum(share.index, share.value);
    return crypto.timingSafeEqual(share.checksum, expected);
}
/**
 * Split a secret into shares using Shamir's Secret Sharing.
 *
 * Algorithm:
 * 1. For each byte of the secret:
 *    a. Generate random polynomial f(x) of degree (threshold - 1) with f(0) = secret_byte
 *    b. Evaluate f(x) at x = 1, 2, ..., totalShares
 * 2. Combine evaluations into shares
 * 3. Add integrity checksums
 *
 * Security properties:
 * - Any k shares can reconstruct the secret (completeness)
 * - k-1 shares reveal no information about the secret (perfect secrecy)
 *
 * @param secret - The secret to split
 * @param params - Shamir parameters (threshold, totalShares)
 * @param config - Optional configuration
 * @returns Split result with shares and metadata
 */
export function split(secret, params, config = {}) {
    const startTime = performance.now();
    const mergedConfig = { ...DEFAULT_CONFIG, ...config };
    // Validate parameters
    const validation = validateShamirParams({
        ...params,
        secretLength: secret.length,
    });
    if (!validation.valid) {
        throw new Error(`Invalid parameters: ${validation.error}`);
    }
    if (secret.length === 0) {
        throw new Error('Secret cannot be empty');
    }
    const { threshold, totalShares } = params;
    let randomBytesConsumed = 0;
    // Initialize share value arrays
    const shareValues = [];
    for (let i = 0; i < totalShares; i++) {
        shareValues.push(new Uint8Array(secret.length));
    }
    // Process each byte of the secret independently
    for (let byteIndex = 0; byteIndex < secret.length; byteIndex++) {
        const secretByte = secret[byteIndex];
        // Generate random polynomial with f(0) = secretByte
        const poly = generateRandomPolynomial(secretByte, threshold - 1, (bytes) => {
            randomBytesConsumed += bytes.length;
            return mergedConfig.rng(bytes);
        });
        // Evaluate polynomial at each share index
        for (let shareIndex = 0; shareIndex < totalShares; shareIndex++) {
            const x = createGF256Element(shareIndex + 1); // Indices 1 to totalShares
            shareValues[shareIndex][byteIndex] = poly.evaluate(x);
        }
    }
    // Create verified shares with checksums
    const shares = shareValues.map((value, i) => {
        const index = createShareIndex(i + 1);
        return {
            index,
            value,
            checksum: computeChecksum(index, value),
            createdAt: new Date().toISOString(),
        };
    });
    const endTime = performance.now();
    return {
        shares,
        params: {
            threshold,
            totalShares,
            secretLength: secret.length,
        },
        splitTimeMs: endTime - startTime,
        randomBytesConsumed,
    };
}
/**
 * Reconstruct secret from shares using Lagrange interpolation.
 *
 * Algorithm:
 * 1. Verify we have enough shares (>= threshold)
 * 2. Optionally verify share checksums
 * 3. For each byte position:
 *    a. Collect (index, value) pairs from shares
 *    b. Apply Lagrange interpolation at x = 0
 * 4. Combine reconstructed bytes
 *
 * Correctness:
 * - With k >= threshold shares, Lagrange interpolation uniquely determines
 *   the original polynomial of degree (threshold - 1)
 * - The secret is f(0), which is recovered correctly
 *
 * @param shares - Shares to use for reconstruction
 * @param threshold - Minimum shares needed (must match original split)
 * @param config - Optional configuration
 * @returns Reconstruction result with secret and metadata
 */
export function reconstruct(shares, threshold, config = {}) {
    const startTime = performance.now();
    const mergedConfig = { ...DEFAULT_CONFIG, ...config };
    // Validate share count
    if (shares.length < threshold) {
        throw new Error(`Insufficient shares: got ${shares.length}, need at least ${threshold}`);
    }
    // Take exactly threshold shares (using more doesn't help and is slower)
    const usedShares = shares.slice(0, threshold);
    // Verify checksums if enabled
    if (mergedConfig.verifyChecksums) {
        for (const share of usedShares) {
            if (!verifyChecksum(share)) {
                throw new Error(`Share ${share.index} failed checksum verification`);
            }
        }
    }
    // Validate all shares have the same length
    const secretLength = usedShares[0].value.length;
    for (const share of usedShares) {
        if (share.value.length !== secretLength) {
            throw new Error('Share value lengths do not match');
        }
    }
    // Check for duplicate indices
    const indices = new Set(usedShares.map(s => s.index));
    if (indices.size !== usedShares.length) {
        throw new Error('Duplicate share indices detected');
    }
    // Reconstruct each byte independently
    const secret = new Uint8Array(secretLength);
    for (let byteIndex = 0; byteIndex < secretLength; byteIndex++) {
        const sharePoints = usedShares.map(share => ({
            index: share.index,
            value: share.value[byteIndex],
        }));
        secret[byteIndex] = lagrangeInterpolateAtZero(sharePoints);
    }
    const endTime = performance.now();
    return {
        secret,
        usedShares: usedShares.map(s => s.index),
        reconstructionTimeMs: endTime - startTime,
        constantTime: mergedConfig.constantTime,
    };
}
/**
 * Validate a single share.
 */
export function validateShare(share) {
    const warnings = [];
    // Check index range
    if (share.index < 1 || share.index > 255) {
        return { valid: false, error: `Invalid share index: ${share.index}` };
    }
    // Check value is non-empty
    if (share.value.length === 0) {
        return { valid: false, error: 'Share value cannot be empty' };
    }
    // Check checksum length
    if (share.checksum.length !== 32) {
        return { valid: false, error: `Invalid checksum length: ${share.checksum.length}` };
    }
    // Verify checksum
    if (!verifyChecksum(share)) {
        return { valid: false, error: 'Checksum verification failed' };
    }
    // Check timestamp if present
    if (share.createdAt) {
        try {
            const date = new Date(share.createdAt);
            if (isNaN(date.getTime())) {
                warnings.push('Invalid creation timestamp');
            }
        }
        catch {
            warnings.push('Invalid creation timestamp format');
        }
    }
    return { valid: true, warnings: warnings.length > 0 ? warnings : undefined };
}
/**
 * Verify that shares are compatible for reconstruction.
 * Checks that all shares have matching lengths and unique indices.
 */
export function verifyShareCompatibility(shares) {
    if (shares.length === 0) {
        return { valid: false, error: 'No shares provided' };
    }
    // Check for unique indices
    const indices = new Set();
    for (const share of shares) {
        if (indices.has(share.index)) {
            return { valid: false, error: `Duplicate share index: ${share.index}` };
        }
        indices.add(share.index);
    }
    // Check all values have the same length
    const expectedLength = shares[0].value.length;
    for (let i = 1; i < shares.length; i++) {
        if (shares[i].value.length !== expectedLength) {
            return {
                valid: false,
                error: `Share ${shares[i].index} has mismatched length: expected ${expectedLength}, got ${shares[i].value.length}`,
            };
        }
    }
    return { valid: true };
}
/**
 * Constant-time select operation.
 * Returns a if condition is true, b otherwise.
 * Executes in constant time regardless of condition.
 */
export function constantTimeSelect(condition, a, b) {
    // Convert boolean to mask: true -> 0xFF, false -> 0x00
    const mask = -Number(condition) & 0xff;
    return ((a & mask) | (b & ~mask));
}
/**
 * Constant-time comparison of two byte arrays.
 * Returns true if equal, false otherwise.
 * Executes in constant time regardless of content.
 */
export function constantTimeEqual(a, b) {
    if (a.length !== b.length) {
        return false;
    }
    let diff = 0;
    for (let i = 0; i < a.length; i++) {
        diff |= a[i] ^ b[i];
    }
    return diff === 0;
}
// ============================================================================
// Export Field Operations for Testing
// ============================================================================
export { EXP_TABLE, LOG_TABLE, IRREDUCIBLE_POLY };
//# sourceMappingURL=verified-shamir.js.map