- L1: layer 1 blockchain, it is BNB Smart Chain.
- Rollup: Zk Rollup based layer-2 network, it is ZkBNB.
- Owner: A user get a L2 account.
- Committer: Entity executing transactions and producing consecutive blocks on L2.
- Eventually: happening within finite time.
- Assets in L2: Assets in L2 smart contract controlled by owners.
- L2 Key: Owner's private key used to send transaction on L2.
- MiMC Signature: The result of signing the owner's message, using his private key, used in L2 internal transactions.
The current implementation we still use EDDSA as the signature scheme, we will soon support switch to EDCSA.
ZkBNB implements a ZK rollup protocol (in short "rollup" below) for:
- BNB and BEP20 fungible token deposit and transfer
- BEP721 non-fungible token deposit and transfer
- mint BEP721 non-fungible tokens on L2
- NFT-marketplace on L2
General rollup workflow is as follows:
- Users can become owners in rollup by calling registerZNS in L1 to register a short name for L2;
- Owners can transfer assets to each other, mint NFT on L2;
- Owners can withdraw assets under their control to any L1 address.
Rollup operation requires the assistance of a committer, who rolls transactions together, also a prover who computes a zero-knowledge proof of the correct state transition, and affects the state transition by interacting with the rollup contract.
We assume that 1 Chunk
= 32 bytes.
Type | Size(Byte) | Type | Comment |
---|---|---|---|
AccountIndex | 4 | uint32 | Incremented number of accounts in Rollup. New account will have the next free id. Max value is 2^32 - 1 = 4.294967295 × 10^9 |
AssetId | 2 | uint16 | Incremented number of tokens in Rollup, max value is 65535 |
PackedTxAmount | 5 | int64 | Packed transactions amounts are represented with 40 bit (5 byte) values, encoded as mantissa × 10^exponent where mantissa is represented with 35 bits, exponent is represented with 5 bits. This gives a range from 0 to 34359738368 × 10^31, providing 10 full decimal digit precision. |
PackedFee | 2 | uint16 | Packed fees must be represented with 2 bytes: 5 bit for exponent, 11 bit for mantissa. |
StateAmount | 16 | *big.Int | State amount is represented as uint128 with a range from 0 to ~3.4 × 10^38. It allows to represent up to 3.4 × 10^20 "units" if standard Ethereum's 18 decimal symbols are used. This should be a sufficient range. |
Nonce | 4 | uint32 | Nonce is the total number of executed transactions of the account. In order to apply the update of this state, it is necessary to indicate the current account nonce in the corresponding transaction, after which it will be automatically incremented. If you specify the wrong nonce, the changes will not occur. |
EthAddress | 20 | string | To make an BNB Smart Chain address from the BNB Smart Chain's public key, all we need to do is to apply Keccak-256 hash function to the key and then take the last 20 bytes of the result. |
Signature | 64 | []byte | Based on EDDSA. |
HashValue | 32 | string | hash value based on MiMC |
Mantissa and exponent parameters used in ZkBNB:
amount = mantissa * radix^{exponent}
Type | Exponent bit width | Mantissa bit width | Radix |
---|---|---|---|
PackedTxAmount | 5 | 35 | 10 |
PackedFee | 5 | 11 | 10 |
We have 3 unique trees: AccountTree(32)
, NftTree(40)
and one sub-tree AssetTree(16)
which
belongs to AccountTree(32)
. The empty leaf for all the trees is just set every attribute as 0
for every node.
AccountTree
is used for storing all accounts info and the node of the account tree is:
type AccountNode struct{
AccountNameHash string // bytes32
PubKey string // bytes32
Nonce int64
CollectionNonce int64
AssetRoot string // bytes32
}
Leaf hash computation:
func ComputeAccountLeafHash(
accountNameHash string,
pk string,
nonce int64,
collectionNonce int64,
assetRoot []byte,
) (hashVal []byte, err error) {
hFunc := mimc.NewMiMC()
var buf bytes.Buffer
buf.Write(common.FromHex(accountNameHash))
err = util.PaddingPkIntoBuf(&buf, pk)
if err != nil {
logx.Errorf("[ComputeAccountAssetLeafHash] unable to write pk into buf: %s", err.Error())
return nil, err
}
util.PaddingInt64IntoBuf(&buf, nonce)
util.PaddingInt64IntoBuf(&buf, collectionNonce)
buf.Write(assetRoot)
hFunc.Reset()
hFunc.Write(buf.Bytes())
hashVal = hFunc.Sum(nil)
return hashVal, nil
}
AssetTree
is sub-tree of AccountTree
and it stores all the assets balance
, and offerCanceledOrFinalized
. The node of asset tree is:
type AssetNode struct {
Balance string
OfferCanceledOrFinalized string // uint128
}
Leaf hash computation:
func ComputeAccountAssetLeafHash(
balance string,
offerCanceledOrFinalized string,
) (hashVal []byte, err error) {
hFunc := mimc.NewMiMC()
var buf bytes.Buffer
err = util.PaddingStringBigIntIntoBuf(&buf, balance)
if err != nil {
logx.Errorf("[ComputeAccountAssetLeafHash] invalid balance: %s", err.Error())
return nil, err
}
err = util.PaddingStringBigIntIntoBuf(&buf, offerCanceledOrFinalized)
if err != nil {
logx.Errorf("[ComputeAccountAssetLeafHash] invalid balance: %s", err.Error())
return nil, err
}
hFunc.Write(buf.Bytes())
return hFunc.Sum(nil), nil
}
NftTree
is used for storing all the NFTs and the node info is:
type NftNode struct {
CreatorAccountIndex int64
OwnerAccountIndex int64
NftContentHash string
NftL1Address string
NftL1TokenId string
CreatorTreasuryRate int64
CollectionId int64 // 32 bit
}
Leaf hash computation:
func ComputeNftAssetLeafHash(
creatorAccountIndex int64,
ownerAccountIndex int64,
nftContentHash string,
nftL1Address string,
nftL1TokenId string,
creatorTreasuryRate int64,
collectionId int64,
) (hashVal []byte, err error) {
hFunc := mimc.NewMiMC()
var buf bytes.Buffer
util.PaddingInt64IntoBuf(&buf, creatorAccountIndex)
util.PaddingInt64IntoBuf(&buf, ownerAccountIndex)
buf.Write(ffmath.Mod(new(big.Int).SetBytes(common.FromHex(nftContentHash)), curve.Modulus).FillBytes(make([]byte, 32)))
err = util.PaddingAddressIntoBuf(&buf, nftL1Address)
if err != nil {
logx.Errorf("[ComputeNftAssetLeafHash] unable to write address to buf: %s", err.Error())
return nil, err
}
err = util.PaddingStringBigIntIntoBuf(&buf, nftL1TokenId)
if err != nil {
logx.Errorf("[ComputeNftAssetLeafHash] unable to write big int to buf: %s", err.Error())
return nil, err
}
util.PaddingInt64IntoBuf(&buf, creatorTreasuryRate)
util.PaddingInt64IntoBuf(&buf, collectionId)
hFunc.Write(buf.Bytes())
hashVal = hFunc.Sum(nil)
return hashVal, nil
}
StateRoot
is the final root that shows the final layer-2 state and will be stored on L1. It is computed by the root of AccountTree
and NftTree
. The computation of StateRoot
works as follows:
StateRoot = MiMC(AccountRoot || NftRoot)
func ComputeStateRootHash(
accountRoot []byte,
nftRoot []byte,
) []byte {
hFunc := mimc.NewMiMC()
hFunc.Write(accountRoot)
hFunc.Write(nftRoot)
return hFunc.Sum(nil)
}
ZkBNB transactions are divided into Rollup transactions (initiated inside Rollup by a Rollup account) and Priority operations (initiated on the BSC by an BNB Smart Chain account).
Rollup transactions:
- EmptyTx
- Transfer
- Withdraw
- CreateCollection
- MintNft
- TransferNft
- AtomicMatch
- CancelOffer
- WithdrawNft
Priority operations:
- RegisterZNS
- Deposit
- DepositNft
- FullExit
- FullExitNft
- User creates a
Transaction
or aPriority operation
. - After processing this request, committer creates a
Rollup operation
and adds it to the block. - Once the block is complete, sender submits it to the ZkBNB smart contract as a block commitment.
Part of the logic of some
Rollup transaction
is checked by the smart contract. - The proof for the block is submitted to the ZkBNB smart contract as the block verification. If the verification succeeds, the new state is considered finalized.
No effects.
Chunks | Significant bytes |
---|---|
1 | 1 |
Field | Size(byte) | Value/type | Description |
---|---|---|---|
TxType | 1 | 0x00 |
Transaction type |
No user transaction
This is a layer-1 transaction and a user needs to call this method first to register a layer-2 account.
Chunks | Significant bytes |
---|---|
6 | 101 |
Name | Size(byte) | Comment |
---|---|---|
TxType | 1 | transaction type |
AccountIndex | 4 | unique account index |
AccountName | 32 | account name |
AccountNameHash | 32 | hash value of the account name |
PubKeyX | 32 | layer-2 account's public key X |
PubKeyY | 32 | layer-2 account's public key Y |
func ConvertTxToRegisterZNSPubData(oTx *tx.Tx) (pubData []byte, err error) {
if oTx.TxType != commonTx.TxTypeRegisterZns {
logx.Errorf("[ConvertTxToRegisterZNSPubData] invalid tx type")
return nil, errors.New("[ConvertTxToRegisterZNSPubData] invalid tx type")
}
// parse tx
txInfo, err := commonTx.ParseRegisterZnsTxInfo(oTx.TxInfo)
if err != nil {
logx.Errorf("[ConvertTxToRegisterZNSPubData] unable to parse tx info: %s", err.Error())
return nil, err
}
var buf bytes.Buffer
buf.WriteByte(uint8(oTx.TxType))
buf.Write(Uint32ToBytes(uint32(txInfo.AccountIndex)))
chunk := SuffixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(chunk)
buf.Write(PrefixPaddingBufToChunkSize(AccountNameToBytes32(txInfo.AccountName)))
buf.Write(PrefixPaddingBufToChunkSize(txInfo.AccountNameHash))
pk, err := ParsePubKey(txInfo.PubKey)
if err != nil {
logx.Errorf("[ConvertTxToRegisterZNSPubData] unable to parse pub key: %s", err.Error())
return nil, err
}
// because we can get Y from X, so we only need to store X is enough
buf.Write(PrefixPaddingBufToChunkSize(pk.A.X.Marshal()))
buf.Write(PrefixPaddingBufToChunkSize(pk.A.Y.Marshal()))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
return buf.Bytes(), nil
}
Name | Size(byte) | Comment |
---|---|---|
AccountName | 32 | account name |
Owner | 20 | account layer-1 address |
PubKey | 32 | layer-2 account's public key |
func VerifyRegisterZNSTx(
api API, flag Variable,
tx RegisterZnsTxConstraints,
accountsBefore [NbAccountsPerTx]AccountConstraints,
) (pubData [PubDataSizePerTx]Variable) {
pubData = CollectPubDataFromRegisterZNS(api, tx)
CheckEmptyAccountNode(api, flag, accountsBefore[0])
return pubData
}
This is a layer-1 transaction and is used for depositing assets into the layer-2 account.
Chunks | Significant bytes |
---|---|
6 | 55 |
Name | Size(byte) | Comment |
---|---|---|
TxType | 1 | transaction type |
AccountIndex | 4 | account index |
AssetId | 2 | asset index |
AssetAmount | 16 | state amount |
AccountNameHash | 32 | account name hash |
func ConvertTxToDepositPubData(oTx *tx.Tx) (pubData []byte, err error) {
if oTx.TxType != commonTx.TxTypeDeposit {
logx.Errorf("[ConvertTxToDepositPubData] invalid tx type")
return nil, errors.New("[ConvertTxToDepositPubData] invalid tx type")
}
// parse tx
txInfo, err := commonTx.ParseDepositTxInfo(oTx.TxInfo)
if err != nil {
logx.Errorf("[ConvertTxToDepositPubData] unable to parse tx info: %s", err.Error())
return nil, err
}
var buf bytes.Buffer
buf.WriteByte(uint8(oTx.TxType))
buf.Write(Uint32ToBytes(uint32(txInfo.AccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.AssetId)))
buf.Write(Uint128ToBytes(txInfo.AssetAmount))
chunk1 := SuffixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(chunk1)
buf.Write(PrefixPaddingBufToChunkSize(txInfo.AccountNameHash))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
return buf.Bytes(), nil
}
Name | Size(byte) | Comment |
---|---|---|
AccountNameHash | 32 | account name hash |
Name | Size(byte) | Comment |
---|---|---|
AssetAddress | 20 | asset layer-1 address |
Amount | 13 | asset layer-1 amount |
AccountNameHash | 32 | account name hash |
func VerifyDepositTx(
api API, flag Variable,
tx DepositTxConstraints,
accountsBefore [NbAccountsPerTx]AccountConstraints,
) (pubData [PubDataSizePerTx]Variable) {
pubData = CollectPubDataFromDeposit(api, tx)
// verify params
IsVariableEqual(api, flag, tx.AccountNameHash, accountsBefore[0].AccountNameHash)
IsVariableEqual(api, flag, tx.AccountIndex, accountsBefore[0].AccountIndex)
IsVariableEqual(api, flag, tx.AssetId, accountsBefore[0].AssetsInfo[0].AssetId)
return pubData
}
This is a layer-1 transaction and is used for depositing NFTs into the layer-2 account.
Chunks | Significant bytes |
---|---|
6 | 134 |
Name | Size(byte) | Comment |
---|---|---|
TxType | 1 | transaction type |
AccountIndex | 4 | account index |
NftIndex | 5 | unique index of a nft |
NftL1Address | 20 | nft layer-1 address |
CreatorAccountIndex | 4 | creator account index |
CreatorTreasuryRate | 2 | creator treasury rate |
CollectionId | 2 | collection id |
NftContentHash | 32 | nft content hash |
NftL1TokenId | 32 | nft layer-1 token id |
AccountNameHash | 32 | account name hash |
func ConvertTxToDepositNftPubData(oTx *tx.Tx) (pubData []byte, err error) {
if oTx.TxType != commonTx.TxTypeDepositNft {
logx.Errorf("[ConvertTxToDepositNftPubData] invalid tx type")
return nil, errors.New("[ConvertTxToDepositNftPubData] invalid tx type")
}
// parse tx
txInfo, err := commonTx.ParseDepositNftTxInfo(oTx.TxInfo)
if err != nil {
logx.Errorf("[ConvertTxToDepositNftPubData] unable to parse tx info: %s", err.Error())
return nil, err
}
var buf bytes.Buffer
buf.WriteByte(uint8(oTx.TxType))
buf.Write(Uint32ToBytes(uint32(txInfo.AccountIndex)))
buf.Write(Uint40ToBytes(txInfo.NftIndex))
buf.Write(AddressStrToBytes(txInfo.NftL1Address))
chunk1 := SuffixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(Uint32ToBytes(uint32(txInfo.CreatorAccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.CreatorTreasuryRate)))
buf.Write(Uint16ToBytes(uint16(txInfo.CollectionId)))
chunk2 := PrefixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(chunk1)
buf.Write(chunk2)
buf.Write(PrefixPaddingBufToChunkSize(txInfo.NftContentHash))
buf.Write(Uint256ToBytes(txInfo.NftL1TokenId))
buf.Write(PrefixPaddingBufToChunkSize(txInfo.AccountNameHash))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
return buf.Bytes(), nil
}
Name | Size(byte) | Comment |
---|---|---|
AccountNameHash | 32 | account name hash |
AssetAddress | 20 | nft contract layer-1 address |
NftTokenId | 32 | nft layer-1 token id |
func VerifyDepositNftTx(
api API,
flag Variable,
tx DepositNftTxConstraints,
accountsBefore [NbAccountsPerTx]AccountConstraints,
nftBefore NftConstraints,
) (pubData [PubDataSizePerTx]Variable) {
pubData = CollectPubDataFromDepositNft(api, tx)
// verify params
// check empty nft
CheckEmptyNftNode(api, flag, nftBefore)
// account index
IsVariableEqual(api, flag, tx.AccountIndex, accountsBefore[0].AccountIndex)
// account name hash
IsVariableEqual(api, flag, tx.AccountNameHash, accountsBefore[0].AccountNameHash)
return pubData
}
This is a layer-2 transaction and is used for transferring assets in the layer-2 network.
Chunks | Significant bytes |
---|---|
6 | 56 |
Name | Size(byte) | Comment |
---|---|---|
TxType | 1 | transaction type |
FromAccountIndex | 4 | from account index |
ToAccountIndex | 4 | receiver account index |
AssetId | 2 | asset index |
AssetAmount | 5 | packed asset amount |
GasFeeAccountIndex | 4 | gas fee account index |
GasFeeAssetId | 2 | gas fee asset id |
GasFeeAssetAmount | 2 | packed fee amount |
CallDataHash | 32 | call data hash |
func ConvertTxToTransferPubData(oTx *tx.Tx) (pubData []byte, err error) {
if oTx.TxType != commonTx.TxTypeTransfer {
logx.Errorf("[ConvertTxToTransferPubData] invalid tx type")
return nil, errors.New("[ConvertTxToTransferPubData] invalid tx type")
}
// parse tx
txInfo, err := commonTx.ParseTransferTxInfo(oTx.TxInfo)
if err != nil {
logx.Errorf("[ConvertTxToDepositPubData] unable to parse tx info: %s", err.Error())
return nil, err
}
var buf bytes.Buffer
buf.WriteByte(uint8(oTx.TxType))
buf.Write(Uint32ToBytes(uint32(txInfo.FromAccountIndex)))
buf.Write(Uint32ToBytes(uint32(txInfo.ToAccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.AssetId)))
packedAmountBytes, err := AmountToPackedAmountBytes(txInfo.AssetAmount)
if err != nil {
logx.Errorf("[ConvertTxToDepositPubData] unable to convert amount to packed amount: %s", err.Error())
return nil, err
}
buf.Write(packedAmountBytes)
buf.Write(Uint32ToBytes(uint32(txInfo.GasAccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.GasFeeAssetId)))
packedFeeBytes, err := FeeToPackedFeeBytes(txInfo.GasFeeAssetAmount)
if err != nil {
logx.Errorf("[ConvertTxToDepositPubData] unable to convert amount to packed fee amount: %s", err.Error())
return nil, err
}
buf.Write(packedFeeBytes)
chunk := SuffixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(chunk)
buf.Write(PrefixPaddingBufToChunkSize(txInfo.CallDataHash))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
pubData = buf.Bytes()
return pubData, nil
}
type TransferTxInfo struct {
FromAccountIndex int64
ToAccountIndex int64
ToAccountNameHash string
AssetId int64
AssetAmount *big.Int
GasAccountIndex int64
GasFeeAssetId int64
GasFeeAssetAmount *big.Int
Memo string
CallData string
CallDataHash []byte
ExpiredAt int64
Nonce int64
Sig []byte
}
func VerifyTransferTx(
api API, flag Variable,
tx *TransferTxConstraints,
accountsBefore [NbAccountsPerTx]AccountConstraints,
) (pubData [PubDataSizePerTx]Variable) {
// collect pub-data
pubData = CollectPubDataFromTransfer(api, *tx)
// verify params
// account index
IsVariableEqual(api, flag, tx.FromAccountIndex, accountsBefore[0].AccountIndex)
IsVariableEqual(api, flag, tx.ToAccountIndex, accountsBefore[1].AccountIndex)
IsVariableEqual(api, flag, tx.GasAccountIndex, accountsBefore[2].AccountIndex)
// account name hash
IsVariableEqual(api, flag, tx.ToAccountNameHash, accountsBefore[1].AccountNameHash)
// asset id
IsVariableEqual(api, flag, tx.AssetId, accountsBefore[0].AssetsInfo[0].AssetId)
IsVariableEqual(api, flag, tx.AssetId, accountsBefore[1].AssetsInfo[0].AssetId)
// gas asset id
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[0].AssetsInfo[1].AssetId)
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[2].AssetsInfo[0].AssetId)
// should have enough balance
tx.AssetAmount = UnpackAmount(api, tx.AssetAmount)
tx.GasFeeAssetAmount = UnpackFee(api, tx.GasFeeAssetAmount)
//tx.GasFeeAssetAmount = UnpackFee(api, tx.GasFeeAssetAmount)
IsVariableLessOrEqual(api, flag, tx.AssetAmount, accountsBefore[0].AssetsInfo[0].Balance)
IsVariableLessOrEqual(api, flag, tx.GasFeeAssetAmount, accountsBefore[0].AssetsInfo[1].Balance)
return pubData
}
This is a layer-2 transaction and is used for withdrawing assets from the layer-2 to the layer-1.
Chunks | Significant bytes |
---|---|
6 | 51 |
Name | Size(byte) | Comment |
---|---|---|
TxType | 1 | transaction type |
AccountIndex | 4 | from account index |
ToAddress | 20 | layer-1 receiver address |
AssetId | 2 | asset index |
AssetAmount | 16 | state amount |
GasFeeAccountIndex | 4 | gas fee account index |
GasFeeAssetId | 2 | gas fee asset id |
GasFeeAssetAmount | 2 | packed fee amount |
func ConvertTxToWithdrawPubData(oTx *tx.Tx) (pubData []byte, err error) {
if oTx.TxType != commonTx.TxTypeWithdraw {
logx.Errorf("[ConvertTxToWithdrawPubData] invalid tx type")
return nil, errors.New("[ConvertTxToWithdrawPubData] invalid tx type")
}
// parse tx
txInfo, err := commonTx.ParseWithdrawTxInfo(oTx.TxInfo)
if err != nil {
logx.Errorf("[ConvertTxToWithdrawPubData] unable to parse tx info: %s", err.Error())
return nil, err
}
var buf bytes.Buffer
buf.WriteByte(uint8(oTx.TxType))
buf.Write(Uint32ToBytes(uint32(txInfo.FromAccountIndex)))
buf.Write(AddressStrToBytes(txInfo.ToAddress))
buf.Write(Uint16ToBytes(uint16(txInfo.AssetId)))
chunk1 := SuffixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(Uint128ToBytes(txInfo.AssetAmount))
buf.Write(Uint32ToBytes(uint32(txInfo.GasAccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.GasFeeAssetId)))
packedFeeBytes, err := FeeToPackedFeeBytes(txInfo.GasFeeAssetAmount)
if err != nil {
logx.Errorf("[ConvertTxToDepositPubData] unable to convert amount to packed fee amount: %s", err.Error())
return nil, err
}
buf.Write(packedFeeBytes)
chunk2 := PrefixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(chunk1)
buf.Write(chunk2)
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
return buf.Bytes(), nil
}
type WithdrawTxInfo struct {
FromAccountIndex int64
AssetId int64
AssetAmount *big.Int
GasAccountIndex int64
GasFeeAssetId int64
GasFeeAssetAmount *big.Int
ToAddress string
ExpiredAt int64
Nonce int64
Sig []byte
}
func VerifyWithdrawTx(
api API, flag Variable,
tx *WithdrawTxConstraints,
accountsBefore [NbAccountsPerTx]AccountConstraints,
) (pubData [PubDataSizePerTx]Variable) {
pubData = CollectPubDataFromWithdraw(api, *tx)
// verify params
// account index
IsVariableEqual(api, flag, tx.FromAccountIndex, accountsBefore[0].AccountIndex)
IsVariableEqual(api, flag, tx.GasAccountIndex, accountsBefore[1].AccountIndex)
// asset id
IsVariableEqual(api, flag, tx.AssetId, accountsBefore[0].AssetsInfo[0].AssetId)
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[0].AssetsInfo[1].AssetId)
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[1].AssetsInfo[0].AssetId)
// should have enough assets
tx.GasFeeAssetAmount = UnpackFee(api, tx.GasFeeAssetAmount)
IsVariableLessOrEqual(api, flag, tx.AssetAmount, accountsBefore[0].AssetsInfo[0].Balance)
IsVariableLessOrEqual(api, flag, tx.GasFeeAssetAmount, accountsBefore[0].AssetsInfo[1].Balance)
return pubData
}
This is a layer-2 transaction and is used for creating a new collection
Chunks | Significant bytes |
---|---|
6 | 15 |
Name | Size(byte) | Comment |
---|---|---|
TxType | 1 | transaction type |
AccountIndex | 4 | account index |
CollectionId | 2 | collection index |
GasAccountIndex | 4 | gas account index |
GasFeeAssetId | 2 | asset id |
GasFeeAssetAmount | 2 | packed fee amount |
func ConvertTxToCreateCollectionPubData(oTx *tx.Tx) (pubData []byte, err error) {
if oTx.TxType != commonTx.TxTypeCreateCollection {
logx.Errorf("[ConvertTxToCreateCollectionPubData] invalid tx type")
return nil, errors.New("[ConvertTxToCreateCollectionPubData] invalid tx type")
}
// parse tx
txInfo, err := commonTx.ParseCreateCollectionTxInfo(oTx.TxInfo)
if err != nil {
logx.Errorf("[ConvertTxToCreateCollectionPubData] unable to parse tx info: %s", err.Error())
return nil, err
}
var buf bytes.Buffer
buf.WriteByte(uint8(oTx.TxType))
buf.Write(Uint32ToBytes(uint32(txInfo.AccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.CollectionId)))
buf.Write(Uint32ToBytes(uint32(txInfo.GasAccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.GasFeeAssetId)))
packedFeeBytes, err := FeeToPackedFeeBytes(txInfo.GasFeeAssetAmount)
if err != nil {
logx.Errorf("[ConvertTxToDepositPubData] unable to convert amount to packed fee amount: %s", err.Error())
return nil, err
}
buf.Write(packedFeeBytes)
chunk := SuffixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(chunk)
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
return buf.Bytes(), nil
}
type CreateCollectionTxInfo struct {
AccountIndex int64
CollectionId int64
Name string
Introduction string
GasAccountIndex int64
GasFeeAssetId int64
GasFeeAssetAmount *big.Int
ExpiredAt int64
Nonce int64
Sig []byte
}
func VerifyCreateCollectionTx(
api API, flag Variable,
tx *CreateCollectionTxConstraints,
accountsBefore [NbAccountsPerTx]AccountConstraints,
) (pubData [PubDataSizePerTx]Variable) {
pubData = CollectPubDataFromCreateCollection(api, *tx)
// verify params
IsVariableLessOrEqual(api, flag, tx.CollectionId, 65535)
// account index
IsVariableEqual(api, flag, tx.AccountIndex, accountsBefore[0].AccountIndex)
IsVariableEqual(api, flag, tx.GasAccountIndex, accountsBefore[1].AccountIndex)
// asset id
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[0].AssetsInfo[0].AssetId)
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[1].AssetsInfo[0].AssetId)
// collection id
IsVariableEqual(api, flag, tx.CollectionId, api.Add(accountsBefore[0].CollectionNonce, 1))
// should have enough assets
tx.GasFeeAssetAmount = UnpackAmount(api, tx.GasFeeAssetAmount)
IsVariableLessOrEqual(api, flag, tx.GasFeeAssetAmount, accountsBefore[0].AssetsInfo[0].Balance)
return pubData
}
This is a layer-2 transaction and is used for minting NFTs in the layer-2 network.
Chunks | Significant bytes |
---|---|
6 | 58 |
Name | Size(byte) | Comment |
---|---|---|
TxType | 1 | transaction type |
CreatorAccountIndex | 4 | creator account index |
ToAccountIndex | 4 | receiver account index |
NftIndex | 5 | unique nft index |
GasFeeAccountIndex | 4 | gas fee account index |
GasFeeAssetId | 2 | gas fee asset id |
GasFeeAssetAmount | 2 | packed fee amount |
CreatorTreasuryRate | 2 | creator treasury rate |
CollectionId | 2 | collection index |
NftContentHash | 32 | nft content hash |
func ConvertTxToMintNftPubData(oTx *tx.Tx) (pubData []byte, err error) {
if oTx.TxType != commonTx.TxTypeMintNft {
logx.Errorf("[ConvertTxToMintNftPubData] invalid tx type")
return nil, errors.New("[ConvertTxToMintNftPubData] invalid tx type")
}
// parse tx
txInfo, err := commonTx.ParseMintNftTxInfo(oTx.TxInfo)
if err != nil {
logx.Errorf("[ConvertTxToMintNftPubData] unable to parse tx info: %s", err.Error())
return nil, err
}
var buf bytes.Buffer
buf.WriteByte(uint8(oTx.TxType))
buf.Write(Uint32ToBytes(uint32(txInfo.CreatorAccountIndex)))
buf.Write(Uint32ToBytes(uint32(txInfo.ToAccountIndex)))
buf.Write(Uint40ToBytes(txInfo.NftIndex))
buf.Write(Uint32ToBytes(uint32(txInfo.GasAccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.GasFeeAssetId)))
packedFeeBytes, err := FeeToPackedFeeBytes(txInfo.GasFeeAssetAmount)
if err != nil {
logx.Errorf("[ConvertTxToDepositPubData] unable to convert amount to packed fee amount: %s", err.Error())
return nil, err
}
buf.Write(packedFeeBytes)
buf.Write(Uint16ToBytes(uint16(txInfo.CreatorTreasuryRate)))
buf.Write(Uint16ToBytes(uint16(txInfo.NftCollectionId)))
chunk := SuffixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(chunk)
buf.Write(PrefixPaddingBufToChunkSize(common.FromHex(txInfo.NftContentHash)))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
return buf.Bytes(), nil
}
type MintNftTxInfo struct {
CreatorAccountIndex int64
ToAccountIndex int64
ToAccountNameHash string
NftIndex int64
NftContentHash string
NftCollectionId int64
CreatorTreasuryRate int64
GasAccountIndex int64
GasFeeAssetId int64
GasFeeAssetAmount *big.Int
ExpiredAt int64
Nonce int64
Sig []byte
}
func VerifyMintNftTx(
api API, flag Variable,
tx *MintNftTxConstraints,
accountsBefore [NbAccountsPerTx]AccountConstraints, nftBefore NftConstraints,
) (pubData [PubDataSizePerTx]Variable) {
pubData = CollectPubDataFromMintNft(api, *tx)
// verify params
// check empty nft
CheckEmptyNftNode(api, flag, nftBefore)
// account index
IsVariableEqual(api, flag, tx.CreatorAccountIndex, accountsBefore[0].AccountIndex)
IsVariableEqual(api, flag, tx.ToAccountIndex, accountsBefore[1].AccountIndex)
IsVariableEqual(api, flag, tx.GasAccountIndex, accountsBefore[2].AccountIndex)
// account name hash
IsVariableEqual(api, flag, tx.ToAccountNameHash, accountsBefore[1].AccountNameHash)
// content hash
isZero := api.IsZero(tx.NftContentHash)
IsVariableEqual(api, flag, isZero, 0)
// gas asset id
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[0].AssetsInfo[0].AssetId)
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[2].AssetsInfo[0].AssetId)
// should have enough balance
tx.GasFeeAssetAmount = UnpackFee(api, tx.GasFeeAssetAmount)
IsVariableLessOrEqual(api, flag, tx.GasFeeAssetAmount, accountsBefore[0].AssetsInfo[0].Balance)
return pubData
}
This is a layer-2 transaction and is used for transferring NFTs to others in the layer-2 network.
Chunks | Significant bytes |
---|---|
6 | 54 |
Name | Size(byte) | Comment |
---|---|---|
TxType | 1 | transaction type |
FromAccountIndex | 4 | from account index |
ToAccountIndex | 4 | receiver account index |
NftIndex | 5 | unique nft index |
GasFeeAccountIndex | 4 | gas fee account index |
GasFeeAssetId | 2 | gas fee asset id |
GasFeeAssetAmount | 2 | packed fee amount |
CallDataHash | 32 | call data hash |
func ConvertTxToTransferNftPubData(oTx *tx.Tx) (pubData []byte, err error) {
if oTx.TxType != commonTx.TxTypeTransferNft {
logx.Errorf("[ConvertTxToMintNftPubData] invalid tx type")
return nil, errors.New("[ConvertTxToMintNftPubData] invalid tx type")
}
// parse tx
txInfo, err := commonTx.ParseTransferNftTxInfo(oTx.TxInfo)
if err != nil {
logx.Errorf("[ConvertTxToMintNftPubData] unable to parse tx info: %s", err.Error())
return nil, err
}
var buf bytes.Buffer
buf.WriteByte(uint8(oTx.TxType))
buf.Write(Uint32ToBytes(uint32(txInfo.FromAccountIndex)))
buf.Write(Uint32ToBytes(uint32(txInfo.ToAccountIndex)))
buf.Write(Uint40ToBytes(txInfo.NftIndex))
buf.Write(Uint32ToBytes(uint32(txInfo.GasAccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.GasFeeAssetId)))
packedFeeBytes, err := FeeToPackedFeeBytes(txInfo.GasFeeAssetAmount)
if err != nil {
logx.Errorf("[ConvertTxToDepositPubData] unable to convert amount to packed fee amount: %s", err.Error())
return nil, err
}
buf.Write(packedFeeBytes)
chunk := SuffixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(chunk)
buf.Write(PrefixPaddingBufToChunkSize(txInfo.CallDataHash))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
return buf.Bytes(), nil
}
type TransferNftTxInfo struct {
FromAccountIndex int64
ToAccountIndex int64
ToAccountNameHash string
NftIndex int64
GasAccountIndex int64
GasFeeAssetId int64
GasFeeAssetAmount *big.Int
CallData string
CallDataHash []byte
ExpiredAt int64
Nonce int64
Sig []byte
}
func VerifyTransferNftTx(
api API,
flag Variable,
tx *TransferNftTxConstraints,
accountsBefore [NbAccountsPerTx]AccountConstraints,
nftBefore NftConstraints,
) (pubData [PubDataSizePerTx]Variable) {
pubData = CollectPubDataFromTransferNft(api, *tx)
// verify params
// account index
IsVariableEqual(api, flag, tx.FromAccountIndex, accountsBefore[0].AccountIndex)
IsVariableEqual(api, flag, tx.ToAccountIndex, accountsBefore[1].AccountIndex)
IsVariableEqual(api, flag, tx.GasAccountIndex, accountsBefore[2].AccountIndex)
// account name
IsVariableEqual(api, flag, tx.ToAccountNameHash, accountsBefore[1].AccountNameHash)
// asset id
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[0].AssetsInfo[0].AssetId)
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[2].AssetsInfo[0].AssetId)
// nft info
IsVariableEqual(api, flag, tx.NftIndex, nftBefore.NftIndex)
IsVariableEqual(api, flag, tx.FromAccountIndex, nftBefore.OwnerAccountIndex)
// should have enough balance
tx.GasFeeAssetAmount = UnpackFee(api, tx.GasFeeAssetAmount)
IsVariableLessOrEqual(api, flag, tx.GasFeeAssetAmount, accountsBefore[0].AssetsInfo[0].Balance)
return pubData
}
This is a layer-2 transaction that will be used for buying or selling Nft in the layer-2 network.
Chunks | Significant bytes |
---|---|
6 | 44 |
Offer
:
Name | Size(byte) | Comment |
---|---|---|
Type | 1 | transaction type, 0 indicates this is a BuyNftOffer , 1 indicate this is a SellNftOffer |
OfferId | 3 | used to identify the offer |
AccountIndex | 4 | who want to buy/sell nft |
AssetId | 2 | the asset id which buyer/seller want to use pay for nft |
AssetAmount | 5 | the asset amount |
ListedAt | 8 | timestamp when the order is signed |
ExpiredAt | 8 | timestamp after which the order is invalid |
Sig | 64 | signature generated by buyer/seller_account_index's private key |
AtomicMatch
(below is the only info that will be uploaded on-chain):
Name | Size(byte) | Comment |
---|---|---|
TxType | 1 | transaction type |
SubmitterAccountIndex | 4 | submitter account index |
BuyerAccountIndex | 4 | buyer account index |
BuyerOfferId | 3 | used to identify the offer |
SellerAccountIndex | 4 | seller account index |
SellerOfferId | 3 | used to identify the offer |
AssetId | 2 | asset id |
AssetAmount | 5 | packed asset amount |
CreatorAmount | 5 | packed creator amount |
TreasuryAmount | 5 | packed treasury amount |
GasFeeAccountIndex | 4 | gas fee account index |
GasFeeAssetId | 2 | gas fee asset id |
GasFeeAssetAmount | 2 | packed fee amount |
func ConvertTxToAtomicMatchPubData(oTx *tx.Tx) (pubData []byte, err error) {
if oTx.TxType != commonTx.TxTypeAtomicMatch {
logx.Errorf("[ConvertTxToAtomicMatchPubData] invalid tx type")
return nil, errors.New("[ConvertTxToAtomicMatchPubData] invalid tx type")
}
// parse tx
txInfo, err := commonTx.ParseAtomicMatchTxInfo(oTx.TxInfo)
if err != nil {
logx.Errorf("[ConvertTxToAtomicMatchPubData] unable to parse tx info: %s", err.Error())
return nil, err
}
var buf bytes.Buffer
buf.WriteByte(uint8(oTx.TxType))
buf.Write(Uint32ToBytes(uint32(txInfo.AccountIndex)))
buf.Write(Uint32ToBytes(uint32(txInfo.BuyOffer.AccountIndex)))
buf.Write(Uint24ToBytes(txInfo.BuyOffer.OfferId))
buf.Write(Uint32ToBytes(uint32(txInfo.SellOffer.AccountIndex)))
buf.Write(Uint24ToBytes(txInfo.SellOffer.OfferId))
buf.Write(Uint40ToBytes(txInfo.BuyOffer.NftIndex))
buf.Write(Uint16ToBytes(uint16(txInfo.SellOffer.AssetId)))
chunk1 := SuffixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
packedAmountBytes, err := AmountToPackedAmountBytes(txInfo.BuyOffer.AssetAmount)
if err != nil {
logx.Errorf("[ConvertTxToDepositPubData] unable to convert amount to packed amount: %s", err.Error())
return nil, err
}
buf.Write(packedAmountBytes)
creatorAmountBytes, err := AmountToPackedAmountBytes(txInfo.CreatorAmount)
if err != nil {
logx.Errorf("[ConvertTxToDepositPubData] unable to convert amount to packed amount: %s", err.Error())
return nil, err
}
buf.Write(creatorAmountBytes)
treasuryAmountBytes, err := AmountToPackedAmountBytes(txInfo.TreasuryAmount)
if err != nil {
logx.Errorf("[ConvertTxToDepositPubData] unable to convert amount to packed amount: %s", err.Error())
return nil, err
}
buf.Write(treasuryAmountBytes)
buf.Write(Uint32ToBytes(uint32(txInfo.GasAccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.GasFeeAssetId)))
packedFeeBytes, err := FeeToPackedFeeBytes(txInfo.GasFeeAssetAmount)
if err != nil {
logx.Errorf("[ConvertTxToDepositPubData] unable to convert amount to packed fee amount: %s", err.Error())
return nil, err
}
buf.Write(packedFeeBytes)
chunk2 := PrefixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(chunk1)
buf.Write(chunk2)
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
return buf.Bytes(), nil
}
type OfferTxInfo struct {
Type int64
OfferId int64
AccountIndex int64
NftIndex int64
AssetId int64
AssetAmount *big.Int
ListedAt int64
ExpiredAt int64
TreasuryRate int64
Sig []byte
}
type AtomicMatchTxInfo struct {
AccountIndex int64
BuyOffer *OfferTxInfo
SellOffer *OfferTxInfo
GasAccountIndex int64
GasFeeAssetId int64
GasFeeAssetAmount *big.Int
CreatorAmount *big.Int
TreasuryAmount *big.Int
Nonce int64
ExpiredAt int64
Sig []byte
}
func VerifyAtomicMatchTx(
api API, flag Variable,
tx *AtomicMatchTxConstraints,
accountsBefore [NbAccountsPerTx]AccountConstraints,
nftBefore NftConstraints,
blockCreatedAt Variable,
hFunc MiMC,
) (pubData [PubDataSizePerTx]Variable, err error) {
pubData = CollectPubDataFromAtomicMatch(api, *tx)
// verify params
IsVariableEqual(api, flag, tx.BuyOffer.Type, 0)
IsVariableEqual(api, flag, tx.SellOffer.Type, 1)
IsVariableEqual(api, flag, tx.BuyOffer.AssetId, tx.SellOffer.AssetId)
IsVariableEqual(api, flag, tx.BuyOffer.AssetAmount, tx.SellOffer.AssetAmount)
IsVariableEqual(api, flag, tx.BuyOffer.NftIndex, tx.SellOffer.NftIndex)
IsVariableEqual(api, flag, tx.BuyOffer.AssetId, accountsBefore[1].AssetsInfo[0].AssetId)
IsVariableEqual(api, flag, tx.SellOffer.AssetId, accountsBefore[2].AssetsInfo[0].AssetId)
IsVariableEqual(api, flag, tx.SellOffer.AssetId, accountsBefore[3].AssetsInfo[0].AssetId)
IsVariableEqual(api, flag, tx.GasAccountIndex, accountsBefore[4].AccountIndex)
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[0].AssetsInfo[0].AssetId)
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[4].AssetsInfo[1].AssetId)
IsVariableLessOrEqual(api, flag, blockCreatedAt, tx.BuyOffer.ExpiredAt)
IsVariableLessOrEqual(api, flag, blockCreatedAt, tx.SellOffer.ExpiredAt)
IsVariableEqual(api, flag, nftBefore.NftIndex, tx.SellOffer.NftIndex)
IsVariableEqual(api, flag, tx.BuyOffer.TreasuryRate, tx.SellOffer.TreasuryRate)
// verify signature
hFunc.Reset()
buyOfferHash := ComputeHashFromOfferTx(tx.BuyOffer, hFunc)
hFunc.Reset()
notBuyer := api.IsZero(api.IsZero(api.Sub(tx.AccountIndex, tx.BuyOffer.AccountIndex)))
notBuyer = api.And(flag, notBuyer)
err = VerifyEddsaSig(notBuyer, api, hFunc, buyOfferHash, accountsBefore[1].AccountPk, tx.BuyOffer.Sig)
if err != nil {
return pubData, err
}
hFunc.Reset()
sellOfferHash := ComputeHashFromOfferTx(tx.SellOffer, hFunc)
hFunc.Reset()
notSeller := api.IsZero(api.IsZero(api.Sub(tx.AccountIndex, tx.SellOffer.AccountIndex)))
notSeller = api.And(flag, notSeller)
err = VerifyEddsaSig(notSeller, api, hFunc, sellOfferHash, accountsBefore[2].AccountPk, tx.SellOffer.Sig)
if err != nil {
return pubData, err
}
// verify account index
// submitter
IsVariableEqual(api, flag, tx.AccountIndex, accountsBefore[0].AccountIndex)
// buyer
IsVariableEqual(api, flag, tx.BuyOffer.AccountIndex, accountsBefore[1].AccountIndex)
// seller
IsVariableEqual(api, flag, tx.SellOffer.AccountIndex, accountsBefore[2].AccountIndex)
// creator
IsVariableEqual(api, flag, nftBefore.CreatorAccountIndex, accountsBefore[3].AccountIndex)
// gas
IsVariableEqual(api, flag, tx.GasAccountIndex, accountsBefore[4].AccountIndex)
// verify buy offer id
buyOfferIdBits := api.ToBinary(tx.BuyOffer.OfferId, 24)
buyAssetId := api.FromBinary(buyOfferIdBits[7:]...)
buyOfferIndex := api.Sub(tx.BuyOffer.OfferId, api.Mul(buyAssetId, OfferSizePerAsset))
buyOfferIndexBits := api.ToBinary(accountsBefore[1].AssetsInfo[1].OfferCanceledOrFinalized, OfferSizePerAsset)
for i := 0; i < OfferSizePerAsset; i++ {
isZero := api.IsZero(api.Sub(buyOfferIndex, i))
isCheckVar := api.And(isZero, flag)
isCheck := api.Compiler().IsBoolean(isCheckVar)
if isCheck {
IsVariableEqual(api, 1, buyOfferIndexBits[i], 0)
}
}
// verify sell offer id
sellOfferIdBits := api.ToBinary(tx.SellOffer.OfferId, 24)
sellAssetId := api.FromBinary(sellOfferIdBits[7:]...)
sellOfferIndex := api.Sub(tx.SellOffer.OfferId, api.Mul(sellAssetId, OfferSizePerAsset))
sellOfferIndexBits := api.ToBinary(accountsBefore[2].AssetsInfo[1].OfferCanceledOrFinalized, OfferSizePerAsset)
for i := 0; i < OfferSizePerAsset; i++ {
isZero := api.IsZero(api.Sub(sellOfferIndex, i))
isCheckVar := api.And(isZero, flag)
isCheck := api.Compiler().IsBoolean(isCheckVar)
if isCheck {
IsVariableEqual(api, 1, sellOfferIndexBits[i], 0)
}
}
// buyer should have enough balance
tx.BuyOffer.AssetAmount = UnpackAmount(api, tx.BuyOffer.AssetAmount)
IsVariableLessOrEqual(api, flag, tx.BuyOffer.AssetAmount, accountsBefore[1].AssetsInfo[0].Balance)
// submitter should have enough balance
tx.GasFeeAssetAmount = UnpackFee(api, tx.GasFeeAssetAmount)
IsVariableLessOrEqual(api, flag, tx.GasFeeAssetAmount, accountsBefore[0].AssetsInfo[0].Balance)
return pubData, nil
}
This is a layer-2 transaction and is used for canceling nft offer.
Chunks | Significant bytes |
---|---|
6 | 16 |
Name | Size(byte) | Comment |
---|---|---|
TxType | 1 | transaction type |
AccountIndex | 4 | account index |
OfferId | 3 | nft offer id |
GasFeeAccountIndex | 4 | gas fee account index |
GasFeeAssetId | 2 | gas fee asset id |
GasFeeAssetAmount | 2 | packed fee amount |
func ConvertTxToCancelOfferPubData(oTx *tx.Tx) (pubData []byte, err error) {
if oTx.TxType != commonTx.TxTypeCancelOffer {
logx.Errorf("[ConvertTxToCancelOfferPubData] invalid tx type")
return nil, errors.New("[ConvertTxToCancelOfferPubData] invalid tx type")
}
// parse tx
txInfo, err := commonTx.ParseCancelOfferTxInfo(oTx.TxInfo)
if err != nil {
logx.Errorf("[ConvertTxToCancelOfferPubData] unable to parse tx info: %s", err.Error())
return nil, err
}
var buf bytes.Buffer
buf.WriteByte(uint8(oTx.TxType))
buf.Write(Uint32ToBytes(uint32(txInfo.AccountIndex)))
buf.Write(Uint24ToBytes(txInfo.OfferId))
buf.Write(Uint32ToBytes(uint32(txInfo.GasAccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.GasFeeAssetId)))
packedFeeBytes, err := FeeToPackedFeeBytes(txInfo.GasFeeAssetAmount)
if err != nil {
logx.Errorf("[ConvertTxToDepositPubData] unable to convert amount to packed fee amount: %s", err.Error())
return nil, err
}
buf.Write(packedFeeBytes)
chunk := SuffixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(chunk)
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
return buf.Bytes(), nil
}
type CancelOfferTxInfo struct {
AccountIndex int64
OfferId int64
GasAccountIndex int64
GasFeeAssetId int64
GasFeeAssetAmount *big.Int
ExpiredAt int64
Nonce int64
Sig []byte
}
func VerifyCancelOfferTx(
api API, flag Variable,
tx *CancelOfferTxConstraints,
accountsBefore [NbAccountsPerTx]AccountConstraints,
) (pubData [PubDataSizePerTx]Variable) {
pubData = CollectPubDataFromCancelOffer(api, *tx)
// verify params
IsVariableEqual(api, flag, tx.AccountIndex, accountsBefore[0].AccountIndex)
IsVariableEqual(api, flag, tx.GasAccountIndex, accountsBefore[1].AccountIndex)
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[0].AssetsInfo[0].AssetId)
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[1].AssetsInfo[0].AssetId)
offerIdBits := api.ToBinary(tx.OfferId, 24)
assetId := api.FromBinary(offerIdBits[7:]...)
IsVariableEqual(api, flag, assetId, accountsBefore[0].AssetsInfo[1].AssetId)
// should have enough balance
tx.GasFeeAssetAmount = UnpackFee(api, tx.GasFeeAssetAmount)
IsVariableLessOrEqual(api, flag, tx.GasFeeAssetAmount, accountsBefore[0].AssetsInfo[1].Balance)
return pubData
}
This is a layer-2 transaction and is used for withdrawing nft from the layer-2 to the layer-1.
Chunks | Significant bytes |
---|---|
6 | 162 |
Name | Size(byte) | Comment |
---|---|---|
TxType | 1 | transaction type |
AccountIndex | 4 | account index |
CreatorAccountIndex | 4 | creator account index |
CreatorTreasuryRate | 2 | creator treasury rate |
NftIndex | 5 | unique nft index |
CollectionId | 2 | collection id |
NftL1Address | 20 | nft layer-1 address |
ToAddress | 20 | receiver address |
GasFeeAccountIndex | 4 | gas fee account index |
GasFeeAssetId | 2 | gas fee asset id |
GasFeeAssetAmount | 2 | packed fee amount |
NftContentHash | 32 | nft content hash |
NftL1TokenId | 32 | nft layer-1 token id |
CreatorAccountNameHash | 32 | creator account name hash |
func ConvertTxToWithdrawNftPubData(oTx *tx.Tx) (pubData []byte, err error) {
if oTx.TxType != commonTx.TxTypeWithdrawNft {
logx.Errorf("[ConvertTxToWithdrawNftPubData] invalid tx type")
return nil, errors.New("[ConvertTxToWithdrawNftPubData] invalid tx type")
}
// parse tx
txInfo, err := commonTx.ParseWithdrawNftTxInfo(oTx.TxInfo)
if err != nil {
logx.Errorf("[ConvertTxToWithdrawNftPubData] unable to parse tx info: %s", err.Error())
return nil, err
}
var buf bytes.Buffer
buf.WriteByte(uint8(oTx.TxType))
buf.Write(Uint32ToBytes(uint32(txInfo.AccountIndex)))
buf.Write(Uint32ToBytes(uint32(txInfo.CreatorAccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.CreatorTreasuryRate)))
buf.Write(Uint40ToBytes(txInfo.NftIndex))
buf.Write(Uint16ToBytes(uint16(txInfo.CollectionId)))
chunk1 := SuffixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(AddressStrToBytes(txInfo.NftL1Address))
chunk2 := PrefixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(AddressStrToBytes(txInfo.ToAddress))
buf.Write(Uint32ToBytes(uint32(txInfo.GasAccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.GasFeeAssetId)))
packedFeeBytes, err := FeeToPackedFeeBytes(txInfo.GasFeeAssetAmount)
if err != nil {
logx.Errorf("[ConvertTxToDepositPubData] unable to convert amount to packed fee amount: %s", err.Error())
return nil, err
}
buf.Write(packedFeeBytes)
chunk3 := PrefixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(chunk1)
buf.Write(chunk2)
buf.Write(chunk3)
buf.Write(PrefixPaddingBufToChunkSize(txInfo.NftContentHash))
buf.Write(Uint256ToBytes(txInfo.NftL1TokenId))
buf.Write(PrefixPaddingBufToChunkSize(txInfo.CreatorAccountNameHash))
return buf.Bytes(), nil
}
type WithdrawNftTxInfo struct {
AccountIndex int64
CreatorAccountIndex int64
CreatorAccountNameHash []byte
CreatorTreasuryRate int64
NftIndex int64
NftContentHash []byte
NftL1Address string
NftL1TokenId *big.Int
CollectionId int64
ToAddress string
GasAccountIndex int64
GasFeeAssetId int64
GasFeeAssetAmount *big.Int
ExpiredAt int64
Nonce int64
Sig []byte
}
func VerifyWithdrawNftTx(
api API,
flag Variable,
tx *WithdrawNftTxConstraints,
accountsBefore [NbAccountsPerTx]AccountConstraints,
nftBefore NftConstraints,
) (pubData [PubDataSizePerTx]Variable) {
pubData = CollectPubDataFromWithdrawNft(api, *tx)
// verify params
// account index
IsVariableEqual(api, flag, tx.AccountIndex, accountsBefore[0].AccountIndex)
IsVariableEqual(api, flag, tx.CreatorAccountIndex, accountsBefore[1].AccountIndex)
IsVariableEqual(api, flag, tx.GasAccountIndex, accountsBefore[2].AccountIndex)
// account name hash
IsVariableEqual(api, flag, tx.CreatorAccountNameHash, accountsBefore[1].AccountNameHash)
// collection id
IsVariableEqual(api, flag, tx.CollectionId, nftBefore.CollectionId)
// asset id
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[0].AssetsInfo[0].AssetId)
IsVariableEqual(api, flag, tx.GasFeeAssetId, accountsBefore[2].AssetsInfo[0].AssetId)
// nft info
IsVariableEqual(api, flag, tx.NftIndex, nftBefore.NftIndex)
IsVariableEqual(api, flag, tx.CreatorAccountIndex, nftBefore.CreatorAccountIndex)
IsVariableEqual(api, flag, tx.CreatorTreasuryRate, nftBefore.CreatorTreasuryRate)
IsVariableEqual(api, flag, tx.AccountIndex, nftBefore.OwnerAccountIndex)
IsVariableEqual(api, flag, tx.NftContentHash, nftBefore.NftContentHash)
IsVariableEqual(api, flag, tx.NftL1TokenId, nftBefore.NftL1TokenId)
IsVariableEqual(api, flag, tx.NftL1Address, nftBefore.NftL1Address)
// have enough assets
tx.GasFeeAssetAmount = UnpackFee(api, tx.GasFeeAssetAmount)
IsVariableLessOrEqual(api, flag, tx.GasFeeAssetAmount, accountsBefore[0].AssetsInfo[0].Balance)
return pubData
}
This is a layer-1 transaction and is used for full exit assets from the layer-2 to the layer-1.
Chunks | Significant bytes |
---|---|
6 | 55 |
Name | Size(byte) | Comment |
---|---|---|
TxType | 1 | transaction type |
AccountIndex | 4 | from account index |
AssetId | 2 | asset index |
AssetAmount | 16 | state amount |
AccountNameHash | 32 | account name hash |
func ConvertTxToFullExitPubData(oTx *tx.Tx) (pubData []byte, err error) {
if oTx.TxType != commonTx.TxTypeFullExit {
logx.Errorf("[ConvertTxToFullExitPubData] invalid tx type")
return nil, errors.New("[ConvertTxToFullExitPubData] invalid tx type")
}
// parse tx
txInfo, err := commonTx.ParseFullExitTxInfo(oTx.TxInfo)
if err != nil {
logx.Errorf("[ConvertTxToFullExitPubData] unable to parse tx info: %s", err.Error())
return nil, err
}
var buf bytes.Buffer
buf.WriteByte(uint8(oTx.TxType))
buf.Write(Uint32ToBytes(uint32(txInfo.AccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.AssetId)))
buf.Write(Uint128ToBytes(txInfo.AssetAmount))
chunk := SuffixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(chunk)
buf.Write(PrefixPaddingBufToChunkSize(txInfo.AccountNameHash))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
buf.Write(PrefixPaddingBufToChunkSize([]byte{}))
return buf.Bytes(), nil
}
Name | Size(byte) | Comment |
---|---|---|
AccountNameHash | 32 | account name hash |
AssetAddress | 20 | asset layer-1 address |
func VerifyFullExitTx(
api API, flag Variable,
tx FullExitTxConstraints,
accountsBefore [NbAccountsPerTx]AccountConstraints,
) (pubData [PubDataSizePerTx]Variable) {
pubData = CollectPubDataFromFullExit(api, tx)
// verify params
IsVariableEqual(api, flag, tx.AccountNameHash, accountsBefore[0].AccountNameHash)
IsVariableEqual(api, flag, tx.AccountIndex, accountsBefore[0].AccountIndex)
IsVariableEqual(api, flag, tx.AssetId, accountsBefore[0].AssetsInfo[0].AssetId)
IsVariableEqual(api, flag, tx.AssetAmount, accountsBefore[0].AssetsInfo[0].Balance)
return pubData
}
This is a layer-1 transaction and is used for full exit NFTs from the layer-2 to the layer-1.
Chunks | Significant bytes |
---|---|
6 | 164 |
Name | Size(byte) | Comment |
---|---|---|
TxType | 1 | transaction type |
AccountIndex | 4 | from account index |
CreatorAccountIndex | 2 | creator account index |
CreatorTreasuryRate | 2 | creator treasury rate |
NftIndex | 5 | unique nft index |
CollectionId | 2 | collection id |
NftL1Address | 20 | nft layer-1 address |
AccountNameHash | 32 | account name hash |
CreatorAccountNameHash | 32 | creator account name hash |
NftContentHash | 32 | nft content hash |
NftL1TokenId | 32 | nft layer-1 token id |
func ConvertTxToFullExitNftPubData(oTx *tx.Tx) (pubData []byte, err error) {
if oTx.TxType != commonTx.TxTypeFullExitNft {
logx.Errorf("[ConvertTxToFullExitNftPubData] invalid tx type")
return nil, errors.New("[ConvertTxToFullExitNftPubData] invalid tx type")
}
// parse tx
txInfo, err := commonTx.ParseFullExitNftTxInfo(oTx.TxInfo)
if err != nil {
logx.Errorf("[ConvertTxToFullExitNftPubData] unable to parse tx info: %s", err.Error())
return nil, err
}
var buf bytes.Buffer
buf.WriteByte(uint8(oTx.TxType))
buf.Write(Uint32ToBytes(uint32(txInfo.AccountIndex)))
buf.Write(Uint32ToBytes(uint32(txInfo.CreatorAccountIndex)))
buf.Write(Uint16ToBytes(uint16(txInfo.CreatorTreasuryRate)))
buf.Write(Uint40ToBytes(txInfo.NftIndex))
buf.Write(Uint16ToBytes(uint16(txInfo.CollectionId)))
chunk1 := SuffixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(AddressStrToBytes(txInfo.NftL1Address))
chunk2 := PrefixPaddingBufToChunkSize(buf.Bytes())
buf.Reset()
buf.Write(chunk1)
buf.Write(chunk2)
buf.Write(PrefixPaddingBufToChunkSize(txInfo.AccountNameHash))
buf.Write(PrefixPaddingBufToChunkSize(txInfo.CreatorAccountNameHash))
buf.Write(PrefixPaddingBufToChunkSize(txInfo.NftContentHash))
buf.Write(Uint256ToBytes(txInfo.NftL1TokenId))
return buf.Bytes(), nil
}
Name | Size(byte) | Comment |
---|---|---|
AccountNameHash | 32 | account name hash |
NftIndex | 5 | unique nft index |
func VerifyFullExitNftTx(
api API, flag Variable,
tx FullExitNftTxConstraints,
accountsBefore [NbAccountsPerTx]AccountConstraints, nftBefore NftConstraints,
) (pubData [PubDataSizePerTx]Variable) {
pubData = CollectPubDataFromFullExitNft(api, tx)
// verify params
IsVariableEqual(api, flag, tx.AccountNameHash, accountsBefore[0].AccountNameHash)
IsVariableEqual(api, flag, tx.AccountIndex, accountsBefore[0].AccountIndex)
IsVariableEqual(api, flag, tx.NftIndex, nftBefore.NftIndex)
isCheck := api.IsZero(api.IsZero(tx.CreatorAccountNameHash))
isCheck = api.And(flag, isCheck)
IsVariableEqual(api, isCheck, tx.CreatorAccountIndex, accountsBefore[1].AccountIndex)
IsVariableEqual(api, isCheck, tx.CreatorAccountNameHash, accountsBefore[1].AccountNameHash)
IsVariableEqual(api, flag, tx.CreatorAccountIndex, nftBefore.CreatorAccountIndex)
IsVariableEqual(api, flag, tx.CreatorTreasuryRate, nftBefore.CreatorTreasuryRate)
isOwner := api.And(api.IsZero(api.Sub(tx.AccountIndex, nftBefore.OwnerAccountIndex)), flag)
IsVariableEqual(api, isOwner, tx.NftContentHash, nftBefore.NftContentHash)
IsVariableEqual(api, isOwner, tx.NftL1Address, nftBefore.NftL1Address)
IsVariableEqual(api, isOwner, tx.NftL1TokenId, nftBefore.NftL1TokenId)
tx.NftContentHash = api.Select(isOwner, tx.NftContentHash, 0)
tx.NftL1Address = api.Select(isOwner, tx.NftL1Address, 0)
tx.NftL1TokenId = api.Select(isOwner, tx.NftL1TokenId, 0)
return pubData
}
Register an ZNS account which is an ENS like domain for layer-1 and a short account name for your layer-2 account.
function registerZNS(string calldata _name, address _owner, bytes32 _zkbnbPubKeyX, bytes32 _zkbnbPubKeyY) external payable nonReentrant
_name
: your favor account name_owner
: account name layer-1 owner address_zkbnbPubKeyX
: ZkBNB layer-2 public key X_zkbnbPubKeyY
: ZkBNB layer-2 public key Y
Deposit BNB to Rollup - transfer BNB from user L1 address into Rollup account
function depositBNB(bytes32 _accountNameHash) external payable
_accountNameHash
: The layer-2
Deposit BEP20 assets to Rollup - transfer BEP20 assets from user L1 address into Rollup account
function depositBEP20(
IERC20 _token,
uint104 _amount,
bytes32 _accountNameHash
) external nonReentrant
_token
: valid BEP20 address_amount
: deposit amount_accountNameHash
: ZNS account name hash
Withdraw BNB/BEP20 token to L1 - Transfer token from contract to owner
function withdrawPendingBalance(
address payable _owner,
address _token,
uint128 _amount
) external nonReentrant
_owner
: layer-1 address_token
: asset address,0
for BNB_amount
: withdraw amount
Withdraw NFT to L1
function withdrawPendingNFTBalance(uint40 _nftIndex) external
_nftIndex
: nft index
Register full exit request to withdraw all token balance from the account. The user needs to call it if she believes that her transactions are censored by the validator.
function requestFullExit(bytes32 _accountNameHash, address _asset) public nonReentrant
_accountNameHash
: ZNS account name hash_asset
: BEP20 asset address,0
for BNB
Register full exit request to withdraw NFT tokens balance from the account. Users need to call it if they believe that their transactions are censored by the validator.
function requestFullExitNFT(bytes32 _accountNameHash, uint32 _nftIndex) public nonReentrant
_accountNameHash
: ZNS account name hash_nftIndex
: nft index
Withdraws token from Rollup to L1 in case of desert mode. User must provide proof that she owns funds.
// TODO
Submit committed block data. Only active validator can make it. On-chain operations will be checked on contract and fulfilled on block verification.
struct StoredBlockInfo {
uint32 blockNumber;
uint64 priorityOperations;
bytes32 pendingOnchainOperationsHash;
uint256 timestamp;
bytes32 stateRoot;
bytes32 commitment;
}
struct CommitBlockInfo {
bytes32 newStateRoot;
bytes publicData;
uint256 timestamp;
uint32[] publicDataOffsets;
uint32 blockNumber;
}
function commitBlocks(
StoredBlockInfo memory _lastCommittedBlockData,
CommitBlockInfo[] memory _newBlocksData
)
external
StoredBlockInfo
: block data that we store on BNB Smart Chain. We store hash of this structure in storage and pass it in tx arguments every time we need to access any of its field.
blockNumber
: rollup block numberpriorityOperations
: priority operations countpendingOnchainOperationsHash
: hash of all on-chain operations that have to be processed when block is finalized (verified)timestamp
: block timestampstateRoot
: root hash of the rollup tree statecommitment
: rollup block commitment
CommitBlockInfo
: data needed for new block commit
newStateRoot
: new layer-2 root hashpublicData
: public data of the executed rollup operationstimestamp
: block timestamppublicDataOffsets
: list of on-chain operations offsetblockNumber
: rollup block number
commitBlocks
and commitOneBlock
are used for committing layer-2 transactions data on-chain.
_lastCommittedBlockData
: last committed block header_newBlocksData
: pending commit blocks
Submit proofs of blocks and make it verified on-chain. Only active validator can make it. This block on-chain operations will be fulfilled.
struct VerifyAndExecuteBlockInfo {
StoredBlockInfo blockHeader;
bytes[] pendingOnchainOpsPubData;
}
function verifyAndExecuteBlocks(VerifyAndExecuteBlockInfo[] memory _blocks, uint256[] memory _proofs) external
VerifyAndExecuteBlockInfo
: block data that is used for verifying blocks
blockHeader
: related block headerpendingOnchainOpsPubdata
: public data of pending on-chain operations
verifyBlocks
: is used for verifying block data and proofs
_blocks
: pending verify blocks_proofs
: Groth16 proofs
Checks if Desert mode must be entered. Desert mode must be entered in case of current BNB Smart Chain block number is higher than the oldest of existed priority requests expiration block number.
function activateDesertMode() public returns (bool)
Revert blocks that were not verified before deadline determined by EXPECT_VERIFICATION_IN
constant.
function revertBlocks(StoredBlockInfo[] memory _blocksToRevert) external
_blocksToRevert
: committed blocks to revert in reverse order starting from last committed.
Set default NFT factory, which will be used for withdrawing NFT by default
function setDefaultNFTFactory(NFTFactory _factory) external
_factory
: NFT factory address
Register NFT factory, which will be used for withdrawing NFT.
function registerNFTFactory(
string calldata _creatorAccountName,
uint32 _collectionId,
NFTFactory _factory
) external
_creatorAccountName
: NFT creator account name_collectionId
: Collection id in the layer-2_factory
: Address of NFTFactory
Get NFT factory which will be used for withdrawing NFT for corresponding creator
function getNFTFactory(bytes32 _creatorAccountNameHash, uint32 _collectionId) public view returns (address)
_creatorAccountNameHash
: Creator account name hash_collectionId
: Collection id
Change current governor. The caller must be current governor.
function changeGovernor(address _newGovernor)
_newGovernor
: Address of the new governor
Add asset to the list of networks assets. The caller must be current asset governance.
function addAsset(address _asset) external
Set asset status as paused or active. The caller must be current governor. It is impossible to create deposits of the paused assets.
function setAssetPaused(address _assetAddress, bool _assetPaused) external
_assetAddress
: asset layer-1 address_assetPausesd
: status
Change validator status (active or not active). The caller must be current governor.
function setValidator(address _validator, bool _active)
function changeAssetGovernance(AssetGovernance _newAssetGovernance) external
_newAssetGovernance
: New asset Governance
Validate that specified address is the token governance address
function requireGovernor(address _address)
_address
: Address to check
Validate that specified address is the active validator
function requireActiveValidator(address _address)
_address
: Address to check
Validate asset address (it must be presented in assets list).
function validateAssetAddress(address _assetAddr) external view returns (uint16)
_assetAddr
: Asset address
Returns: asset id.
Collecting fees for adding an asset and passing the call to the addAsset
function in the governance contract.
function addAsset(address _assetAddress) external
_assetAddress
: BEP20 asset address
Set new listing asset and fee, can be called only by governor.
function setListingFeeAsset(IERC20 _newListingFeeAsset, uint256 _newListingFee) external
_newListingFeeAsset
: address of the asset in which fees will be collected_newListingFee
: amount of tokens that will need to be paid for adding tokens
Set new listing fee, can be called only by governor.
function setListingFee(uint256 _newListingFee)
_newListingFee
: amount of assets that will need to be paid for adding tokens
Enable or disable asset lister, if enabled new assets can be added by that address without payment, can be called only by governor.
function setLister(address _listerAddress, bool _active)
_listerAddress
: address that can list tokens without fee_active
: active flag
Change maximum amount of assets that can be listed using this method, can be called only by governor.
function setListingCap(uint16 _newListingCap)
_newListingCap
: max number of assets that can be listed using this contract
Change address that collects payments for listing assets, can be called only by governor.
function setTreasury(address _newTreasury)
_newTreasury
: address that collects listing payments