Generación de Testigos
Un testigo es la asignación completa de valores a todos los cables en un sistema de restricciones. Achronyme genera testigos vía reproducción de traza — el compilador registra cada computación intermedia durante la compilación, luego reproduce esas operaciones con valores de entrada concretos.
Resumen
Sección titulada «Resumen»El pipeline de generación de testigos:
- Compilación:
R1CSCompiler::compile_ir()recorre el IR y registra unWitnessOppara cada variable intermedia que asigna - Captura:
WitnessGenerator::from_compiler()captura la traza de ops, la disposición de variables y los parámetros de Poseidon - Generación:
generate(inputs)asigna el vector de testigo, llena los valores de entrada y reproduce las ops
Alternativamente, compile_ir_with_witness(program, inputs) combina los tres pasos — también ejecuta el evaluador IR primero para validación temprana.
WitnessOp
Sección titulada «WitnessOp»Cada WitnessOp registra cómo calcular el valor de un cable intermedio:
AssignLC
Sección titulada «AssignLC»AssignLC { target: Variable, lc: LinearCombination }Evalúa una combinación lineal contra el testigo actual: target = lc.evaluate(witness). Emitido por materialize_lc cuando una combinación lineal se materializa en un nuevo cable.
Multiply
Sección titulada «Multiply»Multiply { target: Variable, a: LinearCombination, b: LinearCombination }Calcula target = a.evaluate(witness) × b.evaluate(witness). Emitido por multiply_lcs para multiplicación general.
Inverse
Sección titulada «Inverse»Inverse { target: Variable, operand: LinearCombination }Calcula target = 1 / operand.evaluate(witness). Emitido por divide_lcs para división. Error si el operando evalúa a cero.
BitExtract
Sección titulada «BitExtract»BitExtract { target: Variable, source: LinearCombination, bit_index: u32 }Extrae un solo bit: target = (source >> bit_index) & 1. Emitido por la descomposición booleana de RangeCheck. Los elementos de campo son de 256 bits (4 × 64 bits limbs), así que bit_index puede ser 0–255.
IsZero { diff: LinearCombination, target_inv: Variable, target_result: Variable }El gadget IsZero:
- Si
diff == 0:inv = 0,result = 1 - Si
diff != 0:inv = 1/diff,result = 0
Usado por las instrucciones de comparación IsEq e IsNeq.
PoseidonHash
Sección titulada «PoseidonHash»PoseidonHash { left: Variable, right: Variable, output: Variable, internal_start: usize, internal_count: usize }Calcula el hash Poseidon 2-a-1 reproduciendo la permutación completa nativamente. Llena ~361 valores de cables internos (360 estados de ronda + 1 inicialización de capacidad) comenzando en internal_start. El orden de asignación coincide exactamente con lo que compile_poseidon produce en el backend R1CS.
WitnessGenerator
Sección titulada «WitnessGenerator»struct WitnessGenerator { ops: Vec<WitnessOp>, num_variables: usize, public_inputs: Vec<(String, Variable)>, witnesses: Vec<(String, Variable)>, poseidon_params: Option<PoseidonParams>,}Construcción
Sección titulada «Construcción»let wg = WitnessGenerator::from_compiler(&compiler);Debe llamarse después de compile_ir(). Captura la traza de ops, conteo de variables, disposición de entradas/testigos y parámetros de Poseidon inicializados perezosamente.
Generación
Sección titulada «Generación»let witness: Vec<FieldElement> = wg.generate(inputs)?;El método generate():
- Asigna un vector de
num_variableselementos de campo - Establece el cable 0 = 1 (el cable constante ONE)
- Llena cables de entradas públicas del mapa
inputsproporcionado - Llena cables de testigo del mapa
inputsproporcionado - Reproduce cada
WitnessOpen orden para calcular valores intermedios - Devuelve el vector de testigo completo
Errores
Sección titulada «Errores»enum WitnessError { MissingInput(String), // entrada requerida no proporcionada DivisionByZero { variable_index: usize }, // inverso de cero}Disposición de Cables
Sección titulada «Disposición de Cables»El vector de testigo sigue esta disposición (requerida para compatibilidad con snarkjs):
Índice: 0 1..n_pub n_pub+1.. ONE público testigo + intermedios- Cable 0: Siempre 1 (la constante)
- Cables 1..n_pub: Entradas públicas en orden de declaración
- Cables restantes: Entradas testigo seguidas de variables intermedias
Las entradas públicas deben asignarse antes de las entradas testigo — snarkjs espera este orden.
Pipeline Combinado
Sección titulada «Pipeline Combinado»El uso más común es compile_ir_with_witness(), que hace todo en una llamada:
let witness = compiler.compile_ir_with_witness(&program, &inputs)?;Este método:
- Evalúa el IR con entradas concretas (
ir::eval::evaluate()) para validación temprana - Compila el IR a restricciones (llenando
witness_ops) - Construye un
WitnessGeneratordesde el compilador - Genera el testigo
- Verifica el testigo contra el sistema de restricciones
Tanto R1CSCompiler como PlonkishCompiler proporcionan este método.
Testigo de Poseidon
Sección titulada «Testigo de Poseidon»El hash Poseidon es la computación de testigo más compleja. fill_poseidon reproduce la permutación:
- Inicializar estado:
[left, right, 0](capacidad = 0) - Aplicar la permutación Poseidon (rondas completas → rondas parciales → rondas completas)
- Para cada ronda: agregar constante de ronda, aplicar S-box, multiplicar por matriz MDS
- Registrar cada valor de estado intermedio en el testigo en el índice de cable correcto
El orden de asignación de cables debe coincidir exactamente con lo que compile_poseidon produce — cualquier discrepancia causa que la verificación del testigo falle.
Archivos Fuente
Sección titulada «Archivos Fuente»| Componente | Archivo |
|---|---|
| WitnessOp & WitnessGenerator | compiler/src/witness_gen.rs |
| R1CS compile_ir_with_witness | compiler/src/r1cs_backend.rs |
| Plonkish compile_ir_with_witness | compiler/src/plonkish_backend.rs |
| Parámetros de Poseidon | constraints/src/poseidon.rs |