O brilho e a miséria das trocas atômicas

Quais são os problemas dos swaps atômicos e como os canais podem ajudar, o que aconteceu de importante no hard fork Constantinople e como agir quando não há como pagar pelo gás.

A principal motivação de qualquer especialista em segurança é o desejo de evitar a responsabilidade.

A providência foi generosa, saí do ICO antes da primeira transação irreversível, mas logo me vi envolvido no desenvolvimento de uma exchange de criptomoedas.

Eu definitivamente não sou o Menino Kibalchish, e um olhar severo é suficiente para que eu entregue todas as minhas chaves e senhas. Portanto, meu principal objetivo como arquiteto foi posicionar a quente picada da criptoanálise o mais longe possível dos elementos da infraestrutura que me são caros.

Não são suas chaves, não são seus problemas.

Estamos construindo um sistema de troca de ativos e queremos eliminar o armazenamento intermediário desses ativos junto a nós, mas devemos garantir a segurança da transação.

Podemos atuar como juiz em uma situação controversa e realizar transações com carteiras que exigem duas das três assinaturas: do comprador, do vendedor e do escrow.

No entanto, se um participante atacar com sucesso o escrow, ele obtém as duas assinaturas desejadas.

O swap atômico é um esquema de troca onde o garantidor é um contrato inteligente, que permite apenas comportamentos honestos.

Como numa charada envolvendo um lobo, uma cabra e um repolho, você pode agir apenas de acordo com um único cenário correto e sofrerá perdas se se afastar dele.

Mas, em vez de animais vorazes, a ordem é garantida pela função hash, na qual é tão difícil encontrar uma colisão que nem vale a pena tentar.

Passo um: a charada.

Suponha que Alice, em uma bela manhã, deseje transferir bitcoins para Bob em troca de uma mão cheia de 'criptoeuros'.

  • Ela faz um grande segredo.
  • Recebe dele um hash.
  • Transfere bitcoins para um contrato inteligente, do qual Bob pode retirar o dinheiro apresentando o segredo (o hash dele deve ser igual ao indicado no contrato).
  • Caso Bob não venha buscar seus bitcoins até a noite, Alice pode recuperá-los.

Passo dois: a isca.

Bob entra em cena e transfere 'criptoeuros' para seu contrato, que é escrito de tal forma que:

  • Alice pode recuperar os 'criptoeuros' apresentando um número secreto.
  • Não antes do almoço, Bob, caso Alice não apareça, pode reaver o depósito.

Passo três: a solução da isca.

Alice vem pegar seu dinheiro e retira os fundos do contrato de Bob, revelando seu segredo.

Passo final: o enigma foi resolvido

Bob vê a transação e, com olhar de águia, identifica nela o segredo apresentado por Alice ao contrato. Ele usa esse segredo para reivindicar seus bitcoins.

Quando algo dá errado

Se Alice de repente se tornar mortal, Bob pode pegar seus yens durante o almoço.

Por sua vez, Alice devolve o bitcoin à noite, caso o traiçoeiro Bob decida segurar o dinheiro por um tempo.

Se você prefere a imagem ao texto, há uma explicação mais detalhada e visual para você no Habr. sobre o funcionamento dos swaps atômicos..

A diferença entre os timeouts visa nos proteger da maliciosa Alice, que leva o dinheiro de Bob no último momento, enquanto ele, com dedos trêmulos, digita o hex na transação.

Os participantes não podem perder seu dinheiro, no máximo, terão que esperar o reembolso.

Suporte em blockchains.É um esquema simples, como um calçado, que exige das blockchains interagentes apenas o básico:

  • Suporte a contratos inteligentes com pelo menos uma ramificação.
  • Ambas as blockchains devem suportar os mesmos algoritmos de hashing (não se esqueça de verificar o comprimento do segredo).
  • Time locks.

À primeira vista, já pode-se dizer adeus à bolsa, 'nossa reunião foi um erro', mas não é bem assim.

Apesar de suas qualidades, as soluções de atomic swap não são muito líquidas. Em parte porque na par mais popular, BTC-USD, a parte fiat não estava totalmente tokenizada.
O sucesso do USDT gerou uma onda inteira de moedas estáveis do formato ERC20 para todos os gostos, do mais custódia USDC ao mais algorítmico DAI.

Portanto, para simplificar, discutiremos a partir de agora que Alice vende bitcoins para Bob em troca de alguns tokens ERC20 e esperamos pela sorte dos estabilizadores, visto que ainda temos muitos problemas técnicos pela frente.

Velocidade

Bitcoin e Ethereum, por si só, não são muito rápidos, e aqui temos que esperar primeiro um depósito com todas as confirmações, depois o segundo.

Tudo isso porque primeiro o dinheiro é depositado pelo participante que conhece o segredo, enquanto o oponente aguarda a finalização e só então transfere sua parte.

Além disso, lidamos com um ativo bastante volátil, então, durante esse tempo, o preço pode mudar significativamente, e alterar as condições já não é tão simples.

Confidencialidade

Qualquer troca deixa artefatos em ambas as blockchains. Um observador atento pode notar hashes iguais em contratos inteligentes e tirar a conclusão lógica de que uma transação ocorreu, a partir da qual podem-se fazer várias deduções, desde questões de preços até questões tributárias.

Quando a bolsa sabe sobre seus negócios — isso é bastante desagradável; quando todos sabem — é ainda pior.

Usabilidade

O truque do blockchain em geral e do Ethereum em particular. Vamos ver quais ações o vendedor e o comprador precisam realizar.

Do ponto de vista do vendedor, tudo é relativamente simples: basta transferir bitcoin para um endereço p2sh. Com o Ethereum, a situação é bem mais complicada.

ContratoVamos considerar um contrato médio do GitHub para swaps:

contract iERC20 {
    function totalSupply() public view returns (uint256);
    function transfer(address receiver, uint numTokens) public returns (bool);
    function balanceOf(address tokenOwner) public view returns (uint);
    function approve(address delegate, uint numTokens) public returns (bool);
    function allowance(address owner, address delegate) public view returns (uint);
    function transferFrom(address owner, address buyer, uint numTokens) public returns (bool);
}

contract Swapper {

    struct Swap {
        iERC20 token;
        bytes32 hash;
        uint amount;
        uint refundTime;
        bytes32 secret;
    }

    mapping (address => mapping(address => Swap)) swaps;

    function create(iERC20 token, bytes32 hash, address receiver, uint amount, uint refundTime) public {
        require(swaps[msg.sender][receiver].amount == 0); // check if swap with given hash already exists
        require(token.transferFrom(msg.sender, address(this), amount)); // transfer locked tokens to swap contract
        swaps[msg.sender][receiver] = Swap(token, hash, amount, refundTime, 0x00); // create swap
    }
    
    function hashOf(bytes32 secret) public pure returns(bytes32) {
        return sha256(abi.encodePacked(secret));
    }


    function withdraw(address owner, bytes32 secret) public {
        Swap memory swap = swaps[owner][msg.sender];
        require(swap.secret == bytes32(0));
        require(swap.hash == sha256(abi.encodePacked(secret))); // swap exists
        swaps[owner][msg.sender].secret = secret;
        swap.token.transfer(msg.sender, swap.amount);
    }

    function refund(address receiver) public {
        Swap memory swap = swaps[msg.sender][receiver];
        require(now > swap.refundTime);
        delete swaps[msg.sender][receiver];
        swap.token.transfer(msg.sender, swap.amount);
    }
}

Atenção! Não use este e outros contratos do artigo em produção, eles foram escritos exclusivamente para demonstração. Especialmente este.

  • Bob deve chamar o método do contrato do token approve, dando ao contrato de swap acesso aos seus tokens
  • Bob cria o swap e o contrato usando o método transferFrom retira para seu endereço os tokens do remetente
  • Alice em withdraw revela o segredo e o contrato chama transferência

A maioria das carteiras e exchanges de criptomoedas não suporta approve tokens, e não é à toa.

Os próprios usuários frequentemente cometem erros e simplesmente transferem tokens para o contrato, após o que os tokens simplesmente se perdem. Os comentários no Etherscan estão cheios de lamúrias dos infelizes.

E para chamar o contrato, é necessário pagar uma taxa em ETH, o que significa que ambas as partes precisam se abastecer antes de iniciar a transação, e pouca gente quer lidar com isso.

GasHolder

Para começar, devemos remover a verificação do remetente em todos os lugares possíveis e assumir que temos alguém sofrendo de excesso de gás chamando contratos para todos os interessados.

Contrato modernizado

contract Swapper {

    struct Swap {
        iERC20 token;
        address receiver;
        uint amount;
        address refundAddress;
        uint refundTime;
    }

    mapping (bytes32 => Swap) swaps;

    function create(iERC20 token, bytes32 hash, address receiver, uint amount, address refundAddress, uint refundTime) public {
        require(swaps[hash].amount == 0); // use hash once
        require(token.transferFrom(msg.sender, address(this), amount));
        swaps[hash] = Swap(token, receiver, amount, refundAddress, refundTime);
    }


    function withdraw(bytes memory secret) public {
        bytes32 hash = sha256(secret);
        Swap memory swap = swaps[hash];
        require(swap.amount > 0);
        delete swaps[hash];
        swap.token.transfer(swap.receiver, swap.amount);
    }

    function refund(bytes32 hash) public {
        Swap memory swap = swaps[hash];
        require(now > swap.refundTime);
        delete swaps[hash];
        swap.token.transfer(swap.refundAddress, swap.amount);
    }
}

Dualismo contrato-chave e EIP 712

Como sabemos, um endereço no Ethereum pode ser um contrato ou pode ser uma entidade, ou seja, uma chave.
A principal função da chave é assinar mensagens.

Podemos usar o contrato Bob como remetente, que realiza todas as transações necessárias, verificando antes a assinatura da chave Bob.

Agora, qualquer um pode patrocinar a taxa de um participante, mas apenas aquele que conhece a chave toma a decisão.

Contrato Bob

library EIP712ProxyLibrary {
    function hashCommand(address sender, iERC20 token, Swapper swapper, bytes32 hash, address receiver, uint amount, address refundAddress, uint refundTime) public view returns(bytes32);
}

contract ProxyBob {
    address owner;

    constructor(address _owner) public {
        owner = _owner;
    }

    function createSwap(Swapper swapper, iERC20 token, bytes32 hash, address receiver, uint amount, address refundAddress, uint refundTime, uint8 v, bytes32 r, bytes32 s) public {
        require(owner == ecrecover(EIP712ProxyLibrary.hashCommand(address(this), token, swapper, hash, receiver, amount, refundAddress, refundTime), v, r, s));
        token.approve(address(swapper), amount);
        swapper.create(token, hash, receiver, amount, refundAddress, refundTime);
    }
}

Para trabalhar com assinaturas de estruturas de dados complexas no Ethereum, existe um padrão EIP 712, você pode ler mais sobre isso em o blog da carteira Metamask

Dividir e conquistar

Frequentemente, o roteiro de um ataque ao contrato Ethereum se parece com isto:

  • Um participante deposita fundos no contrato
  • Depois retira os fundos
  • Algo dá errado
  • O atacante retira o dinheiro repetidamente

Se voltarmos ao nosso primeiro exemplo, algo dá errado se o enigma for um conjunto vazio de bytes.

Como roubar um milhãoCriamos um swap com um hash 0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925
Este é o sha256 de 0x0000000000000000000000000000000000000000000000000000000000000000
Transmitimos o segredo e retiramos nossos tokens
Transmitimos mais uma vez e retiramos os tokens de outros, tudo porque 0 = 0

Criando um contrato separado para cada transação, podemos isolar contratos a nível EVM.

Mas isso não é tudo: agora cada transação tem seu próprio endereço, para o qual tokens podem ser enviados de qualquer carteira ou exchange.

Contratos abandonados e create2

Mas agora precisamos criar um contrato para cada transação e esperar que o comprador envie o "crypto-funding". No esquema "de manhã contratos, à noite dinheiro", sempre há o risco de o comprador desistir, enquanto o Ether para criar o contrato já foi gasto.

Não podemos fazer com que de manhã tenha dinheiro e à noite bytes?

No hard fork Constantinople, os desenvolvedores EIP 1014 adicionaram a instrução create2, que cria um novo contrato em um endereço determinístico

keccak256( 0xff ++ address ++ salt ++ keccak256(init_code))[12:]

Onde

  • address — endereço do contrato factory
  • salt — um número, cujo significado saberemos no próximo episódio
  • init_code — bytecode do contrato e parâmetros do construtor.

FactoryA instrução só funciona através de assembly, então a factory parece um pouco intimidante:

contract Factory {
  event Deployed(address addr, uint256 salt);

  function create2(bytes memory code, uint256 salt) public {
    address addr;
    assembly {

      addr := create2(0, add(code, 0x20), mload(code), salt)
    }

    emit Deployed(addr, salt);
  }
}

O código do seu contrato pode ser obtido usando web3:

const MyContract = new web3.eth.Contract(ABI, {})
const сode = MyContract.deploy({
    data: BYTECODE,
    arguments: contructorArgs  
}).encodeABI();
const factory = new web3.eth.Contract(FACTORY_ABI, factoryAddress);
tx = factory.methods.create2(сode, salt);

Devido ao suporte limitado em solidity, o gás para o contrato pode ser calculado incorretamente devido a algumas sutilezas do Ether.

Particularmente adorável é que, em caso de falta de gás, o contrato falha com um erro interno, sem informar que o gás acabou, como era de se esperar.

Agora podemos traduzir tokens para contratos sem criá-los previamente, e enquanto não os publicamos na rede, ninguém descobrirá o que o contrato realmente faz.

Corvo não arranca o olho de corvo.

É claro que um verdadeiro analista, especialmente um que recebeu bons investimentos para combater inimigos do regime através da lavagem de dinheiro, não será detido por tais truques infantis, e após a criação do contrato, ele ainda verá o hash.

Como fazer para que o hash não seja revelado?

O próprio swap é transferido para o off-chain: os participantes trocam assinaturas para a transferência para o contrato de swap, e então o segredo é revelado de forma privada.

Passo a passo.Dois "multisigs" são criados, dos quais os fundos podem ser retirados na presença das assinaturas de Alice e Bob.

Para que a saída offline de qualquer um dos participantes não se torne uma tragédia, adicionaremos um bom e velho time-out.

Alice e Bob depositam simultaneamente.

  • Alice cria um segredo e passa para Bob o hash do segredo e a assinatura da transação que transfere bitcoins para o endereço do swap.
  • Bob fornece a Alice a assinatura para a retirada de tokens no contrato de swap com o hash definido.
  • Alice revela o segredo a Bob.

Neste momento, a harmonia acontece: tanto Alice quanto Bob podem encerrar a transação a qualquer momento. Em um ambiente amigável, eles podem trocar assinaturas para retirar dinheiro para endereços finais.

Para um observador externo, isso parece como se o dinheiro tivesse passado por um contrato com múltiplas assinaturas 2 de 2.

E esse esquema também permite que ambas as partes depositem ao mesmo tempo, já que o segredo é criado após todas as confirmações.

Nível 2.

Agora que podemos retirar dinheiro para um endereço e não publicar a transação intermediária, nada nos impede de retirar dinheiro para vários endereços e fazer um número ilimitado de transações intermediárias. Não é que isso seja um conjunto necessário para a troca, mas uma vez que você começa a coletar swaps, é difícil parar.

Agora Alice e Bob poderão agir plenamente. Por exemplo, calcular automaticamente o preço médio, trocando por satoshis por segundo, ou simplesmente conectar diretamente um market maker e um receptor de liquidez.

Passo a passo.

  • O vendedor cria um segredo e dá ao comprador o hash do segredo e a assinatura da transação onde parte dos fundos é transferida para o endereço p2sh do swap, e o restante é retornado ao endereço do vendedor.
  • O comprador fornece uma assinatura que permite retirar tokens e troco para o endereço do destinatário.
  • O vendedor revela o segredo
  • A história se repete com um novo segredo, adicionando à troca e ao troco a retirada de itens comprados anteriormente para o endereço do comprador e já pagos ao endereço do vendedor.

Agora temos acesso ao comércio p2p de alta velocidade, sendo importante acompanhar o tempo e fechar a negociação antes do timeout.

No entanto, ajustando um pouco nossos contratos, podemos oferecer imortalidade aos nossos canais, o que facilitará bastante a criação da rede.

Mas falaremos sobre isso no próximo episódio.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster