Synopsis

The client needs to interact with the NOCUST smart-contract on the parent-chain and the operator in order to ensure security of his balance, make transactions, deposits, and withdraw funds from the commit-chain.

Motivation

To ensure security of the balance and non-custody there are specific requirements that the client need to fulfill. In this document, we will describe the conditions needed to consider the commit-chain as safe. Further, we will describe the protocol to make off-chain transactions, deposits, withdrawals, and challenges.

Definitions

  • Ai(e) A_i(e) : The initial balance allotment reflected in the account interval tree of checkpoint ee. We usually have AiK(e)=AiK(e).accountProof.rightAiK(e).accountProof.leftA_i^K(e) = \mathfrak{A}_i^K(e)\texttt{.accountProof.right} - \mathfrak{A}_i^K(e)\texttt{.accountProof.left}
  • Rip(e)R^p_i(e): The amount received from transfers during the round
  • Ria(e)R^a_i(e): The amount received from swaps during the round
  • Si(e)S_i(e): The amount spent by doing swaps or transfers during the round
  • Di(e)\mathbb{D}_i(e): The amount deposited during the round
  • WRi(e)\mathbb{WR}_i(e): The amount requested for withdrawal during the round
  • WCi(e)\mathbb{WC}_i(e): The amount of withdrawal confirmed during the round
  • BK:\mathbb{B}^K: Total parent-chain balance of the NOCUST smart-contract of asset token KK

Rip(e)R^p_i(e), Ria(e)R^a_i(e), SiK(e)S_i^K(e), Di(e)\mathbb{D}_i(e), WRi(e)\mathbb{WR}_i(e), and WCi(e)\mathbb{WC}_i(e) MUST be rested to 0 at the beginning of the round.

All variables can have an exponent indicating a specific asset token. For example, AiK(e) A_i^K(e) is the initial balance allotment for asset KK.

Technical Specification

Account Balance

We define the current balance or spendable balance of asset token KK on the commit-chain account PiK\mathbb{P}_i^K equal to: balance(PiK)=AiK(e)+RiK(e)SiK(e)+DiK(e)WiK(e) balance(\mathbb{P}_i^K) = A_i^K(e) + R_i^K(e) - S_i^K(e) + \mathbb{D}_i^K(e) - \mathbb{W}_i^K(e) With Ri(e)=Rip(e)+Ria(e)R_i(e) = R^p_i(e) + R^a_i(e)

New eon

As described in NSPEC003, the NOCUST system will periodically enter a new round. The NOCUST client MUST consider the new round for making transactions and swaps as soon as the block of the new round is published on the parent-chain. The client MUST set the value of the initial allotment for a new round to the balance of the previous round: AiK(e+1)=balance(PiK(e)) A_i^K(e+1) = balance(\mathbb{P}_i^K(e)) . The variables Rip(e)R^p_i(e), Ria(e)R^a_i(e), SiK(e)S_i^K(e), Di(e)\mathbb{D}_i(e), WRi(e)\mathbb{WR}_i(e), and WCi(e)\mathbb{WC}_i(e) MUST be rested to 0 and the active and passive trees MUST be emptied.

Ledger auditing

In NOCUST, users are required to audit their account on a regular basis:

  • The owner of an account PiK\mathbb{P}_i^K MUST come online at least once every eon before the start of the 4th epoch to get his AiK(e)\mathfrak{A}_i^K(e) proof of exclusive balance allotment from the operator and verify its validity.
  • Given a valid AiK(e)\mathfrak{A}_i^K(e), the user MUST complete the delivery verifications.
  • If the operator does not cooperate to provide the proof AiK(e)\mathfrak{A}_i^K(e), the user MUST open a state update challenge before the start of the 4th epoch. The user MUST come back online before the eon finishes to complete the delivery verifications using the AiK(e)\mathfrak{A}_i^K(e) in the operator response of the state update challenge.
  • AiK(e)\mathfrak{A}_i^K(e) MUST be stored by the user until checkpoint e+1e+1 is available on the parent-chain and AiK(e+1)\mathfrak{A}_i^K(e+1) validity is confirmed and delivery verification is completed.

State update verification

In a NOCUST commit-chain you are required to verify the state transition between 2 consequent checkpoints. Given an account PiK(e)\mathbb{P}_i^K(e) and its latest valid active state, the user have to verify the correct transition between PiK(e1)\mathbb{P}_i^K(e-1) and PiK(e)\mathbb{P}_i^K(e) according to the transactions made during the round. A valid state update form PiK(e1)\mathbb{P}_i^K(e-1) to PiK(e)\mathbb{P}_i^K(e) MUST meet the following requirements:

  • There MUST be a valid proof of exclusive balance allotment AiK(e)\mathfrak{A}_i^K(e) matching checkpoint ee (provided by the operator)

  • If the user signed at least one active state during round e1e-1 (made an outgoing transfer or started a swap) we consider case A. We consider case B otherwise. We have: AiK(e)=AiK(e).accountProof.rightAiK(e).accountProof.leftA_i^K(e) = \mathfrak{A}_i^K(e)\texttt{.accountProof.right} - \mathfrak{A}_i^K(e)\texttt{.accountProof.left}

    • CASE A: We denote SiK(e1)\mathfrak{S}_i^K(e-1) the latest valid active state during round e1e-1. The following inequality MUST hold:

      AiK(e)AiK(e1)+SiK(e1).gained  SiK(e1).spent +DiK(e)WRiK(e)+AiK(e).accountAggregate.passiveAggregate.passiveAmountReceived A_i^K(e) \ge A_i^K(e-1) + \mathfrak{S}_i^K(e-1)\texttt{.gained}\ -\ \mathfrak{S}_i^K(e-1)\texttt{.spent}\ + \mathbb{D}_i^K(e) - \mathbb{WR}_i^K(e) + \mathfrak{A}_i^K(e)\texttt{.accountAggregate.passiveAggregate.passiveAmountReceived}

    • CASE B: The following inequality MUST hold: AiK(e)AiK(e1)+DiK(e)WRiK(e)+AiK(e).accountAggregate.passiveAggregate.passiveAmountReceived A_i^K(e) \ge A_i^K(e-1) +\mathbb{D}_i^K(e) - \mathbb{WR}_i^K(e) + \mathfrak{A}_i^K(e)\texttt{.accountAggregate.passiveAggregate.passiveAmountReceived}
  • The NOCUST smart contract MUST have sufficient funds, formally: AiK(e).accountProof.totalAllotementBKjPKDj(e)jPKWRj(e)+jPKWCj(e) \mathfrak{A}_i^K(e)\texttt{.accountProof.totalAllotement} \le \mathbb{B}^K - \sum^{\forall j\in \mathbb{P^K}} \mathbb{D}_j(e) - \sum^{\forall j\in \mathbb{P^K}} \mathbb{WR}_j(e)+ \sum^{\forall j\in \mathbb{P^K}} \mathbb{WC}_j(e) Note that withdrawal requests, withdrawal confirmations and deposits for all users are available on the parent chain.

If any of the previous requirements doesn't hold, the user MUST initiate a state update challenge.

Delivery verifications

Additionally to the state update verification, the user MUST ensure delivery of his transactions. A delivery verification is required for each transaction. The delivery verifications MUST be done after the State Update Verification.

Transfer delivery verification

For all incoming transfers of a given eon the user MUST verify that the transfers where delivered. Meaning that the account was credited as expected and that the transfer is included in the passive tree. From the transfer sub-protocol the sender had to provide, for each transfer: SiK\mathfrak{S}_i^K, TA,BK(e)\mathfrak{T}_{A,B}^K(e), and λP(TA,BKPAK)\lambda_P(\mathfrak{T}_{A,B}^K \in\mathbb{P}_A^K).

For each incoming transfer we MUST have a valid λP(TA,BKPAK)\lambda_P(\mathfrak{T}_{A,B}^K \in\mathbb{P}_A^K)

Additionally, we MUST verify the following for

  • We MUST have λP(TA,BKPAK).deliveryProof.totalAllotementAiK(e)accountAggregate.passiveAggregate.passiveAmountReceived\lambda_P(\mathfrak{T}_{A,B}^K \in\mathbb{P}_A^K)\texttt{.deliveryProof.totalAllotement} \ge \mathfrak{A}_i^K(e)\texttt{accountAggregate.passiveAggregate.passiveAmountReceived}
  • We MUST have λP(TA,BKPAK).deliveryProof.root==AiK(e).accountAggregate.passiveAgreggate.passiveTreeRoot\lambda_P(\mathfrak{T}_{A,B}^K \in\mathbb{P}_A^K)\texttt{.deliveryProof.root} == \mathfrak{A}_i^K(e)\texttt{.accountAggregate.passiveAgreggate.passiveTreeRoot}

Therefore, PjK\mathbb{P}_j^K is expecting his account to have been credited by at least Ti,jK(e).amount\mathfrak{T}_{i,j}^K(e)\texttt{.amount}. Formally, the following inequality MUST hold: AiK(e).accountAggregate.passiveAggregate.passiveAmountReceivediTi,jK(e).amount \mathfrak{A}_i^K(e)\texttt{.accountAggregate.passiveAggregate.passiveAmountReceived} \ge \sum^{\forall i} \mathfrak{T}_{i,j}^K(e)\texttt{.amount}

If the inequality doesn't hold, the user MUST initiate a delivery challenge for the incoming transfer missing in the passive set AiK(e).accountAggregate.passiveAggregate.passiveTreeRoot\mathfrak{A}_i^K(e)\texttt{.accountAggregate.passiveAggregate.passiveTreeRoot}

Swap enactment verification

TODO

Sub-protocols

Registration protocol

A new user AA wishes to join the commit-chain for asset KK. He is required to create an initial active state with the field nocustContractAddressChecksum set to the checksum of the commit-chain smart contract address, tokenContractAddressChecksum set to the checksum of the address of the asset token KK, userAddressChecksum the address of user AA, accountIndex must be set to 0, eonNumber to the current eon, and spent and gained to zero.

Then the user MUST produce signatures according to NSPEC004 over the checksum of the active state and post it to the operator registration endpoint according to the model:

interface operatorRegistrationEndpoint {
    token: string            // Asset token address
    address: string            // Registering address
    authorization: string   // Registration signature
}

The signature is in the RSV form (See NSPEC004). The addresses are the 40 bytes addresses as hexadecimal string without 0x prefix.

Transfer protocol

Given an account PAK\mathbb{P}_A^K sending amount MM of an asset token KK and to recipient account PBK\mathbb{P}_B^K.

In order to make a transfer the sender MUST make all of the following checks:

  • PAK\mathbb{P}_A^K and PBK\mathbb{P}_B^K MUST belong to the same commit-chain.
  • The commit-chain MUST not be in recovery.
  • The sender MUST have sufficient balance, balance(PA)>Mbalance(\mathbb{P}_A) \gt M
  • Account AA MUST be different from account BB
  • Pi\mathbb{P}_i must not have a pending/non-finalized swap
  • The recipient PAK \mathbb{P}_A^K MUST be registered with the commit chain. To ensure that the recipient is registered the sender MUST verify at least one state update (Previous eon or current eon)

We describe the protocol for an account to authorize an outgoing transaction.

Transfer object

We need to construct TA,BK(e)\mathfrak{T}_{A,B}^K(e). First, the sender needs to create a Transfer object as described in NSPEC005. The fields counterpartyAddressChecksum and amount are set according to the address of PA\mathbb{P}_A and MM respectively. index is set to the number of the transfers/swaps made in the round. Meaning, 0 if it is the first transfer or swap, 1 for the second transfer or swap, etc.. This will later represent the leaf index in the active tree. transferTag is set as in NSPEC005. TA,BK(e).transferTag.offset\mathfrak{T}_{A,B}^K(e).\texttt{transferTag.offset} MUST be set to the max Uint256 value or 225612^{256} - 1.

nonce SHOULD be set to a random number chosen by the transaction sender. The transaction sender MAY set the nonce to a previously agreed value with the recipient for transaction authentication purposes. The nonce MUST be unique for the account in the eon.

If the current last element of the active tree set is not finalized, the user MUST first finalize this transfer. To finalize the transfer, the user needs to replace 225612^{256} - 1 in the passive Marker by its actual value provided by the operator. See transaction model for payment. for more information.

Once the last element in the active tree was finalized the new transfer created previously MUST be appended to the active tree of the sender account.

Active state

The sender MUST construct a new active state object SiK\mathfrak{S}_i^K with the account details and the new transfer created above. tokenContractAddressChecksum MUST be set to the address of the asset token KK. activeTreeRoot MUST be set to the root of the active tree with all transfers since the beginning of the around and the newly created transfer above. The spent value must be incremented by MM. The gain value MUST keep its previous value (If this is the first outgoing transfer in the eon and no swap was made, spent will be equal to MM and gain to zero).

Balance Marker

The sender MUST construct a balance marker MiK(e)\mathfrak{M}_i^K(e) as specified in NSPEC005.

