Бляск і галеча atomic swaps

Чым дрэнныя атамарныя свопы і як каналы ім дапамогуць, што важнага адбылося ў хардфорцы Constantinople і як быць, калі няма чым плаціць за газ.

Галоўная матывацыя любога спецыяліста па бяспецы - жаданне пазбегнуць адказнасці.

Провід было літасціва, я пакінуў ICO, не чакаючы першай незваротнай транзакцыі, але неўзабаве выявіў сябе за распрацоўкай крыптабіржы.

Я — рашуча не Хлапеч Кібальчыш, і аднаго строгага погляду дастаткова, каб я здаў усе ключы і паролі. Таму галоўнай маёй мэтай як архітэктара было размясціць распаленае джала крыптааналізу як мага далей ад дарагіх мне элементаў інфраструктуры.

Не твае ключы, не твае праблемы

Мы будуем сістэму абмену актывамі і жадаем выключыць прамежкавае захоўванне гэтых актываў у сябе, але павінны забяспечыць бяспеку ўгоды.

Можна выступіць у якасці суддзі ў спрэчнай сітуацыі і праводзіць здзелкі з кашалькамі, якія патрабуюць два з трох подпісаў: пакупніка, прадаўца і эскроу.

Аднак, калі ўдзельнік паспяхова атакуе эскроу, то ён атрымлівае шуканыя два подпісы.

Атамарны своп - схема абмену, дзе гарантам выступае смарт-кантракт, які дапускае толькі сумленныя паводзіны.

Нібы ў загадцы пра ваўка казу і капусту ты можаш дзейнічаць толькі па адзіным правільным сцэнары і нясеш страты, калі адыходзіш ад яго.

Толькі замест пражэрлівых жывёл парадак забяспечвае хэш функцыя, у якой так складана знайсці калізію, што не варта і пачынаць.

Крок першы: загадка

Выкажам здагадку, што Аліса ў адну цудоўную раніцу хоча перадаць Бобу біткоін за жменю "криптоюаней".

  • Яна загадвае нейкі вялікі сакрэт
  • Атрымлівае ад яго хэш
  • Перакладае біткоіны на смарт кантракт, забраць з якога грошы можа Боб, прад'явіўшы сакрэт (хэш ад яго павінен быць роўны паказанаму ў кантракце)
  • У выпадку, калі Боб не з'яўляецца за сваімі біткоінамі да вечара, Аліса можа забраць іх назад сабе.

Крок другой: прынада

У гульню ўступае Боб і перакладае "крыптаеўра" на свой кантракт, які напісаны такім чынам што:

  • Аліса можа забраць "крыптаены" прад'явіўшы сакрэтны лік
  • Не раней абеду Боб, пры нез'яўленні Алісы можа вярнуць дэпазіт

Крок трэці: адгадка ў прынадзе

Аліса прыходзіць за сваімі грашыма і забірае грошы з кантракта Боба, раскрыўшы пры гэтым свой сакрэт.

Крок завяршальны: загадка разгадана

Транзакцыю бачыць Боб, і арліным позіркам вычляняе з яе сакрэт, прад'яўлены Алісай кантракту. Гэты сакрэт ён выкарыстоўвае, каб забраць ужо свае біткоіны.

Калі нешта ідзе не так

Калі Аліса раптам апыняецца раптоўна смяротная, Боб у абед забірае свае юані.

У сваю чаргу, Аліса да вечара вяртае біткоін, калі вераломны Боб вырашае прытрымаць грошы да лепшых часоў.

Калі вы аддаеце перавагу карцінку тэксту, на Хабры для вас ёсць больш падрабязнае і нагляднае тлумачэнне працы атамарных свопаў.

Розніца паміж таймаўтамі заклікана застрахаваць нас ад шкоднай Алісы, якая забірае грошы Боба ў самы апошні момант, і таймаўт мінае, пакуль той дрыготкімі пальцамі забівае hex у транзакцыю.

Удзельнікі не могуць страціць свае грошы, максімум, давядзецца пачакаць вяртання.

Падтрымка ў блокчэйнахГэта простая, як валёнак, схема, якая патрабуе ад якія ўзаемадзейнічаюць блокчейнов усяго нічога:

  • Падтрымка смарт кантрактаў з хаця б адным галінаваннем
  • Абодва блокчэйна павінны падтрымліваць аднолькавыя алгарытмы хэшавання (не забывайце правяраць даўжыню сакрэту)
  • Таймлакі.

На першы погляд, ужо можна сказаць біржы "бывай, наша сустрэча была памылкай", але не тут-та было.

Пры ўсіх сваіх добрых якасцях рашэння на atomic swap не дзівяць ліквіднасцю. Шмат у чым таму, што ў самай папулярнай пары BTC-USD фіятная частка была не зусім такенізаваная.
Поспех USDT спарадзіў цэлую хвалю стабільных манет фармату ERC20 на любы густ, ад кастадыяльнага USDC да алгарытмічнага DAI.

Таму для прастаты мы разважаем далей аб тым, што Аліса прадае Бобу біткоіны за нейкія ERC20 токены, і спадзяемся на поспех стабілізатараў, балазе ў нас яшчэ шмат больш тэхнічных праблем.

Хуткасць

Біткоін і Ethereum і па асобнасці не занадта хуткія, а тут нам даводзіцца чакаць спачатку адзін дэпазіт з усімі пацверджаннямі, потым другі.

Гэта ўсё таму, што спачатку грошы ўносіць удзельнік, якому вядомы сакрэт, а апанент чакае фінальнасці і толькі потым пераводзіць сваю частку.

Акрамя таго, мы маем справу з вельмі валацільнасць актывам, так што за гэты час курс можа вельмі істотна змяніцца, а памяняць ўмовы ўжо няпроста.

Канфідэнцыяльнасць

Любы абмен пакідае артэфакты на абодвух блокчэйнах. Уважлівы назіральнік можа заўважыць аднолькавыя хэшы ў смарт кантрактах і зрабіць лагічную выснову, што тут адбылася здзелка, з чаго можна зрабіць масу высноў ад курсавых да падатковых.

Калі аб тваіх справах ведае біржа - гэта вельмі непрыемна, калі пра гэта ведае кожны - гэта непрыемна ўдвая.

Юзабіліці

Канёк блокчейна наогул і эфіру ў прыватнасці. Давайце паглядзім, якія рухі цела давядзецца здзейсніць прадаўцу і пакупніку.

З пункту гледжання прадаўца ўсё адносна проста: трэба проста перавесці біткоін на p2sh адрас. З эфірам усё значна хітрэй.

кантрактРазгледзім асераднёны па гітхабе кантракт для свопу:

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 раскрывае сакрэт і кантракт выклікае transfer

Большасць кашалькоў і крыптабіржаў не падтрымліваюць 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 ёсць стандарт 712 г. ЭІП, падрабязней пра яго вы можаце прачытаць у блогу кашалька Metamask

Падзяляй і ўладар

Часта сцэнар узлому Ethereum кантракту выглядае так:

  • Удзельнік кладзе сродкі на кантракт
  • Потым забірае сродкі
  • Нешта ідзе не так
  • Зламыснік забірае грошы зноў і зноў

Калі мы вернемся да нашага першага прыкладу, нешта ідзе не так, калі загадкай з'яўляецца пусты набор байт.

Як выкрасці мільёнСтвараем своп з хэшам 0x66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925
Гэта sha256 ад 0x0000000000000000000000000000000000000000000000000000000000000000
Перадаем сакрэт і забіраем свае токены
Перадаем яшчэ раз і забіраем чужыя, усё з-за таго што 0 = 0

Ствараючы для кожнай здзелкі асобны кантракт, мы можам ізаляваць кантракты на ўзроўні EVM.

Але і гэта яшчэ не ўсё: зараз кожная здзелка мае свой адрас, на які можна перавесці токены з любога кашалька ці біржы.

Кінутыя кантракты і create2

Але цяпер для кожнай здзелкі нам даводзіцца ствараць кантракт і чакаць пакуль пакупнік перавядзе туды працоўны "крыптафенінг". У схеме "раніцай кантракты, вечарам грошы" заўсёды ёсць небяспека, што пакупнік адваліцца, а эфір на стварэнне кантракта ўжо патрачаны.

Ці нельга зрабіць так, каб раніцай гроша, а ўвечар байты?

