Procedimientos de firma

ML-DSA-87 explicado matemáticamente

Cómo firma ML-DSA-87 (FIPS 204) una valoración de McGesund — desde Module-LWE y el rejection sampling hasta la verificación en el navegador.

Actualizado: 2026-09-07

1. De qué trata esto

Cuando alguien deja una valoración en McGesund, en segundo plano ocurre más de lo que el texto deja intuir. Al enviarla, la valoración se firma digitalmente. Cualquier visitante puede recalcular esa firma más tarde en su propio navegador — sin confiar en nosotros y sin preguntarnos.

Para clientes a partir de la tarifa Pro, eso se hace, entre otros, con ML-DSA-87. Este artículo explica qué ocurre matemáticamente en el proceso.

Una precisión previa:

ML-DSA no es un cifrado. El texto de la valoración sigue siendo legible públicamente — ese es justamente el sentido de una valoración. ML-DSA no demuestra confidencialidad, sino origen e integridad.

ML-DSA se desarrolló a partir de CRYSTALS-Dilithium y está estandarizado como FIPS 204. El número 87 designa el nivel de parámetros. Hay tres:

  • ML-DSA-44
  • ML-DSA-65
  • ML-DSA-87

ML-DSA-87 es el más alto y se sitúa en la categoría de seguridad 5 de NIST.


2. ¿Qué se firma exactamente?

Lo que entra en la firma no es el texto de la valoración en sí, sino un objeto de datos compacto que fija ese texto de forma inequívoca. En McGesund tiene, en esencia, este aspecto:

{
  "v":   1,
  "typ": "rev-comment",
  "f":   "<ID de la empresa>",
  "c":   "<ID de la valoración>",
  "h":   "<SHA-256 del texto de la valoración>",
  "rh":  "<SHA-256 del registro de envío completo>",
  "rv":  1,
  "qh":  "<SHA-256 del envelope QR, solo en valoraciones por QR>",
  "iat": 1757203200
}

Este objeto es nuestro mensaje mm. Vincula entre sí seis afirmaciones:

  1. A qué empresa pertenece la valoración (f)
  2. Qué valoración es la referida (c)
  3. Qué texto había detrás — como valor hash (h)
  4. Qué registro en su conjunto se envió (rh): texto, corazones, estado de geolocalización y datos del motivo, serializados de forma canónica y con hash — en la versión de esquema rv
  5. De qué código QR procede la valoración (qh); sin QR el campo no aparece
  6. Cuándo se firmó (iat)

Si alguien modifica más tarde un solo carácter del texto de la valoración, h deja de encajar — y rh tampoco. Quien en cambio solo toca los corazones deja h intacto, pero rompe rh. Y si alguien modifica uno de esos valores hash, la firma deja de encajar. Esa cadena es precisamente el punto.


3. Qué debe lograr la firma

Un visitante que lee la valoración debe poder comprobar por sí mismo tres cosas:

  1. La valoración fue efectivamente emitida por McGesund.
  2. El texto no se ha modificado desde el envío.
  3. Nadie puede inventarse una valoración nueva con aspecto válido.

Para eso existe un par de claves:

  • una clave privada — reside exclusivamente en el servicio de firma
  • una clave pública — puede tenerla cualquiera; se direcciona mediante el identificador de clave (kid) del envelope

Se firma con la clave privada. Se verifica con la pública — y además en el navegador del lector, no en nuestro servidor.


4. ¿Por qué un procedimiento post-cuántico?

Muchos procedimientos de firma habituales hoy se basan en la factorización de números grandes o en logaritmos discretos. Un ordenador cuántico suficientemente potente podría resolver precisamente esos problemas mucho más rápido con algoritmos ya conocidos.

Para una valoración eso no es una cuestión académica. Una valoración debe seguir siendo verificable dentro de diez años. Quien firma hoy, firma para toda la vida útil de la entrada.

Por eso ML-DSA emplea otra base:

Problemas de retıˊculos\boxed{\text{Problemas de retículos}}

Más concretamente: Module-LWE y Module-SIS.


5. ¿Qué es un retículo?

De momento, solo puntos en el espacio. Tomemos dos vectores:

b1=(2,0),b2=(1,2).b_1=(2,0), \qquad b_2=(1,2).

Todas las combinaciones enteras

