Obliv-Java: Máquina Virtual Java para computación segura y su aplicación a los contratos inteligentes

Los cinco niveles ascendentes del intelecto: Listo, Inteligente, Brillante, Genio, Simple.

Albert Einstein

ACTUALIZACIÓN: Por favor, comprueba el repositorio de Obliv-Java.

Más allá del genio de la computación segura, está su simplicidad de uso. Aproximaciones previas a la computación segura han desarrollado lenguajes de programación a medida y nuevos sistemas de tipado. Nosotros vamos un paso más allá: ahora podrás realizar computación segura con tu código original sin introducir cambios significativos que rompan funcionalidades previas, respectando fielmente el estándar del lenguaje Java y manteniendo retro-compatibilidad con todo tu código anterior. En última instancia, buscamos multiplicar la productividad del desarrollador facilitando la refactorización del código para su computación segura. Adicionalmente, todas las herramientas de desarrollo previas seguirán siendo compatibles con Obliv-Java: analizadores estáticos, entornos de desarrollo y de test unitarios, entre otras muchas.

En Obliv-Java, la definición de campos en una clase debe ser anotada utilizando la nueva anotación de tipo “@Obliv(X)”, con X siendo el número asignado a la parte de una computación segura. Por ejemplo,

@Obliv(1)
float inputA;
@Obliv(2)
float inputB;
@Obliv(0)
float compResult;

En el código anterior, inputA es el campo a la que parte 1 asignará un valor de forma segura, inputB es el campo para la parte 2 y compResult es el campo utilizado por ambas partes para obtener el valor resultante durante su revelación al final de la computación segura (por la tanto asignado a la parte 0, esto es, cualquiera de las partes intervinientes en la computación segura). El desarrollador puede utilizar los campos definidos como oblivious como suele hacer normalmente (sumas, restas, multiplicaciones, divisiones, conversiones a otras variables oblivious, …) obviando cualquier detalle criptográfico sobre su computación segura y por lo tanto liberando su mente para resolver complicadas tareas de desarrollo, consiguiendo un efecto muy parecido a como los lenguajes que usan la memoria de forma segura ayudan a mejorar la productividad del desarrollador asumiendo todas las tareas relacionadas con la gestión de memoria.

Para la computación segura, utilizamos implementaciones modernas de protocolos de Computación Segura Multi-Parte (MPC), uno de los secretos mejor guardados de las técnicas del criptógrafo moderno: tras conseguir un aumento en la velocidad del 700.000x en la última década, los protocolos MPC son órdenes de magnitud más rápidos que el cifrado homomórfico y los protocolos de conocimiento cero. Los últimos avances en investigación han producido protocolos MPC especialmente adecuados para los entornos descentralizados donde todas las partes involucradas podrían ser maliciosas, como ocurre en los entornos blockchain (por ejemplo, “Global-Scale Secure Multiparty Computation”).

Nuestros Contratos Inteligentes Privados y Verificables se desarrollan en Obliv-Java, una Máquina Virtual Java (JVM) para la computación segura y Código Acompañado de Demostraciones (PCC). Como ejemplo, echa un vistazo al siguiente ejemplo:

public class Example {
@Obliv(1)
float inputA;
@Obliv(2)
float inputB;
@Obliv(0)
float compResult;

public static void main(String args[]) {
inputA = Float.parseFloat(args[0]);
inputB = Float.parseFloat(args[1]);

securelyCompute();

System.out.println(Example.class.getField(“compResult”).revealOblivFloat(Example.class));
}

//@ ensures inputA > inputB ==> compResult == inputA-inputB
//@ ensures inputA <=inputB ==> compResult == inputA+inputB
public static securelyCompute()
{
if (inputA > inputB)
compResult = inputA – inputB;
else
compResult = inputA + inputB);
}
}

Ten en cuenta que la computación segura será totalmente transparente al desarrollador, que no tiene que saber nada sobre protocolos:

  • Líneas 2-7: definición de variables con la anotación de tipo “@Obliv”.
  • Líneas 10-11: las variables de entrada desde la línea de comandos (args[0], args[1]) son convertidas a tipos en punto flotante. Luego, son convertidas internamente para su computación segura durante su asignación a las variables oblivious inputA, inputB.
  • Línea 15: la variable compResult es extraída utilizando la reflection de Java, y el nuevo método revealOblivFloat devuelve su valor descifrado a ambas partes porque fue definido con el identificador de parte 0.
  • Líneas 22-25: aquí es donde ocurre toda la magia. La variable compResult es igual a inputA menos inputB si inputA es mayor que inputB, o igual a inputA más inputB si inputA es menor que inputB: la computación segura utiliza exactamente el mismo código fuente y no hay necesidad alguna de introducir ningún cambio.

