Protocolo de intercambio de criptodivisas sin exchanges

Alice ha leído un tweet en el que Bob ofrece 5 bitcoins a cambio de 13 ethereums y le interesa realizar el cambio. Sin embargo, ninguno quiere ser quien empiece el intercambio, ya que no pueden asegurarse de que la otra parte cumplirá el trato.

La solución actual ante este problema es acudir a un exchange y adquirir las monedas directamente, o confiar en un mediador que recibirá las monedas de ambos y después las dará a quien corresponda. Ambas soluciones requieren del pago de un pequeño porcentaje como honorarios al proveedor del servicio, que pagan tanto Alice como Bob.

Además, ambas soluciones van profundamente en contra de los fundamentos de las criptomonedas ya que no son soluciones descentralizadas; hay que depositar la confianza en una third-party (el exchange o el mediador), pero no puedes asegurarte de que esa third-party nunca vaya a corromperse parcial o totalmente.  Esa es en última instancia la razón por la que se fundó bitcoin: para que la confianza en la moneda se depositara sobre una red de muchas personas (y por tanto menos corrompible) y no sobre un banco central.

Funcionamiento básico

Dicho esto, esta entrada pretende abordar un protocolo que permita a Alice y Bob ejecutar su transacción sin necesidad de nadie más. Para ello, vamos a utilizar los siguientes conceptos:

  • \texttt{BTC}: Criptomoneda 1 con su propio blockchain (hemos utilizado las siglas de bitcoin, pero realmente es un alias para cualquier tipo de moneda),
  • \texttt{ETH}: Criptomoneda 2 con su propio blockchain (alias),
  • A: Alias para Alice,
  • B: Alias para Bob,
  • W_A^{\texttt{BTC}}: Wallet (monedero) de Alice (A) para monedas del tipo \texttt{BTC}.
  • W_A^{\texttt{ETH}}: Wallet de Alice para monedas del tipo \texttt{ETH}.
  • W_B^{\texttt{BTC}}: Wallet de Bob para monedas del tipo \texttt{BTC}.
  • W_B^{\texttt{ETH}}: Wallet de Bob para monedas del tipo \texttt{ETH}.

Y vamos a reescribir el enunciado, sin pérdida de generalidad, como:

Alice tiene un \texttt{BTC} y lo quiere cambiar por un \texttt{ETH}. Bob tiene un \texttt{ETH} y lo quiere cambiar por un \texttt{BTC}.

Para poder resolver este intercambio, Alice va a escribir un Smart Contract en la blockchain donde tiene sus \texttt{BTC}s. Un Smart Contract es un trozo de lógica que se publica en la blockchain y nunca puede editarse (ojo, sí puede almacenarse información como veremos a continuación, lo que no puede modificarse es la lógica en sí), pero sí permite interactuar con él. Esa interacción es siempre intra-blockchain, es decir, no puede interaccionar con otra red de criptomoneda o con cualquier otro elemento que quede fuera de su red.

Además, también hay que destacar que un Smart Contract puede ser dueño de cierta cantidad de criptomoneda, exactamente igual que cualquier wallet. Para poder “mover” el dinero que hay dentro del Smart Contract, sólo podemos utilizar la lógica que haya sido predefinida.

El Smart Contract que va a escribir Alice va a tener la siguiente forma (en pseudocódigo):

ContractBTC {
    address ADDR_RECIPIENT;
    uint HASH;

    __constructor(address recipient, uint h) {
        ADDR_RECIPIENT = recipient;
        HASH = h;
    }

    withdrawl(string guess) {
        if (hashingFunction(guess) == HASH) {
            transferAllContractMoneyTo(ADDR_RECIPIENT);
        }
    }
}

Antes de lanzar ese código a la red blockchain de \texttt{BTC}, Alice pregunta a Bob cuál es la dirección del wallet donde quiere recibir su \texttt{BTC}s, y Bob le responde que es W_B^{\texttt{BTC}}.

Una vez sabe esto, Alice elige una contraseña (en adelante la llamaremos \textrm{password}) y la hashea con una función de hash (como puede ser MD5, SHA256,…), y con eso obtiene un hash al que llama \textrm{H}, es decir,

\textrm{H} = \texttt{hashFunction}(\textrm{password}).

Sabiendo esta información, Alice ya está en disposición de deployear su Smart Contract a la red blockchain. En cuanto lo haga, Alice no podrá cambiar ni una coma del código fuente; lo escrito, escrito está.

Alice deployea el Smart Contract, inicializándolo (véase cómo el constructor pide 2 valores de entrada) con

\texttt{Contract}(W_B^{\texttt{BTC}}, \textrm{H}).

De esta forma, en el contrato, ahora la variable ADDR_RECIPIENT pasa a ser W_B^{\texttt{BTC}}, y la variable HASH pasa a ser \textrm{H}.

Además, Alice transfiere al Smart Contract un (1) \texttt{BTC}, de forma que este \texttt{BTC} ahora pertenece al contrato, y no a Alice (recordemos que un Smart Contract actúa también como wallet y por tanto puede tener moneda en su propiedad).

En el momento en el que el Smart Contract es deployeado a la red, absolutamente todo el mundo tiene acceso a él, puede leer sus variables ADDR_RECIPIENT (que contiene la dirección del wallet de Bob), HASH (que contiene el valor hasheado de \textrm{password}) e incluso cuánta moneda hay en el contrato.

De hecho, todo el mundo puede invocar al método withdrawl pasándole la variable guess. Pero la funcionalidad de withdrawl sólo se ejecutará si se cumple la condición de que el hash de guess sea igual a la variable HASH, que a su vez es el hash de \textrm{password}, lo que viene a decir que sólo se cumplirá cuando guess sea \textrm{password}. Lo que pasa es que hasta este momento, la única persona en el mundo que conoce \textrm{password} es Alice.

A continuación, Alice manda a Bob la dirección del Smart Contract para que compruebe que todo está correcto (el destinatario es su wallet, el contrato tiene la cantidad de moneda acordada, el código fuente no tiene trampa,…) y Bob elabora su propio Smart Contract, que tiene esta forma:

ContractETH {
    address ADDR_RECIPIENT;
    uint HASH;
    string KEY;

    __constructor(address recipient, uint h) {
        ADDR_RECIPIENT = recipient;
        HASH = h;
    }

    withdrawl(string guess) {
        if (hashingFunction(guess) == HASH) {
            transferAllContractMoneyTo(ADDR_RECIPIENT);
            KEY = guess;
        }
    }
}

Como puede verse, es prácticamente igual a excepción de que éste incluye la variable KEY, cuya utilidad veremos más adelante. Bob seteará de recipient la dirección del wallet de Alice (W_A^{\texttt{ETH}}), y como h pasará el mismo valor que haya leído en la variable HASH del contrato que le ha enviado Alice. Exactamente el mismo valor.

Lo deployeará igualmente a la red blockchain de \texttt{ETH}, le transferirá un (1) \texttt{ETH} y le mandará a Alice la dirección de este contrato.

Al igual que con el contrato de Alice, éste también es público. Todo el mundo puede ver sus variables y su código. Pero es sólo Alice la que puede ejecutar correctamente la función withdrawl del contrato de Bob, ya que Bob en su contrato ha hecho que su variable HASH sea la misma que le enviaba Alice, y por lo tanto sólo cuando guess sea \textrm{password} se ejecutará la funcionalidad, exactamente igual que con el contrato de Alice. Es decir, tenemos dos contratos que se “desbloquean” con la misma contraseña, pero esta contraseña sólo la conoce Alice.

La clave es que Alice quiere el \texttt{ETH} que Bob ha metido en su contrato, así que ejecuta el comando withdrawl pasándole la contraseña correcta, es decir, \textrm{password}, para poder obtener su moneda.

Cuando withdrawl se ejecute con el guess correcto en el contrato de Bob, pasarán dos cosas:

  • transferAllContractMoneyTo(ADDR_RECIPIENT): recordemos que Bob había inicializado la variable ADDR_RECIPIENT al wallet de Alice, así que automáticamente todos los fondos del contrato (el \texttt{ETH} que había transferido Bob al contrato) se transfieren a Alice.
  • KEY = guess: Aquí se asigna el dato de guess introducido (que si hemos llegado hasta aquí es \textrm{password}) a una variable pública del contrato, la variable KEY.

Gracias al segundo punto, Alice al reclamar su dinero ha hecho pública inevitablemente la clave con la que había “encriptado” el \texttt{BTC} a Bob. Ahora Bob no tiene más que ir a su propio contrato y leer la variable KEY, que ha sido seteada a \textrm{password} en el momento en el que Alice ha introducido la contraseña correcta, e introducir esa clave en el withdrawl del contrato que le mandó Alice, lo cual ejecutaría el método que transfiere el \texttt{BTC} a su cuenta.