ab1+bb2,a,bZa\,b_1+b\,b_2, \qquad a,b\in\mathbb{Z}

forman el retículo. Por ejemplo:

2b1+b2=(4,0)+(1,2)=(5,2).2b_1+b_2 = (4,0)+(1,2) = (5,2).
b₁ = (2,0)b₂ = (1,2)(5,2)0
Dos vectores y todas las combinaciones enteras a partir de ellos: un retículo. El punto marcado surge de dos veces b₁ y una vez b₂.

Lo decisivo:

En dimensiones pequeñas los problemas de retículos son fáciles. En dimensiones muy altas, determinadas tareas se vuelven extremadamente difíciles.


6. Polinomios en lugar de números sueltos

ML-DSA no calcula con vectores 2D, sino con polinomios y vectores de polinomios.

Un polinomio como

a(x)=1+2x+x3a(x)=1+2x+x^3

se puede escribir como lista de coeficientes:

a=(1,2,0,1).a=(1,2,0,1).

Se calcula en un anillo:

Rq=Zq[x]/(xn+1).R_q=\mathbb{Z}_q[x]/(x^n+1).

Eso significa dos cosas:

  • Zq\mathbb{Z}_q: calcular módulo qq
  • xn=1x^n=-1: una regla adicional que fija la longitud de los polinomios

Para los tres niveles de ML-DSA se cumple:

n=256,q=8380417.n=256, \qquad q=8380417.

Un polinomio tiene, por tanto, 256 coeficientes, considerados módulo 8.380.417. Lo que cambia entre los niveles no es nn ni qq, sino el tamaño de las matrices — más sobre esto más adelante.

Al margen: en la práctica, la multiplicación de polinomios en este anillo se realiza mediante la NTT, la variante teórico-numérica de la transformada rápida de Fourier. ML-DSA no prescinde en absoluto de las ideas de la FFT; sencillamente están en la aritmética y no en el principio de firma.


7. El truco central: Module-LWE

La idea de fondo es Module Learning With Errors:

t=As1+s2(modq).\mathbf{t}=\mathbf{A}\mathbf{s}_1+\mathbf{s}_2 \pmod q.

Donde:

  • A\mathbf{A} — matriz pública de polinomios, aparentemente aleatoria
  • s1,s2\mathbf{s}_1,\mathbf{s}_2 — vectores secretos pequeños
  • t\mathbf{t} — valor público

Un atacante conoce A\mathbf{A} y t\mathbf{t}, pero no s1,s2\mathbf{s}_1,\mathbf{s}_2. Para él la ecuación parece una ecuación aleatoria con ruido. La idea es que no pueda despejar de ahí los pequeños secretos de forma eficiente.


8. Un ejemplo numérico diminuto

Tomamos a propósito una variante ridículamente pequeña — números normales en lugar de polinomios, dimensión 2 en lugar de 256, y

q=17.q=17.

Sea

A=(3572),s1=(11),s2=(10).A=\begin{pmatrix}3&5\\7&2\end{pmatrix}, \qquad \mathbf{s}_1=\begin{pmatrix}1\\-1\end{pmatrix}, \qquad \mathbf{s}_2=\begin{pmatrix}1\\0\end{pmatrix}.

Entonces:

As1=(3572)=(25),A\mathbf{s}_1=\begin{pmatrix}3-5\\7-2\end{pmatrix}=\begin{pmatrix}-2\\5\end{pmatrix},
t=As1+s2=(25)+(10)=(15)    (165)(mod17).\mathbf{t}=A\mathbf{s}_1+\mathbf{s}_2=\begin{pmatrix}-2\\5\end{pmatrix}+\begin{pmatrix}1\\0\end{pmatrix}=\begin{pmatrix}-1\\5\end{pmatrix} \;\equiv\; \boxed{\begin{pmatrix}16\\5\end{pmatrix}} \pmod{17}.

En este miniformato se podrían probar todas las posibilidades. En ML-DSA-87, A\mathbf{A} es una matriz 8×78\times 7 de polinomios con 256 coeficientes cada uno — eso son más de 14.000 incógnitas en la estructura del retículo.


9. El par de claves del servicio de firma

La clave privada contiene, entre otras cosas, los vectores pequeños s1,s2\mathbf{s}_1,\mathbf{s}_2. En ML-DSA-87 sus coeficientes proceden del intervalo

