# Input vars X_short = BitVec('X', type_bits) Y_short = BitVec('Y', type_bits) # Z3's overflow and underflow conditions actual_overflow = Not(BVSubNoOverflow(X_short, Y_short)) actual_underflow = Not(BVSubNoUnderflow(X_short, Y_short, True)) # cast to full n_bits values X = BVSignedUpCast(X_short, n_bits) Y = BVSignedUpCast(Y_short, n_bits) diff = SUB(X, Y) # Constants maxValue = BVSignedMax(type_bits, n_bits) minValue = BVSignedMin(type_bits, n_bits) # Overflow and underflow checks in YulUtilFunction::overflowCheckedIntSubFunction if type_bits == 256: underflow_check = AND(ISZERO(SLT(Y, 0)), SGT(diff, X)) overflow_check = AND(SLT(Y, 0), SLT(diff, X)) else: underflow_check = SLT(diff, minValue) overflow_check = SGT(diff, maxValue) type_bits += 8 rule.check(actual_underflow, underflow_check != 0) rule.check(actual_overflow, overflow_check != 0)
Requirements: """ rule = Rule() n_bits = 256 # Input vars X = BitVec('X', n_bits) Y = BitVec('Y', n_bits) # Constants BitWidth = BitVecVal(n_bits, n_bits) # Requirements # Non optimized result nonopt_1 = AND(AND(X, Y), Y) nonopt_2 = AND(Y, AND(X, Y)) nonopt_3 = AND(AND(Y, X), Y) nonopt_4 = AND(Y, AND(Y, X)) # Optimized result opt_1 = AND(X, Y) opt_2 = AND(Y, X) rule.check(nonopt_1, opt_1) rule.check(nonopt_2, opt_1) rule.check(nonopt_3, opt_2) rule.check(nonopt_4, opt_2)
from opcodes import BYTE from rule import Rule from z3 import BitVec, BitVecVal, Concat, Extract """ Checks that the byte opcode (implemented using shift) is equivalent to a canonical definition of byte using extract. """ rule = Rule() n_bits = 256 x = BitVec('X', n_bits) for i in range(0, 32): # For Byte, i = 0 corresponds to most significant bit # But for extract i = 0 corresponds to the least significant bit lsb = 31 - i rule.check( BYTE(BitVecVal(i, n_bits), x), Concat(BitVecVal(0, n_bits - 8), Extract(8 * lsb + 7, 8 * lsb, x)))
SHL(B, AND(A, X)) -> AND(SHL(B, X), A << B) Requirements: B < BitWidth """ rule = Rule() n_bits = 128 # Input vars X = BitVec('X', n_bits) A = BitVec('A', n_bits) B = BitVec('B', n_bits) # Constants BitWidth = BitVecVal(n_bits, n_bits) # Requirements rule.require(ULT(B, BitWidth)) # Non optimized result nonopt_1 = SHL(B, AND(X, A)) nonopt_2 = SHL(B, AND(A, X)) # Optimized result Mask = SHL(B, A) opt = AND(SHL(B, X), Mask) rule.check(nonopt_1, opt) rule.check(nonopt_2, opt)
from opcodes import SIGNEXTEND, AND from rule import Rule from z3 import BitVec, BitVecVal, ULT """ Rule: AND(A, SIGNEXTEND(B, X)) -> AND(A, X) given B < WordSize / 8 - 1 AND A & (1 << ((B + 1) * 8) - 1) == A """ n_bits = 128 # Input vars X = BitVec('X', n_bits) A = BitVec('A', n_bits) B = BitVec('B', n_bits) rule = Rule() # Requirements rule.require(ULT(B, BitVecVal(n_bits // 8 - 1, n_bits))) rule.require((A & ((BitVecVal(1, n_bits) << ((B + 1) * 8)) - 1)) == A) rule.check(AND(A, SIGNEXTEND(B, X)), AND(A, X))
from opcodes import BYTE, SHR, DIV from rule import Rule from z3 import BitVec, ULT """ byte(A, shr(B, X)) given A < B / 8 -> 0 """ rule = Rule() n_bits = 256 # Input vars X = BitVec('X', n_bits) A = BitVec('A', n_bits) B = BitVec('B', n_bits) # Non optimized result nonopt = BYTE(A, SHR(B, X)) # Optimized result opt = 0 rule.require(ULT(A, DIV(B, 8))) rule.check(nonopt, opt)
from opcodes import SHL from rule import Rule from z3 import BitVec, If """ Checking conversion of exp(2, X) to shl(X, 1) """ rule = Rule() n_bits = 256 # Proof of exp(2, X) = shl(X, 1) by induction: # # Base case: X = 0, exp(2, 0) = 1 = 1 = shl(0, 1) # Inductive step: assuming exp(2, X) = shl(X, 1) for X <= N # to prove: exp(2, N + 1) = shl(N + 1, 1) # # Notice that exp(2, N + 1) = 2 * exp(2, N) mod 2**256 # since exp(2, N) = shl(N, 1), it is enough to show that # 2 * shl(N, 1) mod 2**256 = shl(N + 1, 1) # # Also note that N + 1 < 2**256 N = BitVec('N', n_bits) inductive_step = 2 * SHL(N, 1) rule.check(inductive_step, If(N == 2**256 - 1, 0, SHL(N + 1, 1)))
from rule import Rule from opcodes import * """ byte(A, X) -> 0 given A >= WordSize / 8 """ rule = Rule() n_bits = 256 # Input vars X = BitVec('X', n_bits) A = BitVec('A', n_bits) rule.require(A >= n_bits / 8) rule.check(BYTE(A, X), 0)
X_short = BitVec('X', type_bits) Y_short = BitVec('Y', type_bits) # Z3's overflow and underflow conditions actual_overflow = Not(BVMulNoOverflow(X_short, Y_short, True)) actual_underflow = Not(BVMulNoUnderflow(X_short, Y_short)) # cast to full n_bits values X = BVSignedUpCast(X_short, n_bits) Y = BVSignedUpCast(Y_short, n_bits) # Constants maxValue = BVSignedMax(type_bits, n_bits) minValue = BVSignedMin(type_bits, n_bits) # Overflow and underflow checks in YulUtilFunction::overflowCheckedIntMulFunction overflow_check_1 = AND(AND(SGT(X, 0), SGT(Y, 0)), GT(X, DIV(maxValue, Y))) underflow_check_1 = AND(AND(SGT(X, 0), SLT(Y, 0)), SLT(Y, SDIV(minValue, X))) underflow_check_2 = AND(AND(SLT(X, 0), SGT(Y, 0)), SLT(X, SDIV(minValue, Y))) overflow_check_2 = AND(AND(SLT(X, 0), SLT(Y, 0)), SLT(X, SDIV(maxValue, Y))) rule.check(actual_overflow, Or(overflow_check_1 != 0, overflow_check_2 != 0)) rule.check(actual_underflow, Or(underflow_check_1 != 0, underflow_check_2 != 0)) type_bits *= 2
from rule import Rule from opcodes import * """ Rules: SUB(SUB(X, A), Y) -> SUB(SUB(X, Y), A) SUB(SUB(A, X), Y) -> SUB(A, ADD(X, Y)) SUB(X, SUB(Y, A)) -> ADD(SUB(X, Y), A) SUB(X, SUB(A, Y)) -> ADD(ADD(X, Y), -A) """ rule = Rule() n_bits = 256 # Input vars X = BitVec('X', n_bits) Y = BitVec('Y', n_bits) A = BitVec('A', n_bits) rule.check(SUB(SUB(X, A), Y), SUB(SUB(X, Y), A)) rule.check(SUB(SUB(A, X), Y), SUB(A, ADD(X, Y))) rule.check(SUB(X, SUB(Y, A)), ADD(SUB(X, Y), A)) rule.check(SUB(X, SUB(A, Y)), ADD(ADD(X, Y), SUB(0, A)))
2) SIGNEXTEND(X, SIGNEXTEND(X, Y)) -> SIGNEXTEND(X, Y) 3) SIGNEXTEND(A, SIGNEXTEND(B, X)) -> SIGNEXTEND(min(A, B), X) """ n_bits = 128 # Input vars X = BitVec('X', n_bits) Y = BitVec('Y', n_bits) A = BitVec('A', n_bits) B = BitVec('B', n_bits) rule1 = Rule() # Requirements rule1.require(UGE(A, BitVecVal(n_bits // 8 - 1, n_bits))) rule1.check(SIGNEXTEND(A, X), X) rule2 = Rule() rule2.check( SIGNEXTEND(X, SIGNEXTEND(X, Y)), SIGNEXTEND(X, Y) ) rule3 = Rule() rule3.check( SIGNEXTEND(A, SIGNEXTEND(B, X)), SIGNEXTEND(If(ULT(A, B), A, B), X) )
n_bits = 256 # Check that YulUtilFunction::cleanupFunction cleanup matches BVSignedCleanupFunction for type_bits in range(8, 256, 8): rule = Rule() # Input vars X = BitVec('X', n_bits) arg = BitVecVal(type_bits / 8 - 1, n_bits) cleaned_reference = BVSignedCleanupFunction(X, type_bits) cleaned = SIGNEXTEND(arg, X) rule.check(cleaned, cleaned_reference) # Check that BVSignedCleanupFunction properly cleans up values. for type_bits in range(8, 256, 8): rule = Rule() # Input vars X_short = BitVec('X', type_bits) dirt = BitVec('dirt', n_bits - type_bits) X = BVSignedUpCast(X_short, n_bits) X_dirty = Concat(dirt, X_short) X_cleaned = BVSignedCleanupFunction(X_dirty, type_bits) rule.check(X, X_cleaned)
from rule import Rule from opcodes import * """ Rule: SHL(A, SIGNEXTEND(B, X)) -> SIGNEXTEND((A >> 3) + B, SHL(A, X)) given return A & 7 == 0 AND A <= WordSize AND B <= WordSize / 8 """ n_bits = 256 # Input vars X = BitVec('X', n_bits) Y = BitVec('Y', n_bits) A = BitVec('A', n_bits) B = BitVec('B', n_bits) rule = Rule() rule.require(A & 7 == 0) rule.require(ULE(A, n_bits)) rule.require(ULE(B, n_bits / 8)) rule.check(SHL(A, SIGNEXTEND(B, X)), SIGNEXTEND(LShR(A, 3) + B, SHL(A, X)))
from rule import Rule from opcodes import * """ Checking the implementation of SIGNEXTEND using Z3's native SignExt and Extract """ rule = Rule() n_bits = 256 x = BitVec('X', n_bits) def SIGNEXTEND_native(i, x): return SignExt(256 - 8 * i - 8, Extract(8 * i + 7, 0, x)) for i in range(0, 32): rule.check(SIGNEXTEND(BitVecVal(i, n_bits), x), SIGNEXTEND_native(i, x)) i = BitVec('I', n_bits) rule.require(UGT(i, BitVecVal(31, n_bits))) rule.check(SIGNEXTEND(i, x), x)
from rule import Rule from opcodes import * """ Shift left workaround that Solidity implements due to a bug in Boost. """ rule = Rule() n_bits = 8 bigint_bits = 16 # Input vars X = BitVec('X', n_bits) A = BitVec('A', n_bits) B = BitVec('B', bigint_bits) # Compute workaround workaround = Int2BV( BV2Int((Int2BV(BV2Int(X), bigint_bits) << Int2BV(BV2Int(A), bigint_bits)) & Int2BV(BV2Int(Int2BV(IntVal(-1), n_bits)), bigint_bits)), n_bits) rule.check(workaround, SHL(A, X))
from rule import Rule from opcodes import * from util import * """ Checking conversion of exp(-1, X) to sub(isZero(and(X, 1)), and(X, 1)) """ rule = Rule() n_bits = 256 X = BitVec('X', n_bits) exp_neg_one = If( MOD(X, 2) == 0, BitVecVal(1, n_bits), BVUnsignedMax(n_bits, n_bits)) rule.check(SUB(ISZERO(AND(X, 1)), AND(X, 1)), exp_neg_one)
from opcodes import * """ Rule: AND(OR(AND(X, A), Y), B) -> OR(AND(X, A & B), AND(Y, B)) """ rule = Rule() # bit width is irrelevant n_bits = 128 # Input vars X = BitVec('X', n_bits) Y = BitVec('Y', n_bits) A = BitVec('A', n_bits) B = BitVec('B', n_bits) # Non optimized result, explicit form nonopt = AND(OR(AND(X, A), Y), B) # Optimized result opt = OR(AND(X, A & B), AND(Y, B)) rule.check(nonopt, opt) # Now the forms as they are constructod in the code. for inner in [AND(X, A), AND(A, X)]: for second in [OR(inner, Y), OR(Y, inner)]: rule.check(AND(second, B), opt) rule.check(AND(B, second), opt)
from rule import Rule from opcodes import * """ Rule: SIGNEXTEND(A, SHR(B, X)) -> SAR(B, X) given B % 8 == 0 AND A <= WordSize AND B <= wordSize AND (WordSize - B) / 8 == A + 1 """ n_bits = 256 # Input vars X = BitVec('X', n_bits) Y = BitVec('Y', n_bits) A = BitVec('A', n_bits) B = BitVec('B', n_bits) rule = Rule() rule.require(B % 8 == 0) rule.require(ULE(A, n_bits)) rule.require(ULE(B, n_bits)) rule.require((BitVecVal(n_bits, n_bits) - B) / 8 == A + 1) rule.check(SIGNEXTEND(A, SHR(B, X)), SAR(B, X))
""" # Approximation with 16-bit base types. n_bits = 16 type_bits = 8 while type_bits <= n_bits: rule = Rule() # Input vars X_short = BitVec('X', type_bits) Y_short = BitVec('Y', type_bits) # Z3's overflow condition actual_overflow = Not(BVMulNoOverflow(X_short, Y_short, False)) # cast to full n_bits values X = BVUnsignedUpCast(X_short, n_bits) Y = BVUnsignedUpCast(Y_short, n_bits) # Constants maxValue = BVUnsignedMax(type_bits, n_bits) # Overflow check in YulUtilFunction::overflowCheckedIntMulFunction overflow_check = AND(ISZERO(ISZERO(X)), GT(Y, DIV(maxValue, X))) rule.check(overflow_check != 0, actual_overflow) type_bits *= 2
# Input vars X_short = BitVec('X', type_bits) Y_short = BitVec('Y', type_bits) # Z3's overflow and underflow conditions actual_overflow = Not(BVMulNoOverflow(X_short, Y_short, True)) actual_underflow = Not(BVMulNoUnderflow(X_short, Y_short)) # cast to full n_bits values X = BVSignedUpCast(X_short, n_bits) Y = BVSignedUpCast(Y_short, n_bits) product_raw = MUL(X, Y) #remove any overflown bits product = BVSignedCleanupFunction(product_raw, type_bits) # Constants min_value = BVSignedMin(type_bits, n_bits) # Overflow and underflow checks in YulUtilFunction::overflowCheckedIntMulFunction if type_bits > n_bits / 2: sol_overflow_check_1 = ISZERO(OR(ISZERO(X), EQ(Y, SDIV(product, X)))) if type_bits == n_bits: sol_overflow_check_2 = AND(SLT(X, 0), EQ(Y, min_value)) sol_overflow_check = Or(sol_overflow_check_1 != 0, sol_overflow_check_2 != 0) else: sol_overflow_check = (sol_overflow_check_1 != 0) else: sol_overflow_check = (ISZERO(EQ(product, product_raw)) != 0) rule.check(Or(actual_overflow, actual_underflow), sol_overflow_check)