Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement: Deprecating use of langspec #366

Merged
merged 23 commits into from
Aug 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions crypto/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ type LogicSigAccount struct {

// MakeLogicSigAccountEscrow creates a new escrow LogicSigAccount. The address
// of this account will be a hash of its program.
// Deprecated: This method is deprecated for not applying basic sanity check over program bytes,
// use `MakeLogicSigAccountEscrowChecked` instead.
func MakeLogicSigAccountEscrow(program []byte, args [][]byte) LogicSigAccount {
return LogicSigAccount{
Lsig: types.LogicSig{
Expand All @@ -198,6 +200,16 @@ func MakeLogicSigAccountEscrow(program []byte, args [][]byte) LogicSigAccount {
}
}

// MakeLogicSigAccountEscrowChecked creates a new escrow LogicSigAccount.
// The address of this account will be a hash of its program.
func MakeLogicSigAccountEscrowChecked(program []byte, args [][]byte) (LogicSigAccount, error) {
lsig, err := MakeLogicSig(program, args, nil, MultisigAccount{})
if err != nil {
return LogicSigAccount{}, err
}
return LogicSigAccount{Lsig: lsig}, nil
}

// MakeLogicSigAccountDelegated creates a new delegated LogicSigAccount. This
// type of LogicSig has the authority to sign transactions on behalf of another
// account, called the delegating account. If the delegating account is a
Expand Down
50 changes: 40 additions & 10 deletions crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ import (
"crypto/rand"
"crypto/sha512"
"encoding/base32"
"encoding/base64"
"encoding/binary"
"fmt"

"golang.org/x/crypto/ed25519"

"github.com/algorand/go-algorand-sdk/encoding/msgpack"
"github.com/algorand/go-algorand-sdk/logic"
"github.com/algorand/go-algorand-sdk/types"
)

Expand Down Expand Up @@ -160,7 +160,7 @@ func SignBytes(sk ed25519.PrivateKey, bytesToSign []byte) (signature []byte, err
return
}

//VerifyBytes verifies that the signature is valid
// VerifyBytes verifies that the signature is valid
func VerifyBytes(pk ed25519.PublicKey, message, signature []byte) bool {
msgParts := [][]byte{bytesPrefix, message}
toBeVerified := bytes.Join(msgParts, nil)
Expand Down Expand Up @@ -454,6 +454,39 @@ func ComputeGroupID(txgroup []types.Transaction) (gid types.Digest, err error) {

/* LogicSig support */

func isAsciiPrintableByte(symbol byte) bool {
isBreakLine := symbol == '\n'
isStdPrintable := symbol >= ' ' && symbol <= '~'
return isBreakLine || isStdPrintable
}

func isAsciiPrintable(program []byte) bool {
for _, b := range program {
if !isAsciiPrintableByte(b) {
return false
}
}
return true
}

// sanityCheckProgram performs heuristic program validation:
// check if passed in bytes are Algorand address or is B64 encoded, rather than Teal bytes
func sanityCheckProgram(program []byte) error {
if len(program) == 0 {
return fmt.Errorf("empty program")
}
if isAsciiPrintable(program) {
if _, err := types.DecodeAddress(string(program)); err == nil {
return fmt.Errorf("requesting program bytes, get Algorand address")
}
if _, err := base64.StdEncoding.DecodeString(string(program)); err == nil {
return fmt.Errorf("program should not be b64 encoded")
}
return fmt.Errorf("program bytes are all ASCII printable characters, not looking like Teal byte code")
}
return nil
}

// VerifyLogicSig verifies that a LogicSig contains a valid program and, if a
// delegated signature is present, that the signature is valid.
//
Expand All @@ -462,7 +495,7 @@ func ComputeGroupID(txgroup []types.Transaction) (gid types.Digest, err error) {
// multsig account). In that case, it should be the address of the delegating
// account.
func VerifyLogicSig(lsig types.LogicSig, singleSigner types.Address) (result bool) {
if err := logic.CheckProgram(lsig.Logic, lsig.Args); err != nil {
if err := sanityCheckProgram(lsig.Logic); err != nil {
return false
}

Expand Down Expand Up @@ -602,20 +635,17 @@ func AddressFromProgram(program []byte) types.Address {

// MakeLogicSig produces a new LogicSig signature.
//
// THIS FUNCTION IS DEPRECATED. It will be removed in v2 of this library. Use
// one of MakeLogicSigAccountEscrow, MakeLogicSigAccountDelegated, or
// Deprecated: THIS FUNCTION IS DEPRECATED.
// It will be removed in v2 of this library.
// Use one of MakeLogicSigAccountEscrow, MakeLogicSigAccountDelegated, or
// MakeLogicSigAccountDelegatedMsig instead.
//
// The function can work in three modes:
// 1. If no sk and ma provided then it returns contract-only LogicSig
// 2. If no ma provides, it returns Sig delegated LogicSig
// 3. If both sk and ma specified the function returns Multisig delegated LogicSig
func MakeLogicSig(program []byte, args [][]byte, sk ed25519.PrivateKey, ma MultisigAccount) (lsig types.LogicSig, err error) {
jasonpaulos marked this conversation as resolved.
Show resolved Hide resolved
if len(program) == 0 {
err = errLsigInvalidProgram
return
}
if err = logic.CheckProgram(program, args); err != nil {
if err = sanityCheckProgram(program); err != nil {
return
}

Expand Down
7 changes: 0 additions & 7 deletions crypto/crypto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,13 +327,6 @@ func TestMakeLogicSigBasic(t *testing.T) {
err = msgpack.Decode(encoded, &lsig1)
require.NoError(t, err)
require.Equal(t, lsig, lsig1)

// check invalid program fails
programMod := make([]byte, len(program))
copy(programMod[:], program)
programMod[0] = 128
lsig, err = MakeLogicSig(programMod, args, sk, pk)
require.Error(t, err)
}

func TestMakeLogicSigSingle(t *testing.T) {
Expand Down
13 changes: 13 additions & 0 deletions logic/logic.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ import (
"github.com/algorand/go-algorand-sdk/types"
)

// Deprecated
type langSpec struct {
EvalMaxVersion int
LogicSigVersion int
Ops []operation
}

// Deprecated
type operation struct {
Opcode int
Name string
Expand All @@ -29,16 +31,23 @@ type operation struct {
Group []string
}

// Deprecated
ahangsu marked this conversation as resolved.
Show resolved Hide resolved
var spec *langSpec

// Deprecated
var opcodes []operation

// CheckProgram performs basic program validation: instruction count and program cost
// Deprecated: Validation relies on metadata (`langspec.json`) that does not accurately represent opcode behavior across program versions.
// The behavior of `CheckProgram` relies on `langspec.json`. Thus, this method is being deprecated.
func CheckProgram(program []byte, args [][]byte) error {
_, _, err := ReadProgram(program, args)
return err
}

// ReadProgram is used to validate a program as well as extract found variables
// Deprecated: Validation relies on metadata (`langspec.json`) that does not accurately represent opcode behavior across program versions.
// The behavior of `ReadProgram` relies on `langspec.json`. Thus, this method is being deprecated.
func ReadProgram(program []byte, args [][]byte) (ints []uint64, byteArrays [][]byte, err error) {
const intcblockOpcode = 32
const bytecblockOpcode = 38
Expand Down Expand Up @@ -138,6 +147,7 @@ func ReadProgram(program []byte, args [][]byte) (ints []uint64, byteArrays [][]b
return
}

// Deprecated
func readIntConstBlock(program []byte, pc int) (size int, ints []uint64, err error) {
size = 1
numInts, bytesUsed := binary.Uvarint(program[pc+size:])
Expand All @@ -163,6 +173,7 @@ func readIntConstBlock(program []byte, pc int) (size int, ints []uint64, err err
return
}

// Deprecated
func readByteConstBlock(program []byte, pc int) (size int, byteArrays [][]byte, err error) {
size = 1
numInts, bytesUsed := binary.Uvarint(program[pc+size:])
Expand Down Expand Up @@ -195,6 +206,7 @@ func readByteConstBlock(program []byte, pc int) (size int, byteArrays [][]byte,
return
}

// Deprecated
func readPushIntOp(program []byte, pc int) (size int, foundInt uint64, err error) {
size = 1
foundInt, bytesUsed := binary.Uvarint(program[pc+size:])
Expand All @@ -207,6 +219,7 @@ func readPushIntOp(program []byte, pc int) (size int, foundInt uint64, err error
return
}

// Deprecated
func readPushByteOp(program []byte, pc int) (size int, byteArray []byte, err error) {
size = 1
itemLen, bytesUsed := binary.Uvarint(program[pc+size:])
Expand Down
34 changes: 33 additions & 1 deletion test/steps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ var txTrace future.DryrunTxnResult
var trace string
var sourceMap logic.SourceMap
var srcMapping map[string]interface{}
var seeminglyProgram []byte
var sanityCheckError error

var assetTestFixture struct {
Creator string
Expand Down Expand Up @@ -387,7 +389,10 @@ func FeatureContext(s *godog.Suite) {
s.Step(`^the resulting source map is the same as the json "([^"]*)"$`, theResultingSourceMapIsTheSameAsTheJson)
s.Step(`^getting the line associated with a pc "([^"]*)" equals "([^"]*)"$`, gettingTheLineAssociatedWithAPcEquals)
s.Step(`^getting the last pc associated with a line "([^"]*)" equals "([^"]*)"$`, gettingTheLastPcAssociatedWithALineEquals)

s.Step(`^a base64 encoded program bytes for heuristic sanity check "([^"]*)"$`, takeB64encodedBytes)
s.Step(`^I start heuristic sanity check over the bytes$`, heuristicCheckOverBytes)
s.Step(`^if the heuristic sanity check throws an error, the error contains "([^"]*)"$`, checkErrorIfMatching)

s.BeforeScenario(func(interface{}) {
stxObj = types.SignedTxn{}
abiMethods = nil
Expand Down Expand Up @@ -2634,3 +2639,30 @@ func theResultingSourceMapIsTheSameAsTheJson(expectedJsonPath string) error {

return nil
}

func takeB64encodedBytes(b64encodedBytes string) error {
var err error
seeminglyProgram, err = base64.StdEncoding.DecodeString(b64encodedBytes)
if err != nil {
return err
}
return nil
}

func heuristicCheckOverBytes() error {
_, sanityCheckError = crypto.MakeLogicSigAccountEscrowChecked(seeminglyProgram, nil)
return nil
}

func checkErrorIfMatching(errMsg string) error {
if len(errMsg) == 0 {
if sanityCheckError != nil {
return fmt.Errorf("expected err message to be empty, but sanity check says %w", sanityCheckError)
}
} else {
if sanityCheckError == nil || !strings.Contains(sanityCheckError.Error(), errMsg) {
return fmt.Errorf("expected err to contain %s, but sanity check error not matching: %w", errMsg, sanityCheckError)
}
}
return nil
}
1 change: 1 addition & 0 deletions test/unit.tags
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
@unit.indexer.logs
@unit.indexer.rekey
@unit.offline
@unit.program_sanity_check
@unit.rekey
@unit.responses
@unit.responses.231
Expand Down