[η,η]conη=2,[-\eta,\eta] \quad\text{con}\quad \eta=2,

es decir, de {2,1,0,1,2}\{-2,-1,0,1,2\}. Esa pequeñez no es un detalle, sino el núcleo: solo porque los secretos son pequeños surge realmente un problema de retículos difícil.

La clave pública es, de forma simplificada,

(ρ,t1).(\rho,\mathbf{t}_1).

ρ\rho es una semilla a partir de la cual A\mathbf{A} se puede reconstruir de forma determinista — la matriz no hace falta transmitirla. t1\mathbf{t}_1 son los bits superiores de t\mathbf{t}; los d=13d=13 bits inferiores se omiten, lo que reduce la clave de forma notable. Esa omisión es más tarde la razón de los llamados hints.

Así surge la asimetría deseada:

verificar en puˊblico    firmar en secreto\boxed{\text{verificar en público}\;\neq\;\text{firmar en secreto}}

10. La valoración se convierte en un número

El servicio de firma calcula primero el hash del objeto payload de la sección 2:

μ=H(m).\mu=H(m).

En nuestro ejemplo de juguete tomamos un minihash artificial. En el sistema real μ\mu tiene 512 bits e incorpora además la clave pública — con lo que una firma no se puede reinterpretar como perteneciente a otra clave.


11. Commitment

El servicio de firma extrae un vector pequeño aleatorio y\mathbf{y}. En nuestro ejemplo:

y=(21).\mathbf{y}=\begin{pmatrix}2\\-1\end{pmatrix}.

De ahí surge un valor intermedio, el commitment:

w=Ay=(32+5(1)72+2(1))=(112).\mathbf{w}=A\mathbf{y} =\begin{pmatrix}3\cdot2+5\cdot(-1)\\7\cdot2+2\cdot(-1)\end{pmatrix} =\begin{pmatrix}1\\12\end{pmatrix}.

Eso todavía no es una firma.


12. De la valoración sale un desafío

Mensaje y commitment se pasan juntos por el hash:

c=H(μ,w).c=H(\mu,\mathbf{w}).

En ML-DSA-87, cc es un polinomio con exactamente τ=60\tau=60 coeficientes de {1,+1}\{-1,+1\}; los 196 restantes son cero. Esa estructura es deliberada: mantiene pequeño cs1c\cdot\mathbf{s}_1.

En nuestro ejemplo de juguete ponemos simplemente

c=2.c=2.

13. La firma propiamente dicha

z=y+cs1.\mathbf{z}=\mathbf{y}+c\,\mathbf{s}_1.

Con nuestros valores:

cs1=2(11)=(22),c\,\mathbf{s}_1=2\begin{pmatrix}1\\-1\end{pmatrix}=\begin{pmatrix}2\\-2\end{pmatrix},
z=(21)+(22)=(43)\mathbf{z}=\begin{pmatrix}2\\-1\end{pmatrix}+\begin{pmatrix}2\\-2\end{pmatrix} =\boxed{\begin{pmatrix}4\\-3\end{pmatrix}}

14. El paso que se pasa fácilmente por alto: rejection sampling

Aquí está el punto en el que ML-DSA se distingue de una construcción ingenua — y no es opcional.

z=y+cs1\mathbf{z}=\mathbf{y}+c\,\mathbf{s}_1 contiene el secreto s1\mathbf{s}_1. Si z\mathbf{z} se publicara sin más siempre, s1\mathbf{s}_1 se podría despejar estadísticamente a partir de un número suficiente de firmas. En un portal de valoraciones con muchísimas firmas al día eso no es un riesgo teórico.

Por eso el servicio de firma comprueba antes de entregarla si z\mathbf{z} revela demasiado y, en caso afirmativo, descarta la firma — entonces se vuelve a empezar desde el principio con un nuevo y\mathbf{y} aleatorio. Eso se denomina Fiat-Shamir with Aborts.

La condición es, en esencia:

z<γ1β.\|\mathbf{z}\|_\infty < \gamma_1-\beta.
commitment w = Aydesafío c = H(μ, w)respuesta z = y + c · s₁‖z‖∞ < γ₁ − β ?se entrega la firmano — nueva y
Tres pasos y un bucle. Si z resulta demasiado grande, no se corrige — se descarta la pasada entera y se vuelve a empezar con una y aleatoria nueva. Por eso firmar es aquí un procedimiento con bucle.

Para ML-DSA-87 se tiene γ1=219\gamma_1=2^{19} y β=τη=602=120\beta=\tau\cdot\eta=60\cdot2=120. A eso se añade una segunda cota sobre los bits inferiores. En la práctica es normal que hagan falta varias pasadas — firmar es, por tanto, un procedimiento con bucle y no un paso único.

Para la verificación es importante: precisamente esa cota la comprueba después también el navegador. Una firma con coeficientes demasiado grandes se rechaza, aunque la ecuación cuadre.


15. Por qué el navegador puede verificarlo

El navegador del lector conoce:

  • la valoración y, con ella, mm
  • la clave pública (ρ,t1)(\rho,\mathbf{t}_1)
  • la firma (c~,z,h)(\tilde c,\mathbf{z},\mathbf{h})

No conoce s1\mathbf{s}_1. La relación que aun así le permite avanzar:

z=y+cs1Az=Ay+cAs1.\mathbf{z}=\mathbf{y}+c\,\mathbf{s}_1 \quad\Longrightarrow\quad A\mathbf{z}=A\mathbf{y}+c\,A\mathbf{s}_1.

Y como se cumple

t=As1+s2\mathbf{t}=A\mathbf{s}_1+\mathbf{s}_2

el término desconocido As1A\mathbf{s}_1 se puede sustituir por el valor público:

Azct=Aycs2.A\mathbf{z}-c\,\mathbf{t}=A\mathbf{y}-c\,\mathbf{s}_2.

Esa es la ecuación central — y dice algo importante: el navegador reconstruye AyA\mathbf{y} no exactamente, sino solo salvo el pequeño término cs2c\,\mathbf{s}_2.


16. El miniejemplo hasta el final

Teníamos:

A=(3572),t=(15),c=2,z=(43).A=\begin{pmatrix}3&5\\7&2\end{pmatrix},\quad \mathbf{t}=\begin{pmatrix}-1\\5\end{pmatrix},\quad c=2,\quad \mathbf{z}=\begin{pmatrix}4\\-3\end{pmatrix}.

Comprobémoslo:

Az=(1215286)=(322),ct=2(15)=(210),A\mathbf{z}=\begin{pmatrix}12-15\\28-6\end{pmatrix}=\begin{pmatrix}-3\\22\end{pmatrix}, \qquad c\,\mathbf{t}=2\begin{pmatrix}-1\\5\end{pmatrix}=\begin{pmatrix}-2\\10\end{pmatrix},
Azct=(112).A\mathbf{z}-c\,\mathbf{t}=\begin{pmatrix}-1\\12\end{pmatrix}.

El commitment original era

w=(112).\mathbf{w}=\begin{pmatrix}1\\12\end{pmatrix}.

La diferencia es

(112)(112)=(20)=cs2.\begin{pmatrix}-1\\12\end{pmatrix}-\begin{pmatrix}1\\12\end{pmatrix} =\begin{pmatrix}-2\\0\end{pmatrix} =-c\,\mathbf{s}_2.

Es decir, exactamente el pequeño término de error predicho. El verificador no obtiene w\mathbf{w}, sino algo que está cerca de w\mathbf{w}.

Precisamente por eso ML-DSA no compara los valores en sí, sino sus bits superiores. Y precisamente por eso la firma contiene además un vector de hints h\mathbf{h}: comunica de forma compacta en qué posiciones el redondeo ha cruzado un límite por culpa del pequeño término de error. En ML-DSA-87 se permiten como máximo ω=75\omega=75 de esas indicaciones. No revelan el secreto — solo reparan el redondeo.

Al final, el navegador vuelve a calcular el desafío. Si coincide,

cnuevo=c,c_{\text{nuevo}}=c,

y todas las normas están dentro de las cotas, la firma es válida.


17. ¿Qué ocurre si alguien modifica la valoración?

Supongamos que alguien con acceso a la base de datos — también alguien de nuestra casa — modifica el texto de la valoración o uno de los corazones. Entonces cambia al menos uno de los dos valores hash del payload (h en el caso del texto, rh en el de cualquier campo del registro):

H(m)H(m).H(m)\neq H(m').

Con ello cambia el desafío:

cc.c\neq c'.

Pero la firma existente se generó para el desafío antiguo. El navegador lo recalcula y constata:

cnuevocfirmaFirma no vaˊlidac_{\text{nuevo}}\neq c_{\text{firma}} \quad\Longrightarrow\quad \boxed{\text{Firma no válida}}

La frase decisiva al respecto: podemos borrar una valoración, pero no podemos modificarla sin que se note. En McGesund esa misma verificación se ejecuta además cada noche del lado del servidor sobre todo el fondo — una valoración que no supera esa comprobación deja de contar en la media de la empresa.


18. ¿Por qué nadie puede inventarse una firma?

Un atacante conoce A\mathbf{A} y t\mathbf{t}, pero no s1,s2\mathbf{s}_1,\mathbf{s}_2. Para construir una firma válida tendría que encontrar una terna (c~,z,h)(\tilde c,\mathbf{z},\mathbf{h}) que

  • satisfaga la ecuación de verificación y
  • respete las cotas de norma y
  • encaje con el desafío que resulta de esos mismos valores.

Eso desemboca, en el fondo, en un problema de retículos difícil — concretamente en Module-SIS: encontrar soluciones cortas de una ecuación homogénea módulo qq. La condición de pequeñez no es aquí un adorno, sino el verdadero grado de dificultad. Sin ella, una solución sería trivial.

datos puˊblicos    problema de retıˊculos difıˊcil\boxed{\text{datos públicos}\;\rightarrow\;\text{problema de retículos difícil}}

19. ¿Por qué «Module»?

La palabra describe la estructura intermedia entre los vectores simples y los retículos generales. En lugar de calcular con números sueltos, ML-DSA trabaja con vectores de polinomios:

a=(a1(x),a2(x),,ak(x)),\mathbf{a}=(a_1(x),a_2(x),\ldots,a_k(x)),

y con matrices formadas por ellos:

A=(a1,1(x)a1,2(x)a2,1(x)a2,2(x)).\mathbf{A}= \begin{pmatrix} a_{1,1}(x)&a_{1,2}(x)&\cdots\\ a_{2,1}(x)&a_{2,2}(x)&\cdots\\ \vdots&\vdots&\ddots \end{pmatrix}.

La ventaja: se obtiene la alta dimensión de un retículo, pero se conserva una representación compacta y eficientemente calculable. La seguridad se puede ajustar con precisión mediante el tamaño de la matriz, sin cambiar de anillo.


20. ¿Y por qué precisamente 87?

Los tres niveles no se diferencian en el anillo, sino en las dimensiones:

ParámetroML-DSA-44ML-DSA-65ML-DSA-87
Tamaño de la matriz (k,)(k,\ell)4×44\times46×56\times58×78\times7
Rango del secreto η\eta242
Peso del desafío τ\tau394960
Clave pública1.312 B1.952 B2.592 B
Firma2.420 B3.309 B4.627 B
Categoría NIST235

Es llamativo: ML-DSA-87 no es simplemente «ML-DSA-65, pero más grande». El rango del secreto η\eta vuelve de 4 a 2; aquí la seguridad procede de la matriz mayor, no de coeficientes mayores. Se trata de una elección de parámetros propia y estandarizada.

estructura matemaˊtica mayormayor margen de seguridad\boxed{\text{estructura matemática mayor}\rightarrow\text{mayor margen de seguridad}}

El precio: 4.627 bytes por firma — por cada sello que se almacena y que se entrega al navegador al verificar. Por eso, en McGesund, junto a ML-DSA-87 también se puede elegir FALCON, que se conforma con 1.280 bytes.


21. Fiat-Shamir: por qué funciona sin interlocutor

Una demostración interactiva transcurriría así:

  1. El servicio de firma envía un commitment.
  2. El verificador envía un desafío aleatorio.
  3. El servicio de firma responde.
  4. El verificador lo comprueba.

En una valoración no existe ese diálogo — el lector llega meses después. La solución es la transformación de Fiat-Shamir: el desafío no se sortea, sino que se obtiene por hash de los propios datos:

c=H(clave puˊblica,  mensaje,  commitment).c=H(\text{clave pública},\;\text{mensaje},\;\text{commitment}).

Así, un diálogo se convierte en un documento. El servicio de firma no puede escoger el desafío, porque para ello tendría que controlar el hash.


22. El proceso completo

SERVICIO DE FIRMA (MCGESUND)NAVEGADOR DEL VISITANTEclave privada s₁, s₂payload m = {empresa, valoración, h, rh, iat}hash μvector aleatorio ycommitment w = Aydesafío c = H(μ, w)z = y + c · s₁firma (c, z, hints) + kidvaloración + firma + clave públicaAz − c·t → bits superiores + hintsrecalcular el desafío¿desafío recalculado = desafío entregado?válidano válidadescartar si z es demasiado grande(rejection sampling)
El recorrido completo de una valoración. El salto de retorno discontinuo es el rejection sampling — se toma tantas veces como haga falta hasta que z sea lo bastante pequeña como para no revelar nada sobre la clave secreta.

23. Qué hace McGesund concretamente con esto

Tres niveles encajan entre sí:

El envelope. Cada valoración firmada lleva una firma Ed25519. Esa es la variante obligatoria — clásica, minúscula, verificable de forma nativa en cualquier navegador.

Los sellos post-cuánticos. Además se pueden colocar al lado una o dos firmas resistentes a la computación cuántica. Cuáles, depende de la tarifa:

Tarifaniveles de firma disponibles
BasisEd25519, FN-DSA-512
KlassikEd25519, FN-DSA-512, FN-DSA-1024
ProEd25519, FN-DSA-1024, ML-DSA-87
PremiumEd25519, FN-DSA-1024, ML-DSA-87, ambas en paralelo

La variante en paralelo es deliberadamente redundante. Si una de las dos familias matemáticas — retículos NTRU en FALCON, retículos de módulos en ML-DSA — resultara ser más débil de lo que hoy se supone, la otra sigue sosteniendo.

El ancla temporal. La huella de la clave de firma se ancla mediante OpenTimestamps en un bloque de Bitcoin. Con ello no solo se puede acreditar que la firma es auténtica, sino también que ya existía en un momento determinado — sin que nadie tenga que creerse nuestra marca de tiempo.

Todo eso se verifica en el navegador del lector, mediante un módulo WASM. Nosotros entregamos los datos; el cálculo se hace en el dispositivo del visitante. Si mañana desapareciéramos de la red, una valoración ya descargada seguiría siendo verificable.


24. ML-DSA y FALCON, uno al lado del otro

PropiedadFALCON (FN-DSA)ML-DSA
Tipofirma digitalfirma digital
Familia de retículosNTRUModule-LWE / Module-SIS
AnilloZq[x]/(xn+1)\mathbb{Z}_q[x]/(x^n+1), q=12289q=12289Zq[x]/(x256+1)\mathbb{Z}_q[x]/(x^{256}+1), q=8380417q=8380417
Mecanismo centralvector corto por muestreo gaussianodesafío-respuesta con abandonos
FFT / NTTFFT en coma flotante, crítica para la seguridadNTT, solo aritmética
Tamaño de firma (nivel más alto)1.280 B4.627 B
Implementaciónexigente (coma flotante)comparativamente directa
Estandarizaciónprevista como FIPS 206 (FN-DSA), aún no cerradaFIPS 204, cerrada

En pocas palabras: ML-DSA es más fácil de implementar y de verificar correctamente, y FALCON ofrece firmas bastante más compactas. En el código QR no va ninguna de las dos — allí figura únicamente el envelope Ed25519. Por eso el tamaño de la firma cuenta en el almacenamiento y la entrega, y la robustez cuenta en la implementación. Por eso ofrecemos ambas.


25. Una frase para llevarse

ML-DSA genera, a partir de un vector secreto corto, una respuesta a undesafıˊo obtenido por hash de la propia valoracioˊn — verificablepor cualquiera que tenga la clave puˊblica.\boxed{ \begin{array}{c} \text{ML-DSA genera, a partir de un vector secreto corto, una respuesta a un}\\ \text{desafío obtenido por hash de la propia valoración — verificable}\\ \text{por cualquiera que tenga la clave pública.} \end{array}}

Quien posee el vector secreto firma en milisegundos. Quien no lo posee tendría que resolver un problema de retículos en más de 14.000 dimensiones — incluso con un ordenador cuántico.

Para el lector de una valoración eso significa, simplemente: no tiene que creernos. Puede recalcularlo.