Блясък и бедност на атомните свопове

Какво е лошото в атомните свопове и как каналите могат да помогнат, какво важно се случи в хардфорка Constantinople и какво да правим, когато нямаме с какво да платим за газа.

Основната мотивация на всеки специалист по безопасност е желанието да избегне отговорността.

Провидението беше благосклонно, напуснах ICO, без да изчакам първата необратима транзакция, но скоро се озовах в разработка на криптоборса.

Аз съм категорично не Момчето Кибальчиш, и един строг поглед е напълно достатъчен, за да дам всички ключове и пароли. Затова основната ми цел като архитект беше да разположа горещата стрела на криптоанализа колкото се може по-далече от скъпите ми инспирационни елементи.

Не твоите ключове, не твоите проблеми

Строим система за обмен на активи и искаме да изключим междинното съхранение на тези активи при нас, но трябва да гарантираме сигурността на сделката.

Може да действате като съдия в спорна ситуация и да извършвате сделки с портфейли, изискващи два от трите подписа: на купувача, продавача и ескроу.

Но, ако участникът успешно атакува ескроу, той получава исканите два подписа.

Атомен своп е схема на обмен, при която гарантът е смарт контракт, който допуска само честно поведение.

Както в загадката за вълка, козата и капустата, можете да действате само по един правилен сценарий и понесете загуби, ако отстъпите от него.

Само че вместо лакомите животни, реда осигурява хеш-функция, при която е толкова трудно да се намери колизия, че дори не си заслужава да започваш.

Стъпка първа: загадката

Нека предположим, че Алиса една страхотна утрин иска да предаде биткойни на Боб срещу шепа "криптоюани".

  • Тя загадва някаква голяма тайна
  • Получава от него хеш
  • Превежда биткойните на смарт контракт, от който парите може да вземе Боб, представяйки тайната (хеш на нея трябва да съвпада с указаното в договора)
  • В случай, че Боб не се появи за биткойните си вечерта, Алиса може да ги вземе обратно.

Стъпка втора: примамката

В играта влиза Боб и превежда "криптоевро" на своя контракт, който е написан по такъв начин, че:

  • Алиса може да вземе "криптоюаните", представяйки секретно число
  • Не по-рано от обяд Боб, при отсъствие на Алиса, може да върне депозита

Стъпка трета: отговорът в примамката

Алисата идва за своите пари и взима парите от договора на Боб, разкривайки в същото време своята тайна.

Завършителна стъпка: загадката е разгадана

Транзакцията вижда Боб и с орлино око извежда от нея тайната, представена от Алисата на договора. Тази тайна той използва, за да вземе вече своите биткойни.

Когато нещо върви на зле

Ако Алисата изведнъж се окаже смъртна, Боб обядва и взима своите юани.

От своя страна, Алисата вечерта връща биткойна, ако вероломният Боб реши да задържи парите до по-добри времена.

Ако предпочитате изображение пред текст, на Хабра има за вас по-подробно и визуално обяснение за работата на атомните свопове.

Разликата между таймаутите е предназначена да ни застрахова от злонамерената Алиса, която взима парите на Боб в най-късния момент, и таймаутът изтича, докато той с треперещи пръсти въвежда hex в транзакцията.

Участниците не могат да загубят парите си, максимум ще трябва да изчакат възстановяването.

Поддръжка в блокчейнитеТова е проста схема, която изисква от взаимодействалните блокчейни единствено:

  • Поддръжка на смарт договори с поне едно разклонение
  • И двата блокчейна трябва да поддържат идентични алгоритми за хеширане (не забравяйте да проверите дължината на тайната)
  • Таймлокове.

На пръв поглед, вече можем да кажем на борсата "сбогом, нашата среща беше грешка", но не така лесно.

При всички свои предимства, решенията на атомен своп не блестят с ликвидност. По-голямата част от това е, защото в най-популярната двойка BTC-USD, фиатната част не е напълно токенизирана.
Успехът на USDT хвърли цяла вълна от стабилни монети във формат ERC20 за всеки вкус, от най-костодиалния USDC до най-алгоритмичния DAI.

Следователно за простота ще разсъждаваме нататък, че Алисата продава на Боб биткойни за някакви ERC20 токени и се надяваме на късмета на стабилизаторите, тъй като имаме много по-технически проблеми.

Скорост

Биткойн и Ethereum поотделно не са прекалено бързи, а тук трябва да изчакаме първо един депозит с всички потвърждения, след това втори.

Всичко това се дължи на факта, че първо парите внася участникът, който знае тайната, а противникът чака финализирането и само тогава прехвърля своята част.

Освен това, имаме работа с доста волатилна актив, така че през това време курсът може да се промени значително, а промяната на условията вече е трудно.

Конфиденциалност

Всяка размяна оставя артефакти и на двете блокчейни. Внимателен наблюдател може да забележи еднакви хешове в смарт контрактите и да направи логичен извод, че е извършена сделка, от което могат да се извлекат множество заключения, от курсови до данъчни.

Когато борсата знае за твоите дела — това е изключително неприятно, когато всеки знае за тях — неприятностите се удвоят.

Удобство

Специалността на блокчейна изобщо и на ефира в частност. Нека да видим какви действия трябва да предприемат продавачът и купувачът.

От гледна точка на продавача всичко е сравнително просто: просто трябва да преведе биткойн на p2sh адрес. С ефира всичко е много по-сложно.

КонтрактНека разгледаме среден контракт за своп по 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);
    }
}

Внимание! Не използвайте този и други контракти от статията в продукция, те са написани изцяло за демонстрация. Особено този.

  • Боб трябва да извика в контракта на токена метод approve, давайки на контракта за своп достъп до своите токени
  • Боб създава своп и контракт чрез метода transferFrom взема токените на изпращача на своя адрес
  • Алиса в withdraw разкрива секрет и контрактът извиква прехвърляне

Повечето портфейли и криптоборси не поддържат approve токени, и не без основание.

Самите потребители често грешат и просто прехвърлят токени на контракта, след което токените просто се губят. Коментарите в Etherscan са пълни с оплаквания на нещастните.

А за да се извика контракт, е нужно да се заплати такса в ETH, което означава, че и двамата участници трябва да имат резерви преди началото на сделката, а това малко хора искат да правят.

Газголдер

Първо, трябва да премахнем проверката на подателя навсякъде, където е възможно, и да предположим, че имаме някой, който страда от изобилие на газ и извиква контрактите за всички желаещи.

Модернизиран контракт

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

Дуализъм на контрактите и EIP 712

Както знаем, адрес в ефира може да бъде контракт или да бъде субект, т.е. ключ.
Основното задължение на ключа е да подписва съобщения.

Можем да използваме контракт Боб като изпращач, който прави всичките необходими предавания, проверявайки преди това подписа на ключа Боб.

Сега, всеки може да спонсорира таксата на участника, но решението взема само онзи, на когото е известен ключът.

Боб-контракт

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

За работа с подписи на сложни структури от данни в Ethereum съществува стандарт EIP 712, можете да прочетете повече за него в блога на портфейла Metamask

Разделяй и владей

Често сценарият на хакване на Ethereum контракт изглежда така:

  • Участникът влага средства в контракта
  • След това прибира средствата
  • Нещо не е наред
  • Злоумышленикът взима парите отново и отново

Ако се върнем към нашия първи пример, нещо не е наред, ако загадката е празен набор от байтове.

Как да откраднем милионСъздаваме своп с хеш 0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925
Това е sha256 от 0x0000000000000000000000000000000000000000000000000000000000000000
Предаваме тайна и прибиране на токените си
Предаваме отново и взимаме чужди, всичко заради това, че 0 = 0

Създавайки отделен контракт за всяка сделка, можем да изолираме контрактите на ниво EVM.

Но и това не е всичко: сега всяка сделка има свой адрес, на който могат да се прехвърлят токени от всякакъв портфейл или борса.

Изоставени контракти и create2

Но сега за всяка сделка ни се налага да създаваме контракт и да чакаме купувачът да преведе там трудовия си “криптофенинг”. В схемата “сутрин контракти, вечер пари” винаги съществува риск, че купувачът ще се откаже, а ефирът за създаване на контракта вече е похарчен.

Не може ли да стане така, че сутрин парите, а вечер байтовете?

В хардфорка Constantinople разработчиците EIP 1014 добавиха инструкция create2, която създава нов контракт на детерминиран адрес

keccak256( 0xff ++ адрес ++ сол ++ keccak256(init_code))[12:]

Къде

  • адрес — адрес на фабричния контракт
  • сол — някакво число, чийто смисъл ще разберем в следващия епизод
  • init_code — байт-код на контракта и параметри на конструктора.

ФабрикаИнструкцията работи само чрез assembly, затова фабриката изглежда малко плашещо:

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

Кодът на вашия контракт можете да получите чрез 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);

Поради ограничената поддръжка в solidity газа за контракта може да се изчислява неправилно поради някои нюанси на ефира.

Особено мило е, че в случай на недостатъчен газ контрактът пада с вътрешна грешка, без да съобщава, че газът е свършил, каквото би могло да се очаква.

Сега можем да прехвърляме токени към контракти, без да ги създаваме предварително, и никой няма да разбере какво точно прави контрактът, преди да го публикуваме в мрежата.

Ворон воронье око не выклюет

Ясно е, че истинският анализатор, особено този, който е получил добри инвестиции за борба с враговете на режима чрез пране на пари, няма да бъде спрян от такива детски трикове, и след създаването на контракта, все пак ще види хеша.

Как да направим така, че хешът да не бъде разкрит?

Самият своп прехвърляме в офчейн: участниците обменят подписи за прехвърляне на своп-контракта, а след това тайно разкриват секрет.

Стъпка по стъпкаСъздават се два "мултисига", от които могат да се вземат средства при наличие на подписи от Алиса и Боб.

За да не стане трагедия при излизането на някой от участниците в офлайн, ще добавим стария добър таймаут.

Алиса и Боб паралелно внасят депозити

  • Алиса измисля секрет и предава на Боб хеш на секретa и подпись на транзакцията, която прехвърля биткойни на адреса на свопа.
  • Боб предава на Алиса подпис за изтегляне на токените на своп контракта с измисления хеш.
  • Алиса съобщава на Боб тайния секрет.

В този момент настъпва хармония: и Алиса, и Боб могат по всяко време да завършат сделката. В такава приятелска атмосфера те могат да обменят подписи, за да изтеглят пари на крайни адреси.

За страничния наблюдател това изглежда, сякаш парите са преминали през контракт с мултиподпис 2 от 2.

И още, такава схема позволява на двете страни да правят депозит едновременно, тъй като секретът се измисля вече след всичките потвърждения.

Level 2

Сега, когато можем да изтегляме пари на един адрес и да не публикуваме междинната транзакция, нищо не пречи да изтегляме пари на няколко адреса и да извършваме неограничен брой междинни транзакции. Не че това е необходимият комплект за размяна, но когато започнеш да събираш своп, трудно е да спреш.

Сега Алиса и Боб могат да се развихрят. Например, автоматически да изчисляват средната цена, обменяйки по сатоши в секунда или просто да свържат маркетмейкъра и получателя на ликвидност.

Стъпка по стъпка

  • Продавачът измисля секрет и предава на купувача хеш на секретa и подпись на транзакция, където част от средствата се прехвърлят на p2sh адреса на свопа, а остатъкът се връща на адреса на продавача.
  • Клиент предоставя подпис, който позволява извеждането на токени и остатъка на адреса на получателя.
  • Продавачът разкрива тайна
  • Историята се повтаря с нова тайна, като към свопа и остатъка се добавя и извеждането на преди закупеното на адреса на клиента и вече платеното на адреса на продавача.

Сега имаме достъп до високо скоростна p2p търговия, важно е да следим времето и да затворим сделката преди таймаута.

Но с леко коригиране на нашите договори можем да подарим безсмъртие на нашите канали, което значително ще улесни създаването на мрежа.

Но за това ще разкажем в следващата серия.

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster