# IntMax1.0 DexのYellow Paper ## 要約 ### 条件付き送金とL2上資産のトレード - 条件付き送金(エスクロー)をIntMax1.0に実装することを前提とする - 条件付き送金とはMakerがある条件を指定して空の宛先に送金を行い(資金のロック)、TakerはそのTxをinputにした条件を満たすTxを発行することでMakerがロックした資金を獲得できる(このようにすることでMakerはnonceの衝突を防ぎながら同時に多数の板を出すことができる) - L2上の資産同士の取引では「Makerが指定した種類と数量を送金する」条件付き送金を利用することで板取引を実行する ### 部分送金 - L2上の資産同士の取引では上記の仕様に加えて部分送金の仕組みも導入する。 - 部分送金が利用できる条件付き送金は「Takerが送付したトークンの量が指定した量に満たないとき、TakerのTx自体がMakerに残金を同じ条件(価格)で支払うことを指定する条件付き送金であることを要求する」こと - たとえばMakerがx ETHをy USDCで販売するとき、Takerがz USDC(y > z)支払うとする。このとき、 - トークンの不足分(y - z) USDCをMakerに送付した場合、残りの送金額(x - x*z/y ETH)をもらうことができる - y - z以下のトークンを送付した場合は同様のフォーマットの条件付き送金であることを求める 以上の条件であれば全額をTakerに支払う。(そして残金分は条件付き送金にロックされる) - 上記の仕様であればn回部分送金したとしてもn回目の部分送金はn-1回目の部分送金Txしか参照しないので計算量が一定以上増加せず、またOneTimeAddressの衝突も発生しない ### LNBTCのトレード - LNのBTCとのやり取りでは販売所形式でシンプルなAtomic Swapで対応する。ここで利用される条件付き送金は「Preimageを知っているかつ指定したアドレスからのTxであること」 - LNを受け取る側はネットワーク外でTakerと価格・数量に合意してから条件付き送金をネットワークに提出、Invoiceを相手方に渡してやりとりする - この条件付き送金ではL2支払側がTxをキャンセルできるのはTxが取り込まれてから一定ブロック経過してからでないといけない(LNで支払ったのに相手方がTxをキャンセルして受け取りができない状態を防ぐため) (要約終わり) --- この章ではIntMax1.0ネットワークを利用したDEXの仕様について説明する。 IntMax1.0ネットワークにおいてはEthereumのようなコントラクト独自のステートを持つことが出来ない。したがってグローバルなステートを持たずに、かつトラストレスに取引ができる取引所を考える必要がある。 また、本ネットワークの特性上、L2上の資産同士の取引だけではなくL2上の資産とLightning Network上のBTCとの売買もできる取引所を考える。 ただしLightning Network上のBTCとの売買に関しては、Lightning NetworkがL2とは異なる特性(トランザクションのアトミック性やInvoiceが必要であること、受け取りの際にオンラインであることが要求されるなど)があることからL2上の資産同士のやり取りとは別に考える必要がある。 ## DEXの概要と条件付き送金 IntMax1.0上でのDEXの構築において一番の障壁となるのは売り手がL2ネットワーク上で価格を提示し取引を行うにあたり、売り手と買い手が互いに合意した条件での取引をステートによる記録無しでトラストレスに行うことである。 この問題点の解決策として、エスクロー全般に利用できる「条件付き送金」を提案する。 条件付き送金ではまず、支払い主が送り先が空の状態の送金を行う。 この送金Txには送金を成功させるための条件が記録されている。 受け取り側は条件付き送金の条件を満たすTx(受け取りTx)を組成し、そのTxのInputに送金Txを含める。 このとき、条件が満たされていれば送金Txの送金が受け取り側アドレスに対して行われる。 また、送金のキャンセルとして送金側がキャンセルTxを打った場合も送金側アドレスに対して返金が行われる。 ```mermaid sequenceDiagram actor S as Sender participant I as Intmax Network actor R as Receiver autonumber S --> I : Lock Token via Conditional Tx I --> I : Comsume OneTimeAddress Hash(Sender Pub + nonce) R --> I : Submit Tx with Sender's Proof I --> I : Check if Receiver's Tx fullfills Sender's condition S --> R : Send Sender's Token I --> S : Send Receipt with OneTimeAddress Hash(Sender Pub + nonce + Suffix) ``` ### 支払側の条件付き送金 条件付き送金では、まず支払い側が空のアドレスに送金する支払いTxを発行する(この際、資金はLockされる)。 このとき、OneTimeAddressである```Hash(Privkey + nonce)```を消費する。 支払い側は通常のProofにくわえて $$ payerSignature = Sig(Hash(assetID + amount + Conditions), payerPrivkey) $$<br /> $$C_{verifySignedPayment}(txHash, payerOneTimeAddress, Conditions, oldState, newState),\\ (payerPublicKey, payerSignature)) \\ \to \{true,false\}$$<br /> $$zkProve((txHash, payerOneTimeAddress, Conditions, oldState, newState), \\ (payerPublicKey, payerSignature), C_{verifySignedPayment}) \\ \to zkProof_{Payer}$$<br /> となり条件を満たすことを確認する。 ### Proofの各種パラメータの説明 |name|説明| | :--: | :-- | |payerSignature|送金者の署名。条件付き送金の条件と送金内容を署名する| |assetId|送金対象となるトークンに一意につけられたID| |$$C_{verifySignedPayment}$$|この回路は <br />1. payerSignatureが正しいことを公開鍵無しで判定する<br />2. このTxが取り込まれた後、Payerの残高がamount分減っていること<br />の2つを判定する| |newState, oldState|Tx前後のステート。回路で真贋を判定する| という条件付き支払いの条件や送金の内容についての署名を検証するZkProofを提出する。 ### 受け取り側の条件付き送金(受け取りTx) 受け取り側は受け取りTxを用意し、このTxは支払いTxをInputにしてTxを実行する。 Inputにした支払いTxのProofの条件が、受け取りTxによって満たされていることが確認されれば、受け取りTxのオーナーに対して支払いが行われる。 このとき、送金Txを送付したPayer側のOneTimeAddressである```Hash(Hash(Payer's Priv key + nonce) + suffix)```を消費する。このOneTimeAddressはnonceは同じであるものの、Lock時とは異なるOneTimeAddressであり、衝突しない。 (そのため、MarketMakerは板を出している間に他のTxを打つことが出来、ユーザビリティの向上に寄与する) また、次のOneTimeAddressとも衝突しないのでMakerの他Txには影響されない。 このとき受け取りTxが提出するProofは $$ receiverSignature = Sig(Hash(receiveTxHash + payerOneTimeAddress), receiverPrivkey) $$<br /> $$C_{verifyReceive}((receiveTxhash, receiverOneTimeAddress, receiverSignature, \\ payerEntryAddress, payerFinishAddress, data, payerTxData, payerTxHash,\\ oldState, newState, addressSMTRoot, addressSMTSiblings), \\ (receiverPublicKey)) \to \{true,false\}$$<br /> $$zkProve((receiveTxhash, receiverOneTimeAddress, receiverSignature, \\ payerEntryAddress, payerFinishAddress, data, payerTxData, payerTxHash,\\ oldState, newState, addressSMTRoot, addressSMTSiblings), (receiverPublicKey), C_{verifyReceive}) \\ \to zkProof_{Receiver}$$<br /> ### Proofの各種パラメータの説明 |name|説明| | :--: | :-- | |receiverSignature|受け取りTxを提出する際の署名。受け取りたい条件付き送金のoneTimeAddressを含んでいる| |receiveTxHash|受け取りTxの内容をハッシュしたもの| |payerEntryAddress|送金Txを作成する際に送金側が使用したOneTimeAddress| |payerFinishAddress|送金Txを利用後に消費されるOneTimeAddress。PayerEntryAddressから生成可能| |$$C_{verifyReceive}$$|この回路は<br /> 1. receiverSignatureが正しいことを公開鍵を明かさずに証明する<br /> 2. payerTxDataのconditionを満たしていることを証明する<br /> 3. payerTxDataとpayerEntryAddressからPayerTxHashが導けること、payerEntryAddressからpayerFinishAddress導けることを証明する<br />4.newStateがoldStateから条件付き送金を達成した状態になっていることを証明する<br />5.payerEntryAddressとpayerTxHashの組がSMTに存在すること<br/>の5点を検証する| |data|条件付き送金の条件を満たしているか検証するために必要なデータ| |payerEntryAddress|支払いTxで利用されたOneTimeAddress| |payerFinishAddress|受け取りTxが実行されると消費され、OneTimeAddress SMTに記録されるOneTimeAddress。payerEntryAddressから誰でも導出可能である必要がある| |payerTxData|送金TxのtxData| |addressSMTRoot|oneTimeAddressのRoot| |addressSMTSiblings|計算に必要なsiblings| また、このときOneTimeAddressのSMTでは以下の2点が証明される 1. 支払いTxを提出したときのOneTimeAddress(payerEntryAddress)は使用されていること 2. 支払いTxの完了OneTimeAddress(payerFinishAddress)が使用されていないこと ### 送金側によるTxのキャンセル 送金キャンセルの場合はキャンセルTxを提出したアドレスが送金Txを提出したアドレスと同じ秘密鍵から生成できることを証明すれば良い。 したがって以下のProofを提出する $$ cancelSignature = Sig((calcelTxHash+ payerOneTimeAddress), privkey) $$<br /> $$ C_{verifyCancel}((oneTimeAddress, payerTxData, payerTxHash, addressSMTRoot, addressSMTSiblings, oldState, newState), \\(Publickey, nonce))$$ <br /> $$ zkProve((oneTimeAddress, payerTxData, payerTxHash, addressSMTRoot, addressSMTSiblings, oldState, newState), \\ (publickey, nonce), C_{verifyCancel}) \to zkProof_{Calcel} $$ ### Proofの各種パラメータの説明 |name|説明| | :--: | :-- | |CancelSignature|キャンセルTxの署名。送金TxのOneTimeAddressを含む| |$$ C_{verifyCancel}$$|この回路は<br />1. cancelSignatureが正しいことを公開鍵を明かさずに証明する<br />2. 送金TxのCalcelできる送金者のOneTimeAddressと今回のOneTimeAddressが同じ秘密鍵から生成できることを証明する| ## L2上の資産同士の取引 L2上の資産の取引においては、Market Makerは上記の条件付き送金で「あるERC20を指定額・指定したアドレスに送金する」ことを条件に指定する。 TakerはそのMarket Makerの条件付き送金TxをInputに指定されたトークンを送金することでSwapを完了する。 これによってほぼ完全に板取引が成立する。 ただし、問題点として条件付き支払いはアトミックなトランザクションであるため、TakerはかならずMakerが提示した額を買う必要があるし、Makerは板を厚くするためには大量のTxを提出しなければならない。 ### 部分送金Tx 部分送金は上記のデメリットを解消するための仕様である。 ただし、以下の2つの条件を満たす必要がある。 1. OneTimeAddressの状態からその条件付き送金が使用済みであるかどうか判断できる 2. 部分送金を繰り返してもProofの計算量が一定以上増加しない 1の条件は、条件付き送金が使用済みであるかどうかはOneTimeAddressのSMTに条件付きTxの使用後OneTimeAddressが含まれているかどうかで判断されるため必須の条件である。たとえば部分送金で使用された割合に基づいて送金TxのOneTimeAddressが変更される場合はこの条件に適していない。 2の条件は部分送金を繰り返した場合、それまでの部分送金のProofすべてがInputになってしまい計算量が増大することなどが考えられるが、ネットワークへの負荷を考慮し部分送金を繰り返したとしても計算量が一定以上増加しないことを要求する。 ### 具体的な方式 部分送金のConditionにおいて以下の条件を設定する。 (例としてMakerがx ETHをy USDC受け取り時に送付することを考える) 1. 要求したトークン量と同じ額(y USDC)を送金した場合 => 通常の条件付き送金を行う。(x ETHを受け取る) 2. 要求したトークン量より少ない額(z: z < y)を送金した場合 => 受け取りTxが以下の条件付き送金の送金Txである場合は全額送金するが受け取れるのは、送ったトークンに比例する量(x*z/y ETH)のみ a. トークンの不足分(y - z)をMakerに送付した場合、残りの送金額(x - x*z/y ETH)をもらうことができる b. トークンの不足分の一部を送付した場合は受け取りTxがこのTxと同じフォーマットであることを要求する c. Makerがキャンセルした場合、残りの送金額の払い戻しが発生する。 3. Makerがキャンセルした場合 => 通常と同様に払い戻す ### 簡略化したサンプルコード ```python= class PartialPayment: def __init__ (self, receiveAmount, sendAmount, recipient): # Takerから送ってもらうトークン量 self.receiveAmount = receiveAmount # こちらから送るトークン量 self.sendAmount = sendAmount # Takerが送ったトークンを受け取るアドレス self.recipient = recipient # 支払いの受け取りを行う def receivePayment(ReceiveTx): # 必要額ピッタリ送金された場合 if(ReceiveTx.sendAmount == self.receiveAmount): # 全額支払う transferToken(ReceiveTx.sender, self.sendAmount) consumeOneTimeAddress() # 必要額より少ない場合は正しい条件付き支払いTxであることを要求する elif (ReceiveTx.sendAmount > self.receiveAmount && isValidReceiveTx(ReceiveTx): # この場合でも全額支払う transferToken(ReceiveTx.sender, self.sendAmount): consumeOneTimeAddress() def isValidReceiveTx(ReceiveTx): # 残りの送金額 nextSendAmount = self.sendAmount - self.sendAmount * ReceiveTx.sendAmount / self.receiveAmount # 残りの受取額 nextReceiveAmount = self.receiveAmount - ReceiveTx.sendAmount # 正しい送金額・受取額でPayerがこのTxのPayerと同じであるPartial Paymentであることを確認する if(ReceiveTx.conditionalPayment == self.__init__(nextReceiveAmount, nextSendAmount, self.recipient)): return true return false def calcelPayment(CalcelTx): # キャンセルする場合は送金者とアドレスが同じであることを要求する if(CancelTx.sender == self.recipient): transferToken(self.recipient, self.sendAmount) consumeOneTimeAddress() ``` ## LN上のBTCと取引する LN上のBTCとの取引においては少数のオンラインのノードを常に運用できる売り手が外部の価格を各々参照し、販売所方式を採用するのが最もよいと思われる。 販売所を使う理由としては 1. PreImageの仕様上、L2で確実にLNでの送金済みの買い手に対して支払いが実行されるようにするためには売り手と買い手の間でインタラクティブな通信が必要であり、価格を先に提示する板取引と相性がわるい 2. 外部から価格を取得してもBTCの流動性は非常に厚いため、問題はほとんど発生しない。 という2点から販売所方式を採用する。 具体的には買い手はなんらかの掲示板において任意の取引価格と取引可能な数量を提示する。 それに合意した売り手は条件付き送金とLN送金を通して資産の交換を行う。 このとき、条件付き送金では以下の2つの条件を要求する。 1. PreImageを知っていること 2. 指定されたアドレスによるTxであること ### TakerはLNBTCを支払ってL2上のトークンを買う MakerはLNの受け取り手であり、TakerはLN支払いを行うため下図のようになる。 価格に合意したTakerは使用予定のL2アドレスを送り、それを元にMakerは条件付き送金を作成する。 TakerはInvoiceを元に条件付き送金が正しいか判断して正しければLNで送金する。 また、このTxにおいてはMakerは板を出してから一定ブロックが経過しないとキャンセルTxを打つことができない。 ```mermaid sequenceDiagram actor M as Maker participant I as Intmax Network actor T as Taker autonumber M --> T : Send L2 Token Price in BTC T --> M : Send Taker's Address M --> I : Send Conditional Payment Tx with Taker's address M --> T : Send Invoice T --> I : Comfirm Maker's Tx T --> M : Pay BTC by LN M --> T : send Preimage T --> I : Submit Receive Tx with Preimage I --> T : Transfer L2 Token ``` ### L2上のトークンを支払ってLNBTCを受け取る MakerはLNの支払い主であり、Takerは受け取りを行うため下図のようになる。 ```mermaid sequenceDiagram actor T as Maker participant I as Intmax Network actor M as Taker autonumber M --> T : Send L2 Token Price in BTC T --> M : Send Taker's Address M --> I : Send Conditional Payment Tx with Taker's address M --> T : Send Invoice T --> I : Comfirm Maker's Tx T --> M : Pay BTC by LN M --> T : send Preimage T --> I : Submit Receive Tx with Preimage I --> T : Transfer L2 Token ``` ただし、この方式だと0 confであれば問題はないがConditional PaymentのTxがL1に提出されるまで待つ場合は非常に時間がかかる方式である。 ###### tags: `Yellow paper`