Recuperación por incumplimiento

Supongamos que Alice elabora el primer contrato, transfiere el \texttt{BTC} al contrato, y después Bob decide que ya no quiere hacer el intercambio. En este caso, y con el esquema explicado hasta ahora, Alice no tiene forma de recuperar su moneda: pertenece al contrato eternamente, ya que la única manera de que salga del contrato es para irse al wallet de Bob, ya que así es como está escrito en el código (recordemos que una vez está en la blockchain, ese código es inalterable).

Para sortear esto, introducimos el concepto de “expiración”: vamos a hacer que si pasado un tiempo todavía no se ha hecho la transacción, el dinero vuelva a su dueño. Para ello, ampliamos el código fuente del contrato tal que:

Contract {
    address ADDR_RECIPIENT;
    address ADDR_DEPLOYER;
    uint HASH;
    string KEY;
    uint CREATE_TIME;
    uint EXPIRES_IN;

    __constructor(address recipient, uint h, address me, uint secondsExpiration) {
        ADDR_RECIPIENT = recipient;
        HASH = h;
        ADDR_DEPLOYER = me;
        CREATE_TIME = NOW();
        EXPIRES_IN = secondsExpiration;
    }

    withdrawl(string guess) {
        if (NOW() > CREATE_TIME + EXPIRES_IN) {
            transferAllContractMoneyTo(ADDR_DEPLOYER);
            return;
        }

        if (hashingFunction(guess) == HASH) {
            transferAllContractMoneyTo(ADDR_RECIPIENT);
            KEY = guess;
        }
    }
}

De esta forma, al crear el contrato, Alice indica la cantidad de tiempo (en segundos) durante la cual el acuerdo es válido. Pasado ese tiempo, las monedas del contrato se transferirían a ADDR_DEPLOYER, que Alice se ha encargado de inicializar con su propio wallet.

Bob, al recibir el contrato de Alice, puede leer cuánto tiempo de expiración ha puesto Alice, y él debe poner una cantidad menor de tiempo en su contrato.

De esta forma, cuando Alice introduzca (a tiempo) la clave \textrm{password} en el contrato de Bob, haciéndola pública en la blockchain y recibiendo la moneda, Bob aún tendrá tiempo (ya que el contrato de Alice caducaba algo más tarde) para coger la clave ahora almacenada en KEY y desbloquear el contrato de Alice, consiguiendo así ambos la moneda a intercambiar.

Si Bob decide no seguir adelante, Alice sólo debe esperar a que pase el tiempo que ha puesto de expiración y volverá a tener acceso a su moneda. Y si Alice al recibir el contrato de Bob nunca llegara a introducir la clave, al final Bob podría recuperar su moneda porque el tiempo de expiración también pasaría.

Resumen

  1. Alice se comunica con Bob pidiéndole la dirección del wallet donde quiere recibir las monedas, y ella misma le da a Bob la dirección del wallet donde quiere recibir las monedas de Bob.
  2. Alice escribe un Smart Contract en el que bloquea la transferencia a Bob a tenor de que se introduzca una clave cuyo hash sea el preestablecido por Alice, y establece un tiempo de expiración del contrato,
  3. Alice transfiere a su Smart Contract la cantidad de moneda a cambiar,
  4. Alice le da a Bob la dirección del Smart Contract,
  5. Bob genera un nuevo Smart Contract en el que bloquea la transferencia a Alice a tenor de que se introduzca una clave cuyo hash es el mismo que el del contrato de Alice, e introduce un tiempo de expiración del contrato menor que el que ha observado en el contrato de Alice,
  6. Bob transfiere a su Smart Contract la cantidad de moneda a cambiar,
  7. Bob le da a Alice la dirección del Smart Contract,
  8. Alice lo recibe, introduce la clave que automáticamente se hace pública para todo el mundo en el blockchain, y recibe su dinero,
  9. Bob ahora puede ver la clave en el blockchain, así que introduce la clave en el Smart Contract que le mandó Alice y recibe su dinero.

Compatibilidad

Para que una crypto currency sea compatible con este protocolo debe cumplir las siguientes propiedades:

  • Disponer de red blockchain propia,
  • Soportar Smart Contracts,
  • Permitir que un Smart Contract albergue moneda,
  • Asegurar la inmutabilidad del código,
  • Asegurar que el código es la única forma de interaccionar con el contrato,
  • …?

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *