From 7b019e5c02b611661ccc1d878fe013bd5eacf915 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 17 Nov 2021 00:19:28 -0600 Subject: [PATCH 01/22] a bunch of changes, might not need them all --- data/transactions/logic/doc.go | 6 + data/transactions/logic/eval.go | 31 + data/transactions/logic/evalDecode64_test.go | 580 +++++++++++++++++++ data/transactions/logic/fields.go | 49 ++ data/transactions/logic/opcodes.go | 1 + 5 files changed, 667 insertions(+) create mode 100644 data/transactions/logic/evalDecode64_test.go diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 98e757f8b8..503846a247 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -107,6 +107,7 @@ var opDocByName = map[string]string{ "gloads": "push Ith scratch space index of the Xth transaction in the current group", "gaid": "push the ID of the asset or application created in the Tth transaction of the current group", "gaids": "push the ID of the asset or application created in the Xth transaction of the current group", + "zaid": "zush the ID of the asset or application created in the Xth transaction of the current group", "bnz": "branch to TARGET if value X is not zero", "bz": "branch to TARGET if value X is zero", @@ -538,3 +539,8 @@ func AppParamsFieldDocs() map[string]string { var EcdsaCurveDocs = map[string]string{ "Secp256k1": "secp256k1 curve", } + +var Base64AlphabetDocs = map[string]string{ + "Standard": `Standard base-64 alphabet as specified in RFC 4648 section 4`, + "URL": `URL and Filename Safe base-64 alphabet as specified in RFC 4648 section 5`, +} diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 9a2530f363..db5e2e73e0 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -20,6 +20,7 @@ import ( "bytes" "crypto/sha256" "crypto/sha512" + "encoding/base64" "encoding/binary" "encoding/hex" "errors" @@ -4034,3 +4035,33 @@ func (cx *EvalContext) PcDetails() (pc int, dis string) { } return cx.pc, dis } + +func base64Decode(encoded []byte, isURL bool) ([]byte, error) { + encoding := base64.URLEncoding + if !isURL { + encoding = base64.StdEncoding + } + + decoded := make([]byte, encoding.DecodedLen(len(encoded))) + n, err := encoding.Strict().Decode(decoded, encoded) + if err != nil { + n = 0 + } + return decoded[:n], err +} + +func opBase64Decode(cx *EvalContext) { + last := len(cx.stack) - 1 + alphabetField := Base64Alphabet(cx.program[cx.pc+1]) + fs, ok := base64AlphabetSpecByField[alphabetField] + if !ok || fs.version > cx.version { + cx.err = fmt.Errorf("invalid base64_decode field %d", alphabetField) + return + } + + isURL := true + if alphabetField == StandardAlphabet { + isURL = false + } + cx.stack[last].Bytes, cx.err = base64Decode(cx.stack[last].Bytes, isURL) +} diff --git a/data/transactions/logic/evalDecode64_test.go b/data/transactions/logic/evalDecode64_test.go new file mode 100644 index 0000000000..8b355ecbb1 --- /dev/null +++ b/data/transactions/logic/evalDecode64_test.go @@ -0,0 +1,580 @@ +// Copyright (C) 2019-2021 Algorand, Inc. +// This file is part of go-algorand +// +// go-algorand is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// go-algorand is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with go-algorand. If not, see . + +package logic + +import ( + "crypto/ecdsa" + "crypto/rand" + "crypto/sha256" + "crypto/sha512" + "encoding/base64" + "encoding/hex" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/algorand/go-algorand/crypto" + "github.com/algorand/go-algorand/crypto/secp256k1" + "github.com/algorand/go-algorand/data/basics" + "github.com/algorand/go-algorand/data/transactions" + "github.com/algorand/go-algorand/test/partitiontest" +) + +type testCase struct { + Encoded string + IsURL bool + Decoded string + Error error +} + +var testCases = []testCase{ + {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=", + false, + `MOBY-DICK; + +or, THE WHALE. + + +By Herman Melville`, + nil, + }, + {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=", + true, + `MOBY-DICK; + +or, THE WHALE. + + +By Herman Melville`, + nil, + }, + {"YWJjMTIzIT8kKiYoKSctPUB+", false, "abc123!?$*&()'-=@~", nil}, + {"YWJjMTIzIT8kKiYoKSctPUB-", true, "abc123!?$*&()'-=@~", nil}, + {"YWJjMTIzIT8kKiYoKSctPUB+", true, "", base64.CorruptInputError(23)}, + {"YWJjMTIzIT8kKiYoKSctPUB-", false, "", base64.CorruptInputError(23)}, +} + +func TestBase64DecodeFunc(t *testing.T) { + partitiontest.PartitionTest(t) // do I need this? + t.Parallel() // do I need this + + for _, testCase := range testCases { + decoded, err := base64Decode([]byte(testCase.Encoded), testCase.IsURL) + require.Equal(t, []byte(testCase.Decoded), decoded) + require.Equal(t, testCase.Error, err) + } + + // b := big.NewInt(0x100) + // r, err := leadingZeros(1, b) + // require.Error(t, err) + // require.Nil(t, r) + + // b = big.NewInt(100) + // r, err = leadingZeros(1, b) + // require.NoError(t, err) + // require.Equal(t, []byte{100}, r) + + // b = big.NewInt(100) + // r, err = leadingZeros(2, b) + // require.NoError(t, err) + // require.Equal(t, []byte{0, 100}, r) + + // v32, err := hex.DecodeString("71a5910445820f57989c027bdf9391c80097874d249e0f38bf90834fdec2877f") + // require.NoError(t, err) + // b = new(big.Int).SetBytes(v32) + // r, err = leadingZeros(32, b) + // require.NoError(t, err) + // require.Equal(t, v32, r) + + // v31 := v32[1:] + // b = new(big.Int).SetBytes(v31) + // r, err = leadingZeros(32, b) + // require.NoError(t, err) + // v31z := append([]byte{0}, v31...) + // require.Equal(t, v31z, r) + + // require.Equal(t, v31z, keyToByte(t, b)) +} + +// OLDER +func TestKeccak256_Z(t *testing.T) { + partitiontest.PartitionTest(t) + + t.Parallel() + /* + pip install sha3 + import sha3 + blob=b'fnord' + sha3.keccak_256(blob).hexdigest() + */ + progText := `byte 0x666E6F7264 +keccak256 +byte 0xc195eca25a6f4c82bfba0287082ddb0d602ae9230f9cf1f1a40b68f8e2c41567 +==` + testAccepts(t, progText, 1) +} + +func TestSHA512_256_Z(t *testing.T) { + partitiontest.PartitionTest(t) + + t.Parallel() + /* + pip cryptography + from cryptography.hazmat.backends import default_backend + from cryptography.hazmat.primitives import hashes + import base64 + digest = hashes.Hash(hashes.SHA512_256(), backend=default_backend()) + digest.update(b'fnord') + base64.b16encode(digest.finalize()) + */ + progText := `byte 0x666E6F7264 +sha512_256 + +byte 0x98D2C31612EA500279B6753E5F6E780CA63EBA8274049664DAD66A2565ED1D2A +==` + testAccepts(t, progText, 1) +} + +func TestEd25519verify_Z(t *testing.T) { + partitiontest.PartitionTest(t) + + t.Parallel() + var s crypto.Seed + crypto.RandBytes(s[:]) + c := crypto.GenerateSignatureSecrets(s) + msg := "62fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd" + data, err := hex.DecodeString(msg) + require.NoError(t, err) + pk := basics.Address(c.SignatureVerifier) + pkStr := pk.String() + + for v := uint64(1); v <= AssemblerMaxVersion; v++ { + t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { + ops, err := AssembleStringWithVersion(fmt.Sprintf(`arg 0 +arg 1 +addr %s +ed25519verify`, pkStr), v) + require.NoError(t, err) + sig := c.Sign(Msg{ + ProgramHash: crypto.HashObj(Program(ops.Program)), + Data: data[:], + }) + var txn transactions.SignedTxn + txn.Lsig.Logic = ops.Program + txn.Lsig.Args = [][]byte{data[:], sig[:]} + sb := strings.Builder{} + pass, err := Eval(ops.Program, defaultEvalParams(&sb, &txn)) + if !pass { + t.Log(hex.EncodeToString(ops.Program)) + t.Log(sb.String()) + } + require.True(t, pass) + require.NoError(t, err) + + // short sig will fail + txn.Lsig.Args[1] = sig[1:] + pass, err = Eval(ops.Program, defaultEvalParams(nil, &txn)) + require.False(t, pass) + require.Error(t, err) + isNotPanic(t, err) + + // flip a bit and it should not pass + msg1 := "52fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd" + data1, err := hex.DecodeString(msg1) + require.NoError(t, err) + txn.Lsig.Args = [][]byte{data1, sig[:]} + sb1 := strings.Builder{} + pass1, err := Eval(ops.Program, defaultEvalParams(&sb1, &txn)) + require.False(t, pass1) + require.NoError(t, err) + isNotPanic(t, err) + }) + } +} + +// bitIntFillBytes is a replacement for big.Int.FillBytes from future Go +// func bitIntFillBytes(tb testing.TB, b *big.Int) []byte { +// k := make([]byte, 32) +// require.NotPanics(tb, func() { +// k = bitIntFillBytes(b, k) +// }) +// return k +// } + +func TestEcdsa_Z(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + key, err := ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) + require.NoError(t, err) + pk := secp256k1.CompressPubkey(key.PublicKey.X, key.PublicKey.Y) + sk := keyToByte(t, key.D) + x := keyToByte(t, key.PublicKey.X) + y := keyToByte(t, key.PublicKey.Y) + + // ecdsa decompress tests + source := ` +byte 0x%s +ecdsa_pk_decompress Secp256k1 +store 0 +byte 0x%s +== +load 0 +byte 0x%s +== +&&` + pkTampered1 := make([]byte, len(pk)) + copy(pkTampered1, pk) + pkTampered1[0] = 0 + pkTampered2 := make([]byte, len(pk)) + copy(pkTampered2, pk[1:]) + + var decompressTests = []struct { + key []byte + pass bool + }{ + {pk, true}, + {pkTampered1, false}, + {pkTampered2, false}, + } + for _, test := range decompressTests { + t.Run(fmt.Sprintf("decompress/pass=%v", test.pass), func(t *testing.T) { + src := fmt.Sprintf(source, hex.EncodeToString(test.key), hex.EncodeToString(x), hex.EncodeToString(y)) + if test.pass { + testAccepts(t, src, 5) + } else { + testPanics(t, src, 5) + } + }) + } + + // ecdsa verify tests + source = ` +byte "%s" +sha512_256 +byte 0x%s +byte 0x%s +byte 0x%s +byte 0x%s +ecdsa_verify Secp256k1 +` + data := []byte("testdata") + msg := sha512.Sum512_256(data) + + sign, err := secp256k1.Sign(msg[:], sk) + require.NoError(t, err) + r := sign[:32] + s := sign[32:64] + v := int(sign[64]) + + rTampered := make([]byte, len(r)) + copy(rTampered, pk) + rTampered[0] = 0 + + var verifyTests = []struct { + data string + r []byte + pass bool + }{ + {"testdata", r, true}, + {"testdata", rTampered, false}, + {"testdata1", r, false}, + } + for _, test := range verifyTests { + t.Run(fmt.Sprintf("verify/pass=%v", test.pass), func(t *testing.T) { + src := fmt.Sprintf(source, test.data, hex.EncodeToString(test.r), hex.EncodeToString(s), hex.EncodeToString(x), hex.EncodeToString(y)) + if test.pass { + testAccepts(t, src, 5) + } else { + testRejects(t, src, 5) + } + }) + } + + // ecdsa recover tests + source = ` +byte 0x%s +int %d +byte 0x%s +byte 0x%s +ecdsa_pk_recover Secp256k1 +dup2 +store 0 +byte 0x%s +== +load 0 +byte 0x%s +== +&& +store 1 +concat // X + Y +byte 0x04 +swap +concat // 0x04 + X + Y +byte 0x%s +== +load 1 +&&` + var recoverTests = []struct { + v int + checker func(t *testing.T, program string, introduced uint64) + }{ + {v, testAccepts}, + {v ^ 1, testRejects}, + {3, func(t *testing.T, program string, introduced uint64) { + testPanics(t, program, introduced) + }}, + } + pkExpanded := secp256k1.S256().Marshal(key.PublicKey.X, key.PublicKey.Y) + + for i, test := range recoverTests { + t.Run(fmt.Sprintf("recover/%d", i), func(t *testing.T) { + src := fmt.Sprintf(source, hex.EncodeToString(msg[:]), test.v, hex.EncodeToString(r), hex.EncodeToString(s), hex.EncodeToString(x), hex.EncodeToString(y), hex.EncodeToString(pkExpanded)) + test.checker(t, src, 5) + }) + } + + // sample sequencing: decompress + verify + source = fmt.Sprintf(`#pragma version 5 +byte "testdata" +sha512_256 +byte 0x%s +byte 0x%s +byte 0x%s +ecdsa_pk_decompress Secp256k1 +ecdsa_verify Secp256k1`, hex.EncodeToString(r), hex.EncodeToString(s), hex.EncodeToString(pk)) + ops := testProg(t, source, 5) + var txn transactions.SignedTxn + txn.Lsig.Logic = ops.Program + pass, err := Eval(ops.Program, defaultEvalParamsWithVersion(nil, &txn, 5)) + require.NoError(t, err) + require.True(t, pass) +} + +// test compatibility with ethereum signatures +func TestEcdsaEthAddress_Z(t *testing.T) { + /* + pip install eth-keys pycryptodome + from eth_keys import keys + pk = keys.PrivateKey(b"\xb2\\}\xb3\x1f\xee\xd9\x12''\xbf\t9\xdcv\x9a\x96VK-\xe4\xc4rm\x03[6\xec\xf1\xe5\xb3d") + msg=b"hello from ethereum" + print("msg: '{}'".format(msg.decode())) + signature = pk.sign_msg(msg) + print("v:", signature.v) + print("r:", signature.r.to_bytes(32, byteorder="big").hex()) + print("s:", signature.s.to_bytes(32, byteorder="big").hex()) + print("addr:", pk.public_key.to_address()) + */ + progText := `byte "hello from ethereum" // msg +keccak256 +int 0 // v +byte 0x745e8f55ac6189ee89ed707c36694868e3903988fbf776c8096c45da2e60c638 // r +byte 0x30c8e4a9b5d2eb53ddc6294587dd00bed8afe2c45dd72f6b4cf752e46d5ba681 // s +ecdsa_pk_recover Secp256k1 +concat // convert public key X and Y to ethereum addr +keccak256 +substring 12 32 +byte 0x5ce9454909639d2d17a3f753ce7d93fa0b9ab12e // addr +==` + testAccepts(t, progText, 5) +} + +func BenchmarkHash_Z(b *testing.B) { + for _, hash := range []string{"sha256", "keccak256", "sha512_256"} { + b.Run(hash+"-small", func(b *testing.B) { // hash 32 bytes + benchmarkOperation(b, "int 32; bzero", hash, "pop; int 1") + }) + b.Run(hash+"-med", func(b *testing.B) { // hash 128 bytes + benchmarkOperation(b, "int 32; bzero", + "dup; concat; dup; concat;"+hash, "pop; int 1") + }) + b.Run(hash+"-big", func(b *testing.B) { // hash 512 bytes + benchmarkOperation(b, "int 32; bzero", + "dup; concat; dup; concat; dup; concat; dup; concat;"+hash, "pop; int 1") + }) + } +} + +func BenchmarkSha256Raw_Z(b *testing.B) { + addr, _ := basics.UnmarshalChecksumAddress("OC6IROKUJ7YCU5NV76AZJEDKYQG33V2CJ7HAPVQ4ENTAGMLIOINSQ6EKGE") + a := addr[:] + b.ResetTimer() + for i := 0; i < b.N; i++ { + t := sha256.Sum256(a) + a = t[:] + } +} + +func BenchmarkEd25519Verifyx1_Z(b *testing.B) { + //benchmark setup + var data [][32]byte + var programs [][]byte + var signatures []crypto.Signature + + for i := 0; i < b.N; i++ { + var buffer [32]byte //generate data to be signed + crypto.RandBytes(buffer[:]) + data = append(data, buffer) + + var s crypto.Seed //generate programs and signatures + crypto.RandBytes(s[:]) + secret := crypto.GenerateSignatureSecrets(s) + pk := basics.Address(secret.SignatureVerifier) + pkStr := pk.String() + ops, err := AssembleStringWithVersion(fmt.Sprintf(`arg 0 +arg 1 +addr %s +ed25519verify`, pkStr), AssemblerMaxVersion) + require.NoError(b, err) + programs = append(programs, ops.Program) + sig := secret.Sign(Msg{ + ProgramHash: crypto.HashObj(Program(ops.Program)), + Data: buffer[:], + }) + signatures = append(signatures, sig) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + var txn transactions.SignedTxn + txn.Lsig.Logic = programs[i] + txn.Lsig.Args = [][]byte{data[i][:], signatures[i][:]} + sb := strings.Builder{} + ep := defaultEvalParams(&sb, &txn) + pass, err := Eval(programs[i], ep) + if !pass { + b.Log(hex.EncodeToString(programs[i])) + b.Log(sb.String()) + } + if err != nil { + require.NoError(b, err) + } + if !pass { + require.True(b, pass) + } + } +} + +// type benchmarkEcdsaData struct { +// x []byte +// y []byte +// pk []byte +// msg [32]byte +// r []byte +// s []byte +// v int +// programs []byte +// } + +func benchmarkEcdsaGenData_Z(b *testing.B) (data []benchmarkEcdsaData) { + data = make([]benchmarkEcdsaData, b.N) + for i := 0; i < b.N; i++ { + key, err := ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) + require.NoError(b, err) + sk := keyToByte(b, key.D) + data[i].x = keyToByte(b, key.PublicKey.X) + data[i].y = keyToByte(b, key.PublicKey.Y) + data[i].pk = secp256k1.CompressPubkey(key.PublicKey.X, key.PublicKey.Y) + + d := []byte("testdata") + data[i].msg = sha512.Sum512_256(d) + + sign, err := secp256k1.Sign(data[i].msg[:], sk) + require.NoError(b, err) + data[i].r = sign[:32] + data[i].s = sign[32:64] + data[i].v = int(sign[64]) + } + return data +} + +func benchmarkEcdsa_Z(b *testing.B, source string) { + data := benchmarkEcdsaGenData_Z(b) + ops, err := AssembleStringWithVersion(source, 5) + require.NoError(b, err) + for i := 0; i < b.N; i++ { + data[i].programs = ops.Program + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + var txn transactions.SignedTxn + txn.Lsig.Logic = data[i].programs + txn.Lsig.Args = [][]byte{data[i].msg[:], data[i].r, data[i].s, data[i].x, data[i].y, data[i].pk, {uint8(data[i].v)}} + sb := strings.Builder{} + ep := defaultEvalParams(&sb, &txn) + pass, err := Eval(data[i].programs, ep) + if !pass { + b.Log(hex.EncodeToString(data[i].programs)) + b.Log(sb.String()) + } + if err != nil { + require.NoError(b, err) + } + if !pass { + require.True(b, pass) + } + } +} + +func BenchmarkVariety(b *testing.B) { + b.Run("ecdsa_verify", func(b *testing.B) { + source := `#pragma version 5 +arg 0 +arg 1 +arg 2 +arg 3 +arg 4 +ecdsa_verify Secp256k1` + benchmarkEcdsa_Z(b, source) + }) + b.Run("ecdsa_pk_decompress", func(b *testing.B) { + source := `#pragma version 5 +arg 5 +ecdsa_pk_decompress Secp256k1 +pop +pop +int 1` + benchmarkEcdsa_Z(b, source) + }) + + b.Run("ecdsa_pk_recover", func(b *testing.B) { + source := `#pragma version 5 +arg 0 +arg 6 +btoi +arg 1 +arg 2 +ecdsa_pk_recover Secp256k1 +pop +pop +int 1` + benchmarkEcdsa_Z(b, source) + }) + + b.Run("add", func(b *testing.B) { + source := `#pragma version 5 +int 1300 +int 37 ++ +int 0 +>` + benchmarkEcdsa_Z(b, source) + }) +} diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 68ae0f12f8..fbc2979c41 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -448,6 +448,45 @@ func (s ecDsaCurveNameSpecMap) getExtraFor(name string) (extra string) { return } +// Base64Alphabet is an enum for the `base64decode` opcode +type Base64Alphabet int + +const ( + URLAlphabet Base64Alphabet = iota + StandardAlphabet + invalidBase64Alphabet +) + +Base64AlphabetNames := [...]string{ + "URL and Filename Safe base-64 Alphabet", + "Standard base-64 Alphabet", +} + +var Base64AlphabetNames []string + +type base64AlphabetSpec struct { + field Base64Alphabet + version uint64 +} + +var base64AlphbetSpecs = []base64AlphabetSpec{ + {URLAlphabet, 5}, + {StandardAlphabet, 5}, +} + +var base64AlphabetSpecByField map[Base64Alphabet]base64AlphabetSpec +var base64AlphabetSpecByName base64AlphabetSpecMap + +type base64AlphabetSpecMap map[string]base64AlphabetSpec + +func (s base64AlphabetSpecMap) getExtraFor(name string) (extra string) { + // Uses 5 here because ecdsa fields were introduced in 5 + if s[name].version > 5 { + extra = fmt.Sprintf("LogicSigVersion >= %d.", s[name].version) + } + return +} + // AssetHoldingField is an enum for `asset_holding_get` opcode type AssetHoldingField int @@ -680,6 +719,16 @@ func init() { for i, ahfn := range EcdsaCurveNames { ecdsaCurveSpecByName[ahfn] = ecdsaCurveSpecByField[EcdsaCurve(i)] } + + base64AlphabetSpecByField = make(map[Base64Alphabet]base64AlphabetSpec, len(Base64AlphabetNames)) + for _, s := range base64AlphbetSpecs { + base64AlphabetSpecByField[s.field] = s + } + + base64AlphabetSpecByName = make(base64AlphabetSpecMap, len(Base64AlphabetNames)) + for i, alphname := range Base64AlphabetNames { + base64AlphabetSpecByName[alphname] = base64AlphabetSpecByField[Base64Alphabet(i)] + } AssetHoldingFieldNames = make([]string, int(invalidAssetHoldingField)) for i := AssetBalance; i < invalidAssetHoldingField; i++ { diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 604db789a2..6fb87c6665 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -249,6 +249,7 @@ var OpSpecs = []OpSpec{ {0x59, "extract_uint16", opExtract16Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5a, "extract_uint32", opExtract32Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5b, "extract_uint64", opExtract64Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, + {0x5c, "base64_decode", opBase64Decode, asmDefault, disDefault, oneBytes, oneBytes, 5, modeAny, costly(42)}, {0x60, "balance", opBalance, asmDefault, disDefault, oneInt, oneInt, 2, runModeApplication, opDefault}, {0x60, "balance", opBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault}, From 64a849cda988d0b8882ee183410ce078a077fd65 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 17 Nov 2021 00:31:14 -0600 Subject: [PATCH 02/22] does my new email work? --- data/transactions/logic/evalDecode64_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/transactions/logic/evalDecode64_test.go b/data/transactions/logic/evalDecode64_test.go index 8b355ecbb1..d80d9c40d8 100644 --- a/data/transactions/logic/evalDecode64_test.go +++ b/data/transactions/logic/evalDecode64_test.go @@ -80,7 +80,7 @@ func TestBase64DecodeFunc(t *testing.T) { require.Equal(t, testCase.Error, err) } - // b := big.NewInt(0x100) + // b := big.NewInt(0x100) // r, err := leadingZeros(1, b) // require.Error(t, err) // require.Nil(t, r) From 882e836c67b1b298a8dd794a8f5203121ed6059c Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 17 Nov 2021 00:34:50 -0600 Subject: [PATCH 03/22] does my new email work? --- data/transactions/logic/evalDecode64_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/transactions/logic/evalDecode64_test.go b/data/transactions/logic/evalDecode64_test.go index d80d9c40d8..8b355ecbb1 100644 --- a/data/transactions/logic/evalDecode64_test.go +++ b/data/transactions/logic/evalDecode64_test.go @@ -80,7 +80,7 @@ func TestBase64DecodeFunc(t *testing.T) { require.Equal(t, testCase.Error, err) } - // b := big.NewInt(0x100) + // b := big.NewInt(0x100) // r, err := leadingZeros(1, b) // require.Error(t, err) // require.Nil(t, r) From 1dc22a0cfd007af773b9654799779040f82356ea Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 17 Nov 2021 00:42:59 -0600 Subject: [PATCH 04/22] lint --- data/transactions/logic/fields.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index fbc2979c41..f28779e75c 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -457,13 +457,11 @@ const ( invalidBase64Alphabet ) -Base64AlphabetNames := [...]string{ +var Base64AlphabetNames [2]string = [...]string{ "URL and Filename Safe base-64 Alphabet", "Standard base-64 Alphabet", } -var Base64AlphabetNames []string - type base64AlphabetSpec struct { field Base64Alphabet version uint64 @@ -719,12 +717,12 @@ func init() { for i, ahfn := range EcdsaCurveNames { ecdsaCurveSpecByName[ahfn] = ecdsaCurveSpecByField[EcdsaCurve(i)] } - + base64AlphabetSpecByField = make(map[Base64Alphabet]base64AlphabetSpec, len(Base64AlphabetNames)) for _, s := range base64AlphbetSpecs { base64AlphabetSpecByField[s.field] = s } - + base64AlphabetSpecByName = make(base64AlphabetSpecMap, len(Base64AlphabetNames)) for i, alphname := range Base64AlphabetNames { base64AlphabetSpecByName[alphname] = base64AlphabetSpecByField[Base64Alphabet(i)] From 4f5ac9eb912ecf5bd8fe8b706d185ba377db44d5 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Wed, 17 Nov 2021 00:59:46 -0600 Subject: [PATCH 05/22] is signing working? --- data/transactions/logic/opcodes.go | 1 + 1 file changed, 1 insertion(+) diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 6fb87c6665..e7761b932a 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -237,6 +237,7 @@ var OpSpecs = []OpSpec{ {0x4e, "cover", opCover, asmDefault, disDefault, oneAny, oneAny, 5, modeAny, stacky(typeCover, "n")}, {0x4f, "uncover", opUncover, asmDefault, disDefault, oneAny, oneAny, 5, modeAny, stacky(typeUncover, "n")}, + // []byte processing {0x50, "concat", opConcat, asmDefault, disDefault, twoBytes, oneBytes, 2, modeAny, opDefault}, {0x51, "substring", opSubstring, assembleSubstring, disDefault, oneBytes, oneBytes, 2, modeAny, immediates("s", "e")}, {0x52, "substring3", opSubstring3, asmDefault, disDefault, byteIntInt, oneBytes, 2, modeAny, opDefault}, From 4874c07ae255b1498baf740e397ee0e57fb8c9c0 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sat, 20 Nov 2021 23:37:46 -0600 Subject: [PATCH 06/22] wip --- data/transactions/logic/assembler.go | 36 +++ data/transactions/logic/eval.go | 15 +- data/transactions/logic/evalDecode64_test.go | 225 +++++++++++++++---- data/transactions/logic/fields.go | 23 +- data/transactions/logic/fields_string.go | 21 +- data/transactions/logic/opcodes.go | 4 +- 6 files changed, 257 insertions(+), 67 deletions(-) diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 5b4d72b383..0bd93f8f68 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -1244,6 +1244,28 @@ func assembleEcdsa(ops *OpStream, spec *OpSpec, args []string) error { return nil } +func assembleBase64Decode(ops *OpStream, spec *OpSpec, args []string) error { + if len(args) != 1 { + return ops.errorf("%s expects one argument", spec.Name) + } + + alph, ok := base64AlphabetSpecByName[args[0]] + if !ok { + return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) + } + if alph.version > ops.Version { + //nolint:errcheck // we continue to maintain typestack + ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], alph.version) + } + + val := alph.field + ops.pending.WriteByte(spec.Opcode) + ops.pending.WriteByte(uint8(val)) + ops.trace("%s (%s)", alph.field.String(), alph.ftype.String()) + ops.returns(alph.ftype) + return nil +} + type assembleFunc func(*OpStream, *OpSpec, []string) error // Basic assembly. Any extra bytes of opcode are encoded as byte immediates. @@ -2668,6 +2690,20 @@ func disEcdsa(dis *disassembleState, spec *OpSpec) (string, error) { return fmt.Sprintf("%s %s", spec.Name, EcdsaCurveNames[arg]), nil } +func disBase64Decode(dis *disassembleState, spec *OpSpec) (string, error) { + lastIdx := dis.pc + 1 + if len(dis.program) <= lastIdx { + missing := lastIdx - len(dis.program) + 1 + return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) + } + dis.nextpc = dis.pc + 2 + b64dArg := dis.program[dis.pc+1] + if int(b64dArg) >= len(base64AlphabetNames) { + return "", fmt.Errorf("invalid base64_decode arg index %d at pc=%d", b64dArg, dis.pc) + } + return fmt.Sprintf("%s %s", spec.Name, base64AlphabetNames[b64dArg]), nil +} + type disInfo struct { pcOffset []PCOffset hasStatefulOps bool diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index db5e2e73e0..c519b4c085 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -4036,12 +4036,7 @@ func (cx *EvalContext) PcDetails() (pc int, dis string) { return cx.pc, dis } -func base64Decode(encoded []byte, isURL bool) ([]byte, error) { - encoding := base64.URLEncoding - if !isURL { - encoding = base64.StdEncoding - } - +func base64Decode(encoded []byte, encoding *base64.Encoding) ([]byte, error) { decoded := make([]byte, encoding.DecodedLen(len(encoded))) n, err := encoding.Strict().Decode(decoded, encoded) if err != nil { @@ -4059,9 +4054,9 @@ func opBase64Decode(cx *EvalContext) { return } - isURL := true - if alphabetField == StandardAlphabet { - isURL = false + encoding := base64.URLEncoding + if alphabetField == StdAlph { + encoding = base64.StdEncoding } - cx.stack[last].Bytes, cx.err = base64Decode(cx.stack[last].Bytes, isURL) + cx.stack[last].Bytes, cx.err = base64Decode(cx.stack[last].Bytes, encoding) } diff --git a/data/transactions/logic/evalDecode64_test.go b/data/transactions/logic/evalDecode64_test.go index 8b355ecbb1..17a97dd74a 100644 --- a/data/transactions/logic/evalDecode64_test.go +++ b/data/transactions/logic/evalDecode64_test.go @@ -36,14 +36,14 @@ import ( "github.com/algorand/go-algorand/test/partitiontest" ) -type testCase struct { +type b64decTestCase struct { Encoded string IsURL bool Decoded string Error error } -var testCases = []testCase{ +var testCases = []b64decTestCase{ {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=", false, `MOBY-DICK; @@ -75,41 +75,191 @@ func TestBase64DecodeFunc(t *testing.T) { t.Parallel() // do I need this for _, testCase := range testCases { - decoded, err := base64Decode([]byte(testCase.Encoded), testCase.IsURL) + encoding := base64.StdEncoding + if testCase.IsURL { + encoding = base64.URLEncoding + } + encoding = encoding.Strict() + decoded, err := base64Decode([]byte(testCase.Encoded), encoding) require.Equal(t, []byte(testCase.Decoded), decoded) require.Equal(t, testCase.Error, err) } +} + +type b64TestArgs struct { + Raw []byte + Encoded []byte + IsURL bool + Program []byte +} + +func testB64DecodeAssembleWithArgs(t *testing.T) []b64TestArgs { + sourceTmpl := `#pragma version 5 + arg 0 + arg 1 + base64_decode %s + ==` + args := []b64TestArgs{} + for _, testCase := range testCases { + if testCase.Error == nil { + field := "StdAlph" + if testCase.IsURL { + field = "URLAlph" + } + source := fmt.Sprintf(sourceTmpl, field) + ops, err := AssembleStringWithVersion(source, 5) + require.NoError(t, err) + + arg := b64TestArgs{ + Raw: []byte(testCase.Decoded), + Encoded: []byte(testCase.Encoded), + IsURL: testCase.IsURL, + Program: ops.Program, + } + args = append(args, arg) + } + } + return args +} + +func testB64DecodeEval(tb testing.TB, args []b64TestArgs) { + for _, data := range args { + var txn transactions.SignedTxn + txn.Lsig.Logic = data.Program + txn.Lsig.Args = [][]byte{data.Raw[:], data.Encoded[:]} + ep := defaultEvalParams(&strings.Builder{}, &txn) + pass, err := Eval(data.Program, ep) + if err != nil { + require.NoError(tb, err) + } + if !pass { + fmt.Printf("FAILING WITH data = %#v", data) + require.True(tb, pass) + } + } +} +func TestOpBase64Decode(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + args := testB64DecodeAssembleWithArgs(t) + testB64DecodeEval(t, args) +} + +func benchmarkB64DecodeGenData(b *testing.B, source string, isURL bool, msgLen int) (args []b64TestArgs, err error) { + var ops *OpStream + ops, err = AssembleStringWithVersion(source, 5) + if err != nil { + require.NoError(b, err) + return + } - // b := big.NewInt(0x100) - // r, err := leadingZeros(1, b) - // require.Error(t, err) - // require.Nil(t, r) - - // b = big.NewInt(100) - // r, err = leadingZeros(1, b) - // require.NoError(t, err) - // require.Equal(t, []byte{100}, r) - - // b = big.NewInt(100) - // r, err = leadingZeros(2, b) - // require.NoError(t, err) - // require.Equal(t, []byte{0, 100}, r) - - // v32, err := hex.DecodeString("71a5910445820f57989c027bdf9391c80097874d249e0f38bf90834fdec2877f") - // require.NoError(t, err) - // b = new(big.Int).SetBytes(v32) - // r, err = leadingZeros(32, b) - // require.NoError(t, err) - // require.Equal(t, v32, r) - - // v31 := v32[1:] - // b = new(big.Int).SetBytes(v31) - // r, err = leadingZeros(32, b) - // require.NoError(t, err) - // v31z := append([]byte{0}, v31...) - // require.Equal(t, v31z, r) - - // require.Equal(t, v31z, keyToByte(t, b)) + encoding := base64.StdEncoding + if isURL { + encoding = base64.URLEncoding + } + encoding = encoding.Strict() + + msg := make([]byte, msgLen) + for i := 0; i < b.N; i++ { + _, err = rand.Read(msg) + if err != nil { + require.NoError(b, err) + return + } + args = append(args, b64TestArgs{ + Raw: msg[:], + Encoded: []byte(encoding.EncodeToString(msg[:])), + IsURL: isURL, + Program: ops.Program[:], + }) + } + return +} + +func benchmarkB64Decode(b *testing.B, scenario string, msgLen int) { + var source string + isURL := false + + switch scenario { + case "base64url": + isURL = true + source = `#pragma version 5 +arg 0 +arg 1 +base64_decode URLAlph +==` + case "base64std": + isURL = false + source = `#pragma version 5 +arg 0 +arg 1 +base64_decode StdAlph +==` + default: + source = `#pragma version 5 +arg 0 +arg 1 +pop +pop +int 1` + } + args, err := benchmarkB64DecodeGenData(b, source, isURL, msgLen) + if err != nil { + require.NoError(b, err) + return + } + benchmarkB64DecodeSanity(b, args) + b.ResetTimer() + testB64DecodeEval(b, args) +} + +func benchmarkB64DecodeSanity(b *testing.B, args []b64TestArgs) { + for _, data := range args { + encoding := base64.StdEncoding + if data.IsURL { + encoding = base64.URLEncoding + } + decoded, err := base64Decode(data.Encoded, encoding) + require.NoError(b, err) + require.Equal(b, data.Raw, decoded) + } +} + +var b64msgLengths = []int{50, 1050, 2050, 3050} + +func benchmarkB64DecodeScenario(b *testing.B, scenario string) { + for _, msgLen := range b64msgLengths { + b.Run(fmt.Sprintf("%s_%d", scenario, msgLen), func(b *testing.B) { + benchmarkB64Decode(b, scenario, msgLen) + }) + } +} + +func TestInvestigation(t *testing.T) { + data := b64TestArgs{ + Raw: []uint8{0x1b, 0x66, 0x1d, 0x29, 0x5e, 0x8e, 0x95, 0x79, 0x36, 0xf7, 0xd5, 0xd3, 0x0, 0x35, 0x7b, 0x25, 0x1f, 0x1e, 0x57, 0x78, 0x58, 0x63, 0x26, 0xae, 0x29, 0xcc, 0x96, 0xee, 0x6c, 0x63, 0xc6, 0x88, 0x7b, 0x26, 0xb0, 0x41, 0x77, 0x5, 0xd6, 0xd3, 0x1f, 0x7f, 0x89, 0x94, 0x3a, 0x12, 0xab, 0xe3, 0x70, 0x73}, + Encoded: []uint8{0x6e, 0x53, 0x46, 0x73, 0x7a, 0x7a, 0x74, 0x55, 0x4e, 0x70, 0x6d, 0x6c, 0x4b, 0x65, 0x39, 0x52, 0x33, 0x2d, 0x6f, 0x72, 0x64, 0x38, 0x66, 0x7a, 0x58, 0x32, 0x79, 0x64, 0x6b, 0x79, 0x37, 0x6e, 0x4b, 0x6d, 0x79, 0x51, 0x73, 0x52, 0x4f, 0x66, 0x6f, 0x43, 0x46, 0x69, 0x70, 0x76, 0x4b, 0x46, 0x48, 0x72, 0x49, 0x77, 0x66, 0x5a, 0x57, 0x48, 0x59, 0x48, 0x63, 0x53, 0x36, 0x54, 0x4a, 0x43, 0x72, 0x33, 0x73, 0x3d}, + IsURL: true, + Program: []uint8{0x5, 0x2d, 0x2e, 0x5c, 0x0, 0x12}, + } + require.Equal(t, uint8(0x1b), data.Raw[0]) + testB64DecodeEval(t, []b64TestArgs{data}) +} +func BenchmarkBase64DecodeVanillaBase(b *testing.B) { + benchmarkB64DecodeScenario(b, "vanilla baseline") +} + +func BenchmarkBase64DecodeURL(b *testing.B) { + benchmarkB64DecodeScenario(b, "base64url") +} + +func TestCoverZZ(t *testing.T) { + t.Parallel() + testAccepts(t, "byte base64 YWJjMTIzIT8kKiYoKSctPUB+; byte base64 YWJjMTIzIT8kKiYoKSctPUB+; ==", 1) + testAccepts(t, "byte base64 abc123!?$*&(); byte base64 abc123!?$*&(); ==", 1) + // testAccepts(t, "int 4; int 3; int 2; int 1; cover 2; pop; pop; int 1; ==; return", 5) + // testPanics(t, obfuscate("int 4; int 3; int 2; int 1; cover 11; int 2; ==; return"), 5) + // testPanics(t, obfuscate("int 4; int 3; int 2; int 1; cover 4; int 2; ==; return"), 5) } // OLDER @@ -208,15 +358,6 @@ ed25519verify`, pkStr), v) } } -// bitIntFillBytes is a replacement for big.Int.FillBytes from future Go -// func bitIntFillBytes(tb testing.TB, b *big.Int) []byte { -// k := make([]byte, 32) -// require.NotPanics(tb, func() { -// k = bitIntFillBytes(b, k) -// }) -// return k -// } - func TestEcdsa_Z(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index f28779e75c..cb3fa53018 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -23,7 +23,7 @@ import ( "github.com/algorand/go-algorand/protocol" ) -//go:generate stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve -output=fields_string.go +//go:generate stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve,Base64Alphabet -output=fields_string.go // TxnField is an enum type for `txn` and `gtxn` type TxnField int @@ -452,24 +452,23 @@ func (s ecDsaCurveNameSpecMap) getExtraFor(name string) (extra string) { type Base64Alphabet int const ( - URLAlphabet Base64Alphabet = iota - StandardAlphabet + URLAlph Base64Alphabet = iota + StdAlph invalidBase64Alphabet ) -var Base64AlphabetNames [2]string = [...]string{ - "URL and Filename Safe base-64 Alphabet", - "Standard base-64 Alphabet", -} +// After running `go generate` these strings will be available: +var base64AlphabetNames [2]string = [...]string{URLAlph.String(), StdAlph.String()} type base64AlphabetSpec struct { field Base64Alphabet + ftype StackType version uint64 } var base64AlphbetSpecs = []base64AlphabetSpec{ - {URLAlphabet, 5}, - {StandardAlphabet, 5}, + {URLAlph, StackBytes, 5}, + {StdAlph, StackBytes, 5}, } var base64AlphabetSpecByField map[Base64Alphabet]base64AlphabetSpec @@ -718,13 +717,13 @@ func init() { ecdsaCurveSpecByName[ahfn] = ecdsaCurveSpecByField[EcdsaCurve(i)] } - base64AlphabetSpecByField = make(map[Base64Alphabet]base64AlphabetSpec, len(Base64AlphabetNames)) + base64AlphabetSpecByField = make(map[Base64Alphabet]base64AlphabetSpec, len(base64AlphabetNames)) for _, s := range base64AlphbetSpecs { base64AlphabetSpecByField[s.field] = s } - base64AlphabetSpecByName = make(base64AlphabetSpecMap, len(Base64AlphabetNames)) - for i, alphname := range Base64AlphabetNames { + base64AlphabetSpecByName = make(base64AlphabetSpecMap, len(base64AlphabetNames)) + for i, alphname := range base64AlphabetNames { base64AlphabetSpecByName[alphname] = base64AlphabetSpecByField[Base64Alphabet(i)] } diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index 82abacb941..5861dedc17 100644 --- a/data/transactions/logic/fields_string.go +++ b/data/transactions/logic/fields_string.go @@ -1,4 +1,4 @@ -// Code generated by "stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve -output=fields_string.go"; DO NOT EDIT. +// Code generated by "stringer -type=TxnField,GlobalField,AssetParamsField,AppParamsField,AssetHoldingField,OnCompletionConstType,EcdsaCurve,Base64Alphabet -output=fields_string.go"; DO NOT EDIT. package logic @@ -227,3 +227,22 @@ func (i EcdsaCurve) String() string { } return _EcdsaCurve_name[_EcdsaCurve_index[i]:_EcdsaCurve_index[i+1]] } +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[URLAlph-0] + _ = x[StdAlph-1] + _ = x[invalidBase64Alphabet-2] +} + +const _Base64Alphabet_name = "URLAlphStdAlphinvalidBase64Alphabet" + +var _Base64Alphabet_index = [...]uint8{0, 7, 14, 35} + +func (i Base64Alphabet) String() string { + if i < 0 || i >= Base64Alphabet(len(_Base64Alphabet_index)-1) { + return "Base64Alphabet(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _Base64Alphabet_name[_Base64Alphabet_index[i]:_Base64Alphabet_index[i+1]] +} diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index e7761b932a..64d939469e 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -237,7 +237,7 @@ var OpSpecs = []OpSpec{ {0x4e, "cover", opCover, asmDefault, disDefault, oneAny, oneAny, 5, modeAny, stacky(typeCover, "n")}, {0x4f, "uncover", opUncover, asmDefault, disDefault, oneAny, oneAny, 5, modeAny, stacky(typeUncover, "n")}, - // []byte processing + // Byteslice processing {0x50, "concat", opConcat, asmDefault, disDefault, twoBytes, oneBytes, 2, modeAny, opDefault}, {0x51, "substring", opSubstring, assembleSubstring, disDefault, oneBytes, oneBytes, 2, modeAny, immediates("s", "e")}, {0x52, "substring3", opSubstring3, asmDefault, disDefault, byteIntInt, oneBytes, 2, modeAny, opDefault}, @@ -250,7 +250,7 @@ var OpSpecs = []OpSpec{ {0x59, "extract_uint16", opExtract16Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5a, "extract_uint32", opExtract32Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5b, "extract_uint64", opExtract64Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, - {0x5c, "base64_decode", opBase64Decode, asmDefault, disDefault, oneBytes, oneBytes, 5, modeAny, costly(42)}, + {0x5c, "base64_decode", opBase64Decode, assembleBase64Decode, disBase64Decode, oneBytes, oneBytes, 5, modeAny, costlyImm(42, "v")}, {0x60, "balance", opBalance, asmDefault, disDefault, oneInt, oneInt, 2, runModeApplication, opDefault}, {0x60, "balance", opBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault}, From fdc3e0589bc97dfcc8c9249f57ff60be8445e9ba Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 22 Nov 2021 11:50:43 -0600 Subject: [PATCH 07/22] unit tests and benchmarks for base64decode --- .gitignore | 2 + data/transactions/logic/evalDecode64_test.go | 721 ------------------- data/transactions/logic/eval_test.go | 219 ++++++ data/transactions/logic/opcodes.go | 2 +- 4 files changed, 222 insertions(+), 722 deletions(-) delete mode 100644 data/transactions/logic/evalDecode64_test.go diff --git a/.gitignore b/.gitignore index 218b3879b8..ebd0ef239b 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,5 @@ assets index.html +# test summary +testresults.json diff --git a/data/transactions/logic/evalDecode64_test.go b/data/transactions/logic/evalDecode64_test.go deleted file mode 100644 index 17a97dd74a..0000000000 --- a/data/transactions/logic/evalDecode64_test.go +++ /dev/null @@ -1,721 +0,0 @@ -// Copyright (C) 2019-2021 Algorand, Inc. -// This file is part of go-algorand -// -// go-algorand is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. -// -// go-algorand is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with go-algorand. If not, see . - -package logic - -import ( - "crypto/ecdsa" - "crypto/rand" - "crypto/sha256" - "crypto/sha512" - "encoding/base64" - "encoding/hex" - "fmt" - "strings" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/algorand/go-algorand/crypto" - "github.com/algorand/go-algorand/crypto/secp256k1" - "github.com/algorand/go-algorand/data/basics" - "github.com/algorand/go-algorand/data/transactions" - "github.com/algorand/go-algorand/test/partitiontest" -) - -type b64decTestCase struct { - Encoded string - IsURL bool - Decoded string - Error error -} - -var testCases = []b64decTestCase{ - {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=", - false, - `MOBY-DICK; - -or, THE WHALE. - - -By Herman Melville`, - nil, - }, - {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=", - true, - `MOBY-DICK; - -or, THE WHALE. - - -By Herman Melville`, - nil, - }, - {"YWJjMTIzIT8kKiYoKSctPUB+", false, "abc123!?$*&()'-=@~", nil}, - {"YWJjMTIzIT8kKiYoKSctPUB-", true, "abc123!?$*&()'-=@~", nil}, - {"YWJjMTIzIT8kKiYoKSctPUB+", true, "", base64.CorruptInputError(23)}, - {"YWJjMTIzIT8kKiYoKSctPUB-", false, "", base64.CorruptInputError(23)}, -} - -func TestBase64DecodeFunc(t *testing.T) { - partitiontest.PartitionTest(t) // do I need this? - t.Parallel() // do I need this - - for _, testCase := range testCases { - encoding := base64.StdEncoding - if testCase.IsURL { - encoding = base64.URLEncoding - } - encoding = encoding.Strict() - decoded, err := base64Decode([]byte(testCase.Encoded), encoding) - require.Equal(t, []byte(testCase.Decoded), decoded) - require.Equal(t, testCase.Error, err) - } -} - -type b64TestArgs struct { - Raw []byte - Encoded []byte - IsURL bool - Program []byte -} - -func testB64DecodeAssembleWithArgs(t *testing.T) []b64TestArgs { - sourceTmpl := `#pragma version 5 - arg 0 - arg 1 - base64_decode %s - ==` - args := []b64TestArgs{} - for _, testCase := range testCases { - if testCase.Error == nil { - field := "StdAlph" - if testCase.IsURL { - field = "URLAlph" - } - source := fmt.Sprintf(sourceTmpl, field) - ops, err := AssembleStringWithVersion(source, 5) - require.NoError(t, err) - - arg := b64TestArgs{ - Raw: []byte(testCase.Decoded), - Encoded: []byte(testCase.Encoded), - IsURL: testCase.IsURL, - Program: ops.Program, - } - args = append(args, arg) - } - } - return args -} - -func testB64DecodeEval(tb testing.TB, args []b64TestArgs) { - for _, data := range args { - var txn transactions.SignedTxn - txn.Lsig.Logic = data.Program - txn.Lsig.Args = [][]byte{data.Raw[:], data.Encoded[:]} - ep := defaultEvalParams(&strings.Builder{}, &txn) - pass, err := Eval(data.Program, ep) - if err != nil { - require.NoError(tb, err) - } - if !pass { - fmt.Printf("FAILING WITH data = %#v", data) - require.True(tb, pass) - } - } -} -func TestOpBase64Decode(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - args := testB64DecodeAssembleWithArgs(t) - testB64DecodeEval(t, args) -} - -func benchmarkB64DecodeGenData(b *testing.B, source string, isURL bool, msgLen int) (args []b64TestArgs, err error) { - var ops *OpStream - ops, err = AssembleStringWithVersion(source, 5) - if err != nil { - require.NoError(b, err) - return - } - - encoding := base64.StdEncoding - if isURL { - encoding = base64.URLEncoding - } - encoding = encoding.Strict() - - msg := make([]byte, msgLen) - for i := 0; i < b.N; i++ { - _, err = rand.Read(msg) - if err != nil { - require.NoError(b, err) - return - } - args = append(args, b64TestArgs{ - Raw: msg[:], - Encoded: []byte(encoding.EncodeToString(msg[:])), - IsURL: isURL, - Program: ops.Program[:], - }) - } - return -} - -func benchmarkB64Decode(b *testing.B, scenario string, msgLen int) { - var source string - isURL := false - - switch scenario { - case "base64url": - isURL = true - source = `#pragma version 5 -arg 0 -arg 1 -base64_decode URLAlph -==` - case "base64std": - isURL = false - source = `#pragma version 5 -arg 0 -arg 1 -base64_decode StdAlph -==` - default: - source = `#pragma version 5 -arg 0 -arg 1 -pop -pop -int 1` - } - args, err := benchmarkB64DecodeGenData(b, source, isURL, msgLen) - if err != nil { - require.NoError(b, err) - return - } - benchmarkB64DecodeSanity(b, args) - b.ResetTimer() - testB64DecodeEval(b, args) -} - -func benchmarkB64DecodeSanity(b *testing.B, args []b64TestArgs) { - for _, data := range args { - encoding := base64.StdEncoding - if data.IsURL { - encoding = base64.URLEncoding - } - decoded, err := base64Decode(data.Encoded, encoding) - require.NoError(b, err) - require.Equal(b, data.Raw, decoded) - } -} - -var b64msgLengths = []int{50, 1050, 2050, 3050} - -func benchmarkB64DecodeScenario(b *testing.B, scenario string) { - for _, msgLen := range b64msgLengths { - b.Run(fmt.Sprintf("%s_%d", scenario, msgLen), func(b *testing.B) { - benchmarkB64Decode(b, scenario, msgLen) - }) - } -} - -func TestInvestigation(t *testing.T) { - data := b64TestArgs{ - Raw: []uint8{0x1b, 0x66, 0x1d, 0x29, 0x5e, 0x8e, 0x95, 0x79, 0x36, 0xf7, 0xd5, 0xd3, 0x0, 0x35, 0x7b, 0x25, 0x1f, 0x1e, 0x57, 0x78, 0x58, 0x63, 0x26, 0xae, 0x29, 0xcc, 0x96, 0xee, 0x6c, 0x63, 0xc6, 0x88, 0x7b, 0x26, 0xb0, 0x41, 0x77, 0x5, 0xd6, 0xd3, 0x1f, 0x7f, 0x89, 0x94, 0x3a, 0x12, 0xab, 0xe3, 0x70, 0x73}, - Encoded: []uint8{0x6e, 0x53, 0x46, 0x73, 0x7a, 0x7a, 0x74, 0x55, 0x4e, 0x70, 0x6d, 0x6c, 0x4b, 0x65, 0x39, 0x52, 0x33, 0x2d, 0x6f, 0x72, 0x64, 0x38, 0x66, 0x7a, 0x58, 0x32, 0x79, 0x64, 0x6b, 0x79, 0x37, 0x6e, 0x4b, 0x6d, 0x79, 0x51, 0x73, 0x52, 0x4f, 0x66, 0x6f, 0x43, 0x46, 0x69, 0x70, 0x76, 0x4b, 0x46, 0x48, 0x72, 0x49, 0x77, 0x66, 0x5a, 0x57, 0x48, 0x59, 0x48, 0x63, 0x53, 0x36, 0x54, 0x4a, 0x43, 0x72, 0x33, 0x73, 0x3d}, - IsURL: true, - Program: []uint8{0x5, 0x2d, 0x2e, 0x5c, 0x0, 0x12}, - } - require.Equal(t, uint8(0x1b), data.Raw[0]) - testB64DecodeEval(t, []b64TestArgs{data}) -} -func BenchmarkBase64DecodeVanillaBase(b *testing.B) { - benchmarkB64DecodeScenario(b, "vanilla baseline") -} - -func BenchmarkBase64DecodeURL(b *testing.B) { - benchmarkB64DecodeScenario(b, "base64url") -} - -func TestCoverZZ(t *testing.T) { - t.Parallel() - testAccepts(t, "byte base64 YWJjMTIzIT8kKiYoKSctPUB+; byte base64 YWJjMTIzIT8kKiYoKSctPUB+; ==", 1) - testAccepts(t, "byte base64 abc123!?$*&(); byte base64 abc123!?$*&(); ==", 1) - // testAccepts(t, "int 4; int 3; int 2; int 1; cover 2; pop; pop; int 1; ==; return", 5) - // testPanics(t, obfuscate("int 4; int 3; int 2; int 1; cover 11; int 2; ==; return"), 5) - // testPanics(t, obfuscate("int 4; int 3; int 2; int 1; cover 4; int 2; ==; return"), 5) -} - -// OLDER -func TestKeccak256_Z(t *testing.T) { - partitiontest.PartitionTest(t) - - t.Parallel() - /* - pip install sha3 - import sha3 - blob=b'fnord' - sha3.keccak_256(blob).hexdigest() - */ - progText := `byte 0x666E6F7264 -keccak256 -byte 0xc195eca25a6f4c82bfba0287082ddb0d602ae9230f9cf1f1a40b68f8e2c41567 -==` - testAccepts(t, progText, 1) -} - -func TestSHA512_256_Z(t *testing.T) { - partitiontest.PartitionTest(t) - - t.Parallel() - /* - pip cryptography - from cryptography.hazmat.backends import default_backend - from cryptography.hazmat.primitives import hashes - import base64 - digest = hashes.Hash(hashes.SHA512_256(), backend=default_backend()) - digest.update(b'fnord') - base64.b16encode(digest.finalize()) - */ - progText := `byte 0x666E6F7264 -sha512_256 - -byte 0x98D2C31612EA500279B6753E5F6E780CA63EBA8274049664DAD66A2565ED1D2A -==` - testAccepts(t, progText, 1) -} - -func TestEd25519verify_Z(t *testing.T) { - partitiontest.PartitionTest(t) - - t.Parallel() - var s crypto.Seed - crypto.RandBytes(s[:]) - c := crypto.GenerateSignatureSecrets(s) - msg := "62fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd" - data, err := hex.DecodeString(msg) - require.NoError(t, err) - pk := basics.Address(c.SignatureVerifier) - pkStr := pk.String() - - for v := uint64(1); v <= AssemblerMaxVersion; v++ { - t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { - ops, err := AssembleStringWithVersion(fmt.Sprintf(`arg 0 -arg 1 -addr %s -ed25519verify`, pkStr), v) - require.NoError(t, err) - sig := c.Sign(Msg{ - ProgramHash: crypto.HashObj(Program(ops.Program)), - Data: data[:], - }) - var txn transactions.SignedTxn - txn.Lsig.Logic = ops.Program - txn.Lsig.Args = [][]byte{data[:], sig[:]} - sb := strings.Builder{} - pass, err := Eval(ops.Program, defaultEvalParams(&sb, &txn)) - if !pass { - t.Log(hex.EncodeToString(ops.Program)) - t.Log(sb.String()) - } - require.True(t, pass) - require.NoError(t, err) - - // short sig will fail - txn.Lsig.Args[1] = sig[1:] - pass, err = Eval(ops.Program, defaultEvalParams(nil, &txn)) - require.False(t, pass) - require.Error(t, err) - isNotPanic(t, err) - - // flip a bit and it should not pass - msg1 := "52fdfc072182654f163f5f0f9a621d729566c74d0aa413bf009c9800418c19cd" - data1, err := hex.DecodeString(msg1) - require.NoError(t, err) - txn.Lsig.Args = [][]byte{data1, sig[:]} - sb1 := strings.Builder{} - pass1, err := Eval(ops.Program, defaultEvalParams(&sb1, &txn)) - require.False(t, pass1) - require.NoError(t, err) - isNotPanic(t, err) - }) - } -} - -func TestEcdsa_Z(t *testing.T) { - partitiontest.PartitionTest(t) - t.Parallel() - - key, err := ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) - require.NoError(t, err) - pk := secp256k1.CompressPubkey(key.PublicKey.X, key.PublicKey.Y) - sk := keyToByte(t, key.D) - x := keyToByte(t, key.PublicKey.X) - y := keyToByte(t, key.PublicKey.Y) - - // ecdsa decompress tests - source := ` -byte 0x%s -ecdsa_pk_decompress Secp256k1 -store 0 -byte 0x%s -== -load 0 -byte 0x%s -== -&&` - pkTampered1 := make([]byte, len(pk)) - copy(pkTampered1, pk) - pkTampered1[0] = 0 - pkTampered2 := make([]byte, len(pk)) - copy(pkTampered2, pk[1:]) - - var decompressTests = []struct { - key []byte - pass bool - }{ - {pk, true}, - {pkTampered1, false}, - {pkTampered2, false}, - } - for _, test := range decompressTests { - t.Run(fmt.Sprintf("decompress/pass=%v", test.pass), func(t *testing.T) { - src := fmt.Sprintf(source, hex.EncodeToString(test.key), hex.EncodeToString(x), hex.EncodeToString(y)) - if test.pass { - testAccepts(t, src, 5) - } else { - testPanics(t, src, 5) - } - }) - } - - // ecdsa verify tests - source = ` -byte "%s" -sha512_256 -byte 0x%s -byte 0x%s -byte 0x%s -byte 0x%s -ecdsa_verify Secp256k1 -` - data := []byte("testdata") - msg := sha512.Sum512_256(data) - - sign, err := secp256k1.Sign(msg[:], sk) - require.NoError(t, err) - r := sign[:32] - s := sign[32:64] - v := int(sign[64]) - - rTampered := make([]byte, len(r)) - copy(rTampered, pk) - rTampered[0] = 0 - - var verifyTests = []struct { - data string - r []byte - pass bool - }{ - {"testdata", r, true}, - {"testdata", rTampered, false}, - {"testdata1", r, false}, - } - for _, test := range verifyTests { - t.Run(fmt.Sprintf("verify/pass=%v", test.pass), func(t *testing.T) { - src := fmt.Sprintf(source, test.data, hex.EncodeToString(test.r), hex.EncodeToString(s), hex.EncodeToString(x), hex.EncodeToString(y)) - if test.pass { - testAccepts(t, src, 5) - } else { - testRejects(t, src, 5) - } - }) - } - - // ecdsa recover tests - source = ` -byte 0x%s -int %d -byte 0x%s -byte 0x%s -ecdsa_pk_recover Secp256k1 -dup2 -store 0 -byte 0x%s -== -load 0 -byte 0x%s -== -&& -store 1 -concat // X + Y -byte 0x04 -swap -concat // 0x04 + X + Y -byte 0x%s -== -load 1 -&&` - var recoverTests = []struct { - v int - checker func(t *testing.T, program string, introduced uint64) - }{ - {v, testAccepts}, - {v ^ 1, testRejects}, - {3, func(t *testing.T, program string, introduced uint64) { - testPanics(t, program, introduced) - }}, - } - pkExpanded := secp256k1.S256().Marshal(key.PublicKey.X, key.PublicKey.Y) - - for i, test := range recoverTests { - t.Run(fmt.Sprintf("recover/%d", i), func(t *testing.T) { - src := fmt.Sprintf(source, hex.EncodeToString(msg[:]), test.v, hex.EncodeToString(r), hex.EncodeToString(s), hex.EncodeToString(x), hex.EncodeToString(y), hex.EncodeToString(pkExpanded)) - test.checker(t, src, 5) - }) - } - - // sample sequencing: decompress + verify - source = fmt.Sprintf(`#pragma version 5 -byte "testdata" -sha512_256 -byte 0x%s -byte 0x%s -byte 0x%s -ecdsa_pk_decompress Secp256k1 -ecdsa_verify Secp256k1`, hex.EncodeToString(r), hex.EncodeToString(s), hex.EncodeToString(pk)) - ops := testProg(t, source, 5) - var txn transactions.SignedTxn - txn.Lsig.Logic = ops.Program - pass, err := Eval(ops.Program, defaultEvalParamsWithVersion(nil, &txn, 5)) - require.NoError(t, err) - require.True(t, pass) -} - -// test compatibility with ethereum signatures -func TestEcdsaEthAddress_Z(t *testing.T) { - /* - pip install eth-keys pycryptodome - from eth_keys import keys - pk = keys.PrivateKey(b"\xb2\\}\xb3\x1f\xee\xd9\x12''\xbf\t9\xdcv\x9a\x96VK-\xe4\xc4rm\x03[6\xec\xf1\xe5\xb3d") - msg=b"hello from ethereum" - print("msg: '{}'".format(msg.decode())) - signature = pk.sign_msg(msg) - print("v:", signature.v) - print("r:", signature.r.to_bytes(32, byteorder="big").hex()) - print("s:", signature.s.to_bytes(32, byteorder="big").hex()) - print("addr:", pk.public_key.to_address()) - */ - progText := `byte "hello from ethereum" // msg -keccak256 -int 0 // v -byte 0x745e8f55ac6189ee89ed707c36694868e3903988fbf776c8096c45da2e60c638 // r -byte 0x30c8e4a9b5d2eb53ddc6294587dd00bed8afe2c45dd72f6b4cf752e46d5ba681 // s -ecdsa_pk_recover Secp256k1 -concat // convert public key X and Y to ethereum addr -keccak256 -substring 12 32 -byte 0x5ce9454909639d2d17a3f753ce7d93fa0b9ab12e // addr -==` - testAccepts(t, progText, 5) -} - -func BenchmarkHash_Z(b *testing.B) { - for _, hash := range []string{"sha256", "keccak256", "sha512_256"} { - b.Run(hash+"-small", func(b *testing.B) { // hash 32 bytes - benchmarkOperation(b, "int 32; bzero", hash, "pop; int 1") - }) - b.Run(hash+"-med", func(b *testing.B) { // hash 128 bytes - benchmarkOperation(b, "int 32; bzero", - "dup; concat; dup; concat;"+hash, "pop; int 1") - }) - b.Run(hash+"-big", func(b *testing.B) { // hash 512 bytes - benchmarkOperation(b, "int 32; bzero", - "dup; concat; dup; concat; dup; concat; dup; concat;"+hash, "pop; int 1") - }) - } -} - -func BenchmarkSha256Raw_Z(b *testing.B) { - addr, _ := basics.UnmarshalChecksumAddress("OC6IROKUJ7YCU5NV76AZJEDKYQG33V2CJ7HAPVQ4ENTAGMLIOINSQ6EKGE") - a := addr[:] - b.ResetTimer() - for i := 0; i < b.N; i++ { - t := sha256.Sum256(a) - a = t[:] - } -} - -func BenchmarkEd25519Verifyx1_Z(b *testing.B) { - //benchmark setup - var data [][32]byte - var programs [][]byte - var signatures []crypto.Signature - - for i := 0; i < b.N; i++ { - var buffer [32]byte //generate data to be signed - crypto.RandBytes(buffer[:]) - data = append(data, buffer) - - var s crypto.Seed //generate programs and signatures - crypto.RandBytes(s[:]) - secret := crypto.GenerateSignatureSecrets(s) - pk := basics.Address(secret.SignatureVerifier) - pkStr := pk.String() - ops, err := AssembleStringWithVersion(fmt.Sprintf(`arg 0 -arg 1 -addr %s -ed25519verify`, pkStr), AssemblerMaxVersion) - require.NoError(b, err) - programs = append(programs, ops.Program) - sig := secret.Sign(Msg{ - ProgramHash: crypto.HashObj(Program(ops.Program)), - Data: buffer[:], - }) - signatures = append(signatures, sig) - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - var txn transactions.SignedTxn - txn.Lsig.Logic = programs[i] - txn.Lsig.Args = [][]byte{data[i][:], signatures[i][:]} - sb := strings.Builder{} - ep := defaultEvalParams(&sb, &txn) - pass, err := Eval(programs[i], ep) - if !pass { - b.Log(hex.EncodeToString(programs[i])) - b.Log(sb.String()) - } - if err != nil { - require.NoError(b, err) - } - if !pass { - require.True(b, pass) - } - } -} - -// type benchmarkEcdsaData struct { -// x []byte -// y []byte -// pk []byte -// msg [32]byte -// r []byte -// s []byte -// v int -// programs []byte -// } - -func benchmarkEcdsaGenData_Z(b *testing.B) (data []benchmarkEcdsaData) { - data = make([]benchmarkEcdsaData, b.N) - for i := 0; i < b.N; i++ { - key, err := ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) - require.NoError(b, err) - sk := keyToByte(b, key.D) - data[i].x = keyToByte(b, key.PublicKey.X) - data[i].y = keyToByte(b, key.PublicKey.Y) - data[i].pk = secp256k1.CompressPubkey(key.PublicKey.X, key.PublicKey.Y) - - d := []byte("testdata") - data[i].msg = sha512.Sum512_256(d) - - sign, err := secp256k1.Sign(data[i].msg[:], sk) - require.NoError(b, err) - data[i].r = sign[:32] - data[i].s = sign[32:64] - data[i].v = int(sign[64]) - } - return data -} - -func benchmarkEcdsa_Z(b *testing.B, source string) { - data := benchmarkEcdsaGenData_Z(b) - ops, err := AssembleStringWithVersion(source, 5) - require.NoError(b, err) - for i := 0; i < b.N; i++ { - data[i].programs = ops.Program - } - - b.ResetTimer() - for i := 0; i < b.N; i++ { - var txn transactions.SignedTxn - txn.Lsig.Logic = data[i].programs - txn.Lsig.Args = [][]byte{data[i].msg[:], data[i].r, data[i].s, data[i].x, data[i].y, data[i].pk, {uint8(data[i].v)}} - sb := strings.Builder{} - ep := defaultEvalParams(&sb, &txn) - pass, err := Eval(data[i].programs, ep) - if !pass { - b.Log(hex.EncodeToString(data[i].programs)) - b.Log(sb.String()) - } - if err != nil { - require.NoError(b, err) - } - if !pass { - require.True(b, pass) - } - } -} - -func BenchmarkVariety(b *testing.B) { - b.Run("ecdsa_verify", func(b *testing.B) { - source := `#pragma version 5 -arg 0 -arg 1 -arg 2 -arg 3 -arg 4 -ecdsa_verify Secp256k1` - benchmarkEcdsa_Z(b, source) - }) - b.Run("ecdsa_pk_decompress", func(b *testing.B) { - source := `#pragma version 5 -arg 5 -ecdsa_pk_decompress Secp256k1 -pop -pop -int 1` - benchmarkEcdsa_Z(b, source) - }) - - b.Run("ecdsa_pk_recover", func(b *testing.B) { - source := `#pragma version 5 -arg 0 -arg 6 -btoi -arg 1 -arg 2 -ecdsa_pk_recover Secp256k1 -pop -pop -int 1` - benchmarkEcdsa_Z(b, source) - }) - - b.Run("add", func(b *testing.B) { - source := `#pragma version 5 -int 1300 -int 37 -+ -int 0 ->` - benchmarkEcdsa_Z(b, source) - }) -} diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 74b9f931cb..51f18ef296 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -17,6 +17,7 @@ package logic import ( + "crypto/rand" "encoding/base64" "encoding/binary" "encoding/hex" @@ -4909,3 +4910,221 @@ func TestPcDetails(t *testing.T) { }) } } + +type b64DecodeTestCase struct { + Encoded string + IsURL bool + Decoded string + Error error +} + +var testCases = []b64DecodeTestCase{ + {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=", + false, + `MOBY-DICK; + +or, THE WHALE. + + +By Herman Melville`, + nil, + }, + {"TU9CWS1ESUNLOwoKb3IsIFRIRSBXSEFMRS4KCgpCeSBIZXJtYW4gTWVsdmlsbGU=", + true, + `MOBY-DICK; + +or, THE WHALE. + + +By Herman Melville`, + nil, + }, + {"YWJjMTIzIT8kKiYoKSctPUB+", false, "abc123!?$*&()'-=@~", nil}, + {"YWJjMTIzIT8kKiYoKSctPUB-", true, "abc123!?$*&()'-=@~", nil}, + {"YWJjMTIzIT8kKiYoKSctPUB+", true, "", base64.CorruptInputError(23)}, + {"YWJjMTIzIT8kKiYoKSctPUB-", false, "", base64.CorruptInputError(23)}, +} + +func TestBase64DecodeFunc(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + for _, testCase := range testCases { + encoding := base64.StdEncoding + if testCase.IsURL { + encoding = base64.URLEncoding + } + encoding = encoding.Strict() + decoded, err := base64Decode([]byte(testCase.Encoded), encoding) + require.Equal(t, []byte(testCase.Decoded), decoded) + require.Equal(t, testCase.Error, err) + } +} + +type b64DecodeTestArgs struct { + Raw []byte + Encoded []byte + IsURL bool + Program []byte +} + +func testB64DecodeAssembleWithArgs(t *testing.T) []b64DecodeTestArgs { + sourceTmpl := `#pragma version 5 +arg 0 +arg 1 +base64_decode %s +==` + args := []b64DecodeTestArgs{} + for _, testCase := range testCases { + if testCase.Error == nil { + field := "StdAlph" + if testCase.IsURL { + field = "URLAlph" + } + source := fmt.Sprintf(sourceTmpl, field) + ops, err := AssembleStringWithVersion(source, 5) + require.NoError(t, err) + + arg := b64DecodeTestArgs{ + Raw: []byte(testCase.Decoded), + Encoded: []byte(testCase.Encoded), + IsURL: testCase.IsURL, + Program: ops.Program, + } + args = append(args, arg) + } + } + return args +} + +func testB64DecodeEval(tb testing.TB, args []b64DecodeTestArgs) { + for _, data := range args { + var txn transactions.SignedTxn + txn.Lsig.Logic = data.Program + txn.Lsig.Args = [][]byte{data.Raw[:], data.Encoded[:]} + ep := defaultEvalParams(&strings.Builder{}, &txn) + pass, err := Eval(data.Program, ep) + if err != nil { + require.NoError(tb, err) + } + if !pass { + fmt.Printf("FAILING WITH data = %#v", data) + require.True(tb, pass) + } + } +} + +func TestOpBase64Decode(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + args := testB64DecodeAssembleWithArgs(t) + testB64DecodeEval(t, args) +} + +func benchmarkB64DecodeGenData(b *testing.B, source string, isURL bool, msgLen int) (args []b64DecodeTestArgs, err error) { + args = make([]b64DecodeTestArgs, b.N) + var ops *OpStream + ops, err = AssembleStringWithVersion(source, 5) + if err != nil { + require.NoError(b, err) + return + } + + encoding := base64.StdEncoding + if isURL { + encoding = base64.URLEncoding + } + encoding = encoding.Strict() + + msg := make([]byte, msgLen) + for i := 0; i < b.N; i++ { + _, err = rand.Read(msg) + if err != nil { + require.NoError(b, err) + return + } + args[i].Raw = make([]byte, msgLen) + copy(args[i].Raw, msg) + eStr := encoding.EncodeToString(msg) + args[i].Encoded = make([]byte, len(eStr)) + copy(args[i].Encoded, []byte(eStr)) + args[i].IsURL = isURL + args[i].Program = make([]byte, len(ops.Program)) + copy(args[i].Program, ops.Program[:]) + } + return +} + +func benchmarkB64Decode(b *testing.B, scenario string, msgLen int) { + var source string + isURL := false + + switch scenario { + case "baseline": + source = `#pragma version 5 +arg 0 +dup +arg 1 +pop +==` + case "base64url": + isURL = true + source = `#pragma version 5 +arg 0 +arg 1 +dup +pop +base64_decode URLAlph +==` + case "base64std": + isURL = false + source = `#pragma version 5 +arg 0 +arg 1 +dup +pop +base64_decode StdAlph +==` + default: + source = fmt.Sprintf(`#pragma version 5 +arg 0 +dup +arg 1 +%s +pop +==`, scenario) + } + args, err := benchmarkB64DecodeGenData(b, source, isURL, msgLen) + if err != nil { + require.NoError(b, err) + return + } + b.ResetTimer() + testB64DecodeEval(b, args) +} + +var b64DecodeMsgLengths = []int{50, 1050, 2050, 3050} + +var b64DecodeScenarios = []string{ + "baseline", + "len", + "store 0\nload 0", + "swap\nswap", + "dup\nb&", + "b~", + "keccak256", + "sha256", + "sha512_256", + "base64url", + "base64std", +} + +func BenchmarkB64DecodeWithComparisons(b *testing.B) { + for _, msgLen := range b64DecodeMsgLengths { + for _, scenario := range b64DecodeScenarios { + b.Run(fmt.Sprintf("%s_%d", scenario, msgLen), func(b *testing.B) { + benchmarkB64Decode(b, scenario, msgLen) + }) + } + } +} diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 64d939469e..e87ac98308 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -237,7 +237,7 @@ var OpSpecs = []OpSpec{ {0x4e, "cover", opCover, asmDefault, disDefault, oneAny, oneAny, 5, modeAny, stacky(typeCover, "n")}, {0x4f, "uncover", opUncover, asmDefault, disDefault, oneAny, oneAny, 5, modeAny, stacky(typeUncover, "n")}, - // Byteslice processing + // byteslice processing / StringOps {0x50, "concat", opConcat, asmDefault, disDefault, twoBytes, oneBytes, 2, modeAny, opDefault}, {0x51, "substring", opSubstring, assembleSubstring, disDefault, oneBytes, oneBytes, 2, modeAny, immediates("s", "e")}, {0x52, "substring3", opSubstring3, asmDefault, disDefault, byteIntInt, oneBytes, 2, modeAny, opDefault}, From 4adc7a239175e6367dd82e12a14d2df53232d246 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 22 Nov 2021 12:16:20 -0600 Subject: [PATCH 08/22] lint errors on exporting and set cost at 50 (rather than the meaning of life) --- data/transactions/logic/doc.go | 1 + data/transactions/logic/fields.go | 1 + data/transactions/logic/opcodes.go | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 503846a247..eb51a82b3d 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -540,6 +540,7 @@ var EcdsaCurveDocs = map[string]string{ "Secp256k1": "secp256k1 curve", } +// Base64AlphabetDocs for notes on opcode `base64decode` var Base64AlphabetDocs = map[string]string{ "Standard": `Standard base-64 alphabet as specified in RFC 4648 section 4`, "URL": `URL and Filename Safe base-64 alphabet as specified in RFC 4648 section 5`, diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index cb3fa53018..7d27990aaf 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -452,6 +452,7 @@ func (s ecDsaCurveNameSpecMap) getExtraFor(name string) (extra string) { type Base64Alphabet int const ( + // base64url and base64std alphabets according to https://www.rfc-editor.org/rfc/rfc4648.html URLAlph Base64Alphabet = iota StdAlph invalidBase64Alphabet diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index e87ac98308..1b7bd41516 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -250,7 +250,7 @@ var OpSpecs = []OpSpec{ {0x59, "extract_uint16", opExtract16Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5a, "extract_uint32", opExtract32Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5b, "extract_uint64", opExtract64Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, - {0x5c, "base64_decode", opBase64Decode, assembleBase64Decode, disBase64Decode, oneBytes, oneBytes, 5, modeAny, costlyImm(42, "v")}, + {0x5c, "base64_decode", opBase64Decode, assembleBase64Decode, disBase64Decode, oneBytes, oneBytes, 5, modeAny, costlyImm(50, "v")}, {0x60, "balance", opBalance, asmDefault, disDefault, oneInt, oneInt, 2, runModeApplication, opDefault}, {0x60, "balance", opBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault}, From fb3e02f2ab6bb25e5f2bdb6d9b71564ed8337347 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 22 Nov 2021 11:57:20 -0600 Subject: [PATCH 09/22] Update data/transactions/logic/doc.go --- data/transactions/logic/doc.go | 1 - 1 file changed, 1 deletion(-) diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index eb51a82b3d..329176fa28 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -107,7 +107,6 @@ var opDocByName = map[string]string{ "gloads": "push Ith scratch space index of the Xth transaction in the current group", "gaid": "push the ID of the asset or application created in the Tth transaction of the current group", "gaids": "push the ID of the asset or application created in the Xth transaction of the current group", - "zaid": "zush the ID of the asset or application created in the Xth transaction of the current group", "bnz": "branch to TARGET if value X is not zero", "bz": "branch to TARGET if value X is zero", From 0a6dff4a63160fe7151cfd4032ba863f44ab6e1c Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 22 Nov 2021 13:39:54 -0600 Subject: [PATCH 10/22] try lint fix again --- data/transactions/logic/fields.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 7d27990aaf..9779a88d44 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -452,8 +452,9 @@ func (s ecDsaCurveNameSpecMap) getExtraFor(name string) (extra string) { type Base64Alphabet int const ( - // base64url and base64std alphabets according to https://www.rfc-editor.org/rfc/rfc4648.html + // URLAlph represents the base64url alphabet defined in https://www.rfc-editor.org/rfc/rfc4648.html URLAlph Base64Alphabet = iota + // StdAlph represents the standard alphabet of the RFC StdAlph invalidBase64Alphabet ) From 024973c4199c6f46ef6b287011272ef539c20aaa Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 22 Nov 2021 13:58:58 -0600 Subject: [PATCH 11/22] v6 --- data/transactions/logic/assembler_test.go | 1 + data/transactions/logic/opcodes.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 1c78f9d404..f8e5c5f246 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -344,6 +344,7 @@ itxna Logs 3 const v6Nonsense = v5Nonsense + ` itxn_next +base64_decode URLAlph ` var nonsense = map[uint64]string{ diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 1b7bd41516..3a448b8546 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -250,7 +250,7 @@ var OpSpecs = []OpSpec{ {0x59, "extract_uint16", opExtract16Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5a, "extract_uint32", opExtract32Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5b, "extract_uint64", opExtract64Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, - {0x5c, "base64_decode", opBase64Decode, assembleBase64Decode, disBase64Decode, oneBytes, oneBytes, 5, modeAny, costlyImm(50, "v")}, + {0x5c, "base64_decode", opBase64Decode, assembleBase64Decode, disBase64Decode, oneBytes, oneBytes, 6, modeAny, costlyImm(50, "v")}, {0x60, "balance", opBalance, asmDefault, disDefault, oneInt, oneInt, 2, runModeApplication, opDefault}, {0x60, "balance", opBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault}, From 93afa06c69aa2c678fbff023e401ef33d296e91c Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Tue, 23 Nov 2021 11:43:01 -0600 Subject: [PATCH 12/22] pass units excepting docs tests --- data/transactions/logic/assembler_test.go | 2 +- data/transactions/logic/eval_test.go | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index f8e5c5f246..19abfefb23 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -362,7 +362,7 @@ var compiled = map[uint64]string{ 3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e", 4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164", 5: "052004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03", - 6: "062004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03b6", + 6: "062004010002b7a60c26050242420c68656c6c6f20776f726c6421070123456789abcd208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292b0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482b50512a632223524100034200004322602261222704634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f447825225314225427042455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f012a57000823810858235b235a2359b03139330039b1b200b322c01a23c1001a2323c21a23c3233e233f8120af06002a494905002a49490700b53a03b65c00", } func pseudoOp(opcode string) bool { diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 51f18ef296..eb205f469c 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -4911,6 +4911,8 @@ func TestPcDetails(t *testing.T) { } } +var minB64DecodeVersion uint64 = 6 + type b64DecodeTestCase struct { Encoded string IsURL bool @@ -4969,7 +4971,7 @@ type b64DecodeTestArgs struct { } func testB64DecodeAssembleWithArgs(t *testing.T) []b64DecodeTestArgs { - sourceTmpl := `#pragma version 5 + sourceTmpl := `#pragma version %d arg 0 arg 1 base64_decode %s @@ -4981,8 +4983,8 @@ base64_decode %s if testCase.IsURL { field = "URLAlph" } - source := fmt.Sprintf(sourceTmpl, field) - ops, err := AssembleStringWithVersion(source, 5) + source := fmt.Sprintf(sourceTmpl, minB64DecodeVersion, field) + ops, err := AssembleStringWithVersion(source, minB64DecodeVersion) require.NoError(t, err) arg := b64DecodeTestArgs{ @@ -5024,7 +5026,7 @@ func TestOpBase64Decode(t *testing.T) { func benchmarkB64DecodeGenData(b *testing.B, source string, isURL bool, msgLen int) (args []b64DecodeTestArgs, err error) { args = make([]b64DecodeTestArgs, b.N) var ops *OpStream - ops, err = AssembleStringWithVersion(source, 5) + ops, err = AssembleStringWithVersion(source, minB64DecodeVersion) if err != nil { require.NoError(b, err) return @@ -5061,7 +5063,7 @@ func benchmarkB64Decode(b *testing.B, scenario string, msgLen int) { switch scenario { case "baseline": - source = `#pragma version 5 + source = `#pragma version 6 arg 0 dup arg 1 @@ -5069,7 +5071,7 @@ pop ==` case "base64url": isURL = true - source = `#pragma version 5 + source = `#pragma version 6 arg 0 arg 1 dup @@ -5078,7 +5080,7 @@ base64_decode URLAlph ==` case "base64std": isURL = false - source = `#pragma version 5 + source = `#pragma version 6 arg 0 arg 1 dup @@ -5086,7 +5088,7 @@ pop base64_decode StdAlph ==` default: - source = fmt.Sprintf(`#pragma version 5 + source = fmt.Sprintf(`#pragma version 6 arg 0 dup arg 1 From 91d1be030ad9969a7781d635c18516edb88d5e30 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 25 Nov 2021 17:05:18 -0600 Subject: [PATCH 13/22] docs --- data/transactions/logic/README.md | 1 + data/transactions/logic/TEAL_opcodes.md | 9 +++++++++ data/transactions/logic/doc.go | 5 ++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 6bdc194573..6d6e0d9911 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -191,6 +191,7 @@ bytes on outputs. | `b==` | A is equals to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} | | `b!=` | A is not equal to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} | | `b%` | A modulo B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Fail if B is zero. | +| `base64_decode v` | the base64 decoding of X using the alphabet denoted by argument 0 | These opcodes operate on the bits of byte-array values. The shorter array is interpreted as though left padded with zeros until it is the diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 082f545530..714dc55790 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -854,6 +854,15 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on - pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails - LogicSigVersion >= 5 +## base64_decode v + +- Opcode: 0x5c {uint8 alphabet index} +- Pops: *... stack*, []byte +- Pushes: []byte +- the base64 decoding of X using the alphabet denoted by argument 0 +- **Cost**: 50 +- LogicSigVersion >= 6 + ## balance - Opcode: 0x60 diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 329176fa28..ae3b004e36 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -133,6 +133,7 @@ var opDocByName = map[string]string{ "extract_uint16": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B+2 is larger than the array length, the program fails", "extract_uint32": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B+4 is larger than the array length, the program fails", "extract_uint64": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails", + "base64_decode": "the base64 decoding of X using the alphabet denoted by argument 0", "balance": "get balance for account A, in microalgos. The balance is observed after the effects of previous transactions in the group, and after the fee for the current transaction is deducted.", "min_balance": "get minimum required balance for account A, in microalgos. Required balance is affected by [ASA](https://developer.algorand.org/docs/features/asa/#assets-overview) and [App](https://developer.algorand.org/docs/features/asc1/stateful/#minimum-balance-requirement-for-a-smart-contract) usage. When creating or opting into an app, the minimum balance grows before the app code runs, therefore the increase is visible there. When deleting or closing out, the minimum balance decreases after the app executes.", @@ -229,6 +230,8 @@ var opcodeImmediateNotes = map[string]string{ "ecdsa_verify": "{uint8 curve index}", "ecdsa_pk_decompress": "{uint8 curve index}", "ecdsa_pk_recover": "{uint8 curve index}", + + "base64_decode": "{uint8 alphabet index}", } // OpImmediateNote returns a short string about immediate data which follows the op byte @@ -296,7 +299,7 @@ func OpDocExtra(opName string) string { var OpGroups = map[string][]string{ "Arithmetic": {"sha256", "keccak256", "sha512_256", "ed25519verify", "ecdsa_verify", "ecdsa_pk_recover", "ecdsa_pk_decompress", "+", "-", "/", "*", "<", ">", "<=", ">=", "&&", "||", "shl", "shr", "sqrt", "bitlen", "exp", "==", "!=", "!", "len", "itob", "btoi", "%", "|", "&", "^", "~", "mulw", "addw", "divmodw", "expw", "getbit", "setbit", "getbyte", "setbyte", "concat"}, "Byte Array Slicing": {"substring", "substring3", "extract", "extract3", "extract_uint16", "extract_uint32", "extract_uint64"}, - "Byte Array Arithmetic": {"b+", "b-", "b/", "b*", "b<", "b>", "b<=", "b>=", "b==", "b!=", "b%"}, + "Byte Array Arithmetic": {"b+", "b-", "b/", "b*", "b<", "b>", "b<=", "b>=", "b==", "b!=", "b%", "base64_decode"}, "Byte Array Logic": {"b|", "b&", "b^", "b~"}, "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "args", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gaid", "gaids"}, "Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "cover", "uncover", "swap", "select", "assert", "callsub", "retsub"}, From 82962384d9a18e2d7a7b32e4c660f06bab1b4382 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 28 Nov 2021 17:12:18 -0600 Subject: [PATCH 14/22] better docs --- data/transactions/logic/README.md | 2 +- data/transactions/logic/TEAL_opcodes.md | 4 +++- data/transactions/logic/doc.go | 9 ++------- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 6d6e0d9911..c6aa680563 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -191,7 +191,7 @@ bytes on outputs. | `b==` | A is equals to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} | | `b!=` | A is not equal to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} | | `b%` | A modulo B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Fail if B is zero. | -| `base64_decode v` | the base64 decoding of X using the alphabet denoted by argument 0 | +| `base64_decode v` | the base64 decoding of X with the alphabet denoted by V (imm arg). Fail if X is not base64 encoded with alphabet V | These opcodes operate on the bits of byte-array values. The shorter array is interpreted as though left padded with zeros until it is the diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 714dc55790..e7ffe60959 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -859,10 +859,12 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on - Opcode: 0x5c {uint8 alphabet index} - Pops: *... stack*, []byte - Pushes: []byte -- the base64 decoding of X using the alphabet denoted by argument 0 +- the base64 decoding of X with the alphabet denoted by V (imm arg). Fail if X is not base64 encoded with alphabet V - **Cost**: 50 - LogicSigVersion >= 6 +decodes X using the base64 alphabet V. Specify the alphabet with an immediate arg either as URL and Filename Safe (`URLAlph`) or Standard (`StdAlph`). See RFC 4648 (sections 4 and 5) + ## balance - Opcode: 0x60 diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index ae3b004e36..c54f19bf4f 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -133,7 +133,7 @@ var opDocByName = map[string]string{ "extract_uint16": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B+2 is larger than the array length, the program fails", "extract_uint32": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B+4 is larger than the array length, the program fails", "extract_uint64": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails", - "base64_decode": "the base64 decoding of X using the alphabet denoted by argument 0", + "base64_decode": "the base64 decoding of X with the alphabet denoted by V (imm arg). Fail if X is not base64 encoded with alphabet V", "balance": "get balance for account A, in microalgos. The balance is observed after the effects of previous transactions in the group, and after the fee for the current transaction is deducted.", "min_balance": "get minimum required balance for account A, in microalgos. Required balance is affected by [ASA](https://developer.algorand.org/docs/features/asa/#assets-overview) and [App](https://developer.algorand.org/docs/features/asc1/stateful/#minimum-balance-requirement-for-a-smart-contract) usage. When creating or opting into an app, the minimum balance grows before the app code runs, therefore the increase is visible there. When deleting or closing out, the minimum balance decreases after the app executes.", @@ -286,6 +286,7 @@ var opDocExtras = map[string]string{ "itxn_begin": "`itxn_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the top-level transaction, and all other fields to zero values.", "itxn_field": "`itxn_field` fails if X is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `itxn_field` also fails if X is an account or asset that does not appear in `txn.Accounts` or `txn.ForeignAssets` of the top-level transaction. (Setting addresses in asset creation are exempted from this requirement.)", "itxn_submit": "`itxn_submit` resets the current transaction so that it can not be resubmitted. A new `itxn_begin` is required to prepare another inner transaction.", + "base64_decode": "decodes X using the base64 alphabet V. Specify the alphabet with an immediate arg either as URL and Filename Safe (`URLAlph`) or Standard (`StdAlph`). See RFC 4648 (sections 4 and 5)", } // OpDocExtra returns extra documentation text about an op @@ -541,9 +542,3 @@ func AppParamsFieldDocs() map[string]string { var EcdsaCurveDocs = map[string]string{ "Secp256k1": "secp256k1 curve", } - -// Base64AlphabetDocs for notes on opcode `base64decode` -var Base64AlphabetDocs = map[string]string{ - "Standard": `Standard base-64 alphabet as specified in RFC 4648 section 4`, - "URL": `URL and Filename Safe base-64 alphabet as specified in RFC 4648 section 5`, -} From 46b8b14953664e4b38cc297ed672780e182e8c59 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 28 Nov 2021 17:58:32 -0600 Subject: [PATCH 15/22] pass the stateful test by providing a special TEAL sequence for base64_decode --- data/transactions/logic/evalStateful_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 5a212629c6..2098d3af41 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2391,7 +2391,8 @@ func TestReturnTypes(t *testing.T) { "args": "args", "itxn": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn CreatedAssetID", // This next one is a cop out. Can't use itxna Logs until we have inner appl - "itxna": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn NumLogs", + "itxna": "itxn_begin; int pay; itxn_field TypeEnum; itxn_submit; itxn NumLogs", + "base64_decode": `pushbytes "YWJjMTIzIT8kKiYoKSctPUB+"; base64_decode StdAlph; pushbytes "abc123!?$*&()'-=@~"; ==; pushbytes "YWJjMTIzIT8kKiYoKSctPUB-"; base64_decode URLAlph; pushbytes "abc123!?$*&()'-=@~"; ==; &&; assert`, } // these require special input data and tested separately From 4ed53661a29e7cd1aeb228a2359e3f849452d5ba Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Sun, 28 Nov 2021 23:48:03 -0600 Subject: [PATCH 16/22] benchmars for base64decode and a cost of 25 --- data/transactions/logic/eval_test.go | 157 ++++++++------------------- data/transactions/logic/opcodes.go | 2 +- 2 files changed, 49 insertions(+), 110 deletions(-) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index eb205f469c..13767b0335 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -17,7 +17,6 @@ package logic import ( - "crypto/rand" "encoding/base64" "encoding/binary" "encoding/hex" @@ -3720,6 +3719,54 @@ func BenchmarkBigMath(b *testing.B) { } } +func BenchmarkBase64Decode(b *testing.B) { + smallStd := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + smallURL := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" + medStd := strings.Repeat(smallStd, 16) + medURL := strings.Repeat(smallURL, 16) + bigStd := strings.Repeat(medStd, 4) + bigURL := strings.Repeat(medURL, 4) + + tags := []string{"small", "medium", "large"} + stds := []string{smallStd, medStd, bigStd} + urls := []string{smallURL, medURL, bigURL} + ops := []string{ + "", + "len", + "b~", + "int 1; pop", + "keccak256", + "sha256", + "sha512_256", + "base64_decode StdAlph", + "base64_decode URLAlph", + } + benches := [][]string{} + for i, tag := range tags { + for _, op := range ops { + testName := op + encoded := stds[i] + if op == "base64_decode URLAlph" { + encoded = urls[i] + } + if len(op) > 0 { + op += "; " + } + op += "pop" + benches = append(benches, []string{ + fmt.Sprintf("%s_%s", testName, tag), + "", + fmt.Sprintf(`byte "%s"; %s`, encoded, op), + "int 1", + }) + } + } + for _, bench := range benches { + b.Run(bench[0], func(b *testing.B) { + benchmarkOperation(b, bench[1], bench[2], bench[3]) + }) + } +} func BenchmarkAddx64(b *testing.B) { progs := [][]string{ {"add long stack", addBenchmarkSource}, @@ -5022,111 +5069,3 @@ func TestOpBase64Decode(t *testing.T) { args := testB64DecodeAssembleWithArgs(t) testB64DecodeEval(t, args) } - -func benchmarkB64DecodeGenData(b *testing.B, source string, isURL bool, msgLen int) (args []b64DecodeTestArgs, err error) { - args = make([]b64DecodeTestArgs, b.N) - var ops *OpStream - ops, err = AssembleStringWithVersion(source, minB64DecodeVersion) - if err != nil { - require.NoError(b, err) - return - } - - encoding := base64.StdEncoding - if isURL { - encoding = base64.URLEncoding - } - encoding = encoding.Strict() - - msg := make([]byte, msgLen) - for i := 0; i < b.N; i++ { - _, err = rand.Read(msg) - if err != nil { - require.NoError(b, err) - return - } - args[i].Raw = make([]byte, msgLen) - copy(args[i].Raw, msg) - eStr := encoding.EncodeToString(msg) - args[i].Encoded = make([]byte, len(eStr)) - copy(args[i].Encoded, []byte(eStr)) - args[i].IsURL = isURL - args[i].Program = make([]byte, len(ops.Program)) - copy(args[i].Program, ops.Program[:]) - } - return -} - -func benchmarkB64Decode(b *testing.B, scenario string, msgLen int) { - var source string - isURL := false - - switch scenario { - case "baseline": - source = `#pragma version 6 -arg 0 -dup -arg 1 -pop -==` - case "base64url": - isURL = true - source = `#pragma version 6 -arg 0 -arg 1 -dup -pop -base64_decode URLAlph -==` - case "base64std": - isURL = false - source = `#pragma version 6 -arg 0 -arg 1 -dup -pop -base64_decode StdAlph -==` - default: - source = fmt.Sprintf(`#pragma version 6 -arg 0 -dup -arg 1 -%s -pop -==`, scenario) - } - args, err := benchmarkB64DecodeGenData(b, source, isURL, msgLen) - if err != nil { - require.NoError(b, err) - return - } - b.ResetTimer() - testB64DecodeEval(b, args) -} - -var b64DecodeMsgLengths = []int{50, 1050, 2050, 3050} - -var b64DecodeScenarios = []string{ - "baseline", - "len", - "store 0\nload 0", - "swap\nswap", - "dup\nb&", - "b~", - "keccak256", - "sha256", - "sha512_256", - "base64url", - "base64std", -} - -func BenchmarkB64DecodeWithComparisons(b *testing.B) { - for _, msgLen := range b64DecodeMsgLengths { - for _, scenario := range b64DecodeScenarios { - b.Run(fmt.Sprintf("%s_%d", scenario, msgLen), func(b *testing.B) { - benchmarkB64Decode(b, scenario, msgLen) - }) - } - } -} diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 3a448b8546..70d3d79ef9 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -250,7 +250,7 @@ var OpSpecs = []OpSpec{ {0x59, "extract_uint16", opExtract16Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5a, "extract_uint32", opExtract32Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5b, "extract_uint64", opExtract64Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, - {0x5c, "base64_decode", opBase64Decode, assembleBase64Decode, disBase64Decode, oneBytes, oneBytes, 6, modeAny, costlyImm(50, "v")}, + {0x5c, "base64_decode", opBase64Decode, assembleBase64Decode, disBase64Decode, oneBytes, oneBytes, 6, modeAny, costlyImm(25, "v")}, {0x60, "balance", opBalance, asmDefault, disDefault, oneInt, oneInt, 2, runModeApplication, opDefault}, {0x60, "balance", opBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault}, From df3f3ec669ba01e9559d7713d7e53176a8ac77b6 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 29 Nov 2021 00:56:24 -0600 Subject: [PATCH 17/22] opdoc extra condition --- data/transactions/logic/fields.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 9779a88d44..20291714ed 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -479,7 +479,7 @@ var base64AlphabetSpecByName base64AlphabetSpecMap type base64AlphabetSpecMap map[string]base64AlphabetSpec func (s base64AlphabetSpecMap) getExtraFor(name string) (extra string) { - // Uses 5 here because ecdsa fields were introduced in 5 + // Uses 5 here because base64_decode fields were introduced in v 6 if s[name].version > 5 { extra = fmt.Sprintf("LogicSigVersion >= %d.", s[name].version) } From 570bbf93177709c991cc4076e69a297357f94d6a Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 29 Nov 2021 23:47:47 -0600 Subject: [PATCH 18/22] revert in attempting to fix docgen --- data/transactions/logic/fields.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 20291714ed..7b9691086e 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -479,8 +479,8 @@ var base64AlphabetSpecByName base64AlphabetSpecMap type base64AlphabetSpecMap map[string]base64AlphabetSpec func (s base64AlphabetSpecMap) getExtraFor(name string) (extra string) { - // Uses 5 here because base64_decode fields were introduced in v 6 - if s[name].version > 5 { + // Uses 6 here because base64_decode fields were introduced in 6 + if s[name].version > 6 { extra = fmt.Sprintf("LogicSigVersion >= %d.", s[name].version) } return From 855754b566695aa2bf2d6c85e2607ab37caed888 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 29 Nov 2021 23:50:51 -0600 Subject: [PATCH 19/22] cost 25 in opcodes doc --- data/transactions/logic/TEAL_opcodes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 45052b0337..49ba82e6ff 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -860,7 +860,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on - Pops: *... stack*, []byte - Pushes: []byte - the base64 decoding of X with the alphabet denoted by V (imm arg). Fail if X is not base64 encoded with alphabet V -- **Cost**: 50 +- **Cost**: 25 - LogicSigVersion >= 6 decodes X using the base64 alphabet V. Specify the alphabet with an immediate arg either as URL and Filename Safe (`URLAlph`) or Standard (`StdAlph`). See RFC 4648 (sections 4 and 5) From 1e823eb8d4b35d988a803f64ef914915a4acaab3 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Mon, 29 Nov 2021 23:55:54 -0600 Subject: [PATCH 20/22] v6 --- data/transactions/logic/fields.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 7b9691086e..37ef079cc5 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -469,8 +469,8 @@ type base64AlphabetSpec struct { } var base64AlphbetSpecs = []base64AlphabetSpec{ - {URLAlph, StackBytes, 5}, - {StdAlph, StackBytes, 5}, + {URLAlph, StackBytes, 6}, + {StdAlph, StackBytes, 6}, } var base64AlphabetSpecByField map[Base64Alphabet]base64AlphabetSpec From f55986f242926e509e5cfd1f1d535f70daed3549 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Thu, 2 Dec 2021 10:04:47 -0600 Subject: [PATCH 21/22] change per reviewer suggestions --- data/transactions/logic/assembler.go | 2 +- data/transactions/logic/eval.go | 4 ++-- data/transactions/logic/eval_test.go | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 0bd93f8f68..56cb84d424 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -1251,7 +1251,7 @@ func assembleBase64Decode(ops *OpStream, spec *OpSpec, args []string) error { alph, ok := base64AlphabetSpecByName[args[0]] if !ok { - return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) + return ops.errorf("%s unknown alphabet: %#v", spec.Name, args[0]) } if alph.version > ops.Version { //nolint:errcheck // we continue to maintain typestack diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index c519b4c085..cc7006f03a 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -4040,7 +4040,7 @@ func base64Decode(encoded []byte, encoding *base64.Encoding) ([]byte, error) { decoded := make([]byte, encoding.DecodedLen(len(encoded))) n, err := encoding.Strict().Decode(decoded, encoded) if err != nil { - n = 0 + return decoded[:0], err } return decoded[:n], err } @@ -4050,7 +4050,7 @@ func opBase64Decode(cx *EvalContext) { alphabetField := Base64Alphabet(cx.program[cx.pc+1]) fs, ok := base64AlphabetSpecByField[alphabetField] if !ok || fs.version > cx.version { - cx.err = fmt.Errorf("invalid base64_decode field %d", alphabetField) + cx.err = fmt.Errorf("invalid base64_decode alphabet %d", alphabetField) return } diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 13767b0335..c374a3022e 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -5017,7 +5017,7 @@ type b64DecodeTestArgs struct { Program []byte } -func testB64DecodeAssembleWithArgs(t *testing.T) []b64DecodeTestArgs { +func b64TestDecodeAssembleWithArgs(t *testing.T) []b64DecodeTestArgs { sourceTmpl := `#pragma version %d arg 0 arg 1 @@ -5046,7 +5046,7 @@ base64_decode %s return args } -func testB64DecodeEval(tb testing.TB, args []b64DecodeTestArgs) { +func b64TestDecodeEval(tb testing.TB, args []b64DecodeTestArgs) { for _, data := range args { var txn transactions.SignedTxn txn.Lsig.Logic = data.Program @@ -5066,6 +5066,6 @@ func testB64DecodeEval(tb testing.TB, args []b64DecodeTestArgs) { func TestOpBase64Decode(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - args := testB64DecodeAssembleWithArgs(t) - testB64DecodeEval(t, args) + args := b64TestDecodeAssembleWithArgs(t) + b64TestDecodeEval(t, args) } From 757daef255b2577f8a5e8c70504a5be7d9262c82 Mon Sep 17 00:00:00 2001 From: Zeph Grunschlag Date: Fri, 3 Dec 2021 14:59:36 -0600 Subject: [PATCH 22/22] per code review --- data/transactions/logic/README.md | 2 +- data/transactions/logic/TEAL_opcodes.md | 6 +++--- data/transactions/logic/assembler.go | 2 +- data/transactions/logic/doc.go | 8 ++++---- data/transactions/logic/opcodes.go | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index c6aa680563..b76869db1b 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -165,6 +165,7 @@ various sizes. | `extract_uint16` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B+2 is larger than the array length, the program fails | | `extract_uint32` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B+4 is larger than the array length, the program fails | | `extract_uint64` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails | +| `base64_decode e` | decode X which was base64-encoded using _encoding alphabet_ E. Fail if X is not base64 encoded with alphabet E | These opcodes take byte-array values that are interpreted as big-endian unsigned integers. For mathematical operators, the @@ -191,7 +192,6 @@ bytes on outputs. | `b==` | A is equals to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} | | `b!=` | A is not equal to B, where A and B are byte-arrays interpreted as big-endian unsigned integers => { 0 or 1} | | `b%` | A modulo B, where A and B are byte-arrays interpreted as big-endian unsigned integers. Fail if B is zero. | -| `base64_decode v` | the base64 decoding of X with the alphabet denoted by V (imm arg). Fail if X is not base64 encoded with alphabet V | These opcodes operate on the bits of byte-array values. The shorter array is interpreted as though left padded with zeros until it is the diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 49ba82e6ff..fe667cc781 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -854,16 +854,16 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on - pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails - LogicSigVersion >= 5 -## base64_decode v +## base64_decode e - Opcode: 0x5c {uint8 alphabet index} - Pops: *... stack*, []byte - Pushes: []byte -- the base64 decoding of X with the alphabet denoted by V (imm arg). Fail if X is not base64 encoded with alphabet V +- decode X which was base64-encoded using _encoding alphabet_ E. Fail if X is not base64 encoded with alphabet E - **Cost**: 25 - LogicSigVersion >= 6 -decodes X using the base64 alphabet V. Specify the alphabet with an immediate arg either as URL and Filename Safe (`URLAlph`) or Standard (`StdAlph`). See RFC 4648 (sections 4 and 5) +decodes X using the base64 encoding alphabet E. Specify the alphabet with an immediate arg either as URL and Filename Safe (`URLAlph`) or Standard (`StdAlph`). See RFC 4648 (sections 4 and 5) ## balance diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 56cb84d424..34151d4c0c 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -1261,7 +1261,7 @@ func assembleBase64Decode(ops *OpStream, spec *OpSpec, args []string) error { val := alph.field ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(uint8(val)) - ops.trace("%s (%s)", alph.field.String(), alph.ftype.String()) + ops.trace("%s (%s)", alph.field, alph.ftype) ops.returns(alph.ftype) return nil } diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 9354eb7076..ff258f01e3 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -133,7 +133,7 @@ var opDocByName = map[string]string{ "extract_uint16": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B+2 is larger than the array length, the program fails", "extract_uint32": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B+4 is larger than the array length, the program fails", "extract_uint64": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails", - "base64_decode": "the base64 decoding of X with the alphabet denoted by V (imm arg). Fail if X is not base64 encoded with alphabet V", + "base64_decode": "decode X which was base64-encoded using _encoding alphabet_ E. Fail if X is not base64 encoded with alphabet E", "balance": "get balance for account A, in microalgos. The balance is observed after the effects of previous transactions in the group, and after the fee for the current transaction is deducted.", "min_balance": "get minimum required balance for account A, in microalgos. Required balance is affected by [ASA](https://developer.algorand.org/docs/features/asa/#assets-overview) and [App](https://developer.algorand.org/docs/features/asc1/stateful/#minimum-balance-requirement-for-a-smart-contract) usage. When creating or opting into an app, the minimum balance grows before the app code runs, therefore the increase is visible there. When deleting or closing out, the minimum balance decreases after the app executes.", @@ -286,7 +286,7 @@ var opDocExtras = map[string]string{ "itxn_begin": "`itxn_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the top-level transaction, and all other fields to zero values.", "itxn_field": "`itxn_field` fails if X is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `itxn_field` also fails if X is an account or asset that does not appear in `txn.Accounts` or `txn.ForeignAssets` of the top-level transaction. (Setting addresses in asset creation are exempted from this requirement.)", "itxn_submit": "`itxn_submit` resets the current transaction so that it can not be resubmitted. A new `itxn_begin` is required to prepare another inner transaction.", - "base64_decode": "decodes X using the base64 alphabet V. Specify the alphabet with an immediate arg either as URL and Filename Safe (`URLAlph`) or Standard (`StdAlph`). See RFC 4648 (sections 4 and 5)", + "base64_decode": "decodes X using the base64 encoding alphabet E. Specify the alphabet with an immediate arg either as URL and Filename Safe (`URLAlph`) or Standard (`StdAlph`). See RFC 4648 (sections 4 and 5)", } // OpDocExtra returns extra documentation text about an op @@ -299,8 +299,8 @@ func OpDocExtra(opName string) string { // opcodes consecutively, even if their opcode values are not. var OpGroups = map[string][]string{ "Arithmetic": {"sha256", "keccak256", "sha512_256", "ed25519verify", "ecdsa_verify", "ecdsa_pk_recover", "ecdsa_pk_decompress", "+", "-", "/", "*", "<", ">", "<=", ">=", "&&", "||", "shl", "shr", "sqrt", "bitlen", "exp", "==", "!=", "!", "len", "itob", "btoi", "%", "|", "&", "^", "~", "mulw", "addw", "divmodw", "expw", "getbit", "setbit", "getbyte", "setbyte", "concat"}, - "Byte Array Slicing": {"substring", "substring3", "extract", "extract3", "extract_uint16", "extract_uint32", "extract_uint64"}, - "Byte Array Arithmetic": {"b+", "b-", "b/", "b*", "b<", "b>", "b<=", "b>=", "b==", "b!=", "b%", "base64_decode"}, + "Byte Array Slicing": {"substring", "substring3", "extract", "extract3", "extract_uint16", "extract_uint32", "extract_uint64", "base64_decode"}, + "Byte Array Arithmetic": {"b+", "b-", "b/", "b*", "b<", "b>", "b<=", "b>=", "b==", "b!=", "b%"}, "Byte Array Logic": {"b|", "b&", "b^", "b~"}, "Loading Values": {"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "bzero", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "args", "txn", "gtxn", "txna", "txnas", "gtxna", "gtxnas", "gtxns", "gtxnsa", "gtxnsas", "global", "load", "loads", "store", "stores", "gload", "gloads", "gaid", "gaids"}, "Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "cover", "uncover", "swap", "select", "assert", "callsub", "retsub"}, diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 70d3d79ef9..11f1b258f7 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -250,7 +250,7 @@ var OpSpecs = []OpSpec{ {0x59, "extract_uint16", opExtract16Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5a, "extract_uint32", opExtract32Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5b, "extract_uint64", opExtract64Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, - {0x5c, "base64_decode", opBase64Decode, assembleBase64Decode, disBase64Decode, oneBytes, oneBytes, 6, modeAny, costlyImm(25, "v")}, + {0x5c, "base64_decode", opBase64Decode, assembleBase64Decode, disBase64Decode, oneBytes, oneBytes, 6, modeAny, costlyImm(25, "e")}, {0x60, "balance", opBalance, asmDefault, disDefault, oneInt, oneInt, 2, runModeApplication, opDefault}, {0x60, "balance", opBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault},