From 6fd7762facbab8716b5a36da8f09faa692a9f99f Mon Sep 17 00:00:00 2001 From: Lawliet-Chan <1576710154@qq.com> Date: Fri, 8 Oct 2021 13:52:07 +0800 Subject: [PATCH 1/4] bitwise --- specs/opcode/16AND.md | 50 ++++++++++++++++++++++++++ specs/opcode/17OR.md | 49 +++++++++++++++++++++++++ specs/opcode/18XOR.md | 50 ++++++++++++++++++++++++++ src/zkevm_specs/opcode/bitwise.py | 60 +++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+) create mode 100644 specs/opcode/16AND.md create mode 100644 specs/opcode/17OR.md create mode 100644 specs/opcode/18XOR.md create mode 100644 src/zkevm_specs/opcode/bitwise.py diff --git a/specs/opcode/16AND.md b/specs/opcode/16AND.md new file mode 100644 index 000000000..5a8f68d51 --- /dev/null +++ b/specs/opcode/16AND.md @@ -0,0 +1,50 @@ +# AND op code + +## Procedure + +A stack initalize empty with stack pointer to 1024, pop operation can only happen when stack is not empty, and it will increase by 1 of stack pointer. + +The And value will be dropped directly without no any more checking & utilizing. + +## Constraints + +1. opId = OpcodeId(0x16u8) +2. state transition: + - gc + 3 + - stack_pointer + 1 + - pc + 1 + - gas + 3 + + +## Exceptions + +1. stack underflow: when stack is empty +2. gas out: remaining gas is not enough + +```python +class Stack(): + def __init__(self): + self.items = [0] * 1024 + self.top = 1024 + def is_empty(self): + return self.top == 1024 + def size(self): + return 1024 ## fixed size + def push(self, evm_word): + if self.top == 0: + return ## or throw error + self.top -= 3 + self.items[self.top] = evm_word[0] + self.items[self.top - 1] =evm_word[1] + self.items[self.top - 2] =evm_word[2] + def peek(self): + # self.items.append(item) + return self.items[self.top] + def pop(self): + if self.top == 1024: + return ## or throw error + self.items[self.top] = 0 + self.items[self.top + 1] = 0 + self.items[self.top + 2] = 0 + self.top += 3 +``` \ No newline at end of file diff --git a/specs/opcode/17OR.md b/specs/opcode/17OR.md new file mode 100644 index 000000000..7152a2b16 --- /dev/null +++ b/specs/opcode/17OR.md @@ -0,0 +1,49 @@ +# OR op code + +## Procedure + +A stack initalize empty with stack pointer to 1024, pop operation can only happen when stack is not empty, and it will increase by 1 of stack pointer. + +The or-ed value will be dropped directly without no any more checking & utilizing. + +## Constraints + +1. opId = OpcodeId(0x17u8) +2. state transition: + - gc + 3 + - stack_pointer + 1 + - pc + 1 + - gas + 3 + + +## Exceptions + +1. stack underflow: when stack is empty +2. gas out: remaining gas is not enough +```python +class Stack(): + def __init__(self): + self.items = [0] * 1024 + self.top = 1024 + def is_empty(self): + return self.top == 1024 + def size(self): + return 1024 ## fixed size + def push(self, evm_word): + if self.top == 0: + return ## or throw error + self.top -= 3 + self.items[self.top] = evm_word[0] + self.items[self.top - 1] =evm_word[1] + self.items[self.top - 2] =evm_word[2] + def peek(self): + # self.items.append(item) + return self.items[self.top] + def pop(self): + if self.top == 1024: + return ## or throw error + self.items[self.top] = 0 + self.items[self.top + 1] = 0 + self.items[self.top + 2] = 0 + self.top += 3 +``` \ No newline at end of file diff --git a/specs/opcode/18XOR.md b/specs/opcode/18XOR.md new file mode 100644 index 000000000..8a9382dc2 --- /dev/null +++ b/specs/opcode/18XOR.md @@ -0,0 +1,50 @@ +# XOR op code + +## Procedure + +A stack initalize empty with stack pointer to 1024, pop operation can only happen when stack is not empty, and it will increase by 1 of stack pointer. + +The Xor-ed value will be dropped directly without no any more checking & utilizing. + +## Constraints + +1. opId = OpcodeId(0x18u8) +2. state transition: + - gc + 3 + - stack_pointer + 1 + - pc + 1 + - gas + 3 + + +## Exceptions + +1. stack underflow: when stack is empty +2. gas out: remaining gas is not enough + +```python +class Stack(): + def __init__(self): + self.items = [0] * 1024 + self.top = 1024 + def is_empty(self): + return self.top == 1024 + def size(self): + return 1024 ## fixed size + def push(self, evm_word): + if self.top == 0: + return ## or throw error + self.top -= 3 + self.items[self.top] = evm_word[0] + self.items[self.top - 1] =evm_word[1] + self.items[self.top - 2] =evm_word[2] + def peek(self): + # self.items.append(item) + return self.items[self.top] + def pop(self): + if self.top == 1024: + return ## or throw error + self.items[self.top] = 0 + self.items[self.top + 1] = 0 + self.items[self.top + 2] = 0 + self.top += 3 +``` \ No newline at end of file diff --git a/src/zkevm_specs/opcode/bitwise.py b/src/zkevm_specs/opcode/bitwise.py new file mode 100644 index 000000000..65ba3cc85 --- /dev/null +++ b/src/zkevm_specs/opcode/bitwise.py @@ -0,0 +1,60 @@ +from typing import Sequence +from ..encoding import U8, is_circuit_code + +@is_circuit_code +def check_xor( + a8s: Sequence[U8], + b8s: Sequence[U8], + c8s: Sequence[U8] +): + assert len(a8s) == len(b8s) == len(c8s) == 32 + + for i in range(0, 32): + assert a8s[i] ^ b8s[i] == c8s[i] + + +def test_check_xor(): + a8s = [1] + b8s = [4] + c8s = [5] + check_xor(a8s, b8s, c8s) + + + +@is_circuit_code +def check_or( + a8s: Sequence[U8], + b8s: Sequence[U8], + c8s: Sequence[U8] +): + assert len(a8s) == len(b8s) == len(c8s) == 32 + + for i in range(0, 32): + assert a8s[i] | b8s[i] == c8s[i] + + +def test_check_or(): + a8s = [1] + b8s = [4] + c8s = [5] + check_xor(a8s, b8s, c8s) + + + +@is_circuit_code +def check_and( + a8s: Sequence[U8], + b8s: Sequence[U8], + c8s: Sequence[U8] +): + assert len(a8s) == len(b8s) == len(c8s) == 32 + + for i in range(0, 32): + assert a8s[i] & b8s[i] == c8s[i] + + +def test_check_and(): + a8s = [1] + b8s = [4] + c8s = [0] + check_xor(a8s, b8s, c8s) From 22e4238158a83b7968d7f09d2a182257cb8f1ebe Mon Sep 17 00:00:00 2001 From: Lawliet-Chan <1576710154@qq.com> Date: Tue, 12 Oct 2021 15:02:31 +0800 Subject: [PATCH 2/4] modify comments --- specs/opcode/16AND.md | 4 ++-- specs/opcode/17OR.md | 2 +- specs/opcode/18XOR.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/opcode/16AND.md b/specs/opcode/16AND.md index 5a8f68d51..3360e30fc 100644 --- a/specs/opcode/16AND.md +++ b/specs/opcode/16AND.md @@ -2,9 +2,9 @@ ## Procedure -A stack initalize empty with stack pointer to 1024, pop operation can only happen when stack is not empty, and it will increase by 1 of stack pointer. +A stack initalize empty with stack pointer to 1024, AND operation can only happen when stack is not empty, and it will increase by 1 of stack pointer. -The And value will be dropped directly without no any more checking & utilizing. +The And value will be dropped directly without any further checking & utilizing. ## Constraints diff --git a/specs/opcode/17OR.md b/specs/opcode/17OR.md index 7152a2b16..8b7efec93 100644 --- a/specs/opcode/17OR.md +++ b/specs/opcode/17OR.md @@ -2,7 +2,7 @@ ## Procedure -A stack initalize empty with stack pointer to 1024, pop operation can only happen when stack is not empty, and it will increase by 1 of stack pointer. +A stack initalize empty with stack pointer to 1024, OR operation can only happen when stack is not empty, and it will increase by 1 of stack pointer. The or-ed value will be dropped directly without no any more checking & utilizing. diff --git a/specs/opcode/18XOR.md b/specs/opcode/18XOR.md index 8a9382dc2..5e9d7e112 100644 --- a/specs/opcode/18XOR.md +++ b/specs/opcode/18XOR.md @@ -2,7 +2,7 @@ ## Procedure -A stack initalize empty with stack pointer to 1024, pop operation can only happen when stack is not empty, and it will increase by 1 of stack pointer. +A stack initalize empty with stack pointer to 1024, XOR operation can only happen when stack is not empty, and it will increase by 1 of stack pointer. The Xor-ed value will be dropped directly without no any more checking & utilizing. From 15d2bdb85e6f05d699bd1d626a66c558c7bcdbe2 Mon Sep 17 00:00:00 2001 From: Haichen Shen Date: Fri, 19 Nov 2021 16:37:52 -0800 Subject: [PATCH 3/4] address comments --- specs/opcode/16AND.md | 50 -------------------------- specs/opcode/16AND_17OR_18XOR.md | 39 ++++++++++++++++++++ specs/opcode/17OR.md | 49 ------------------------- specs/opcode/18XOR.md | 50 -------------------------- src/zkevm_specs/opcode/__init__.py | 1 + src/zkevm_specs/opcode/bitwise.py | 57 +++++++++--------------------- tests/test_bitwise.py | 38 ++++++++++++++++++++ 7 files changed, 94 insertions(+), 190 deletions(-) delete mode 100644 specs/opcode/16AND.md create mode 100644 specs/opcode/16AND_17OR_18XOR.md delete mode 100644 specs/opcode/17OR.md delete mode 100644 specs/opcode/18XOR.md create mode 100644 tests/test_bitwise.py diff --git a/specs/opcode/16AND.md b/specs/opcode/16AND.md deleted file mode 100644 index 3360e30fc..000000000 --- a/specs/opcode/16AND.md +++ /dev/null @@ -1,50 +0,0 @@ -# AND op code - -## Procedure - -A stack initalize empty with stack pointer to 1024, AND operation can only happen when stack is not empty, and it will increase by 1 of stack pointer. - -The And value will be dropped directly without any further checking & utilizing. - -## Constraints - -1. opId = OpcodeId(0x16u8) -2. state transition: - - gc + 3 - - stack_pointer + 1 - - pc + 1 - - gas + 3 - - -## Exceptions - -1. stack underflow: when stack is empty -2. gas out: remaining gas is not enough - -```python -class Stack(): - def __init__(self): - self.items = [0] * 1024 - self.top = 1024 - def is_empty(self): - return self.top == 1024 - def size(self): - return 1024 ## fixed size - def push(self, evm_word): - if self.top == 0: - return ## or throw error - self.top -= 3 - self.items[self.top] = evm_word[0] - self.items[self.top - 1] =evm_word[1] - self.items[self.top - 2] =evm_word[2] - def peek(self): - # self.items.append(item) - return self.items[self.top] - def pop(self): - if self.top == 1024: - return ## or throw error - self.items[self.top] = 0 - self.items[self.top + 1] = 0 - self.items[self.top + 2] = 0 - self.top += 3 -``` \ No newline at end of file diff --git a/specs/opcode/16AND_17OR_18XOR.md b/specs/opcode/16AND_17OR_18XOR.md new file mode 100644 index 000000000..12a1ee0ee --- /dev/null +++ b/specs/opcode/16AND_17OR_18XOR.md @@ -0,0 +1,39 @@ +# AND, OR, XOR op code + +## Procedure + +Pop two EVM words `a` and `b` from the stack, and the output `c` is pushed to +the stack. + +`a`, `b`, and `c` are all EVM words. We break three EVM words into 32 bytes and +apply the lookup to the 32 chunks of `a`, `b`, and `c` to see if +`a[i] OP b[i] == c[i]` holds for `i = 0..31`, where `OP` belongs to +`[AND, OR, XOR]`. + +## Constraints + +1. opcodeId checks + - opId == OpcodeId(0x16) for `AND` + - opId == OpcodeId(0x17) for `OR` + - opId == OpcodeId(0x18) for `XOR` +2. state transition: + - gc + 3 + - stack_pointer + 1 + - pc + 1 + - gas + 3 +3. Lookups: 35 busmapping lookups + - `a` is at the top of the stack + - `b` is at the second position of the stack + - `c`, the result, is at the new top of the stack + - Apply the lookup to 32 tuples of `a, b, c` chunks, + `(a[i], b[i], c[i]), i = 0..31`, with opcode corresponding table + (`BitwiseAnd`, `BitwiseOr`, and `BitwiseXor`). + +## Exceptions + +1. stack underflow: `1023 <= stack_pointer <= 1024` +2. out of gas: remaining gas is not enough + +## Code + +See `src/zkevm_specs/opcode/bitwise.py` diff --git a/specs/opcode/17OR.md b/specs/opcode/17OR.md deleted file mode 100644 index 8b7efec93..000000000 --- a/specs/opcode/17OR.md +++ /dev/null @@ -1,49 +0,0 @@ -# OR op code - -## Procedure - -A stack initalize empty with stack pointer to 1024, OR operation can only happen when stack is not empty, and it will increase by 1 of stack pointer. - -The or-ed value will be dropped directly without no any more checking & utilizing. - -## Constraints - -1. opId = OpcodeId(0x17u8) -2. state transition: - - gc + 3 - - stack_pointer + 1 - - pc + 1 - - gas + 3 - - -## Exceptions - -1. stack underflow: when stack is empty -2. gas out: remaining gas is not enough -```python -class Stack(): - def __init__(self): - self.items = [0] * 1024 - self.top = 1024 - def is_empty(self): - return self.top == 1024 - def size(self): - return 1024 ## fixed size - def push(self, evm_word): - if self.top == 0: - return ## or throw error - self.top -= 3 - self.items[self.top] = evm_word[0] - self.items[self.top - 1] =evm_word[1] - self.items[self.top - 2] =evm_word[2] - def peek(self): - # self.items.append(item) - return self.items[self.top] - def pop(self): - if self.top == 1024: - return ## or throw error - self.items[self.top] = 0 - self.items[self.top + 1] = 0 - self.items[self.top + 2] = 0 - self.top += 3 -``` \ No newline at end of file diff --git a/specs/opcode/18XOR.md b/specs/opcode/18XOR.md deleted file mode 100644 index 5e9d7e112..000000000 --- a/specs/opcode/18XOR.md +++ /dev/null @@ -1,50 +0,0 @@ -# XOR op code - -## Procedure - -A stack initalize empty with stack pointer to 1024, XOR operation can only happen when stack is not empty, and it will increase by 1 of stack pointer. - -The Xor-ed value will be dropped directly without no any more checking & utilizing. - -## Constraints - -1. opId = OpcodeId(0x18u8) -2. state transition: - - gc + 3 - - stack_pointer + 1 - - pc + 1 - - gas + 3 - - -## Exceptions - -1. stack underflow: when stack is empty -2. gas out: remaining gas is not enough - -```python -class Stack(): - def __init__(self): - self.items = [0] * 1024 - self.top = 1024 - def is_empty(self): - return self.top == 1024 - def size(self): - return 1024 ## fixed size - def push(self, evm_word): - if self.top == 0: - return ## or throw error - self.top -= 3 - self.items[self.top] = evm_word[0] - self.items[self.top - 1] =evm_word[1] - self.items[self.top - 2] =evm_word[2] - def peek(self): - # self.items.append(item) - return self.items[self.top] - def pop(self): - if self.top == 1024: - return ## or throw error - self.items[self.top] = 0 - self.items[self.top + 1] = 0 - self.items[self.top + 2] = 0 - self.top += 3 -``` \ No newline at end of file diff --git a/src/zkevm_specs/opcode/__init__.py b/src/zkevm_specs/opcode/__init__.py index 156c59b18..2d1ff45fe 100644 --- a/src/zkevm_specs/opcode/__init__.py +++ b/src/zkevm_specs/opcode/__init__.py @@ -1,4 +1,5 @@ from .add_sub import * +from .bitwise import * from .byte import * from .comparator import * from .lt_gt import * diff --git a/src/zkevm_specs/opcode/bitwise.py b/src/zkevm_specs/opcode/bitwise.py index 65ba3cc85..01b194ae4 100644 --- a/src/zkevm_specs/opcode/bitwise.py +++ b/src/zkevm_specs/opcode/bitwise.py @@ -2,59 +2,34 @@ from ..encoding import U8, is_circuit_code @is_circuit_code -def check_xor( - a8s: Sequence[U8], - b8s: Sequence[U8], - c8s: Sequence[U8] +def check_and( + a8s: Sequence[U8], + b8s: Sequence[U8], + c8s: Sequence[U8], ): assert len(a8s) == len(b8s) == len(c8s) == 32 - - for i in range(0, 32): - assert a8s[i] ^ b8s[i] == c8s[i] - - -def test_check_xor(): - a8s = [1] - b8s = [4] - c8s = [5] - check_xor(a8s, b8s, c8s) - + for i in range(32): + assert a8s[i] & b8s[i] == c8s[i] @is_circuit_code def check_or( - a8s: Sequence[U8], - b8s: Sequence[U8], - c8s: Sequence[U8] + a8s: Sequence[U8], + b8s: Sequence[U8], + c8s: Sequence[U8], ): assert len(a8s) == len(b8s) == len(c8s) == 32 - - for i in range(0, 32): + for i in range(32): assert a8s[i] | b8s[i] == c8s[i] -def test_check_or(): - a8s = [1] - b8s = [4] - c8s = [5] - check_xor(a8s, b8s, c8s) - - @is_circuit_code -def check_and( - a8s: Sequence[U8], - b8s: Sequence[U8], - c8s: Sequence[U8] +def check_xor( + a8s: Sequence[U8], + b8s: Sequence[U8], + c8s: Sequence[U8], ): assert len(a8s) == len(b8s) == len(c8s) == 32 - - for i in range(0, 32): - assert a8s[i] & b8s[i] == c8s[i] - - -def test_check_and(): - a8s = [1] - b8s = [4] - c8s = [0] - check_xor(a8s, b8s, c8s) + for i in range(32): + assert a8s[i] ^ b8s[i] == c8s[i] diff --git a/tests/test_bitwise.py b/tests/test_bitwise.py new file mode 100644 index 000000000..2f442f06e --- /dev/null +++ b/tests/test_bitwise.py @@ -0,0 +1,38 @@ +import random +import pytest + +from zkevm_specs.encoding import u256_to_u8s, u8s_to_u256 +from zkevm_specs.opcode import check_and, check_or, check_xor + + +def test_and(): + for _ in range(5): + a = random.randint(0, 2**256) + b = random.randint(0, 2**256) + c = a & b + a8s = u256_to_u8s(a) + b8s = u256_to_u8s(b) + c8s = u256_to_u8s(c) + check_and(a8s, b8s, c8s) + + +def test_check_or(): + for _ in range(5): + a = random.randint(0, 2**256) + b = random.randint(0, 2**256) + c = a | b + a8s = u256_to_u8s(a) + b8s = u256_to_u8s(b) + c8s = u256_to_u8s(c) + check_or(a8s, b8s, c8s) + +def test_check_xor(): + for i in range(5): + print(i) + a = random.randint(0, 2**256) + b = random.randint(0, 2**256) + c = a ^ b + a8s = u256_to_u8s(a) + b8s = u256_to_u8s(b) + c8s = u256_to_u8s(c) + check_xor(a8s, b8s, c8s) From fbe5fbbfa03c5cc2cc27b7ede8267c5fcd126c23 Mon Sep 17 00:00:00 2001 From: Haichen Shen Date: Wed, 24 Nov 2021 23:12:46 -0800 Subject: [PATCH 4/4] address comments --- specs/opcode/16AND_17OR_18XOR.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/opcode/16AND_17OR_18XOR.md b/specs/opcode/16AND_17OR_18XOR.md index 12a1ee0ee..cb2ff2ec6 100644 --- a/specs/opcode/16AND_17OR_18XOR.md +++ b/specs/opcode/16AND_17OR_18XOR.md @@ -7,7 +7,7 @@ the stack. `a`, `b`, and `c` are all EVM words. We break three EVM words into 32 bytes and apply the lookup to the 32 chunks of `a`, `b`, and `c` to see if -`a[i] OP b[i] == c[i]` holds for `i = 0..31`, where `OP` belongs to +`a[i] OP b[i] == c[i]` holds for `i = 0..32`, where `OP` belongs to `[AND, OR, XOR]`. ## Constraints @@ -26,7 +26,7 @@ apply the lookup to the 32 chunks of `a`, `b`, and `c` to see if - `b` is at the second position of the stack - `c`, the result, is at the new top of the stack - Apply the lookup to 32 tuples of `a, b, c` chunks, - `(a[i], b[i], c[i]), i = 0..31`, with opcode corresponding table + `(a[i], b[i], c[i]), i = 0..32`, with opcode corresponding table (`BitwiseAnd`, `BitwiseOr`, and `BitwiseXor`). ## Exceptions