/**
 * Zero-Knowledge Circuit Definitions
 *
 * Defines ZK circuits for various privacy-preserving verification use cases:
 * - Age verification (prove age > threshold without revealing DOB)
 * - Range proofs (prove value in range without revealing value)
 * - Set membership proofs (prove membership without revealing identity)
 * - Credential verification (prove valid credential without showing it)
 *
 * This module provides circuit definitions that can be used with SNARK
 * proving systems. The implementations use Schnorr-based proofs for
 * simpler cases with documentation for production SNARK integration.
 *
 * @packageDocumentation
 */
import { createLogger } from '../../common/logger.js';
import { VorionError } from '../../common/errors.js';
import { ZKCircuitType, circuitMetadataSchema, } from './types.js';
const logger = createLogger({ component: 'zkp-circuits' });
// =============================================================================
// ERRORS
// =============================================================================
/**
 * Circuit-related errors
 */
export class CircuitError extends VorionError {
    circuitType;
    code = 'CIRCUIT_ERROR';
    statusCode = 400;
    constructor(message, circuitType, details) {
        super(message, { circuitType, ...details });
        this.circuitType = circuitType;
        this.name = 'CircuitError';
    }
}
// =============================================================================
// CIRCUIT REGISTRY
// =============================================================================
/**
 * Registry for ZK circuits
 *
 * Manages circuit definitions, metadata, and provides circuit lookup functionality.
 *
 * @example
 * ```typescript
 * const registry = new CircuitRegistry();
 * registry.registerCircuit(ageVerificationCircuit);
 *
 * const circuit = registry.getCircuit(ZKCircuitType.AGE_VERIFICATION);
 * ```
 */
export class CircuitRegistry {
    circuits = new Map();
    metadata = new Map();
    constructor() {
        // Register built-in circuits
        this.registerBuiltinCircuits();
        logger.info('Circuit registry initialized with built-in circuits');
    }
    /**
     * Register a circuit
     */
    registerCircuit(circuit) {
        const meta = circuit.getMetadata();
        circuitMetadataSchema.parse(meta);
        this.circuits.set(meta.type, circuit);
        this.metadata.set(meta.type, meta);
        logger.info({ circuitType: meta.type, version: meta.version }, 'Circuit registered');
    }
    /**
     * Get a circuit by type
     */
    getCircuit(type) {
        return this.circuits.get(type);
    }
    /**
     * Get circuit metadata
     */
    getMetadata(type) {
        return this.metadata.get(type);
    }
    /**
     * Get all registered circuits
     */
    getAllCircuits() {
        return Array.from(this.circuits.values());
    }
    /**
     * Get all circuit metadata
     */
    getAllMetadata() {
        return Array.from(this.metadata.values());
    }
    /**
     * Check if a circuit is registered
     */
    hasCircuit(type) {
        return this.circuits.has(type);
    }
    /**
     * Register built-in circuits
     */
    registerBuiltinCircuits() {
        this.registerCircuit(new AgeVerificationCircuit());
        this.registerCircuit(new RangeProofCircuit());
        this.registerCircuit(new SetMembershipCircuit());
        this.registerCircuit(new CredentialVerificationCircuit());
    }
}
// =============================================================================
// AGE VERIFICATION CIRCUIT
// =============================================================================
/**
 * Age Verification Circuit
 *
 * Proves that a user's age is above a threshold without revealing their
 * exact date of birth.
 *
 * Public Inputs:
 * - Current timestamp (to prevent replay)
 * - Age threshold (e.g., 18, 21)
 *
 * Private Inputs:
 * - Date of birth
 *
 * Constraints:
 * - age = (currentTimestamp - birthTimestamp) / SECONDS_PER_YEAR
 * - age >= threshold
 *
 * @example
 * ```typescript
 * const circuit = new AgeVerificationCircuit();
 * const witness = await circuit.computeWitness(
 *   { birthDate: new Date('1990-01-15') },
 *   [Date.now().toString(), '18']
 * );
 * ```
 */
export class AgeVerificationCircuit {
    static SECONDS_PER_YEAR = 365.25 * 24 * 60 * 60 * 1000;
    getMetadata() {
        return {
            type: ZKCircuitType.AGE_VERIFICATION,
            name: 'Age Verification Circuit',
            description: 'Proves age is above a threshold without revealing exact date of birth',
            version: '1.0.0',
            publicInputs: ['currentTimestamp', 'ageThreshold'],
            privateInputs: ['birthDate'],
            registeredAt: new Date(),
        };
    }
    validatePrivateInputs(inputs) {
        if (!inputs.birthDate) {
            return false;
        }
        const birthDate = inputs.birthDate instanceof Date
            ? inputs.birthDate
            : new Date(inputs.birthDate);
        if (isNaN(birthDate.getTime())) {
            return false;
        }
        // Birth date must be in the past
        if (birthDate.getTime() > Date.now()) {
            return false;
        }
        return true;
    }
    validatePublicInputs(inputs) {
        if (inputs.length !== 2) {
            return false;
        }
        const [currentTimestamp, ageThreshold] = inputs;
        // Validate timestamp
        const timestamp = parseInt(currentTimestamp, 10);
        if (isNaN(timestamp) || timestamp <= 0) {
            return false;
        }
        // Validate threshold
        const threshold = parseInt(ageThreshold, 10);
        if (isNaN(threshold) || threshold < 0 || threshold > 150) {
            return false;
        }
        return true;
    }
    async computeWitness(privateInputs, publicInputs) {
        if (!this.validatePrivateInputs(privateInputs)) {
            throw new CircuitError('Invalid private inputs for age verification', ZKCircuitType.AGE_VERIFICATION);
        }
        if (!this.validatePublicInputs(publicInputs)) {
            throw new CircuitError('Invalid public inputs for age verification', ZKCircuitType.AGE_VERIFICATION);
        }
        const birthDate = privateInputs.birthDate instanceof Date
            ? privateInputs.birthDate
            : new Date(privateInputs.birthDate);
        const currentTimestamp = parseInt(publicInputs[0], 10);
        const ageThreshold = parseInt(publicInputs[1], 10);
        // Calculate age
        const age = (currentTimestamp - birthDate.getTime()) / AgeVerificationCircuit.SECONDS_PER_YEAR;
        // Check constraint
        if (age < ageThreshold) {
            throw new CircuitError('Age verification constraint not satisfied', ZKCircuitType.AGE_VERIFICATION, { message: 'User does not meet age requirement' });
        }
        // Compute witness (in production, this would be the full SNARK witness)
        // For now, we create a Schnorr-style commitment
        const witnessData = {
            birthTimestamp: birthDate.getTime(),
            currentTimestamp,
            ageThreshold,
            ageSatisfied: true,
            witnessNonce: this.generateNonce(),
        };
        return this.serializeWitness(witnessData);
    }
    extractPublicInputs(privateInputs) {
        // Age threshold must be provided separately
        // Current timestamp is the current time
        return [Date.now().toString()];
    }
    generateNonce() {
        const array = new Uint8Array(32);
        crypto.getRandomValues(array);
        return Array.from(array, b => b.toString(16).padStart(2, '0')).join('');
    }
    serializeWitness(data) {
        const json = JSON.stringify(data);
        return new TextEncoder().encode(json);
    }
}
// =============================================================================
// RANGE PROOF CIRCUIT
// =============================================================================
/**
 * Range Proof Circuit
 *
 * Proves that a value lies within a specified range [min, max] without
 * revealing the actual value. Useful for salary ranges, credit scores, etc.
 *
 * Public Inputs:
 * - Range minimum
 * - Range maximum
 * - Commitment to the value
 *
 * Private Inputs:
 * - The actual value
 * - Commitment randomness (blinding factor)
 *
 * Constraints:
 * - value >= min
 * - value <= max
 * - commitment = Commit(value, randomness)
 *
 * @example
 * ```typescript
 * const circuit = new RangeProofCircuit();
 * const witness = await circuit.computeWitness(
 *   { value: 75000, randomness: '...' },
 *   ['50000', '100000', 'commitment...']
 * );
 * ```
 */
export class RangeProofCircuit {
    getMetadata() {
        return {
            type: ZKCircuitType.RANGE_PROOF,
            name: 'Range Proof Circuit',
            description: 'Proves a value is within a range without revealing the value',
            version: '1.0.0',
            publicInputs: ['rangeMin', 'rangeMax', 'commitment'],
            privateInputs: ['value', 'randomness'],
            registeredAt: new Date(),
        };
    }
    validatePrivateInputs(inputs) {
        if (typeof inputs.value !== 'number') {
            return false;
        }
        if (!Number.isFinite(inputs.value)) {
            return false;
        }
        return true;
    }
    validatePublicInputs(inputs) {
        if (inputs.length < 2) {
            return false;
        }
        const [min, max] = inputs;
        const minValue = parseFloat(min);
        const maxValue = parseFloat(max);
        if (isNaN(minValue) || isNaN(maxValue)) {
            return false;
        }
        if (minValue > maxValue) {
            return false;
        }
        return true;
    }
    async computeWitness(privateInputs, publicInputs) {
        if (!this.validatePrivateInputs(privateInputs)) {
            throw new CircuitError('Invalid private inputs for range proof', ZKCircuitType.RANGE_PROOF);
        }
        if (!this.validatePublicInputs(publicInputs)) {
            throw new CircuitError('Invalid public inputs for range proof', ZKCircuitType.RANGE_PROOF);
        }
        const value = privateInputs.value;
        const min = parseFloat(publicInputs[0]);
        const max = parseFloat(publicInputs[1]);
        // Check range constraints
        if (value < min || value > max) {
            throw new CircuitError('Range proof constraint not satisfied', ZKCircuitType.RANGE_PROOF, { message: 'Value is not within the specified range' });
        }
        // Compute witness for range proof
        // In production, this would decompose the value into bits and prove
        // that value - min >= 0 and max - value >= 0
        const witnessData = {
            value,
            min,
            max,
            valueLessDelta: value - min,
            deltaLessValue: max - value,
            rangeSatisfied: true,
            witnessNonce: this.generateNonce(),
        };
        return this.serializeWitness(witnessData);
    }
    extractPublicInputs(privateInputs) {
        // Cannot extract public inputs from private inputs alone
        // Range bounds must be provided separately
        return [];
    }
    /**
     * Create a commitment to a value for use in range proofs
     */
    async createValueCommitment(value) {
        const randomness = this.generateNonce();
        // Simple commitment: H(value || randomness)
        // In production, use Pedersen commitment for homomorphic properties
        const data = `${value}|${randomness}`;
        const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(data));
        const commitment = Array.from(new Uint8Array(hashBuffer))
            .map(b => b.toString(16).padStart(2, '0'))
            .join('');
        return { commitment, randomness };
    }
    generateNonce() {
        const array = new Uint8Array(32);
        crypto.getRandomValues(array);
        return Array.from(array, b => b.toString(16).padStart(2, '0')).join('');
    }
    serializeWitness(data) {
        const json = JSON.stringify(data);
        return new TextEncoder().encode(json);
    }
}
// =============================================================================
// SET MEMBERSHIP CIRCUIT
// =============================================================================
/**
 * Set Membership Circuit
 *
 * Proves that a user is a member of an allowed set without revealing
 * which specific member they are. Uses Merkle tree proofs.
 *
 * Public Inputs:
 * - Merkle root of the set
 * - Nullifier (to prevent double-use without revealing identity)
 *
 * Private Inputs:
 * - User's element (leaf value)
 * - Merkle proof (siblings and path indices)
 * - Nullifier secret
 *
 * Constraints:
 * - MerkleVerify(root, leaf, siblings, pathIndices) = true
 * - nullifier = Hash(element, nullifierSecret)
 *
 * @example
 * ```typescript
 * const circuit = new SetMembershipCircuit();
 * const witness = await circuit.computeWitness(
 *   {
 *     element: 'user123',
 *     merkleProof: { siblings: [...], pathIndices: [...] },
 *     nullifierSecret: '...'
 *   },
 *   ['merkleRoot', 'nullifier']
 * );
 * ```
 */
export class SetMembershipCircuit {
    getMetadata() {
        return {
            type: ZKCircuitType.SET_MEMBERSHIP,
            name: 'Set Membership Circuit',
            description: 'Proves membership in a set without revealing which member',
            version: '1.0.0',
            publicInputs: ['merkleRoot', 'nullifier'],
            privateInputs: ['element', 'merkleProof', 'nullifierSecret'],
            registeredAt: new Date(),
        };
    }
    validatePrivateInputs(inputs) {
        if (!inputs.element || typeof inputs.element !== 'string') {
            return false;
        }
        if (!inputs.merkleProof) {
            return false;
        }
        const proof = inputs.merkleProof;
        if (!Array.isArray(proof.siblings) || !Array.isArray(proof.pathIndices)) {
            return false;
        }
        if (proof.siblings.length !== proof.pathIndices.length) {
            return false;
        }
        return true;
    }
    validatePublicInputs(inputs) {
        if (inputs.length < 1) {
            return false;
        }
        const [merkleRoot] = inputs;
        if (!merkleRoot || merkleRoot.length === 0) {
            return false;
        }
        return true;
    }
    async computeWitness(privateInputs, publicInputs) {
        if (!this.validatePrivateInputs(privateInputs)) {
            throw new CircuitError('Invalid private inputs for set membership', ZKCircuitType.SET_MEMBERSHIP);
        }
        if (!this.validatePublicInputs(publicInputs)) {
            throw new CircuitError('Invalid public inputs for set membership', ZKCircuitType.SET_MEMBERSHIP);
        }
        const element = privateInputs.element;
        const merkleProof = privateInputs.merkleProof;
        const expectedRoot = publicInputs[0];
        // Verify Merkle proof
        const computedRoot = await this.computeMerkleRoot(element, merkleProof);
        if (computedRoot !== expectedRoot) {
            throw new CircuitError('Set membership constraint not satisfied', ZKCircuitType.SET_MEMBERSHIP, { message: 'Element is not in the set' });
        }
        // Compute nullifier
        const nullifierSecret = privateInputs.nullifierSecret || this.generateNonce();
        const nullifier = await this.computeNullifier(element, nullifierSecret);
        const witnessData = {
            element,
            siblings: merkleProof.siblings,
            pathIndices: merkleProof.pathIndices,
            computedRoot,
            nullifier,
            membershipVerified: true,
            witnessNonce: this.generateNonce(),
        };
        return this.serializeWitness(witnessData);
    }
    extractPublicInputs(privateInputs) {
        // Cannot extract root from private inputs
        return [];
    }
    /**
     * Compute Merkle root from leaf and proof
     */
    async computeMerkleRoot(leaf, proof) {
        let currentHash = await this.hashLeaf(leaf);
        for (let i = 0; i < proof.siblings.length; i++) {
            const sibling = proof.siblings[i];
            const isRight = proof.pathIndices[i] === 1;
            if (isRight) {
                currentHash = await this.hashPair(sibling, currentHash);
            }
            else {
                currentHash = await this.hashPair(currentHash, sibling);
            }
        }
        return currentHash;
    }
    /**
     * Build a Merkle tree from a set of elements
     */
    async buildMerkleTree(elements) {
        if (elements.length === 0) {
            throw new CircuitError('Cannot build Merkle tree from empty set', ZKCircuitType.SET_MEMBERSHIP);
        }
        // Pad to power of 2
        const size = Math.pow(2, Math.ceil(Math.log2(elements.length)));
        const paddedElements = [...elements];
        while (paddedElements.length < size) {
            paddedElements.push('');
        }
        // Hash leaves
        const leaves = await Promise.all(paddedElements.map(e => this.hashLeaf(e)));
        // Build tree levels
        const levels = [leaves];
        let currentLevel = leaves;
        while (currentLevel.length > 1) {
            const nextLevel = [];
            for (let i = 0; i < currentLevel.length; i += 2) {
                const left = currentLevel[i];
                const right = currentLevel[i + 1] ?? left;
                nextLevel.push(await this.hashPair(left, right));
            }
            levels.push(nextLevel);
            currentLevel = nextLevel;
        }
        const root = currentLevel[0];
        // Generate proofs for original elements
        const proofs = new Map();
        for (let i = 0; i < elements.length; i++) {
            const element = elements[i];
            const proof = this.generateProof(i, levels);
            proofs.set(element, {
                leaf: element,
                root,
                siblings: proof.siblings,
                pathIndices: proof.pathIndices,
                leafIndex: i,
            });
        }
        return { root, proofs };
    }
    generateProof(leafIndex, levels) {
        const siblings = [];
        const pathIndices = [];
        let index = leafIndex;
        for (let i = 0; i < levels.length - 1; i++) {
            const level = levels[i];
            const isRight = index % 2 === 1;
            const siblingIndex = isRight ? index - 1 : index + 1;
            if (siblingIndex < level.length) {
                siblings.push(level[siblingIndex]);
            }
            else {
                siblings.push(level[index]);
            }
            pathIndices.push(isRight ? 1 : 0);
            index = Math.floor(index / 2);
        }
        return { siblings, pathIndices };
    }
    async hashLeaf(value) {
        const data = `leaf:${value}`;
        const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(data));
        return Array.from(new Uint8Array(hashBuffer))
            .map(b => b.toString(16).padStart(2, '0'))
            .join('');
    }
    async hashPair(left, right) {
        const data = `node:${left}|${right}`;
        const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(data));
        return Array.from(new Uint8Array(hashBuffer))
            .map(b => b.toString(16).padStart(2, '0'))
            .join('');
    }
    async computeNullifier(element, secret) {
        const data = `nullifier:${element}|${secret}`;
        const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(data));
        return Array.from(new Uint8Array(hashBuffer))
            .map(b => b.toString(16).padStart(2, '0'))
            .join('');
    }
    generateNonce() {
        const array = new Uint8Array(32);
        crypto.getRandomValues(array);
        return Array.from(array, b => b.toString(16).padStart(2, '0')).join('');
    }
    serializeWitness(data) {
        const json = JSON.stringify(data);
        return new TextEncoder().encode(json);
    }
}
// =============================================================================
// CREDENTIAL VERIFICATION CIRCUIT
// =============================================================================
/**
 * Credential Verification Circuit
 *
 * Proves possession of a valid credential without revealing the credential
 * itself. Supports selective disclosure of specific claims.
 *
 * Public Inputs:
 * - Issuer public key or DID
 * - Credential schema hash
 * - Disclosed claim hashes
 * - Predicate results
 *
 * Private Inputs:
 * - Full credential
 * - Credential signature
 * - Claims to prove
 *
 * Constraints:
 * - VerifySignature(issuerPK, credential, signature) = true
 * - Hash(credential.schema) = schemaHash
 * - For each disclosed claim: Hash(claim) matches public input
 * - For each predicate: predicate evaluation is true
 *
 * @example
 * ```typescript
 * const circuit = new CredentialVerificationCircuit();
 * const witness = await circuit.computeWitness(
 *   {
 *     credential: { ... },
 *     selectiveDisclosure: {
 *       reveal: ['name'],
 *       predicates: [{ claim: 'age', operator: 'gte', value: 21 }]
 *     }
 *   },
 *   ['issuerDID', 'schemaHash']
 * );
 * ```
 */
export class CredentialVerificationCircuit {
    getMetadata() {
        return {
            type: ZKCircuitType.CREDENTIAL_VERIFICATION,
            name: 'Credential Verification Circuit',
            description: 'Proves possession of a valid credential with selective disclosure',
            version: '1.0.0',
            publicInputs: ['issuerIdentifier', 'schemaHash', 'disclosedClaimHashes', 'predicateResults'],
            privateInputs: ['credential', 'signature', 'selectiveDisclosure'],
            registeredAt: new Date(),
        };
    }
    validatePrivateInputs(inputs) {
        if (!inputs.credential || typeof inputs.credential !== 'object') {
            return false;
        }
        const credential = inputs.credential;
        if (!credential.issuer || !credential.subject || !credential.claims) {
            return false;
        }
        return true;
    }
    validatePublicInputs(inputs) {
        if (inputs.length < 1) {
            return false;
        }
        const [issuerIdentifier] = inputs;
        if (!issuerIdentifier) {
            return false;
        }
        return true;
    }
    async computeWitness(privateInputs, publicInputs) {
        if (!this.validatePrivateInputs(privateInputs)) {
            throw new CircuitError('Invalid private inputs for credential verification', ZKCircuitType.CREDENTIAL_VERIFICATION);
        }
        if (!this.validatePublicInputs(publicInputs)) {
            throw new CircuitError('Invalid public inputs for credential verification', ZKCircuitType.CREDENTIAL_VERIFICATION);
        }
        const credential = privateInputs.credential;
        const selectiveDisclosure = privateInputs.selectiveDisclosure;
        const expectedIssuer = publicInputs[0];
        // Verify issuer matches
        if (credential.issuer !== expectedIssuer) {
            throw new CircuitError('Credential issuer mismatch', ZKCircuitType.CREDENTIAL_VERIFICATION, { message: 'Credential was not issued by the expected issuer' });
        }
        // Check expiration
        if (credential.expiresAt) {
            const expiresAt = new Date(credential.expiresAt);
            if (expiresAt.getTime() < Date.now()) {
                throw new CircuitError('Credential has expired', ZKCircuitType.CREDENTIAL_VERIFICATION, { message: 'Credential is no longer valid' });
            }
        }
        // Evaluate predicates
        const predicateResults = [];
        if (selectiveDisclosure?.predicates) {
            for (const predicate of selectiveDisclosure.predicates) {
                const result = this.evaluatePredicate(credential.claims, predicate);
                predicateResults.push(result);
                if (!result) {
                    throw new CircuitError('Predicate not satisfied', ZKCircuitType.CREDENTIAL_VERIFICATION, { claim: predicate.claim, operator: predicate.operator });
                }
            }
        }
        // Compute disclosed claim hashes
        const disclosedClaimHashes = [];
        if (selectiveDisclosure?.reveal) {
            for (const claimName of selectiveDisclosure.reveal) {
                if (claimName in credential.claims) {
                    const claimValue = credential.claims[claimName];
                    const hash = await this.hashClaim(claimName, claimValue);
                    disclosedClaimHashes.push(hash);
                }
            }
        }
        const witnessData = {
            issuer: credential.issuer,
            subject: credential.subject,
            type: credential.type,
            disclosedClaimHashes,
            predicateResults,
            credentialValid: true,
            witnessNonce: this.generateNonce(),
        };
        return this.serializeWitness(witnessData);
    }
    extractPublicInputs(privateInputs) {
        const credential = privateInputs.credential;
        if (credential) {
            return [credential.issuer];
        }
        return [];
    }
    /**
     * Evaluate a predicate against credential claims
     */
    evaluatePredicate(claims, predicate) {
        const claimValue = claims[predicate.claim];
        if (claimValue === undefined) {
            return false;
        }
        switch (predicate.operator) {
            case 'gt':
                return typeof claimValue === 'number' && claimValue > predicate.value;
            case 'gte':
                return typeof claimValue === 'number' && claimValue >= predicate.value;
            case 'lt':
                return typeof claimValue === 'number' && claimValue < predicate.value;
            case 'lte':
                return typeof claimValue === 'number' && claimValue <= predicate.value;
            case 'eq':
                return claimValue === predicate.value;
            case 'neq':
                return claimValue !== predicate.value;
            case 'in':
                return Array.isArray(predicate.value) && predicate.value.includes(claimValue);
            case 'not_in':
                return Array.isArray(predicate.value) && !predicate.value.includes(claimValue);
            default:
                return false;
        }
    }
    async hashClaim(name, value) {
        const data = `claim:${name}:${JSON.stringify(value)}`;
        const hashBuffer = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(data));
        return Array.from(new Uint8Array(hashBuffer))
            .map(b => b.toString(16).padStart(2, '0'))
            .join('');
    }
    generateNonce() {
        const array = new Uint8Array(32);
        crypto.getRandomValues(array);
        return Array.from(array, b => b.toString(16).padStart(2, '0')).join('');
    }
    serializeWitness(data) {
        const json = JSON.stringify(data);
        return new TextEncoder().encode(json);
    }
}
// =============================================================================
// FACTORY
// =============================================================================
/**
 * Create a new circuit registry with built-in circuits
 */
export function createCircuitRegistry() {
    return new CircuitRegistry();
}
/**
 * Get a specific circuit by type
 */
export function getCircuit(type) {
    const registry = new CircuitRegistry();
    const circuit = registry.getCircuit(type);
    if (!circuit) {
        throw new CircuitError(`Circuit not found: ${type}`, type);
    }
    return circuit;
}
//# sourceMappingURL=circuits.js.map