Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unify byte order in zktrie node's fields being stored #133

Merged
merged 1 commit into from
Jul 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions core/types/zktrie/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Type for zktrie

## Data Format in stateDb

All data node being stored via stateDb are encoded by following syntax:

``` EBNF
node = magic string | node data ;

magic string = "THIS IS SOME MAGIC BYTES FOR SMT m1rRXgP2xpDI" ;

node data = middle node | leaf node | empty node ;

empty node = '0x2' ;

middle node = '0x0', left hash, right hash ;

field = 32 * hex char ;

left hash = field ;

right hash = field ;

leaf node = node key , value len , compress flag , <value len> * value field, key preimage ;

node key = field ;

compress flag = 3 * byte ;

value len = byte ;

value field = field | compressed field, compressed field ;

compressed field = 16 * hex char ;

key preimage = '0x0' | preimage bytes ;

preimage bytes = len, <len> * byte ;

len = byte ;
```

A `field` is an element in prime field of BN256 represented by **big endian** integer and contained in fixed length (32) bytes;

A `compressed field` is a field represented by **big endian** integer which could be contained in 16 bytes;

For the total `value len` items of `value field` (maximum 255), the first 24 `value field`s can be recorded as `field` or 2x `compressed field` (i.e. a byte32). The corresonpdoing bit in `compress flag` is set to 1 if it was recorded as byte32, or 0 for a field.

## Key scheme

The key of data node is obtained from one or more poseidon hash calculation: `poseidon := (field, field) => field`.

For middle node:

```
key = poseidon(<left hash>, <right hash>)
```

For leaf node:

```
key = poseidon(<pre key>, <value hash>)

pre key = poseidon(field(1), <node key>)

value hash = poseidon(<leaf element>, <leaf element>) | poseidon(<value hash>, <value hash>)

leaf element = <value field as field> | poseidon(<compressed field as field>, <compressed field as field>) | field(0)

```

That is, to calculate the key of a leaf node:

1. In the sequence of `value field`s, take which is recorded as 'compressed' and calculate the 2x `compressed field` for its poseidon hash, replace the corresponding `value field` ad-hoc in the sequence;

2. Consider the sequence from 1 as the leafs of a binary merkle tree (append a 0 field for odd leafs) and calculate its root by poseidon hash;

For empty node:

```
key = field(0)
```

## Account data

Each account data is saved in one leaf node of account zktrie as 4 `value field`s:

1. Nonce as `field`
2. Balance as `field`
3. CodeHash as `compressed field` (byte32)
4. Storage root as `field`

The key for an account data is calculated from the 20-bit account address as following:

```

32-byte-zero-end-padding-addr := address, 16 * bytes (0)

key = poseidon(<first 16 byte of 32-byte-zero-end-padding-addr as field>, <last 16 byte of 32-byte-zero-end-padding-addr as field>)

```

## Data examples

### A leaf node in account trie:

> 0x017f9d3bbc51d12566ecc6049ca6bf76e32828c22b197405f63a833b566fe7da0a040400000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000029b74e075daad9f17eb39cd893c2dd32f52ecd99084d63964842defd00ebcbe208a2f471d50e56ac5000ab9e82f871e36b5a636b19bd02f70aa666a3bd03142f00

Can be decompose to:

+ `0x01`: node type prefix for leaf node
+ `7f9d3bbc51d12566ecc6049ca6bf76e32828c22b197405f63a833b566fe7da0a`: node key as field
+ `04`: value len (4 value fields)
+ `040000`: compress flag, a 24 bit array, indicating the third field is compressed
+ `0000000000000000000000000000000000000000000000000000000000000001`: value field 0 (nonce)
+ `0000000000000000000000000000000000000000000000000000000000000000`: value field 1 (balance)
+ `29b74e075daad9f17eb39cd893c2dd32f52ecd99084d63964842defd00ebcbe2`: value field 2 (codeHash, as byte32)
+ `08a2f471d50e56ac5000ab9e82f871e36b5a636b19bd02f70aa666a3bd03142f`: value field 3 (storage root)
+ `00`: key preimage is not avaliable

The key calculation for this node is:

```

arr = [<value field 0>, <value field 1>, <value field 2>, <value field 3>]

hash_pre = poseidon(<first 16 byte for value field 2>, <last 16 byte for value field 2>)

arr[2] = hash_pre

layer1 = [poseidon(arr[0], arr[1]), poseidon(arr[2], arr[3])]

key = poseidon(layer1[0], layer1[1])

```

Notice all field and compressed field are represented as **big endian** integer.

### A middle node in account trie:

> 0x00000000000000000000000000000000000000000000000000000000000000000004470b58d80eeb26da85b2c2db5c254900656fb459c07729f556ff02534ab32a

Notice the left child of this node is an empty node (so its key is field(0))
14 changes: 6 additions & 8 deletions trie/zk_trie_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,13 @@ func NewNodeFromBytes(b []byte) (*Node, error) {
if len(b) != 2*zkt.ElemBytesLen {
return nil, ErrNodeBytesBadSize
}
n.ChildL, n.ChildR = &zkt.Hash{}, &zkt.Hash{}
copy(n.ChildL[:], b[:zkt.ElemBytesLen])
copy(n.ChildR[:], b[zkt.ElemBytesLen:zkt.ElemBytesLen*2])
n.ChildL, _ = zkt.NewHashFromBytes(b[:zkt.ElemBytesLen])
n.ChildR, _ = zkt.NewHashFromBytes(b[zkt.ElemBytesLen : zkt.ElemBytesLen*2])
case NodeTypeLeaf:
if len(b) < zkt.ElemBytesLen+4 {
return nil, ErrNodeBytesBadSize
}
n.NodeKey = &zkt.Hash{}
copy(n.NodeKey[:], b[0:32])
n.NodeKey, _ = zkt.NewHashFromBytes(b[0:32])
mark := binary.LittleEndian.Uint32(b[32:36])
preimageLen := int(mark & 255)
n.CompressedFlags = mark >> 8
Expand Down Expand Up @@ -181,12 +179,12 @@ func (n *Node) Value() []byte {
switch n.Type {
case NodeTypeMiddle: // {Type || ChildL || ChildR}
bytes := []byte{byte(n.Type)}
bytes = append(bytes, n.ChildL[:]...)
bytes = append(bytes, n.ChildR[:]...)
bytes = append(bytes, n.ChildL.Bytes()...)
bytes = append(bytes, n.ChildR.Bytes()...)
return bytes
case NodeTypeLeaf: // {Type || Data...}
bytes := []byte{byte(n.Type)}
bytes = append(bytes, n.NodeKey[:]...)
bytes = append(bytes, n.NodeKey.Bytes()...)
tmp := make([]byte, 4)
compressedFlag := (n.CompressedFlags << 8) + uint32(len(n.ValuePreimage))
binary.LittleEndian.PutUint32(tmp, compressedFlag)
Expand Down