Además, las anotaciones JML en las líneas 18-19 pueden ser convertidas en obligaciones de demostración que el resto de partes pueden comprobar ante de ejecutar código en el que no se confía (aunque todo esto es asunto de otra publicación en el blog). Todas tus herramientas para demostrar la corrección del software siguen siendo tan útiles como siempre.

APLICACIONES A CONTRATOS INTELIGENTES

Los siguientes ejemplos muestran cómo Obliv-Java mejora el estado del arte de los contratos inteligentes:

  • Subastas de Vickrey: una subasta veraz donde los pujadores son incentivados a que pujen por su auténtica valoración y el subastador termina obteniendo la máxima recaudación. Se trata de una subasta a sobre cerrado por lo que no puede llevarse a cabo en un blockchain público porque todas las pujas quedan a la vista de todos.

Aunque no ocurre lo mismo con Obliv-Java. Considera la siguiente implementación en Java (una leve modificación de Vickrey-JADE):

public AID getWinner(){
AID winner = bidderList[0].getName();
for(AID agent:bids.keySet()){
if(bids.get(agent) >= max){
max = bids.get(agent);
winner = agent;
}
}
return winner;
}

public void computeSecondPrice() {
for(AID agent:bids.keySet()){
if(bids.get(agent) >= max){
secondMax = max;
max = bids.get(agent);
} else if (bids.get(agent) >= secondMax) {
secondMax = bids.get(agent);
}
}
}

El método getWinner() retorna el máximo pujador y el método computeSecondPrice() obtiene el segundo precio más alto en la variable secondMax. Con Obliv-Java, sólo tendrías que anotar las campos secondMax y max con “@Obliv”, así como cada una de las pujas: no harían falta más modificaciones.

@Obliv(0)
Double max = Double.NEGATIVE_INFINITY;
@Obliv(0)
Double secondMax = Double.NEGATIVE_INFINITY;
  • Ofertas Iniciales de Monedas (ICOs) y otras inversiones crowdfunding:

En blockchains públicos, todas las transferencias son públicas a todos los participantes incluyendo aquellas realizadas cuando se participa en una ICO, creando toda clase de consecuencias imprevistas y no deseadas. Por ejemplo, un astuto participante podría llevar una estrategia de esperar y aprender de las pujas de los otros participantes para razonar mejor sobre el proceso de formación de precios: ¿Cuántas ballenas están participando? ¿Cuáles son las estadísticas generales de las pujas -media, mediana, moda, …-? ¿Acabará sobre-suscribiéndose la oferta?

Aunque no ocurre lo mismo con Obliv-Java. A continuación, un simple ejemplo para comprobar si las contribuciones privadas han alcanzado límite secreto:

public class ICO {
@Obliv(1)
int  maxCap;
@Obliv(0)
int cumulativeContributions;
@Obliv(0)
boolean isCapReached;

public void isCapReached() {
isCapReached = cumulativeContributions >= maxCap;
if (ICO.class.getField("isCapReached").revealOblivBoolean(ICO.class))
stopAcceptingTransfers();
}
}
  • Bolsas de cambio descentralizadas (inversión ventajista –front-running– y otros ataques):

En las bolsas de cambio descentralizadas, todas las órdenes son públicamente visibles para todos los corredores de comercio por lo que los ataques de inversión ventajista –front-running– son incluso peores que cuando sólo son llevados a cabo por un agente de comercio en una bolsa de cambio tradicional: todo el mundo podría engañarte. Este problema ha sido denunciado muchas veces anteriormente, llegando algunos incluso a crear ataques prácticos (“Implementing Ethereum trading front-runs on the Bancor exchange in Python”). Las soluciones actuales son muy insatisfactorias: por ejemplo, sacar el libro de órdenes de compra/venta fuera del blockchain reintroduce los problemas de la centralización y con ello, la inversión ventajista –front-running– por parte de la nueva parte que cierra las órdenes de compra/venta –matcher-.

Aunque no ocurre lo mismo con Obliv-Java. Considera el siguiente libro de órdenes de compra/venta refactorizado para su computación segura:

if (side == buy)
mapOrders = asks;
else if (side == sell)
mapOrders = bids;

for (Order ord:mapOrders.keySet()) {
price = mapOrders.getPrice(ord);
currentSize = targetSize - sizeCompleted;

if (mapOrders.getSize(ord) < currentSize)
sizeCurrentOrder = mapOrders.getSize(ord);
else
sizeCurrentOrder = currentSize;

sizeCompleted += sizeCurrentOrder;
amount += sizeCurrentOrder * price;

if (sizeCompleted == targetSize) {
if (!foundAmount) {
foundAmount = true;
retAmount = amount;
}
}
}

Como con los casos anteriores, simplemente con anotar las variables implicadas con “@Obliv” sería suficiente para refactorizar el algoritmo anterior para su computación segura.

Estaremos encantados de liberar el código fuente de Obliv-Java: mantente atento a la próxima Distribución Inicial.