From 69c4254e26b36f7bb4069b104483644ee2960e57 Mon Sep 17 00:00:00 2001 From: Ethan Reesor Date: Thu, 11 Jul 2024 16:22:29 -0500 Subject: [PATCH] Address comments --- pkg/api/v3/types_gen.go | 2 +- pkg/types/encoding/eip712.go | 14 +- protocol/signature.go | 2 +- protocol/signature_eip712.go | 6 + scripts/images/ci-golang/Dockerfile | 2 +- test/cmd/gen-testdata/main.go | 475 ---------------------------- tools/cmd/gen-types/go.go | 44 +-- 7 files changed, 17 insertions(+), 528 deletions(-) diff --git a/pkg/api/v3/types_gen.go b/pkg/api/v3/types_gen.go index 09a3503c4..2128650ea 100644 --- a/pkg/api/v3/types_gen.go +++ b/pkg/api/v3/types_gen.go @@ -7724,7 +7724,7 @@ func init() { }, "RecordRange", "recordRange") encoding.RegisterTypeDefinition(&[]*encoding.TypeField{ - encoding.NewTypeField("type", "uint64"), + encoding.NewTypeField("type", "ServiceType"), encoding.NewTypeField("argument", "string"), }, "ServiceAddress", "serviceAddress") diff --git a/pkg/types/encoding/eip712.go b/pkg/types/encoding/eip712.go index 9cd847c2d..5051ebb01 100644 --- a/pkg/types/encoding/eip712.go +++ b/pkg/types/encoding/eip712.go @@ -42,10 +42,7 @@ var EIP712DomainHash []byte var Eip712Domain = EIP712Domain{ Name: "Accumulate", Version: "1.0.0", - - // Use a fake domain for the moment - ChainId: big.NewInt(1), - // ChainId: big.NewInt(281), + ChainId: big.NewInt(281), } type Eip712Encoder struct { @@ -123,7 +120,6 @@ func mapEnumTypes[T any, R any](f Func[T, R]) (string, map[string]string) { } op, err := f(*enumType) if err != nil { - //fmt.Println("Error calling function with enum:", err) continue } @@ -175,7 +171,7 @@ func FromTypedInterfaceToBytes(v interface{}, typesAliasMap map[string]string) ( return nil, fmt.Errorf("invalid data entry type: %T", vv["type"]) } - b, err := d.hash(vv, t) //encodeData(vv, t, d) + b, err := d.hash(vv, t) if err != nil { return nil, err } @@ -509,7 +505,7 @@ func NewTypeField(n string, tp string) *TypeField { return nil, fmt.Errorf("eip712 field %s", tp) } - b, err := fields.hash(v, tp) //encodeData(m, tp, fields) + b, err := fields.hash(v, tp) if err != nil { return nil, err } @@ -751,9 +747,7 @@ func FromfloatToBytes(value float64) ([]byte, error) { } func hexStringToBytes(hexStr string) ([]byte, error) { - if strings.HasPrefix(hexStr, "0x") { - hexStr = hexStr[2:] - } + hexStr = strings.TrimPrefix(hexStr, "0x") b, err := hex.DecodeString(hexStr) if err != nil { return nil, err diff --git a/protocol/signature.go b/protocol/signature.go index d829f63ae..876d02dc9 100644 --- a/protocol/signature.go +++ b/protocol/signature.go @@ -1210,7 +1210,7 @@ func SignEip712TypedData(sig *Eip712TypedDataSignature, privateKey []byte, outer sig.TransactionHash = txn.Hash() sig.Signature, err = eth.Sign(hash, priv) - return nil + return err } // GetSigner returns Signer. diff --git a/protocol/signature_eip712.go b/protocol/signature_eip712.go index eba58b710..239600b95 100644 --- a/protocol/signature_eip712.go +++ b/protocol/signature_eip712.go @@ -1,3 +1,9 @@ +// Copyright 2024 The Accumulate Authors +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + package protocol import ( diff --git a/scripts/images/ci-golang/Dockerfile b/scripts/images/ci-golang/Dockerfile index be9ae01ee..a61cd896f 100644 --- a/scripts/images/ci-golang/Dockerfile +++ b/scripts/images/ci-golang/Dockerfile @@ -1,4 +1,4 @@ ARG VERSION=1.20 FROM golang:${VERSION} -RUN apt update -y && apt install -y git-lfs \ No newline at end of file +RUN apt update -y && apt install -y git-lfs nodejs npm diff --git a/test/cmd/gen-testdata/main.go b/test/cmd/gen-testdata/main.go index b47833b51..1807266b4 100644 --- a/test/cmd/gen-testdata/main.go +++ b/test/cmd/gen-testdata/main.go @@ -19,14 +19,9 @@ import ( "math/big" "os" "path/filepath" - "reflect" - "regexp" - "strings" - "unicode" "github.com/spf13/cobra" "gitlab.com/accumulatenetwork/accumulate/pkg/client/signing" - "gitlab.com/accumulatenetwork/accumulate/pkg/types/encoding" "gitlab.com/accumulatenetwork/accumulate/pkg/types/messaging" "gitlab.com/accumulatenetwork/accumulate/pkg/url" . "gitlab.com/accumulatenetwork/accumulate/protocol" @@ -57,7 +52,6 @@ var cmdMain = &cobra.Command{ Run: run, } -var GenerateEip712Vectors bool var DataDir string func run(cmd *cobra.Command, args []string) { @@ -81,9 +75,6 @@ func run(cmd *cobra.Command, args []string) { check(ts.Store(file)) - schema := transactionTests(txnTypedDataTestVectors) - check(StoreTransactionSchema("eip712"+file, schema)) - if len(args) > 1 { lts := &sdktest.TestSuite{ Transactions: transactionTests(txnUnsignedEnvelopeTestVectors), @@ -95,27 +86,12 @@ func run(cmd *cobra.Command, args []string) { func main() { cmdMain.Flags().StringVar(&DataDir, "corpus", "corpus", "Data output directory") - cmdMain.Flags().BoolVar(&GenerateEip712Vectors, "typed-data", false, "Generate test vectors for typed data transactions") _ = cmdMain.Execute() } type TCG = sdktest.TestCaseGroup type TC = sdktest.TestCase -func StoreTransactionSchema(file string, tc []*TCG) error { - tsm := make(map[string]*json.RawMessage) - for _, v := range tc { - tsm[v.Name] = &v.Cases[0].JSON - } - - b, err := json.Marshal(tsm) - if err != nil { - return err - } - - return os.WriteFile(file, b, 0755) -} - func transactionTests(txnTest func(*url.URL, TransactionBody) *TC) []*TCG { var txnTests = []*TCG{ {Name: "CreateIdentity", Cases: []*TC{ @@ -293,454 +269,3 @@ func txnUnsignedEnvelopeTestVectors(originUrl *url.URL, body TransactionBody) *T } return sdktest.NewTxnTest(env) } - -// txnTypedDataTestVectors creates an unsigned envelope which is used to create envelope test -// vectors for use by external signers such as the Ledger hardware wallet - -type TypesEntry struct { - Name string `json:"name,omitempty" form:"name" query:"name" validate:"required"` - Type string `json:"type,omitempty" form:"type" query:"type" validate:"required"` -} - -type TypedData struct { - TypeName string - Structs []*TypedData - Types []TypesEntry -} - -var EIP712DomainType = []TypesEntry{ - {Name: "name", Type: "string"}, - {Name: "version", Type: "string"}, - {Name: "chainId", Type: "string"}, -} - -// -//type EIP712Domain struct { -// Name string `json:"name,omitempty" form:"name" query:"name" validate:"required"` -// Version string `json:"version,omitempty" form:"version" query:"version" validate:"required"` -// ChainId string `json:"chainId,omitempty" form:"chainId" query:"chainId" validate:"required"` -//} - -//var EIP712Domain = EIP712Domain{ -// Name: "Accumulate", -// Version: "0.0.7", -// ChainId: 281, -//} - -type TransactionHeaderType struct { - fieldsSet []bool - Principal *url.URL `json:"principal,omitempty" form:"principal" query:"principal" validate:"required"` - Initiator [32]byte `json:"initiator,omitempty" form:"initiator" query:"initiator" validate:"required"` - Memo string `json:"memo,omitempty" form:"memo" query:"memo"` - Metadata []byte `json:"metadata,omitempty" form:"metadata" query:"metadata"` - // Expire expires the transaction as pending once the condition(s) are met. - Expire *ExpireOptions `json:"expire,omitempty" form:"expire" query:"expire"` - // HoldUntil holds the transaction as pending until the condition(s) are met. - HoldUntil *HoldUntilOptions `json:"holdUntil,omitempty" form:"holdUntil" query:"holdUntil"` - // Authorities is a list of additional authorities that must approve the transaction. - Authorities []*url.URL `json:"authorities,omitempty" form:"authorities" query:"authorities"` - extraData []byte -} - -var knownReducedTypes = map[string]string{ - "URL": "string", - "url.URL": "string", - "time.Time": "uint256", - "Time": "uint256", - "big.Int": "uint256", - "Int": "uint256", - "TransactionType": "string", -} - -var knownDataTypes = map[string][]TypesEntry{} - -func mergeStructFields(typeEntries *TypedData, t *TypedData) error { - if t == nil { - return nil - } - //loop through the typed data and merge in the types - for _, v := range t.Types { - haveEntry := false - for _, u := range typeEntries.Types { - if u.Name == v.Name { - if u.Type == v.Type { - haveEntry = true - break - } else { - return fmt.Errorf("duplicate types in %s %s", typeEntries.TypeName, u.Type) - } - } else { - } - } - if !haveEntry { - typeEntries.Types = append(typeEntries.Types, v) - } - } - - for _, u := range t.Structs { - haveEntry := false - for _, v := range typeEntries.Structs { - if v.TypeName == u.TypeName { - haveEntry = true - break - } - } - if !haveEntry { - typeEntries.Structs = append(typeEntries.Structs, u) - } - } - - return nil -} - -type Func[T any, R any] func(T) (R, error) -type Enum interface { - SetEnumValue(id uint64) bool -} - -func typeFields[T any, R any](typeEntries *TypedData, fieldTypeName string, fieldTypeNameTarget string, f Func[T, R]) *TypedData { - if strings.Contains(fieldTypeName, fieldTypeNameTarget) { - enumType := new(T) - enumValue, ok := any(enumType).(Enum) - if !ok { - return nil - } - operationTypeData := TypedData{ - TypeName: fieldTypeName, - } - typeEntries.Structs = append(typeEntries.Structs, &operationTypeData) - for maxType := uint64(0); ; maxType++ { - if !enumValue.SetEnumValue(maxType) { - break - } - op, err := f(*enumType) - if err != nil { - //fmt.Println("Error calling function with enum:", err) - continue - } - - t := reflect.TypeOf(op) - if t.Kind() == reflect.Ptr { - t = t.Elem() // Safely obtaining the element type - } - - name := t.Name() - name = strings.TrimLeft(name, ".") - - t2 := printStructFields(t.Name(), t, "") - mergeStructFields(&operationTypeData, t2) - } - } - return typeEntries -} - -func printStructFields(typeName string, t reflect.Type, indent string) *TypedData { - // Ensure we're dealing with the type, not a pointer to the type - if t.Kind() == reflect.Ptr { - t = t.Elem() - } - re, err := regexp.Compile(`\[.*?\]`) - if err != nil { - panic(fmt.Sprintf("Error compiling regex:", err)) - } - - typeEntries := TypedData{} - - parts := strings.Split(typeName, ".") - typeName = parts[len(parts)-1] - - typeEntries.TypeName = typeName - // Process only if it's a struct - if t.Kind() == reflect.Struct { - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - // Extract JSON tag, falling back to the field name if the tag isn't present - jsonTag := field.Tag.Get("json") - if jsonTag == "" { - //assuming if there is no tag, we are only interested in the type if it appears in a map - continue //jsonTag = field.Name - } - parts := strings.FieldsFunc(jsonTag, func(r rune) bool { - // Define delimiters: whitespace, comma, or end of line. - return unicode.IsSpace(r) || r == ',' || r == '\n' - }) - - //we will at least have 1 parts because jsonTag is not empty - fieldName := parts[0] - - fieldType := field.Type - - var fieldTypeName string - var structName string - if fieldType.Kind() == reflect.Slice { - if fieldType.Elem().Kind() == reflect.Ptr { - fieldTypeName = fieldType.String() //Elem().Elem().Name() - } else { - fieldTypeName = fieldType.String() //Elem().String() - } - - fieldTypeName = strings.ReplaceAll(fieldTypeName, "*", "") - matchBytes := re.FindAllString(fieldTypeName, -1) - - // Replace all occurrences of the pattern with an empty string - fieldTypeName = re.ReplaceAllString(fieldTypeName, "") - - v, ok := knownReducedTypes[fieldTypeName] - if ok { - fieldTypeName = v - } - - structName = fieldTypeName - fieldTypeName += strings.Join(matchBytes, "") - } else if fieldType.Kind() == reflect.Array { - fieldTypeName = fieldType.Elem().Name() - v, ok := knownReducedTypes[fieldTypeName] - if ok { - fieldTypeName = v - } - - structName = fieldTypeName - fieldTypeName = fmt.Sprintf("%s[%d]", fieldTypeName, fieldType.Size()) - } else if fieldType.Kind() == reflect.Ptr { - fieldTypeName = fieldType.Elem().Name() - v, ok := knownReducedTypes[fieldTypeName] - if ok { - fieldTypeName = v - } - } else { - fieldTypeName = fieldType.String() - - v, ok := knownReducedTypes[fieldTypeName] - if ok { - fieldTypeName = v - } - structName = fieldTypeName - } - - parts = strings.Split(structName, ".") - structName = parts[len(parts)-1] - - parts = strings.Split(fieldTypeName, ".") - fieldTypeName = parts[len(parts)-1] - - encoding.RegisterTypeDefinitionResolver("KeyPageOperation", func() error { - _ = typeFields(&typeEntries, structName, "KeyPageOperation", NewKeyPageOperation) - return nil - }) - encoding.RegisterTypeDefinitionResolver("DataEntry", func() error { - _ = typeFields(&typeEntries, structName, "DataEntry", NewDataEntry) - return nil - }) - _ = typeFields(&typeEntries, structName, "KeyPageOperation", NewKeyPageOperation) - - typeEntries.Types = append(typeEntries.Types, TypesEntry{Name: fieldName, Type: fieldTypeName}) - - // If the field is a struct or a pointer to a struct, inspect recursively - if fieldType.Kind() == reflect.Struct || (fieldType.Kind() == reflect.Ptr && fieldType.Elem().Kind() == reflect.Struct) { - s := printStructFields(fieldTypeName, fieldType, indent+" ") - if s != nil { - typeEntries.Structs = append(typeEntries.Structs, s) - } - } - - // If the field is a slice or array, check the element type - if fieldType.Kind() == reflect.Slice || fieldType.Kind() == reflect.Array { - elemType := fieldType.Elem() - - if elemType.Kind() == reflect.Struct || (elemType.Kind() == reflect.Ptr && elemType.Elem().Kind() == reflect.Struct) { - s := printStructFields(elemType.Elem().String(), elemType, indent+" ") - if s != nil { - typeEntries.Structs = append(typeEntries.Structs, s) - } - } - } - } - } - if len(typeEntries.Types) == 0 && len(typeEntries.Structs) == 0 { - return nil - } - return &typeEntries -} - -func parseTypes(txh any) []TypesEntry { - t := reflect.TypeOf(txh) - - types := []TypesEntry{} - //pattern := regexp.MustCompile(`(\[\d*\])?(\*?\w+)`) - pattern := regexp.MustCompile(`(\[\d*\])?(\*?.+)`) - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i) - - // Extract JSON tag, falling back to the field name if the tag isn't present - jsonTag := field.Tag.Get("json") - if jsonTag == "" { - //assuming if there is no tag, we are only interested in the type if it appears in a map - continue //jsonTag = field.Name - } - - parts := strings.FieldsFunc(jsonTag, func(r rune) bool { - // Define delimiters: whitespace, comma, or end of line. - return unicode.IsSpace(r) || r == ',' || r == '\n' - }) - - //we will at least have 1 parts because jsonTag is not empty - name := parts[0] - // - //parts = strings.FieldsFunc(field.Type.String(), func(r rune) bool { - // // Define delimiters: whitespace, comma, or end of line. - // return unicode.IsSpace(r) || r == '*' - //}) - matches := pattern.FindStringSubmatch(field.Type.String()) - - array := "" - typeName := "" - - // Check if the array part was matched - if len(matches) > 1 && matches[1] != "" { - array = matches[1] // The array part, e.g., "[32]" - } - // The type name, possibly including an asterisk for pointers - if len(matches) > 2 { - typeName = matches[2] // The type name, e.g., "*int" - } - - types = append(types, TypesEntry{Name: name, Type: typeName + array}) - //fmt.Printf("Name: %s, Type: %s%s\n", name, typeName, array) - } - - return types -} - -func printTypedData(data *TypedData, typedDataList []*TypedData, typesList *map[string]json.RawMessage) { - if data == nil { - return - } - - for _, s := range typedDataList { - printTypedData(s, nil, typesList) - } - - d, err := json.Marshal(data.Types) - if err != nil { - return - } - - (*typesList)[data.TypeName] = json.RawMessage(d) - return -} - -func mapTypedData(typedDataMap *map[string]*TypedData, typedDataList *[]*TypedData, data *TypedData) { - if data == nil { - return - } - - if typedDataMap == nil { - typedDataMap = new(map[string]*TypedData) - *typedDataMap = make(map[string]*TypedData) - } - - ok := false - for i, s := range data.Structs { - if s == nil { - continue - } - mapTypedData(typedDataMap, typedDataList, s) - _, ok = (*typedDataMap)[s.TypeName] - if !ok { - (*typedDataMap)[s.TypeName] = data.Structs[i] - *typedDataList = append(*typedDataList, data.Structs[i]) - } - } -} - -// TODO: deal with DataEntry and Account, also mising URL -func txnTypedDataTestVectors(originUrl *url.URL, body TransactionBody) *TC { - txh := TransactionHeader{} - _ = txh - - //txnHeaderEntries := parseTypes(txh) - exampleType := reflect.TypeOf(body) - // fmt.Printf("Inspecting ExampleNestedStruct: %s\n", exampleType.Elem().Name()) - - typedData := printStructFields(exampleType.Elem().Name(), exampleType, "") - _ = typedData - //for v := range typedData.Structs { - // printStructs() - //} - //Reflect the transaction - // txnBodyEntries := parseTypes(body) - - tx := Transaction{} - tx.Body = body - tx.Header = txh - td := encoding.TypeDefinition{} - body.MarshalSchema(&td) - err := tx.MarshalSchema(&td) - fmt.Printf("%v", td) - var typedDataList []*TypedData - - mapTypedData(nil, &typedDataList, typedData) - - // eip712 := EIP712Domain{"Accumulate", "0.0.1", "281"} - // d, err := json.Marshal(eip712) - // if err != nil { - // return nil - // } - tc := TC{} - typesList := map[string]json.RawMessage{} - - printTypedData(typedData, typedDataList, &typesList) - - d, err := json.Marshal(typesList) - - if err != nil { - return nil - } - - //primaryType := map[string]json.RawMessage{ - // exampleType.Elem().Name(): json.RawMessage(d), - //} - // - //d, err = json.Marshal(primaryType) - // - //if err != nil { - // return nil - //} - - tc.JSON = d - fmt.Printf("%s\n", d) - - // _ = txnBodyEntries - //_ = txnHeaderEntries - /* - signer := new(signing.Builder) - // In reality this would not work, but *shrug* it's a marshalling test - signer.Type = SignatureTypeEip712TypedData - signer.Url = originUrl - signer.SetPrivateKey(key) - signer.Version = 1 - //provide a deterministic timestamp - signer.SetTimestamp(uint64(1234567890)) - env := new(messaging.Envelope) - txn := new(Transaction) - - env.Transaction = []*Transaction{txn} - txn.Header.Principal = originUrl - txn.Body = body - - if *useSimpleHash { - signer.InitMode = signing.InitWithSimpleHash - } - - sig, err := signer.Initiate(txn) - if err != nil { - panic(err) - } - - env.Signatures = append(env.Signatures, sig) - return sdktest.NewTxnTest(env) - - */ - return &tc -} diff --git a/tools/cmd/gen-types/go.go b/tools/cmd/gen-types/go.go index d1db5eb42..9d158b3ec 100644 --- a/tools/cmd/gen-types/go.go +++ b/tools/cmd/gen-types/go.go @@ -288,74 +288,38 @@ func GoResolveType(field *Field, forNew, ignoreRepeatable bool) string { func GoResolveEip712Type(field *Field, forNew, ignoreRepeatable bool) string { typ := field.Type.GoType() - isPod := true - cast := "undefined" - _ = cast switch code := field.EffectiveMarshalType(); code { - case Int: - typ = "int64" - cast = "int64" - case Uint: - typ = "uint64" - cast = "uint64" - case Bool, String: + case Int, Uint, Bool, String: typ = field.Type.GoType() - cast = typ case Hash: typ = "bytes32" - cast = "string" //bytes are expected to be encoded in hex case Bytes: typ = "bytes" - cast = "string" //bytes are expected to be encoded in hex case Url, TxID: typ = "string" // reduces the Url and transaction id to a string - cast = "string" case Time, Duration: typ = "string" //???? reduces time.Time and time.Duration to a string - cast = "string" case BigInt: typ = "uint256" - cast = "big.Int" case RawJson: typ = "string" - cast = "string" case Float: typ = "float" - cast = "float64" case Any: typ = code.Title() - isPod = false - default: - isPod = false } field.Type.GoType() if field.AsEnum() { typ = "string" //interpret as string types - cast = "string" - isPod = true } ret := "" lcName := typegen.LowerFirstWord(field.Name) - ptr := "" - _ = ptr - if forNew { - ptr = "*" - } - if isPod { - if field.Repeatable && !ignoreRepeatable { - ret = "encoding.NewTypeField(\"" + lcName + "\",\"" + typ + "[]\")" - } else { - ret = "encoding.NewTypeField(\"" + lcName + "\",\"" + typ + "\")" - } + if field.Repeatable && !ignoreRepeatable { + ret = "encoding.NewTypeField(\"" + lcName + "\",\"" + typ + "[]\")" } else { - if field.Repeatable && !ignoreRepeatable { - ret = "encoding.NewTypeField(\"" + lcName + "\",\"" + typ + "[]\")" - } else { - //we're dealing with complex type - ret = "encoding.NewTypeField(\"" + lcName + "\",\"" + typ + "\")" - } + ret = "encoding.NewTypeField(\"" + lcName + "\",\"" + typ + "\")" } return ret }