Repository: algorand/pyteal

Issue:
Support byteslice math
# Summary
TEAL v4 adds support for doing math on byteslices, meaning up to 512-bit uint math is now possible. PyTeal should support this as well.

# Scope
Supporting this properly may take 2 steps:
1. Add basic support for byteslice math by directly exposing functions like `BytesAdd(a, b)` to do math on byteslices.
2. Create a more robust type system for PyTeal. This could mean new uint types (128-bit, 256-bit, etc.) that would compile to byteslices and use byteslice math if needed.

This issue is for just the first step.

Discussion:

Patch:
diff --git a/README.md b/README.md
index 95425b4..3ee9475 100644
--- a/README.md
+++ b/README.md
@@ -38,14 +38,15 @@ In pyteal root directory:
 Setup venv (one time):
  * `python3 -m venv venv`
 
-
 Active venv:
  * `. venv/bin/activate.fish` (if your shell is fish)
  * `. venv/bin/activate` (if your shell is bash/zsh)
 
-
 Pip install pyteal in editable state
  * `pip install -e .`
+
+Install dependencies :
+* `pip3 install -r requirements.txt`
  
 Type checking using mypy
 * `mypy pyteal`
diff --git a/docs/arithmetic_expression.rst b/docs/arithmetic_expression.rst
index 4e49229..60fa3cb 100644
--- a/docs/arithmetic_expression.rst
+++ b/docs/arithmetic_expression.rst
@@ -45,6 +45,39 @@ The associativity and precedence of the overloaded Python arithmetic operators a
  * :code:`Int(1) + Int(2) + Int(3)` is equivalent to :code:`Add(Add(Int(1), Int(2)), Int(3))`
  * :code:`Int(1) + Int(2) * Int(3)` is equivalent to :code:`Add(Int(1), Mul(Int(2), Int(3)))` 
 
+Byteslice Arithmetic
+--------------------
+
+Byteslice arithemetic is available for Teal V4 and above. 
+Byteslice arithmetic operators allow up to 512-bit arithmetic.
+In PyTeal, byteslice arithmetic expressions include 
+:code:`TealType.Bytes` values as arguments (with the exception of :code:`BytesZero`)
+and must be 64 bytes or less.
+The table below summarizes the byteslize arithmetic operations in PyTeal.
+
+======================================= ======================= ====================================================================== ==================
+Operator                                Return Type             Example                                                                Example Result
+======================================= ======================= ====================================================================== ==================
+:any:`BytesLt(a, b) <BytesLt>`          :code:`TealType.uint64` :code:`BytesLt(Bytes("base16", "0xFF"), Bytes("base16", "0xFE"))`      :code:`0`
+:any:`BytesGt(a, b) <BytesGt>`          :code:`TealType.uint64` :code:`BytesGt(Bytes("base16", "0xFF"), Bytes("base16", "0xFE"))`      :code:`1`
+:any:`BytesLe(a, b) <BytesLe>`          :code:`TealType.uint64` :code:`BytesLe(Bytes("base16", "0xFF"), Bytes("base16", "0xFE"))`      :code:`0`
+:any:`BytesGe(a, b) <BytesGe>`          :code:`TealType.uint64` :code:`BytesGe(Bytes("base16", "0xFF"), Bytes("base16", "0xFE"))`      :code:`1`
+:any:`BytesEq(a, b) <BytesEq>`          :code:`TealType.uint64` :code:`BytesEq(Bytes("base16", "0xFF"), Bytes("base16", "0xFF"))`      :code:`1`
+:any:`BytesNeq(a, b) <BytesNeq>`        :code:`TealType.uint64` :code:`BytesNeq(Bytes("base16", "0xFF"), Bytes("base16", "0xFF"))`     :code:`0`
+:any:`BytesAdd(a, b) <BytesAdd>`        :code:`TealType.Bytes`  :code:`BytesAdd(Bytes("base16", "0xFF"), Bytes("base16", "0xFE"))`     :code:`0x01FD`
+:any:`BytesMinus(a, b) <BytesMinus>`    :code:`TealType.Bytes`  :code:`BytesMinus(Bytes("base16", "0xFF"), Bytes("base16", "0xFE"))`   :code:`0x01`
+:any:`BytesMul(a, b) <BytesMul>`        :code:`TealType.Bytes`  :code:`BytesMul(Bytes("base16", "0xFF"), Bytes("base16", "0xFE"))`     :code:`0xFD02`
+:any:`BytesDiv(a, b) <BytesDiv>`        :code:`TealType.Bytes`  :code:`BytesDiv(Bytes("base16", "0xFF"), Bytes("base16", "0x11"))`     :code:`0x0F`
+:any:`BytesMod(a, b) <BytesMod>`        :code:`TealType.Bytes`  :code:`BytesMod(Bytes("base16", "0xFF"), Bytes("base16", "0x12"))`     :code:`0x03`
+:any:`BytesAnd(a, b) <pyteal.BytesAnd>` :code:`TealType.Bytes`  :code:`BytesAnd(Bytes("base16", "0xBEEF"), Bytes("base16", "0x1337"))` :code:`0x1227`
+:any:`BytesOr(a, b) <pyteal.BytesOr>`   :code:`TealType.Bytes`  :code:`BytesOr(Bytes("base16", "0xBEEF"), Bytes("base16", "0x1337"))`  :code:`0xBFFF`
+:any:`BytesXor(a, b) <pyteal.BytesXor>` :code:`TealType.Bytes`  :code:`BytesXor(Bytes("base16", "0xBEEF"), Bytes("base16", "0x1337"))` :code:`0xADD8`
+:any:`BytesNot(a) <pyteal.BytesNot>`    :code:`TealType.Bytes`  :code:`BytesNot(Bytes("base16", "0xFF00"))`                            :code:`0x00FF`
+:any:`BytesZero(a) <pyteal.BytesZero>`  :code:`TealType.Bytes`  :code:`BytesZero(Int(4))`                                              :code:`0x00000000`
+======================================= ======================= ====================================================================== ==================
+
+Currently, byteslice arithmetic operations are not overloaded, and must be explicitly called.
+
 .. _bit_and_byte_manipulation:
 
 Bit and Byte Operations
diff --git a/docs/conf.py b/docs/conf.py
index 57b7ac4..ab2f8f8 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -18,7 +18,7 @@ sys.path.insert(0, os.path.abspath('..'))
 # -- Project information -----------------------------------------------------
 
 project = 'PyTeal'
-copyright = '2020, Algorand'
+copyright = '2021, Algorand'
 author = 'Algorand'
 
 
diff --git a/pyteal/ast/__init__.py b/pyteal/ast/__init__.py
index da419fa..08a29e8 100644
--- a/pyteal/ast/__init__.py
+++ b/pyteal/ast/__init__.py
@@ -23,10 +23,10 @@ from .tmpl import Tmpl
 from .nonce import Nonce
 
 # unary ops
-from .unaryexpr import UnaryExpr, Btoi, Itob, Len, BitLen, Sha256, Sha512_256, Keccak256, Not, BitwiseNot, Sqrt, Pop, Return, Balance, MinBalance
+from .unaryexpr import UnaryExpr, Btoi, Itob, Len, BitLen, Sha256, Sha512_256, Keccak256, Not, BitwiseNot, Sqrt, Pop, Return, Balance, MinBalance, BytesNot, BytesZero
 
 # binary ops
-from .binaryexpr import BinaryExpr, Add, Minus, Mul, Div, Mod, Exp, BitwiseAnd, BitwiseOr, BitwiseXor, ShiftLeft, ShiftRight, Eq, Neq, Lt, Le, Gt, Ge, GetBit, GetByte
+from .binaryexpr import BinaryExpr, Add, Minus, Mul, Div, Mod, Exp, BitwiseAnd, BitwiseOr, BitwiseXor, ShiftLeft, ShiftRight, Eq, Neq, Lt, Le, Gt, Ge, GetBit, GetByte, BytesAdd, BytesMinus, BytesDiv, BytesMul, BytesMod, BytesAnd, BytesOr, BytesXor, BytesEq, BytesNeq, BytesLt, BytesLe, BytesGt, BytesGe
 
 # ternary ops
 from .ternaryexpr import Ed25519Verify, Substring, SetBit, SetByte
@@ -129,4 +129,20 @@ __all__ = [
     "ScratchStackStore",
     "ScratchVar",
     "MaybeValue",
+    "BytesAdd",
+    "BytesMinus",
+    "BytesDiv",
+    "BytesMul",
+    "BytesMod", 
+    "BytesAnd", 
+    "BytesOr", 
+    "BytesXor", 
+    "BytesEq", 
+    "BytesNeq", 
+    "BytesLt", 
+    "BytesLe", 
+    "BytesGt", 
+    "BytesGe",
+    "BytesNot",
+    "BytesZero",
 ]
diff --git a/pyteal/ast/binaryexpr.py b/pyteal/ast/binaryexpr.py
index 13dfcdf..f2ef48d 100644
--- a/pyteal/ast/binaryexpr.py
+++ b/pyteal/ast/binaryexpr.py
@@ -259,3 +259,206 @@ def GetByte(value: Expr, index: Expr) -> BinaryExpr:
         index: The index of the byte to extract. Must evaluate to an integer less than Len(value).
     """
     return BinaryExpr(Op.getbyte, (TealType.bytes, TealType.uint64), TealType.uint64, value, index)
+
+def BytesAdd(left: Expr, right: Expr) -> BinaryExpr:
+    """Add two numbers as bytes.
+    
+    Produces left + right, where left and right are interpreted as big-endian unsigned integers.
+    Arguments must not exceed 64 bytes.
+
+    Requires TEAL version 4 or higher.
+
+    Args:
+        left: Must evaluate to bytes.
+        right: Must evaluate to bytes.
+    """
+    return BinaryExpr(Op.b_add, TealType.bytes, TealType.bytes, left, right)
+
+def BytesMinus(left: Expr, right: Expr) -> BinaryExpr:
+    """Subtract two numbers as bytes.
+    
+    Produces left - right, where left and right are interpreted as big-endian unsigned integers.
+    Arguments must not exceed 64 bytes.
+
+    Requires TEAL version 4 or higher.
+
+    Args:
+        left: Must evaluate to bytes.
+        right: Must evaluate to bytes.
+    """
+    return BinaryExpr(Op.b_minus, TealType.bytes, TealType.bytes, left, right)
+    
+def BytesDiv(left: Expr, right: Expr) -> BinaryExpr:
+    """Divide two numbers as bytes.
+    
+    Produces left / right, where left and right are interpreted as big-endian unsigned integers.
+    Arguments must not exceed 64 bytes.
+
+    Panics if right is 0. 
+
+    Requires TEAL version 4 or higher.
+
+    Args:
+        left: Must evaluate to bytes.
+        right: Must evaluate to bytes.
+    """
+    return BinaryExpr(Op.b_div, TealType.bytes, TealType.bytes, left, right)
+    
+def BytesMul(left: Expr, right: Expr) -> BinaryExpr:
+    """Multiply two numbers as bytes.
+    
+    Produces left * right, where left and right are interpreted as big-endian unsigned integers.
+    Arguments must not exceed 64 bytes.
+
+    Requires TEAL version 4 or higher.
+
+    Args:
+        left: Must evaluate to bytes.
+        right: Must evaluate to bytes.
+    """
+    return BinaryExpr(Op.b_mul, TealType.bytes, TealType.bytes, left, right)
+
+def BytesMod(left: Expr, right: Expr) -> BinaryExpr:
+    """Modulo expression with bytes as arguments.
+    
+    Produces left % right, where left and right are interpreted as big-endian unsigned integers.
+    Arguments must not exceed 64 bytes.
+
+    Panics if right is 0. 
+
+    Requires TEAL version 4 or higher.
+
+    Args:
+        left: Must evaluate to bytes.
+        right: Must evaluate to bytes.
+    """
+    return BinaryExpr(Op.b_mod, TealType.bytes, TealType.bytes, left, right)
+
+def BytesAnd(left: Expr, right: Expr) -> BinaryExpr:
+    """Bitwise and expression with bytes as arguments.
+
+    Produces left & right.
+    Left and right are zero-left extended to the greater of their lengths.
+    Arguments must not exceed 64 bytes.
+
+    Requires TEAL version 4 or higher.
+
+    Args:
+        left: Must evaluate to bytes.
+        right: Must evaluate to bytes.
+    """
+    return BinaryExpr(Op.b_and, TealType.bytes, TealType.bytes, left, right)
+
+def BytesOr(left: Expr, right: Expr) -> BinaryExpr:
+    """Bitwise or expression with bytes as arguments.
+
+    Produces left | right.
+    Left and right are zero-left extended to the greater of their lengths.
+    Arguments must not exceed 64 bytes.
+
+    Requires TEAL version 4 or higher.
+
+    Args:
+        left: Must evaluate to bytes.
+        right: Must evaluate to bytes.
+    """
+    return BinaryExpr(Op.b_or, TealType.bytes, TealType.bytes, left, right)
+
+def BytesXor(left: Expr, right: Expr) -> BinaryExpr:
+    """Bitwise xor expression with bytes as arguments.
+
+    Produces left ^ right.
+    Left and right are zero-left extended to the greater of their lengths.
+    Arguments must not exceed 64 bytes.
+
+    Requires TEAL version 4 or higher.
+
+    Args:
+        left: Must evaluate to bytes.
+        right: Must evaluate to bytes.
+    """
+    return BinaryExpr(Op.b_xor, TealType.bytes, TealType.bytes, left, right)
+
+def BytesEq(left: Expr, right: Expr) -> BinaryExpr:
+    """Equality expression with bytes as arguments.
+    
+    Checks if left == right, where left and right are interpreted as big-endian unsigned integers.
+    Arguments must not exceed 64 bytes.
+
+    Requires TEAL version 4 or higher.
+
+    Args:
+        left: Must evaluate to bytes.
+        right: Must evaluate to bytes.
+    """
+    return BinaryExpr(Op.b_eq, TealType.bytes, TealType.uint64, left, right)
+
+def BytesNeq(left: Expr, right: Expr) -> BinaryExpr:
+    """Difference expression with bytes as arguments.
+    
+    Checks if left != right, where left and right are interpreted as big-endian unsigned integers.
+    Arguments must not exceed 64 bytes.
+
+    Requires TEAL version 4 or higher.
+
+    Args:
+        left: Must evaluate to bytes.
+        right: Must evaluate to bytes.
+    """
+    return BinaryExpr(Op.b_neq, TealType.bytes, TealType.uint64, left, right)
+
+def BytesLt(left: Expr, right: Expr) -> BinaryExpr:
+    """Less than expression with bytes as arguments.
+    
+    Checks if left < right, where left and right are interpreted as big-endian unsigned integers.
+    Arguments must not exceed 64 bytes.
+
+    Requires TEAL version 4 or higher.
+
+    Args:
+        left: Must evaluate to bytes.
+        right: Must evaluate to bytes.
+    """
+    return BinaryExpr(Op.b_lt, TealType.bytes, TealType.uint64, left, right)
+
+def BytesLe(left: Expr, right: Expr) -> BinaryExpr:
+    """Less than or equal to expression with bytes as arguments.
+    
+    Checks if left <= right, where left and right are interpreted as big-endian unsigned integers.
+    Arguments must not exceed 64 bytes.
+
+    Requires TEAL version 4 or higher.
+
+    Args:
+        left: Must evaluate to bytes.
+        right: Must evaluate to bytes.
+    """
+    return BinaryExpr(Op.b_le, TealType.bytes, TealType.uint64, left, right)
+
+def BytesGt(left: Expr, right: Expr) -> BinaryExpr:
+    """Greater than expression with bytes as arguments.
+    
+    Checks if left > right, where left and right are interpreted as big-endian unsigned integers.
+    Arguments must not exceed 64 bytes.
+
+    Requires TEAL version 4 or higher.
+
+    Args:
+        left: Must evaluate to bytes.
+        right: Must evaluate to bytes.
+    """
+    return BinaryExpr(Op.b_gt, TealType.bytes, TealType.uint64, left, right)
+
+def BytesGe(left: Expr, right: Expr) -> BinaryExpr:
+    """Greater than or equal to expression with bytes as arguments.
+    
+    Checks if left >= right, where left and right are interpreted as big-endian unsigned integers.
+    Arguments must not exceed 64 bytes.
+
+    Requires TEAL version 4 or higher.
+    
+    Args:
+        left: Must evaluate to bytes.
+        right: Must evaluate to bytes.
+    """
+    return BinaryExpr(Op.b_ge, TealType.bytes, TealType.uint64, left, right)
diff --git a/pyteal/ast/unaryexpr.py b/pyteal/ast/unaryexpr.py
index 144422c..60dc447 100644
--- a/pyteal/ast/unaryexpr.py
+++ b/pyteal/ast/unaryexpr.py
@@ -115,3 +115,22 @@ def MinBalance(account: Expr) -> UnaryExpr:
     Requires TEAL version 3 or higher. This operation is only permitted in application mode.
     """
     return UnaryExpr(Op.min_balance, TealType.uint64, TealType.uint64, account)
+
+def BytesNot(arg: Expr) -> UnaryExpr:
+    """Get the bitwise inverse of bytes.
+    
+    Produces ~arg.
+    Argument must not exceed 64 bytes.
+
+    Requires TEAL version 4 or higher.
+    """
+    return UnaryExpr(Op.b_not, TealType.bytes, TealType.bytes, arg)
+
+def BytesZero(arg: Expr) -> UnaryExpr:
+    """Get a byte-array of a specified length, containing all zero bytes.
+
+    Argument must evaluate to uint64. 
+
+    Requires TEAL version 4 or higher.    
+    """
+    return UnaryExpr(Op.bzero, TealType.uint64, TealType.bytes, arg)


Test patch:
diff --git a/pyteal/ast/binaryexpr_test.py b/pyteal/ast/binaryexpr_test.py
index fd9c6fe..996b1ec 100644
--- a/pyteal/ast/binaryexpr_test.py
+++ b/pyteal/ast/binaryexpr_test.py
@@ -891,3 +891,339 @@ def test_get_byte_invalid():
     
     with pytest.raises(TealTypeError):
         GetBit(Bytes("base16", "0xFF"), Bytes("index"))
+
+def test_b_add():
+    args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), Bytes("base16", "0xFFFFFFFFFFFFFFFFFE")]
+    expr = BytesAdd(args[0], args[1])
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"),
+        TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFFE"),
+        TealOp(expr, Op.b_add)
+    ])
+
+    actual, _ = expr.__teal__(options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_add_invalid():
+    with pytest.raises(TealTypeError):
+        BytesAdd(Int(2), Txn.receiver())
+    
+    with pytest.raises(TealTypeError):
+        BytesAdd(Bytes("base16", "0xFF"), Int(2))
+
+def test_b_minus():
+    args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), Bytes("base16", "0xFFFFFFFFFFFFFFFFFE")]
+    expr = BytesMinus(args[0], args[1])
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"),
+        TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFFE"),
+        TealOp(expr, Op.b_minus)
+    ])
+
+    actual, _ = expr.__teal__(options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_minus_invalid():
+    with pytest.raises(TealTypeError):
+        BytesMinus(Int(2), Txn.receiver())
+    
+    with pytest.raises(TealTypeError):
+        BytesMinus(Bytes("base16", "0xFF"), Int(2))
+
+def test_b_div():
+    args = [Bytes("base16", "0xFFFFFFFFFFFFFFFF00"), Bytes("base16", "0xFF")]
+    expr = BytesDiv(args[0], args[1])
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFF00"),
+        TealOp(args[1], Op.byte, "0xFF"),
+        TealOp(expr, Op.b_div)
+    ])
+
+    actual, _ = expr.__teal__(options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_div_invalid():
+    with pytest.raises(TealTypeError):
+        BytesDiv(Int(2), Txn.receiver())
+    
+    with pytest.raises(TealTypeError):
+        BytesDiv(Bytes("base16", "0xFF"), Int(2))
+
+def test_b_mul():
+    args = [Bytes("base16", "0xFFFFFFFFFFFFFFFF"), Bytes("base16", "0xFF")]
+    expr = BytesMul(args[0], args[1])
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFF"),
+        TealOp(args[1], Op.byte, "0xFF"),
+        TealOp(expr, Op.b_mul)
+    ])
+
+    actual, _ = expr.__teal__(options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_mul_invalid():
+    with pytest.raises(TealTypeError):
+        BytesMul(Int(2), Txn.receiver())
+    
+    with pytest.raises(TealTypeError):
+        BytesMul(Bytes("base16", "0xFF"), Int(2))
+
+def test_b_mod():
+    args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), Bytes("base16", "0xFF")]
+    expr = BytesMod(args[0], args[1])
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"),
+        TealOp(args[1], Op.byte, "0xFF"),
+        TealOp(expr, Op.b_mod)
+    ])
+
+    actual, _ = expr.__teal__(options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_mod_invalid():
+    with pytest.raises(TealTypeError):
+        BytesMod(Int(2), Txn.receiver())
+    
+    with pytest.raises(TealTypeError):
+        BytesMod(Bytes("base16", "0xFF"), Int(2))
+
+def test_b_and():
+    args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), Bytes("base16", "0xFF")]
+    expr = BytesAnd(args[0], args[1])
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFF0"),
+        TealOp(args[1], Op.byte, "0xFF"),
+        TealOp(expr, Op.b_and)
+    ])
+
+    actual, _ = expr.__teal__(options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_and_invalid():
+    with pytest.raises(TealTypeError):
+        BytesAnd(Int(2), Txn.receiver())
+    
+    with pytest.raises(TealTypeError):
+        BytesAnd(Bytes("base16", "0xFF"), Int(2))
+
+def test_b_or():
+    args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), Bytes("base16", "0xFF")]
+    expr = BytesOr(args[0], args[1])
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFF0"),
+        TealOp(args[1], Op.byte, "0xFF"),
+        TealOp(expr, Op.b_or)
+    ])
+
+    actual, _ = expr.__teal__(options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_or_invalid():
+    with pytest.raises(TealTypeError):
+        BytesOr(Int(2), Txn.receiver())
+    
+    with pytest.raises(TealTypeError):
+        BytesOr(Bytes("base16", "0xFF"), Int(2))
+
+def test_b_xor():
+    args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), Bytes("base16", "0xFF")]
+    expr = BytesXor(args[0], args[1])
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFF0"),
+        TealOp(args[1], Op.byte, "0xFF"),
+        TealOp(expr, Op.b_xor)
+    ])
+
+    actual, _ = expr.__teal__(options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_xor_invalid():
+    with pytest.raises(TealTypeError):
+        BytesXor(Int(2), Txn.receiver())
+    
+    with pytest.raises(TealTypeError):
+        BytesXor(Bytes("base16", "0xFF"), Int(2))
+
+def test_b_eq():
+    args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), Bytes("base16", "0xFFFFFFFFFFFFFFFFFF")]
+    expr = BytesEq(args[0], args[1])
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"),
+        TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFFF"),
+        TealOp(expr, Op.b_eq)
+    ])
+
+    actual, _ = expr.__teal__(options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_eq_invalid():
+    with pytest.raises(TealTypeError):
+        BytesEq(Int(2), Txn.receiver())
+    
+    with pytest.raises(TealTypeError):
+        BytesEq(Bytes("base16", "0xFF"), Int(2))
+
+def test_b_neq():
+    args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), Bytes("base16", "0xFFFFFFFFFFFFFFFFFF")]
+    expr = BytesNeq(args[0], args[1])
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"),
+        TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFFF"),
+        TealOp(expr, Op.b_neq)
+    ])
+
+    actual, _ = expr.__teal__(options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_neq_invalid():
+    with pytest.raises(TealTypeError):
+        BytesNeq(Int(2), Txn.receiver())
+    
+    with pytest.raises(TealTypeError):
+        BytesNeq(Bytes("base16", "0xFF"), Int(2))
+
+def test_b_lt():
+    args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), Bytes("base16", "0xFFFFFFFFFFFFFFFFFF")]
+    expr = BytesLt(args[0], args[1])
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFF0"),
+        TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFFF"),
+        TealOp(expr, Op.b_lt)
+    ])
+
+    actual, _ = expr.__teal__(options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_lt_invalid():
+    with pytest.raises(TealTypeError):
+        BytesLt(Int(2), Txn.receiver())
+    
+    with pytest.raises(TealTypeError):
+        BytesLt(Bytes("base16", "0xFF"), Int(2))
+
+def test_b_le():
+    args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFF0"), Bytes("base16", "0xFFFFFFFFFFFFFFFFFF")]
+    expr = BytesLe(args[0], args[1])
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFF0"),
+        TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFFF"),
+        TealOp(expr, Op.b_le)
+    ])
+
+    actual, _ = expr.__teal__(options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_le_invalid():
+    with pytest.raises(TealTypeError):
+        BytesLe(Int(2), Txn.receiver())
+    
+    with pytest.raises(TealTypeError):
+        BytesLe(Bytes("base16", "0xFF"), Int(2))
+
+def test_b_gt():
+    args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), Bytes("base16", "0xFFFFFFFFFFFFFFFFF0")]
+    expr = BytesGt(args[0], args[1])
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"),
+        TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFF0"),
+        TealOp(expr, Op.b_gt)
+    ])
+
+    actual, _ = expr.__teal__(options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_gt_invalid():
+    with pytest.raises(TealTypeError):
+        BytesGt(Int(2), Txn.receiver())
+    
+    with pytest.raises(TealTypeError):
+        BytesGt(Bytes("base16", "0xFF"), Int(2))
+
+def test_b_ge():
+    args = [Bytes("base16", "0xFFFFFFFFFFFFFFFFFF"), Bytes("base16", "0xFFFFFFFFFFFFFFFFF0")]
+    expr = BytesGe(args[0], args[1])
+    assert expr.type_of() == TealType.uint64
+    
+    expected = TealSimpleBlock([
+        TealOp(args[0], Op.byte, "0xFFFFFFFFFFFFFFFFFF"),
+        TealOp(args[1], Op.byte, "0xFFFFFFFFFFFFFFFFF0"),
+        TealOp(expr, Op.b_ge)
+    ])
+
+    actual, _ = expr.__teal__(options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_ge_invalid():
+    with pytest.raises(TealTypeError):
+        BytesGe(Int(2), Txn.receiver())
+    
+    with pytest.raises(TealTypeError):
+        BytesGe(Bytes("base16", "0xFF"), Int(2))
diff --git a/pyteal/ast/unaryexpr_test.py b/pyteal/ast/unaryexpr_test.py
index e278de1..5d3b941 100644
--- a/pyteal/ast/unaryexpr_test.py
+++ b/pyteal/ast/unaryexpr_test.py
@@ -331,3 +331,43 @@ def test_min_balance():
 def test_min_balance_invalid():
     with pytest.raises(TealTypeError):
         MinBalance(Txn.receiver())
+
+def test_b_not():
+    arg = Bytes("base16", "0xFFFFFFFFFFFFFFFFFF")
+    expr = BytesNot(arg)
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(arg, Op.byte, "0xFFFFFFFFFFFFFFFFFF"),
+        TealOp(expr, Op.b_not)
+    ])
+
+    actual, _ = expr.__teal__(teal4Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_not_invalid():
+    with pytest.raises(TealTypeError):
+        BytesNot(Int(2))
+
+def test_b_zero():
+    arg = Int(8)
+    expr = BytesZero(arg)
+    assert expr.type_of() == TealType.bytes
+    
+    expected = TealSimpleBlock([
+        TealOp(arg, Op.int, 8),
+        TealOp(expr, Op.bzero)
+    ])
+
+    actual, _ = expr.__teal__(teal4Options)
+    actual.addIncoming()
+    actual = TealBlock.NormalizeBlocks(actual)
+    
+    assert actual == expected
+
+def test_b_zero_invalid():
+    with pytest.raises(TealTypeError):
+        BytesZero(Bytes("base16", "0x11"))