У хардфорцы Constantinople распрацоўшчыкі 1014 г. ЭІП дадалі інструкцыю create2, якая стварае новы кантракт на дэтэрмінаваным адрасе

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

Дзе

  • address - адрас кантракту фабрыкі
  • salt - нейкі лік, сэнс якога мы даведаемся ў наступнай серыі
  • 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 газ для кантракта можа разлічвацца няправільна з-за некаторых тонкасцяў эфіру.

Асабліва міла, што ў выпадку недахопу газу кантракт падае з унутранай памылкай, не паведамляючы пры гэтым, што газу не хапіла, як таго можна чакаць.

Цяпер мы можам пераводзіць токены на кантракты не ствараючы іх загадзя і пакуль мы іх не апублікуем у сетцы ніхто не здагадаецца, што менавіта робіць кантракт.

Крумкач ​​варону вачэй не выклюе

Зразумела, што сапраўднага аналітыка, які асабліва атрымаў добрыя інвестыцыі на барацьбу з ворагамі рэжыму адмываннем грошай, такія дзіцячыя хітрасці не спыняць, і пасля стварэння кантракта ён усё роўна ўбачыць хэш.

Як зрабіць так, каб хэш не засвяціўся?

Сам своп мы пераносім у афчэйн: удзельнікі абменьваюцца подпісамі для пераводу на своп-кантракт, а затым прыватна раскрываецца сакрэт.

Крок за крокамСтвараюцца два "мультысігі", з якіх можна забраць сродкі пры наяўнасці подпісаў Алісы і Боба.

Каб сыход у афлайн каго-небудзь з удзельнікаў не стаў трагедыяй, дадамо стары добры таймаўт.

Аліса і Боб паралельна ўносяць дэпазіты

  • Аліса загадвае сакрэт і перадае Бобу хэш сакрэту і подпіс транзакцыі, які перакладае біткоіны на адрас свопу.
  • Боб перадае Алісе подпіс на выснову токенаў на кантракт свопу з загаданым хэшам.
  • Аліса паведамляе Бобу сакрэт.

У гэты момант надыходзіць гармонія: і Аліса і Боб могуць у любы момант скончыць здзелку.

Для іншага назіральніка гэта выглядае як быццам грошы прайшлі праз кантракт з мультыподпісам 2 з 2.

А яшчэ такая схема дазваляе абодвум бакам рабіць дэпазіт адначасова, бо сакрэт загадваецца ўжо пасля ўсіх пацверджанняў.

Узровень 2

Раз мы можам выводзіць грошы на адзін адрас і не публікаваць прамежкавую транзакцыю, нічога не мяшае нам выводзіць грошы на некалькі адрасоў і здзяйсняць неабмежаваную колькасць прамежкавых транзакцый.

Цяпер Аліса і Боб змогуць разгарнуцца на ўсю моц. Напрыклад, аўтаматычна вылічваць сярэдні кошт, абменьваючы па сатошы ў секунду, ці проста наўпрост злучыць маркетмэйкера і атрымальніка ліквіднасці.

Крок за крокам

  • Прадавец загадвае сакрэт і аддае пакупніку хэш сакрэту і подпіс транзакцыі, дзе частка сродкаў пераводзіцца на p2sh адрас свопу, а рэшта вяртаецца на адрас прадаўца.
  • Пакупнік перадае подпіс, які дазваляе вывесці на своп токены і рэшту на адрас атрымальніка.
  • Прадавец раскрывае сакрэт
  • Гісторыя паўтараецца з новым сакрэтам, пры гэтым да свопу і здачы дадаецца яшчэ выснова раней набытага на адрас пакупніка і ўжо аплачанага на адрас прадаўца.

Цяпер нам даступны высакахуткасная p2p гандаль, галоўнае сачыць за часам і закрыць здзелку да таймаўту.

Аднак, крыху паправіўшы нашы кантракты, мы можам падарыць нашым каналам неўміручасць, што моцна спросціць нам стварэнне сеткі.

Але пра гэта мы раскажам у наступнай серыі.

Крыніца: habr.com

Купіць надзейны хостынг для сайтаў з абаронай ад DDoS, VPS VDS серверы 🔥 Купіць надзейны хостынг для сайтаў з абаронай ад DDoS, VPS VDS серверы | ProHoster