Splendore e miseria degli atomic swaps

Cosa c'è di sbagliato negli swap atomici e come i canali possono aiutarli, cosa è successo di importante nell'hard fork Constantinople e come comportarsi quando non si ha nulla da pagare per il gas.

La principale motivazione di qualsiasi esperto di sicurezza è il desiderio di evitare responsabilità.

La provvidenza è stata benevola, ho lasciato l'ICO senza aspettare la prima transazione irreversibile, ma ben presto mi sono ritrovato a sviluppare un exchange di criptovalute.

Non sono affatto un Mal'chish Kibal'chish, e uno sguardo severo è sufficiente perché io consegni tutte le chiavi e le password. Pertanto, il mio obiettivo principale come architetto era posizionare la punta ardente dell'analisi crittografica il più lontano possibile dagli elementi della mia infrastruttura a cui tengo.

Non sono le tue chiavi, non sono i tuoi problemi.

Stiamo costruendo un sistema di scambio di asset e vogliamo escludere il deposito intermedio di questi asset presso di noi, ma dobbiamo garantire la sicurezza della transazione.

Possiamo fungere da arbitri in una situazione controversa e condurre transazioni con portafogli che richiedono due delle tre firme: quella dell'acquirente, del venditore e dell'escrow.

Tuttavia, se un partecipante attacca con successo l'escrow, ottiene le due firme desiderate.

Lo swap atomico è uno schema di scambio in cui un contratto intelligente funge da garante, che consente solo un comportamento onesto.

Come nel rompicapo del lupo, della capra e del cavolo, puoi agire solo secondo l'unico scenario giusto e subisci perdite se ti discosti da esso.

Solo invece di animali ingordi, l'ordine è garantito da una funzione hash, così difficile da collisionare che non vale nemmeno la pena iniziare.

Passo primo: rompicapo.

Supponiamo che Alice un bel mattino voglia trasferire a Bob un bitcoin in cambio di una manciata di 'cripto-yuan'.

  • Lei si pone un grande segreto.
  • Riceve da lui un hash.
  • Trasferisce i bitcoin su un contratto intelligente, da cui Bob può prelevare i fondi mostrando il segreto (l'hash deve corrispondere a quello indicato nel contratto).
  • Nel caso in cui Bob non si presenti a reclamare i suoi bitcoin entro sera, Alice può riprenderli indietro.

Passo secondo: esca.

Entra in gioco Bob e trasferisce 'cripto-euro' sul suo contratto, redatto in modo tale che:

  • Alice può riprendere 'cripto-yen' presentando un numero segreto.
  • Solo dopo pranzo Bob, se Alice non si presenta, può restituire il deposito.

Passo terzo: indovinare l'esca.

Alice arriva per i suoi soldi e ritira il denaro dal contratto di Bob, rivelando così il suo segreto.

Passo finale: l'enigma è risolto

La transazione viene vista da Bob, che con occhio da aquila ne estrae il segreto presentato da Alice nel contratto. Questo segreto lo utilizza per ritirare già i suoi bitcoin.

Quando qualcosa va storto

Se Alice si trovasse improvvisamente a essere mortale, Bob ritira i suoi yuan a pranzo.

A sua volta, Alice restituisce il bitcoin entro sera, se il perfido Bob decide di trattenere il denaro fino a tempi migliori.

Se preferite un'immagine al testo, su Habr c'è una spiegazione più dettagliata e visiva del funzionamento degli swap atomici.

La differenza nei timeout è progettata per proteggerci da Alice malefica, che ritira i soldi di Bob all'ultimo momento, mentre il timeout scade mentre lui digita tremando l'hex nella transazione.

I partecipanti non possono perdere il proprio denaro, al massimo dovranno attendere il ritorno.

Supporto nei blockchainÈ uno schema semplice come una ciabatta, che richiede dai blockchain interagenti solo il minimo:

  • Supporto per contratti intelligenti con almeno una diramazione
  • Entrambi i blockchain devono supportare gli stessi algoritmi di hashing (non dimenticate di controllare la lunghezza del segreto)
  • Time-lock.

A prima vista, si potrebbe già dire alla borsa 'addio, il nostro incontro è stato un errore', ma non è così semplice.

Nonostante i suoi meriti, le soluzioni basate su atomic swap non brillano per liquidità. In gran parte perché nella coppia BTC-USD più popolare, la parte fiat non era completamente tokenizzata.
Il successo di USDT ha generato una vera ondata di stablecoin in formato ERC20 per tutti i gusti, dal più custodian USDC al più algoritmico DAI.

Pertanto, per semplicità, discutiamo di come Alice venda bitcoin a Bob in cambio di alcuni token ERC20, e speriamo nella fortuna dei stabilizzatori, dato che abbiamo ancora molti problemi tecnici da affrontare.

Velocità

Bitcoin ed Ethereum non sono molto veloci da soli, e qui dobbiamo prima aspettare un deposito con tutte le conferme, poi il secondo.

Tutto ciò è perché prima il denaro viene versato dal partecipante che conosce il segreto, mentre l'avversario attende la finalità e solo allora trasferisce la sua parte.

Inoltre, stiamo trattando un attivo estremamente volatile, quindi in questo lasso di tempo il tasso può variare in modo significativo, e cambiare le condizioni non è facile.

Riservatezza

Qualsiasi scambio lascia artefatti su entrambe le blockchain. Un osservatore attento può notare hash identici nei contratti smart e trarre una conclusione logica, secondo cui qui è avvenuta una transazione, da cui derivare molte deduzioni, dai tassi fiscali a quelli di cambio.

Quando l’exchange è a conoscenza delle tue operazioni, è estremamente sgradevole; quando lo sa chiunque, è doppiamente sgradevole.

Usabilità

Il punto di forza della blockchain in generale e di Ethereum in particolare. Vediamo quali azioni deve compiere il venditore e l'acquirente.

Dal punto di vista del venditore, tutto è relativamente semplice: deve semplicemente trasferire Bitcoin a un indirizzo p2sh. Con Ethereum è molto più complicato.

ContrattoConsideriamo un contratto medio su GitHub per lo scambio:

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); 
        require(token.transferFrom(msg.sender, address(this), amount)); 
        swaps[msg.sender][receiver] = Swap(token, hash, amount, refundTime, 0x00);
    }
    
    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))); 
        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);
    }
}

Attenzione! Non utilizzare questo e altri contratti dell'articolo in produzione, sono scritti esclusivamente per scopi dimostrativi. Soprattutto questo.

  • Bob deve chiamare il metodo del contratto del token approve, dando al contratto dello swap accesso ai suoi token
  • Bob crea lo swap e il contratto utilizzando il metodo transferFrom ritira sul proprio indirizzo i token del mittente
  • Alice in withdraw rivela il segreto e chiama il contratto trasferire

La maggior parte dei portafogli e delle criptovalute non supportano approve token, e non a caso.

Gli utenti stessi spesso sbagliano e semplicemente trasferiscono i token al contratto, dopo di che i token si perdono. I commenti su Etherscan sono pieni di lamenti di sfortunati.

E per chiamare il contratto, è necessario pagare una commissione in ETH, quindi entrambe le parti devono procurarsi ETH prima di iniziare la transazione, cosa a cui pochi vogliono dedicarsi.

Gasholder

Per iniziare, è opportuno rimuovere il controllo del mittente ovunque sia possibile e presupporre che abbiamo qualcuno che soffre di un eccesso di gas e che chiama contratti per tutti i desiderosi.

Contratto modernizzato

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); // usa l'hash una sola volta
        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 contrattuale e chiave e EIP 712

Come sappiamo, un indirizzo su Ethereum può essere un contratto o un soggetto, cioè una chiave.
L'attività principale della chiave è firmare alcuni messaggi.

Possiamo utilizzare come mittente il contratto Bob, che effettua tutte le operazioni necessarie, verificando prima la firma della chiave Bob.

Ora, chiunque può sponsorizzare la commissione del partecipante, ma la decisione viene presa solo da chi conosce la chiave.

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

Per lavorare con le firme di strutture dati complesse in Ethereum esiste uno standard EIP 712, puoi leggere di più su di lui nel blog del portafoglio Metamask

Dividi e conquista

Spesso lo schema di hacking di un contratto Ethereum appare così:

  • Un partecipante deposita fondi nel contratto
  • Poi ritira i fondi
  • Qualcosa va storto
  • L'attaccante ritira i soldi ancora e ancora

Se torniamo al nostro primo esempio, qualcosa va storto se l'enigma è un insieme di byte vuoto.

Come rubare un milioneCreiamo uno swap con l'hash 0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925
Questo è sha256 di 0x0000000000000000000000000000000000000000000000000000000000000000
Trasferiamo il segreto e ritiriamo i nostri token
Trasferiamo di nuovo e ritiriamo quelli degli altri, tutto perché 0 = 0

Creando un contratto separato per ogni transazione, possiamo isolare i contratti a livello EVM.

Ma non è tutto: ora ogni transazione ha il proprio indirizzo, su cui possono essere trasferiti token da qualsiasi portafoglio o scambio.

Contratti abbandonati e create2

Ma ora per ogni transazione dobbiamo creare un contratto e aspettare che l'acquirente trasferisca il "lavoro di crypto-veneer" lì. Nel schema "contratti al mattino, soldi alla sera" c'è sempre il rischio che l'acquirente si disimpegni, mentre l'ethereum per la creazione del contratto è già stato speso.

Non si può fare in modo che al mattino ci siano soldi, e alla sera byte?

Nella hard fork Constantinople, gli sviluppatori EIP 1014 hanno aggiunto l'istruzione create2, che crea un nuovo contratto a un indirizzo deterministico

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

Dove

  • address — l'indirizzo del contratto fabbrica
  • salt — un numero, il cui significato scopriremo nella prossima serie
  • init_code — codice byte del contratto e parametri del costruttore.

FabbricaL'istruzione funziona solo tramite assembly, quindi la fabbrica appare piuttosto minacciosa:

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

Il codice del tuo contratto può essere ottenuto tramite 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);

A causa del supporto limitato in solidity, il gas per il contratto potrebbe essere calcolato in modo errato a causa di alcune sottigliezze dell'ethereum.

Particolarmente gradevole è che in caso di insufficienza di gas, il contratto fallisce con un errore interno, senza comunicare che il gas è finito, come ci si potrebbe aspettare.

Ora possiamo trasferire token su contratti senza crearli in anticipo e finché non li pubblichiamo in rete, nessuno potrà indovinare cosa faccia il contratto.

Il corvo non scaverà l'occhio all'altro corvo

È chiaro che un vero analista, specialmente uno che ha ricevuto buoni investimenti per combattere i nemici del regime tramite riciclaggio di denaro, tali trucchi infantili non lo fermeranno, e dopo la creazione del contratto vedrà comunque l'hash.

Come fare in modo che l'hash non venga svelato?

Il swap viene spostato off-chain: i partecipanti scambiano firme per il trasferimento al contratto di swap, e poi il segreto viene rivelato privatamente.

Passo dopo passoVengono creati due "multisig" da cui è possibile prelevare fondi con le firme di Alice e Bob.

Per evitare che l'uscita offline di uno dei partecipanti diventi una tragedia, aggiungiamo un vecchio buon timeout.

Alice e Bob depositano contemporaneamente

  • Alice pensa a un segreto e passa a Bob l'hash del segreto e la firma della transazione che trasferisce i bitcoin all'indirizzo dello swap.
  • Bob passa ad Alice la firma per il prelievo dei token sul contratto di swap con l'hash prestabilito.
  • Alice comunica a Bob il segreto.

In quel momento scatta l'armonia: sia Alice che Bob possono chiudere l'affare in qualsiasi momento. In un'atmosfera così amichevole, possono scambiarsi firme per il prelievo dei soldi sugli indirizzi finali.

Per un osservatore esterno, sembra che i soldi siano passati attraverso un contratto con firma multipla 2 su 2.

Inoltre, questo schema consente a entrambe le parti di effettuare il deposito contemporaneamente, poiché il segreto viene pensato solo dopo tutte le conferme.

Livello 2

Poiché possiamo prelevare denaro su un singolo indirizzo e non pubblicare la transazione intermedia, non c'è nulla che ci impedisca di prelevare denaro su più indirizzi e di effettuare un numero illimitato di transazioni intermedie. Non che sia un set necessario per lo scambio, ma se hai iniziato a raccogliere swap, è difficile fermarsi.

Ora Alice e Bob possono dare il meglio di sé. Ad esempio, possono calcolare automaticamente il prezzo medio, scambiando a satoshi al secondo, o semplicemente collegare direttamente il market maker e il destinatario della liquidità.

Passo dopo passo

  • Il venditore pensa a un segreto e dà all'acquirente l'hash del segreto e la firma della transazione in cui parte dei fondi viene trasferita all'indirizzo p2sh dello swap e il resto torna all'indirizzo del venditore.
  • Il compratore fornisce una firma che consente di trasferire i token e il resto all'indirizzo del destinatario.
  • Il venditore rivela un segreto
  • La storia si ripete con un nuovo segreto, mentre al swap e al resto si aggiunge anche il trasferimento di quanto precedentemente acquistato all'indirizzo del compratore e già pagato all'indirizzo del venditore.

Ora possiamo accedere al trading p2p ad alta velocità, è importante tenere d'occhio il tempo e chiudere l'affare prima della scadenza.

Tuttavia, modificando leggermente i nostri contratti, possiamo regalare immortalità ai nostri canali, semplificando notevolmente la creazione della rete.

Ma di questo parleremo nel prossimo episodio.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster