Frontend de Circom
Cómo los archivos .circom llegan a ProveIR: lexer, parser, análisis, lowering y composición de componentes.
Visión general
El crate circom es la implementación de Achronyme del compilador de Circom 2.x — solo el frontend. La semántica de circuito se baja a ProveIR, y luego se entrega al mismo pipeline de instanciación → IR → backend que los bloques prove {}.
Para uso de cara al usuario (importar plantillas, llamarlas desde .ach), ver el capítulo Interoperabilidad con Circom. Esta página documenta los internals para colaboradores.
Pipeline
Punto de entrada: compile_to_prove_ir(source: &str) -> Result<CircomCompileResult, CircomError> (archivo: circom/src/lib.rs).
pub struct CircomCompileResult {
pub prove_ir: ProveIR,
pub output_names: HashSet<String>,
pub capture_values: HashMap<String, u64>,
pub warnings: Vec<Diagnostic>,
}
API multi-archivo: compile_file(path, library_dirs) resuelve cadenas de include con deduplicación mutua (flag CLI -l/--lib, [circom] libs en achronyme.toml).
Lexer
Archivo: circom/src/lexer/. Escáner de bytes single-pass. Desambiguación maximal-munch para la familia de operadores de señales:
| Tokens | Significado |
|---|---|
< | menor que |
<= | menor o igual |
<== | asignación de señal con constraint (LHS computado de RHS, también con constraint) |
<-- | asignación de señal sin constraint (witness hint, sin constraint emitida) |
=== | constraint puro |
==> | asignación de señal con constraint, dirección inversa |
--> | asignación de señal sin constraint, dirección inversa |
El lexer debe comprometerse al match más largo; de lo contrario <== sería lexeado como <= seguido de =.
Parser
Archivo: circom/src/parser/. Pratt + descenso recursivo escrito a mano para Circom 2.x. Produce:
pub struct CircomProgram {
pub version: Option<PragmaVersion>,
pub definitions: Vec<Definition>,
}
pub enum Definition {
Template { name, params, body: TemplateBody },
Function { name, params, body: FunctionBody }, // imperative, compile-time evaluator
Bus { … },
Component { name, component_type, params: Vec<Expr> }, // top-level main
Include { path },
}
pub struct TemplateBody {
pub signals: Vec<SignalDecl>,
pub statements: Vec<Stmt>,
}
pub enum SignalDecl {
Input { name, array_size: Option<Expr> },
Output { name, array_size: Option<Expr> },
Intermediate { name, array_size: Option<Expr> },
}
La forma de sentencia pura es soportada (Circom 2.1+): for (...) stmt; sin llaves.
Análisis (circom::analysis)
Archivo: circom/src/analysis/. Corre después del parseo:
- Verificación de emparejamiento de constraints — las asignaciones
<--/-->son witness hints, no constraints. Si la señal asignada no queda fijada directamente por===o indirectamente a través de un acumuladorvarrastreado, el analizador emite E100. La asignación doble es una advertencia separada W103. - Resolución de includes — recorre como DAG las cadenas
include "file.circom";, detecta ciclos, deduplica includes mutuos. - Validación del componente main — exactamente una declaración
component main { … }es requerida (E210 si falta, E211 si referencia una plantilla indefinida).
Lowering (circom::lowering)
Archivo: circom/src/lowering/. Traduce el AST de Circom validado a ProveIR.
Submódulos (split post-auditoría, 21 módulos):
circom/src/lowering/mod.rs— drivercircom/src/lowering/components.rs— instanciación de componentes, enlace de parámetroscircom/src/lowering/signals.rs— extracción de dimensiones de señalescircom/src/lowering/expressions/— lowering deCircuitExprcircom/src/lowering/statements/— lowering deCircuitNodecircom/src/lowering/const_fold.rs— evaluación de constantes en tiempo de compilación (BigVal, fold de ternarios, constantes de array)circom/src/lowering/artik_lift/— cuerpos de funciones que no pueden inlinearse → bytecode Artikcircom/src/lowering/context.rs— entorno, capturas, known_constants
Comportamientos importantes:
- Palabra clave
function— intérprete en tiempo de compilación para cuerpos imperativos (var,while,for,if/else,return,++,*=, llamadas anidadas). Desbloqueanbits(),log2(), etc. Resultado alimentado aprecompute_varspara resolver dimensiones de señales. - Constant-fold de ternarios — los ternarios conocidos en tiempo de compilación seleccionan una rama en lowering, evitando errores de
ArrayIndexen ramas muertas comoxL[-1]. - Desenrollado de for-loops — auto-desenrolla cuando el cuerpo referencia
known_array_values. Variantes de ForRange:Literal,WithCapture(acotado por parámetro),WithExpr(límite computado, p.ej.,n+1). - Arrays de componentes —
component muls[n]; muls[i] = Template()se desenrolla en lowering usandoknown_constants. - Parámetros de plantilla tipo array —
Ark(t, C, 0)pasa arrays a través del inlining de componentes;Captureresuelve desde los parámetros del padre. - Arrays 2D —
var M[t][t] = GET_MATRIX(t)— aplanado con strides + resolución multi-dim de índices. - Evaluador BigVal — el
varen tiempo de compilación es complemento a dos de 256 bits[u64; 4]. Arregla overflow1 << 128enCompConstant.
Composición de componentes
Una instanciación de componente:
component lt = LessThan(8);
lt.in[0] <== a;
lt.in[1] <== b;
out <== lt.out;
… se expande en tiempo de lowering: el cuerpo de lt se inlinea con (8) sustituido por el parámetro de plantilla n, las señales de entrada de lt se vuelven let-bindings en el árbol de expresiones del padre, la señal de salida de lt se realimenta a out vía ===.
Las capturas del padre fluyen al hijo vía CaptureDef. El anidamiento multi-nivel (p.ej., EscalarMulAny(254) que contiene Pedersen y EscalarMulFix) fue el foco de los fixes de bug de deep-inlining (2026-04-04).
Witness hints
<-- (y -->) emiten nodos WitnessHint { name, hint } en ProveIR. Le dan un valor al prover pero no emiten constraint. Combinados con ===, implementan el patrón estándar “testigo + verificación”.
Para loops con asignación de señales tipo array (out[i] <-- expr), el lowering emite WitnessHintIndexed.
Códigos de diagnóstico
Rangos específicos de Circom:
| Rango | Severidad | Significado |
|---|---|---|
| E100–E102 | Error | Errores de parser (sintaxis, ops de señales malformadas) |
| W101–W103 | Warning | Problemas con pragma de versión, formas deprecadas, doble asignación (W103) |
| E200–E211 | Error | Errores de lowering (plantilla indefinida, falta main, type mismatch, dim mismatch) |
| E300–E306 | Error | Errores de análisis de constraints (señales bajo-constraint, problemas estructurales) |
Códigos seleccionados:
- E101 — histórico; rebajado a W103 en beta.20.
- E210 —
NoMainComponent: no se encontró declaracióncomponent main. - E211 —
MainTemplateNotFound:mainreferencia una plantilla indefinida. - W103 —
DoubleSignalAssignment: una señal se asigna dos veces (permitido para cobertura de ramas if-else).
Todos los diagnósticos llevan sugerencias “did you mean?” con distancia de Levenshtein y usan el DiagnosticRenderer estándar para salida estilo rustc.
Evaluación de constantes en tiempo de compilación
- Los cuerpos de funciones se evalúan en tiempo de compilación cuando sus llamadores necesitan una constante (p.ej., dimensiones de señales).
EvalValue∈{Scalar(BigVal), Array(Vec<EvalValue>), Expr(CircuitExpr)}.eval_function_to_valuecorre el intérprete imperativo; las ramas if-else que retornan valores distintos se unifican.precompute_array_varsexpandevar C[n] = POSEIDON_C(t)tipo array aConstlets por elemento.
Lift a Artik
Para cuerpos de funciones que no pueden ser inlineados como CircuitExpr (porque tienen control de flujo dependiente de señales o escrituras de array), el lowering los entrega a circom::lowering::artik_lift. Salida: un blob de bytecode Artik, embebido en CircuitNode::WitnessCall.
Ver Artik Witness VM para la ruta de dispatch.
Integración con el optimizador R1CS
El frontend de Circom produce ProveIR que, después de la instanciación, es optimizado por pases O1 + O2:
| Pase | Efecto |
|---|---|
| Propagación de constantes | Folding de campo BN254 + inlining de señales constantes |
| Eliminación lineal | Sustitución estilo Gaussiana |
| Manejo de zero-product | Elimina x · y == 0 redundantes cuando un lado es probadamente cero |
| DEDUCE | Heurística de frecuencia + remoción de lineales tautológicos |
El harness de benchmarks registra conteos de constraints contra circom --O2 para plantillas de referencia como Num2Bits, LessThan, MiMCSponge y EscalarMulAny. Trata los conteos exactos como salida de benchmark, no como arquitectura: vuelve a correr circom/tests/e2e_optimization/benchmark/ antes de publicar números actuales.
Tests adversariales de soundness
Archivo: circom/tests/adversarial.rs. Los tests adversariales cubren testigos forjados para primitivas representativas para que las ganancias del optimizador no se conviertan silenciosamente en circuitos bajo-restringidos. Ejemplos:
- Forjar bits no-bool en
Num2Bits→ la aserción falla. - Forjar violación de suma en
Num2Bits→ la aserción falla. - Forjar salida de
LessThan(ambas direcciones) → la aserción falla.
Archivos fuente
| Componente | Archivo |
|---|---|
| Lexer | circom/src/lexer/ |
| Parser | circom/src/parser/ |
| Análisis | circom/src/analysis/ |
| Driver del lowering | circom/src/lowering/mod.rs |
| Componentes | circom/src/lowering/components.rs |
| Señales | circom/src/lowering/signals.rs |
| Expresiones | circom/src/lowering/expressions/ |
| Sentencias | circom/src/lowering/statements/ |
| Const fold | circom/src/lowering/const_fold.rs |
| Lift a Artik | circom/src/lowering/artik_lift/ |
| Context | circom/src/lowering/context.rs |
| Diagnósticos | diagnostics/src/ y circom/src/lib.rs |
| Tests adversariales | circom/tests/adversarial.rs |
| Tests E2E | circom/tests/ |
| API pública | circom/src/lib.rs |
Ver Interoperabilidad con Circom para uso de cara al usuario. Ver ProveIR para el formato producido por lowering. Ver Artik Witness VM para cuerpos de funciones no inlinables.