Luce e miseria degli atomic swaps

Quali sono i problemi degli atomic swaps e come i canali possono aiutare, cosa è successo di importante nel fork Constantinople e come comportarsi quando non hai abbastanza gas da pagare.

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

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

Io— decisamente non sono il ragazzino Kibalchish, e un solo sguardo severo è sufficiente perché io consegni tutte le chiavi e le password. Pertanto, il mio obiettivo principale come architetto era posizionare il pungiglione infuocato della criptoanalisi il più lontano possibile dagli elementi infrastrutturali a me cari.

Non le tue chiavi, non i tuoi problemi

Stiamo costruendo un sistema di scambio di asset e vogliamo escludere l'archiviazione intermedia di questi asset, ma dobbiamo garantire la sicurezza della transazione.

Possiamo fungere da arbitri in situazioni di contenzioso e condurre transazioni con portafogli che richiedono due delle tre firme: 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 smart funge da garante, consentendo solo comportamenti onesti.

Proprio come nel dilemma del lupo, della capra e del cavolo, puoi agire solo secondo uno scenario corretto e subire perdite se ti discosti da esso.

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

Passo primo: l'enigma

Immagina che Alice una bella mattina voglia inviare a Bob del bitcoin in cambio di un pugno di "criptovalute".

  • Lei escogita un grande segreto
  • Riceve da lui un hash
  • Trasferisce i bitcoin a un contratto smart, da cui Bob può prelevare i fondi presentando il segreto (l'hash deve corrispondere a quello specificato nel contratto)
  • Se Bob non si presenta a ritirare i suoi bitcoin entro sera, Alice può riprenderli.

Passo secondo: l'esca

Bob entra in gioco e trasferisce "criptoeuro" al suo contratto, che è scritto in modo tale che:

  • Alice può ritirare i "criptoyen" presentando un numero segreto.
  • Non prima di mezzogiorno Bob, in assenza di Alice, può riavere il deposito.

Passaggio tre: la chiave è nell'esca

Alice viene a prendere i suoi soldi e ritira il denaro dal contratto di Bob, rivelando nel frattempo il suo segreto.

Passaggio finale: il mistero è risolto

Bob vede la transazione e con uno sguardo acuto estrae il segreto presentato da Alice al contratto. Usa questo segreto per riappropriarsi dei suoi Bitcoin.

Quando qualcosa va storto

Se all'improvviso Alice si rivela mortale, Bob ritira i suoi yuan a pranzo.

A sua volta, Alice restituisce Bitcoin entro sera, se il traditore Bob decide di trattenere i soldi fino a tempi migliori.

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

La differenza tra i timeout serve a proteggerci da una malintenzionata Alice, che preleva i soldi di Bob all'ultimo momento, mentre il timeout scade mentre lui digita tremando l'hex nella transazione.

I partecipanti non possono perdere i propri soldi, al massimo dovranno aspettare il rimborso.

Supporto nei blockchainQuesto è uno schema semplice, che richiede ai blockchain interagenti davvero poco:

  • Supporto dei contratti smart con almeno un ramo.
  • Entrambi i blockchain devono supportare gli stessi algoritmi di hash (non dimenticate di controllare la lunghezza del segreto).
  • Time lock.

A prima vista, si potrebbe dire 'addio all'exchange, il nostro incontro è stato un errore', ma non è così semplice.

Nonostante tutti i suoi vantaggi, le soluzioni basate su atomic swap non impressionano per liquidità. In gran parte perché nella coppia più popolare BTC-USD, la parte fiat non è stata completamente tokenizzata.
Il successo di USDT ha generato una vera e propria ondata di stablecoin in formato ERC20 per tutti i gusti, dall'iper-custodiale USDC all'iper-algoritmico DAI.

Per semplicità, discuteremo d'ora in poi di come Alice venda bitcoin a Bob in cambio di alcuni token ERC20, e speriamo nella buona sorte dei stabilizzatori, considerando che abbiamo ancora molti problemi tecnici da affrontare.

Velocità

Bitcoin ed Ethereum, anche separatamente, non sono troppo veloci, e qui dobbiamo aspettare prima un deposito con tutte le conferme, poi il secondo.

Questo avviene perché prima di tutto il denaro viene depositato dall partecipante che conosce il segreto, mentre l'avversario aspetta la finalizzazione e solo dopo trasferisce la propria parte.

Inoltre, stiamo trattando un asset piuttosto volatile, quindi in questo periodo il tasso potrebbe cambiare notevolmente, rendendo difficile modificare le condizioni.

Riservatezza

Qualsiasi scambio lascia artefatti su entrambi i blockchain. Un osservatore attento può notare hash identici nei contratti intelligenti e trarre una conclusione logica che qui si è svolto uno scambio, da cui possono derivare molte deduzioni, dalle variazioni dei tassi fino a quelle fiscali.

Quando le tue attività sono conosciute da uno scambio, è estremamente sgradevole; quando è noto a tutti, è doppiamente sgradevole.

Usabilità

Questo è il punto forte della blockchain in generale e di Ethereum in particolare. Vediamo quali passaggi dovrà compiere il venditore e l'acquirente.

Dal punto di vista del venditore, è tutto relativamente semplice: deve semplicemente trasferire Bitcoin su un indirizzo p2sh. Con Ethereum, invece, le cose sono molto più complicate.

ContrattoEsaminiamo un contratto medio trovato su GitHub per uno swap:

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

contratto Swapper {

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

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

    funzione create(iERC20 token, bytes32 hash, indirizzo receiver, uint amount, uint refundTime) public {
        require(swaps[msg.sender][receiver].amount == 0); // verifica se lo swap con l'hash dato esiste già
        require(token.transferFrom(msg.sender, address(this), amount)); // trasferisci token bloccati al contratto swap
        swaps[msg.sender][receiver] = Swap(token, hash, amount, refundTime, 0x00); // crea lo swap
    }
    
    funzione hashOf(bytes32 secret) public pure returns(bytes32) {
        return sha256(abi.encodePacked(secret));
    }


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

    funzione refund(indirizzo 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 nell'ambiente di produzione, sono scritti esclusivamente per scopi dimostrativi. In particolare questo.

  • Bob deve chiamare il metodo del contratto del token approvare, dando al contratto dello swap accesso ai propri token
  • Bob crea uno swap e un contratto utilizzando il metodo transferFrom preleva sul proprio indirizzo i token dell'inviatario
  • Alice in withdraw rivela un segreto e il contratto chiama transfer

La maggior parte dei portafogli e delle criptoborse non supportano approvare token, e non a caso.

Gli utenti stessi spesso commettono errori e semplicemente trasferiscono i token al contratto, dopo di che i token semplicemente scompaiono. I commenti su Etherscan sono pieni di lamenti dei disgraziati.

E per chiamare un contratto, è necessario pagare una commissione in ETH, il che significa che entrambi i partecipanti devono essere forniti di esso prima dell'inizio dell'affare, cosa che a pochi interessa.

GasHolder

Per prima cosa, è consigliabile rimuovere il controllo del mittente ovunque possibile e presumere che abbiamo qualcuno che soffre per eccesso di gas che chiama i contratti per tutti coloro che lo desiderano.

Contratto modernizzato

contratto 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 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 EIP 712

Come sappiamo, un indirizzo su Ethereum può essere un contratto o un soggetto, ossia una chiave.
Il compito principale della chiave è firmare qualche messaggio.

Possiamo utilizzare come mittente un contratto Bob, che compie tutte le operazioni necessarie, dopo aver verificato la firma della chiave di Bob.

Ora, chiunque può sponsorizzare la commissione di un partecipante, ma solo colui che conosce la chiave prende la decisione.

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 esso nel blog del wallet Metamask

Dividi e conquista

Spesso lo scenario di un attacco a un contratto Ethereum appare in questo modo:

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

Se torniamo al nostro primo esempio, qualcosa va storto se il mistero è un insieme vuoto di byte.

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

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

Ma non è tutto: ora ogni transazione ha il proprio indirizzo, su cui è possibile trasferire token da qualsiasi wallet o exchange.

Contratti abbandonati e create2

Ma ora per ogni transazione dobbiamo creare un contratto e aspettare che l'acquirente trasferisca lì il suo 'crypto-funding'. Nello schema 'contratti al mattino, soldi alla sera' c'è sempre il rischio che l'acquirente si ritiri, mentre il gas per creare il contratto è già stato speso.

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

Nell'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 factory
  • salt — un numero, il significato del quale scopriremo nel prossimo episodio
  • init_code — il bytecode del contratto e i parametri del costruttore.

FactoryL'istruzione funziona solo tramite assembly, quindi la factory appare un po' intimidatoria:

contratto Factory {
  evento Deployed(address addr, uint256 salt);

  funzione create2(bytes memory code, uint256 salt) pubblica {
    address addr;
    assembly {

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

    emit Deployed(addr, salt);
  }
}

Il codice del tuo contratto può essere ottenuto utilizzando 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 peculiarità di Ether.

È particolarmente sconcertante che, in caso di esaurimento del gas, il contratto fallisca con un errore interno, senza segnalare che il gas è esaurito, come ci si potrebbe aspettare.

Ora possiamo trasferire token sui contratti senza crearli in anticipo e finché non vengono pubblicati sulla rete, nessuno potrà indovinare cosa fa realmente il contratto.

Il corvo non scava l'occhio al corvo

È evidente che un vero analista, specialmente uno che ha ricevuto buoni investimenti per combattere i nemici del regime attraverso il riciclaggio di denaro, non sarà fermato da questi trucci infantili, e dopo la creazione del contratto vedrà comunque l'hash.

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

Il swap viene trasferito off-chain: i partecipanti scambiano firme per la conversione nel contratto swap, dopodiché il segreto viene rivelato privatamente.

Passo dopo passoVengono creati due "multisig", da cui si possono prelevare i fondi con le firme di Alice e Bob.

Per evitare che l'allontanamento di uno dei partecipanti diventi una tragedia, aggiungiamo un vecchio e caro timeout.

Alice e Bob effettuano simultaneamente i depositi.

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

In questo momento si raggiunge l'armonia: sia Alice che Bob possono concludere l'affare in qualsiasi momento. In un ambiente così amichevole possono scambiarsi le firme per prelevare i fondi agli indirizzi finali.

Per un osservatore esterno, sembra come se il denaro fosse passato attraverso un contratto con firma multipla 2 su 2.

Inoltre, questo schema consente a entrambe le parti di fare un deposito simultaneamente, poiché il segreto viene concepito solo dopo tutte le conferme.

Livello 2

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

Ora Alice e Bob possono finalmente sbizzarrirsi. Ad esempio, possono calcolare automaticamente il prezzo medio, scambiando per satoshi al secondo, oppure possono semplicemente connettere direttamente il market maker e il ricevente della liquidità.

Passo dopo passo

  • Il venditore sceglie un segreto e fornisce al compratore l'hash del segreto e la firma della transazione in cui una parte dei fondi viene trasferita su un indirizzo p2sh dello swap, mentre il resto ritorna all'indirizzo del venditore.
  • L'acquirente trasmette la firma che consente di prelevare i token dallo swap e il resto all'indirizzo del ricevente.
  • Il venditore rivela il segreto
  • La storia si ripete con un nuovo segreto, aggiungendo allo swap e al resto anche il prelievo di quanto acquistato in precedenza all'indirizzo dell'acquirente e già pagato all'indirizzo del venditore.

Ora abbiamo accesso a un trading p2p ad alta velocità, l'importante è tenere d'occhio il tempo e chiudere l'affare prima del timeout.

Tuttavia, apportando alcune modifiche ai nostri contratti, possiamo garantire immortalità ai nostri canali, il che semplificherà notevolmente la creazione della rete.

Ma di questo parleremo nella prossima serie.

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