L'éclat et la misère des échanges atomiques

Quels sont les inconvénients des échanges atomiques et comment les canaux peuvent-ils les aider, que s'est-il passé de significatif lors du hard fork Constantinople et que faire lorsque l'on n'a pas de quoi payer le gaz ?

La principale motivation de tout spécialiste en sécurité est le désir d'éviter la responsabilité.

La providence a été clémente, j'ai quitté l'ICO sans attendre la première transaction irréversible, mais je me suis vite retrouvé à développer une plateforme d'échange de cryptomonnaies.

Je ne suis absolument pas M. Kibalchich, et un seul regard sévère suffit pour que je remette toutes les clés et mots de passe. Ainsi, mon objectif principal en tant qu'architecte était de placer l'ardent dard de la crypto-analyse aussi loin que possible des éléments d'infrastructure qui me sont chers.

Pas vos clés, pas vos problèmes.

Nous construisons un système d'échange d'actifs et voulons éliminer le stockage intermédiaire de ces actifs chez nous, tout en garantissant la sécurité de la transaction.

On peut jouer le rôle d'arbitre dans une situation litigieuse et effectuer des transactions avec des portefeuilles nécessitant deux des trois signatures : celle de l'acheteur, celle du vendeur et celle de l'entremetteur.

Cependant, si un participant réussit à attaquer l'entremetteur, il obtient les deux signatures requises.

Un échange atomique est un schéma d'échange où un contrat intelligent agit comme garant, permettant uniquement un comportement honnête.

Tout comme dans l'énigme du loup, de la chèvre et du chou, vous ne pouvez agir que selon un seul scénario correct et subissez des pertes si vous vous en écartez.

Mais à la place des animaux voraces, l'ordre est assuré par une fonction de hachage, dont il est si difficile de trouver des collisions qu'il ne vaut même pas la peine de commencer.

Étape un : l'énigme

Supposons qu'Alice veuille un beau matin transférer des bitcoins à Bob en échange d'une poignée de "cryptos".

  • Elle concocte un grand secret.
  • Elle reçoit un hash de celui-ci.
  • Elle transfère les bitcoins vers un contrat intelligent, de lequel Bob peut récupérer l'argent en présentant le secret (le hash doit correspondre à celui spécifié dans le contrat).
  • Si Bob ne vient pas chercher ses bitcoins d'ici ce soir, Alice peut les récupérer.

Étape deux : l’appât

Bob entre en jeu et transfère des "cryptoeuros" vers son contrat, qui est rédigé de manière à ce que :

  • Alice puisse récupérer les "cryptoyens" en présentant un chiffre secret.
  • Pas avant midi, si Alice ne se manifeste pas, Bob pourra récupérer son dépôt.

Étape trois : la solution dans l’appât.

Alice vient chercher son argent et prend l'argent du contrat de Bob, révélant ainsi son secret.

Dernière étape : le mystère est résolu

Bob observe la transaction et déchiffre le secret présenté par Alice au contrat. Il utilise ce secret pour récupérer ses propres bitcoins.

Quand quelque chose ne va pas

Si jamais Alice se trouve être mortelle, Bob récupère ses yuans à midi.

Pour sa part, Alice rend le bitcoin le soir si le traître Bob décide de garder l'argent jusqu'à de meilleurs temps.

Si vous préférez une image au texte, il existe une explication plus détaillée et visuelle sur Habr. explication du fonctionnement des swaps atomiques.

La différence entre les délais est conçue pour nous protéger d'Alice malveillante, qui prend l'argent de Bob à la dernière minute, alors que celui-ci saisit un hex dans la transaction avec des doigts tremblants.

Les participants ne peuvent pas perdre leur argent, au pire, il faudra attendre le retour.

Support dans les blockchainsC'est un schéma aussi simple qu'une chaussette, qui exige des blockchains en interaction le strict minimum :

  • Support des contrats intelligents avec au moins une bifurcation
  • Les deux blockchains doivent supporter les mêmes algorithmes de hachage (n'oubliez pas de vérifier la longueur du secret)
  • Verrous temporels.

À première vue, on pourrait dire au marché "au revoir, notre rencontre était une erreur", mais pas si vite.

Malgré tous ses avantages, les solutions de swap atomique ne brillent pas par leur liquidité. En grande partie parce que, dans la paire BTC-USD, la partie fiat n'était pas complètement tokenisée.
Le succès de l'USDT a engendré toute une vague de stablecoins au format ERC20 pour tous les goûts, de l'USDC le plus conservateur au DAI le plus algorithmique.

Pour simplifier, considérons que Alice vend des bitcoins à Bob contre des tokens ERC20, en espérant la chance des stabilisateurs, étant donné qu'il nous reste encore beaucoup de problèmes techniques.

Vitesse

Le bitcoin et Ethereum, séparément, ne sont pas particulièrement rapides, et ici nous devons d'abord attendre un dépôt avec toutes les confirmations, puis le second.

Tout cela parce qu'au départ l'argent est déposé par le participant qui connaît le secret, tandis que l'adversaire attend la finalité et ne transfère sa part que par la suite.

De plus, nous avons affaire à un actif très volatile, donc au cours de cette période, le taux peut changer considérablement, et il est déjà difficile de modifier les conditions.

Confidentialité

Tout échange laisse des artefacts sur les deux blockchains. Un observateur attentif peut remarquer des hachages identiques dans les contrats intelligents et en déduire logiquement qu'une transaction a eu lieu, ce qui peut entraîner de nombreuses conclusions allant des taux aux questions fiscales.

Lorsque la bourse connaît vos affaires, c'est très désagréable ; lorsque chacun est au courant, c'est désagréable au double.

Utilisabilité

C'est le cœur même de la blockchain, en particulier d'Ethereum. Voyons quels mouvements devront être réalisés par le vendeur et l'acheteur.

Du point de vue du vendeur, c'est relativement simple : il suffit de transférer des bitcoins à une adresse p2sh. Avec Ethereum, c'est beaucoup plus compliqué.

ContratExaminons un contrat moyen pour un swap sur GitHub :

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 is 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);
    }
}

Attention ! N'utilisez pas ce contrat et d'autres contrats de cet article en production, ils sont uniquement conçus à des fins de démonstration. Surtout celui-ci.

  • Bob doit appeler la méthode du contrat token approve, donnant au contrat de swap accès à ses tokens
  • Bob crée un swap et un contrat à l'aide de la méthode transferFrom qui récupère les tokens de l'expéditeur sur son adresse
  • Alice dans withdraw révèle le secret et appelle le contrat transfert

La plupart des portefeuilles et des échanges de crypto-monnaies ne prennent pas en charge approve les tokens, et ce n’est pas sans raison.

Les utilisateurs eux-mêmes font souvent des erreurs et envoient simplement des tokens au contrat, après quoi les tokens se perdent. Les commentaires sur Etherscan sont pleins des lamentations des malheureux.

Et pour appeler le contrat, il faut payer des frais en ETH, ce qui signifie que les deux participants doivent en avoir en réserve avant le début de l'opération, ce que peu de gens souhaitent faire.

Gazholder

Pour commencer, il est conseillé de supprimer la vérification de l'expéditeur partout où cela est possible, et de supposer que nous avons quelqu'un souffrant d'un surplus de gaz et appelant des contrats pour tous ceux qui le souhaitent.

Contrat modernisé

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);
    }
}

Dualisme contrat-clé et EIP 712

Comme nous le savons, une adresse sur Ethereum peut être un contrat ou un sujet, c'est-à-dire une clé.
L'activité principale de la clé est de signer des messages.

Nous pouvons utiliser comme expéditeur le contrat Bob, qui exécute tous les passes nécessaires, ayant d'abord vérifié la signature de la clé Bob.

Maintenant, quiconque peut sponsoriser les frais d'un participant, mais seule la personne qui connaît la clé prend la décision.

Contrat 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);
    }
}

Pour travailler avec les signatures de structures de données complexes dans Ethereum, il existe une norme EIP 712, vous pouvez en lire plus dans le blog du portefeuille Metamask

Diviser pour mieux régner

Souvent, le scénario de piratage d'un contrat Ethereum ressemble à ceci :

  • Un participant dépose des fonds dans le contrat
  • Puis retire des fonds
  • Quelque chose ne va pas
  • L'attaquant récupère l'argent encore et encore

Si nous revenons à notre premier exemple, quelque chose ne va pas si l'énigme est un ensemble vide d'octets.

Comment voler un millionCréons un swap avec un hachage 0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925
C'est le sha256 de 0x0000000000000000000000000000000000000000000000000000000000000000
Nous transférons le secret et récupérons nos jetons
Nous transférons encore une fois et récupérons les autres, tout ça parce que 0 = 0

En créant un contrat distinct pour chaque transaction, nous pouvons isoler les contrats au niveau de l'EVM.

Mais ce n'est pas tout : maintenant chaque transaction a sa propre adresse à laquelle des jetons peuvent être transférés depuis n'importe quel portefeuille ou échange.

Contrats abandonnés et create2

Mais maintenant, pour chaque transaction, nous devons créer un contrat et attendre que l'acheteur y transfère son "crypto-argent". Dans le schéma "les contrats le matin, l'argent le soir", il y a toujours un risque que l'acheteur se retire, et l'Ether pour créer le contrat a déjà été dépensé.

Ne pourrait-on pas faire en sorte que l'argent soit le matin et les octets le soir ?

Dans le hard fork Constantinople, les développeurs EIP 1014 ont ajouté l'instruction create2, qui crée un nouveau contrat à une adresse déterminée

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

Où

  • address — l'adresse du contrat de la fabrique
  • salt — un nombre quelconque, dont nous découvrirons le sens dans le prochain épisode
  • init_code — le code octal du contrat et les paramètres du constructeur.

FabriqueL'instruction ne fonctionne que via l'assembly, donc la fabrique a un aspect quelque peu intimidant :

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);
  }
}

Le code de votre contrat peut être obtenu à l'aide de web3 :

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

En raison d'un support limité dans solidity, le gaz pour le contrat peut être mal calculé en raison de certaines subtilités d'Ether.

Particulièrement charmant, c'est qu'en cas de manque de gaz, le contrat échoue avec une erreur interne, sans indiquer que le gaz était insuffisant, comme on pourrait s'y attendre.

Nous pouvons désormais transférer des jetons sur des contrats sans les créer au préalable, et tant que nous ne les publions pas sur le réseau, personne ne devinera ce que fait effectivement le contrat.

Un corbeau ne crève pas l'œil d'un autre corbeau.

Il est évident qu'un véritable analyste, surtout s'il a reçu de bons investissements pour lutter contre les ennemis du régime par le biais du blanchiment d'argent, ne sera pas arrêté par de telles ruses enfantines, et après la création du contrat, il verra tout de même le hachage.

Comment faire pour que le hachage ne soit pas exposé ?

Nous déplaçons l'échange hors chaîne : les participants échangent des signatures pour le transfert sur le contrat d'échange, puis le secret est révélé de manière privée.

Pas à pas.Deux 'multi-signatures' sont créées, à partir desquelles les fonds peuvent être récupérés avec les signatures d'Alice et de Bob.

Pour que le départ hors ligne de l'un des participants ne soit pas une tragédie, ajoutons un bon vieux timeout.

Alice et Bob effectuent des dépôts en parallèle.

  • Alice choisit un secret et transmet à Bob le hachage du secret ainsi que la signature de la transaction qui transfère des bitcoins à l'adresse de l'échange.
  • Bob transmet à Alice la signature pour le retrait de jetons sur le contrat d'échange avec le hachage choisi.
  • Alice révèle à Bob le secret.

À ce moment, l'harmonie se fait sentir : Alice et Bob peuvent à tout moment finaliser l'accord. Dans une atmosphère aussi amicale, ils peuvent échanger des signatures pour retirer des fonds vers les adresses finales.

Pour un observateur extérieur, cela semble comme si l'argent était passé par un contrat avec une multi-signature 2 sur 2.

De plus, ce schéma permet aux deux parties d'effectuer un dépôt en même temps, car le secret est choisi après toutes les confirmations.

Niveau 2.

Puisque nous pouvons retirer de l'argent sur une seule adresse sans publier de transaction intermédiaire, rien ne nous empêche de retirer de l'argent sur plusieurs adresses et de réaliser un nombre illimité de transactions intermédiaires. Ce n'est pas un ensemble nécessaire pour un échange, mais une fois que vous avez commencé à assembler un échange, il est difficile de s'arrêter.

Maintenant, Alice et Bob pourront s'épanouir. Par exemple, calculer automatiquement le prix moyen, en échangeant par satoshi par seconde, ou simplement relier directement un market maker et un récipiendaire de liquidités.

Pas à pas.

  • Le vendeur choisit un secret et remet à l'acheteur le hachage du secret ainsi que la signature de la transaction où une partie des fonds est transférée à l'adresse p2sh de l'échange, tandis que le reste est renvoyé à l'adresse du vendeur.
  • L'acheteur transmet une signature permettant de retirer les tokens en swap et le reste à l'adresse du destinataire.
  • Le vendeur révèle un secret
  • L'histoire se répète avec un nouveau secret, tandis qu'au swap et au reste s'ajoute également le retrait de ce qui a été acheté précédemment à l'adresse de l'acheteur et déjà payé à l'adresse du vendeur.

Nous avons désormais accès à un trading p2p à haute vitesse, il est essentiel de surveiller le temps et de conclure l'affaire avant le délai d'expiration.

Cependant, en modifiant légèrement nos contrats, nous pouvons offrir à nos canaux l'immortalité, ce qui simplifiera considérablement la création de notre réseau.

Mais nous en reparlerons dans le prochain épisode.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster