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

Add Base64Decode #418

Merged
merged 3 commits into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions pyteal/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ __all__ = [
"AssetHolding",
"AssetParam",
"Balance",
"Base64Decode",
"BinaryExpr",
"BitLen",
"BitwiseAnd",
Expand Down
2 changes: 2 additions & 0 deletions pyteal/ast/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@
ExtractUint32,
ExtractUint64,
)
from pyteal.ast.base64decode import Base64Decode

# ternary ops
from pyteal.ast.ternaryexpr import Divw, Ed25519Verify, SetBit, SetByte
Expand Down Expand Up @@ -272,6 +273,7 @@
"ExtractUint16",
"ExtractUint32",
"ExtractUint64",
"Base64Decode",
"Log",
"While",
"For",
Expand Down
85 changes: 85 additions & 0 deletions pyteal/ast/base64decode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from typing import TYPE_CHECKING
from enum import Enum

from pyteal.types import TealType, require_type
from pyteal.errors import verifyFieldVersion
from pyteal.ir import TealOp, Op, TealBlock
from pyteal.ast.expr import Expr
from pyteal.ast.leafexpr import LeafExpr

if TYPE_CHECKING:
from pyteal.compiler import CompileOptions


class Base64Encoding(Enum):
# fmt: off
# id | name | min version
url = (0, "URLEncoding", 7)
std = (1, "StdEncoding", 7)
# fmt: on

def __init__(self, id: int, name: str, min_version: int) -> None:
self.id = id
self.arg_name = name
self.min_version = min_version


Base64Encoding.__module__ = "pyteal"


class Base64Decode(LeafExpr):
"""An expression that decodes a base64-encoded byte string according to a specific encoding.

See [RFC 4648](https://rfc-editor.org/rfc/rfc4648.html#section-4) (sections 4 and 5) for information on specifications.

It is assumed that the encoding ends with the exact number of = padding characters as required by the RFC.
When padding occurs, any unused pad bits in the encoding must be set to zero or the decoding will fail.
The special cases of \\n and \\r are allowed but completely ignored. An error will result when attempting
to decode a string with a character that is not in the encoding alphabet or not one of =, \\r, or \\n.
"""

def __init__(self, encoding: Base64Encoding, base64: Expr) -> None:
super().__init__()
self.encoding = encoding

require_type(base64, TealType.bytes)
self.base64 = base64

def __teal__(self, options: "CompileOptions"):
verifyFieldVersion(
self.encoding.arg_name, self.encoding.min_version, options.version
)

op = TealOp(self, Op.base64_decode, self.encoding.arg_name)
return TealBlock.FromOp(options, op, self.base64)

def __str__(self):
return "(Base64Decode {})".format(self.encoding.arg_name)

def type_of(self):
return TealType.bytes

@classmethod
def url(cls, base64: Expr) -> "Base64Decode":
"""Decode a base64-encoded byte string according to the URL encoding.

Refer to the `Base64Decode` class documentation for more information.

Args:
base64: A base64-encoded byte string.
"""
return cls(Base64Encoding.url, base64)

@classmethod
def std(cls, base64: Expr) -> "Base64Decode":
"""Decode a base64-encoded byte string according to the Standard encoding.

Refer to the `Base64Decode` class documentation for more information.

Args:
base64: A base64-encoded byte string.
"""
return cls(Base64Encoding.std, base64)


Base64Decode.__module__ = "pyteal"
59 changes: 59 additions & 0 deletions pyteal/ast/base64decode_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import pytest

import pyteal as pt

teal6Options = pt.CompileOptions(version=6)
teal7Options = pt.CompileOptions(version=7)


def test_base64decode_std():
arg = pt.Bytes("aGVsbG8gd29ybGQ=")
expr = pt.Base64Decode.std(arg)
assert expr.type_of() == pt.TealType.bytes

expected = pt.TealSimpleBlock(
[
pt.TealOp(arg, pt.Op.byte, '"aGVsbG8gd29ybGQ="'),
pt.TealOp(expr, pt.Op.base64_decode, "StdEncoding"),
]
)

actual, _ = expr.__teal__(teal7Options)
actual.addIncoming()
actual = pt.TealBlock.NormalizeBlocks(actual)

with pt.TealComponent.Context.ignoreExprEquality():
assert actual == expected
jdtzmn marked this conversation as resolved.
Show resolved Hide resolved

with pytest.raises(pt.TealInputError):
expr.__teal__(teal6Options)


def test_base64decode_url():
arg = pt.Bytes("aGVsbG8gd29ybGQ")
expr = pt.Base64Decode.url(arg)
assert expr.type_of() == pt.TealType.bytes

expected = pt.TealSimpleBlock(
[
pt.TealOp(arg, pt.Op.byte, '"aGVsbG8gd29ybGQ"'),
pt.TealOp(expr, pt.Op.base64_decode, "URLEncoding"),
]
)

actual, _ = expr.__teal__(teal7Options)
actual.addIncoming()
actual = pt.TealBlock.NormalizeBlocks(actual)

with pt.TealComponent.Context.ignoreExprEquality():
assert actual == expected
jdtzmn marked this conversation as resolved.
Show resolved Hide resolved

with pytest.raises(pt.TealInputError):
expr.__teal__(teal6Options)


def test_base64decode_invalid():
with pytest.raises(pt.TealTypeError):
pt.Base64Decode.std(pt.Int(0))
with pytest.raises(pt.TealTypeError):
pt.Base64Decode.url(pt.Int(0))
1 change: 1 addition & 0 deletions pyteal/ir/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ def min_version(self) -> int:
gitxnas = OpType("gitxnas", Mode.Application, 6)
gloadss = OpType("gloadss", Mode.Application, 6)
acct_params_get = OpType("acct_params_get", Mode.Application, 6)
base64_decode = OpType("base64_decode", Mode.Application | Mode.Signature, 7)
# fmt: on


Expand Down