nocustContractAddressChecksum, tokenContractAddressChecksum, and userAddressChecksum are the same as in the active state of the account. eonNumber is set to the current eon number. balance is set to the balance after the transfer, calculated according to the balance formula .

Signatures

The sender MUST produce signatures according to NSPEC004.

The 2 following signatures MUST be produced with the private key of the account Pi\mathbb{P}_i:

  • SIGi(SiK)SIG_{i}(\mathfrak{S}_i^K) A signature over the checksum of SiK \mathfrak{S}_i^K the active state object created above.
  • SIGi(MiK)SIG_{i}(\mathfrak{M}_i^K) A signature over the checksum of the BalanceMarker created above.

Sending the authorization to the operator

The sender of the transfer needs to send to the operator the signatures and the transfer details

Operator response

The operator must ratify the new active state including the new transfer by sending to the client his signature SIGop(SiK)SIG_{op}(\mathfrak{S}_i^K) . The user MUST verify this signature to consider that the operator committed to the transfer.

Communicating the transfer to the recipient

To ensure that the operator delivers the transfer (recipient account is credited) in the upcoming checkpoint. The transaction sender MUST inform the recipient of the transaction and send him directly SIGi(SiK)SIG_{i}(\mathfrak{S}_i^K) , SIGop(SiK)SIG_{op}(\mathfrak{S}_i^K) , SiK\mathfrak{S}_i^K, TA,BK(e)\mathfrak{T}_{A,B}^K(e), and λP(TA,BKPAK)\lambda_P(\mathfrak{T}_{A,B}^K \in\mathbb{P}_A^K). These values will allow the recipient to challenge delivery of the transfer if needed in the future.

Swap protocol

For a new swap XiX,Y(e)\mathfrak{X}_i^{X,Y}(e). Pi(e)\mathbb{P}_i(e) wishes to exchange amount MM of asset token XX for NN amount of asset token YY. We call PiX\mathbb{P}_i^X the debited account and PiY\mathbb{P}_i^Y the credited account.

In order to make such a swap the user MUST make all of the following checks:

  • The commit-chain MUST not be in recovery.
  • The debited wallet MUST have an exact balance of MM
  • Asset token XX and YY MUST be different
  • PiX\mathbb{P}_i^X and PiY\mathbb{P}_i^Y MUST not have a pending/non-finalized swap.
  • PiX\mathbb{P}_i^X and PiY\mathbb{P}_i^Y MUST be registered with the commit chain.
  • PiX\mathbb{P}_i^X and PiY\mathbb{P}_i^Y MUST belong to the same commit-chain.
  • PiX\mathbb{P}_i^X and PiY\mathbb{P}_i^Y MUST have the same owner (same address).

We describe the protocol for an account to authorize a swap.

Swap object

We first need to construct XiX,Y(e)\mathfrak{X}_i^{X,Y}(e). sellTokenChecksum and buyTokenChecksum are set according to XX and YY respectively. index is set to the leaf index of the credited account. sellAmount and buyAmount are set according to MM and NN respectively. startingBalance is set to AiX(e) A_i^X(e) . nonce is set to a random number.

Active states

We need to prepare 2 active states, SiX\mathfrak{S}_i^X and SiY\mathfrak{S}_i^Y. The swap object MUST be appended to the active trees of both active states, the activeTreeRoot MUST be set accordingly for each. SiX.spent\mathfrak{S}_i^X\texttt{.spent} is incremented by MM (SiY.gained\mathfrak{S}_i^Y\texttt{.gained} is kept unchanged).

Balance markers

The user MUST construct 2 balance markers MiX(e)\mathfrak{M}_i^X(e) and MiY(e)\mathfrak{M}_i^Y(e). one for each active state SiX\mathfrak{S}_i^X and SiY\mathfrak{S}_i^Y.

nocustContractAddressChecksum, tokenContractAddressChecksum, and userAddressChecksum are the same as in their respective active states. eonNumber is set to the current eon number. The balance field of MiX(e)\mathfrak{M}_i^X(e) is set to the current balance after the swap calculated according to the balance formula. For MiY(e)\mathfrak{M}_i^Y(e) the balance field MUST be set to 0.

Signatures

The sender MUST produce signatures according to NSPEC004.

The 4 following signature MUST be produced with the private key of the account Pi\mathbb{P}_i:

  • SIGi(SiX)SIG_{i}(\mathfrak{S}_i^X)
  • SIGi(SiY)SIG_{i}(\mathfrak{S}_i^Y)
  • SIGi(MiX)SIG_{i}(\mathfrak{M}_i^X)
  • SIGi(MiY)SIG_{i}(\mathfrak{M}_i^Y)

Send the authorizations to the operator

The sender of the swap needs to send to the operator the signatures and the swap details

Operator response

The operator must ratify the new active states including the new swap by sending to the client his signatures SIGop(SiX)SIG_{op}(\mathfrak{S}_i^X) and SIGop(SiY)SIG_{op}(\mathfrak{S}_i^Y) . The user MUST verify the signatures to consider that the operator committed to the swap. The user account is now blocked, the user MUST not make any outgoing transfer or additional swaps before the pending swap get finalized.

Swap finalization

To unblock the account and spend freely the amount gained in the credited wallet the user need to finalize the swap. To complete the finalization the user and the operator need to produce and sign new active states for the credited and debited wallet. In this new active state we need updated spent and gained values that reflect the result of the matching of the swap.

The user and the operator must come to an agreement on what should be spent and gained in the final active states. The user must make sure that he is getting a fair price. For the final active states SiX \mathfrak{S}_i^X (debited) and SiY \mathfrak{S}_i^Y (credited) the user MUST ensure that: M(SiY.gainedSiY.spent)N(SiX.spentSiX.gained) M(\mathfrak{S}_i^Y\texttt{.gained} - \mathfrak{S}_i^Y\texttt{.spent})\ge N(\mathfrak{S}_i^X\texttt{.spent} - \mathfrak{S}_i^X\texttt{.gained})

In the final active state the startingBalance field of the swap MUST be set to 225612^{256} - 1. The 2 active states MUST be signed by both party and the signatures MUST be shared with each-other.

Note that the user MAY or MAY NOT finalize a swap. If the swap is not finalized during an eon the operator can still match the swap without the user approval. In this case, the swap delivery challenge will ensure that the user get a fair price for the swap.

Deposit protocol

For depositing funds to the NOCUST commit-chain the user is required to call the deposit function on the NOCUST smart-contract. The contract requires 3 parameters. The token parameter is the contract address of the asset token to deposit. If the user wishes to deposit the parent-chain native asset token, the token must be set to the NOCUST smart-contract address. The beneficiary is the address of the account to credit, and amount is the amount of the asset token to deposit. The amount field MUST be equal to the amount of funds send together with the deposit call. Note that some asset token standard might require transfers from third party contracts to be authorized (For Ethereum see the approve function).

The NOCUST operator will credit the commit-chain account after some period of time according to the BLOCK_CONFIRMATION variable.

Withdrawal protocol

The process of sending funds from the commit-chain back to the parent-chain is a 2 step process separated by a period of a complete eon. The withdrawal is first requested and after a full eon has passed it can be confirmed.

Withdrawal request

To initiate the withdrawal the user needs to call the function requestWithdrawal. The function require to pass the token to withdraw, the withdrawalAmount and the proof of exclusive balance allotment of the account for round e1e-1. The sender of the request MUST be the withdrawing account.

We have the following condition on the balance:

  • The amount to withdraw MUST be inferior to the initial allotment in the previous checkpoint:

    withdrawalAmountAi(e2) \texttt{withdrawalAmount} \le A_i(e-2)

  • The amount to withdraw MUST be inferior the current balance or the withdrawal will be subject slashing.

Note that the full balance is not always available to withdraw. Only the balance that was included in a checkpoint that went through a full round. Therefore, it is required to wait to withdraw freshly received funds.

After the withdrawal request transaction is confirmed on the parent-chain, we increment WRiK(e)\mathbb{WR}_i^K(e)

Withdrawal confirmation

After some period time, the withdrawal can be confirmed and the funds will be transferred from the NOCUST smart-contract to the the address on the parent-chain. A withdrawal initiated during eon ee can only be confirmed in eon e+2e+2. It is required to call the function confirmWithdrawal with the token and recipient parameter.

The confirmWithdrawal will confirm all the pending withdrawals.

After the withdrawal confirmation transaction is confirmed on the parent-chain, we increment WCiK(e)\mathbb{WC}_i^K(e)

Example Implementation

NOCUST client library: https://github.com/liquidity-network/nocust-client-library

History

14.10.2019 - Added deposit and withdrawal 30.10.2019 - Added new eons

All content herein is licensed under GPL License.

results matching ""

    No results matching ""