diff --git a/typed2/typed copy.go b/typed2/typed copy.go deleted file mode 100644 index 88de69bf..00000000 --- a/typed2/typed copy.go +++ /dev/null @@ -1,239 +0,0 @@ -package typed2 - -import ( - "bytes" - "encoding/hex" - "fmt" - "math/big" - "regexp" - - "github.com/NethermindEth/juno/core/felt" - "github.com/NethermindEth/starknet.go/curve" - "github.com/NethermindEth/starknet.go/utils" -) - -type TypedData struct { - Types map[string]TypeDef - PrimaryType string - Domain Domain - Message TypedMessage -} - -type Domain struct { - Name string - Version string - ChainId string -} - -type TypeDef struct { - Encoding *big.Int - Definitions []Definition -} - -type Definition struct { - Name string - Type string -} - -type TypedMessage interface { - FmtDefinitionEncoding(string) []*big.Int -} - -// FmtDefinitionEncoding formats the definition (standard Starknet Domain) encoding. -// -// Parameters: -// - field: the field to format the encoding for -// Returns: -// - fmtEnc: a slice of big integers -func (dm Domain) FmtDefinitionEncoding(field string) (fmtEnc []*big.Int) { - processStrToBig := func(fieldVal string) { - feltVal := strToFelt(fieldVal) - bigInt := utils.FeltToBigInt(feltVal) - fmtEnc = append(fmtEnc, bigInt) - } - - switch field { - case "name": - processStrToBig(dm.Name) - case "version": - processStrToBig(dm.Version) - case "chainId": - processStrToBig(dm.ChainId) - } - return fmtEnc -} - -// strToFelt converts a string (decimal, hexadecimal or UTF8 charset) to a *felt.Felt. -// -// Parameters: -// - str: the string to convert to a *felt.Felt -// Returns: -// - *felt.Felt: a *felt.Felt with the value of str -func strToFelt(str string) *felt.Felt { - var f = new(felt.Felt) - asciiRegexp := regexp.MustCompile(`^([[:graph:]]|[[:space:]]){1,31}$`) - - if b, ok := new(big.Int).SetString(str, 0); ok { - f.SetBytes(b.Bytes()) - return f - } - // TODO: revisit conversation on seperate 'ShortString' conversion - if asciiRegexp.MatchString(str) { - hexStr := hex.EncodeToString([]byte(str)) - if b, ok := new(big.Int).SetString(hexStr, 16); ok { - f.SetBytes(b.Bytes()) - return f - } - } - - return f -} - -// NewTypedData initializes a new TypedData object with the given types, primary type, and domain -// for interacting and signing in accordance with https://github.com/0xs34n/starknet.js/tree/develop/src/utils/typedData -// If the primary type is invalid, it returns an error with the message "invalid primary type: {pType}". -// If there is an error encoding the type hash, it returns an error with the message "error encoding type hash: {enc.String()} {err}". -// -// Parameters: -// - types: a map[string]TypeDef representing the types associated with their names. -// - pType: a string representing the primary type. -// - dom: a Domain representing the domain. -// Returns: -// - td: a TypedData object -// - err: an error if any -func NewTypedData(types map[string]TypeDef, pType string, dom Domain) (td TypedData, err error) { - td = TypedData{ - Types: types, - PrimaryType: pType, - Domain: dom, - } - if _, ok := td.Types[pType]; !ok { - return td, fmt.Errorf("invalid primary type: %s", pType) - } - - for k, v := range td.Types { - enc, err := td.GetTypeHash(k) - if err != nil { - return td, fmt.Errorf("error encoding type hash: %s %w", enc.String(), err) - } - v.Encoding = enc - td.Types[k] = v - } - return td, nil -} - -// GetMessageHash calculates the hash of a typed message for a given account using the StarkCurve. -// (ref: https://github.com/0xs34n/starknet.js/blob/767021a203ac0b9cdb282eb6d63b33bfd7614858/src/utils/typedData/index.ts#L166) -// -// Parameters: -// - account: A pointer to a big.Int representing the account. -// - msg: A TypedMessage object representing the message. -// Returns: -// - hash: A pointer to a big.Int representing the calculated hash. -func (td TypedData) GetMessageHash(account *big.Int, msg TypedMessage) (hash *big.Int) { - elements := []*big.Int{utils.UTF8StrToBig("StarkNet Message")} - - domEnc := td.GetTypedMessageHash("StarkNetDomain", td.Domain) - - elements = append(elements, domEnc) - elements = append(elements, account) - - msgEnc := td.GetTypedMessageHash(td.PrimaryType, msg) - - elements = append(elements, msgEnc) - - return curve.ComputeHashOnElements(elements) -} - -// GetTypedMessageHash calculates the hash of a typed message using the provided StarkCurve. -// -// Parameters: -// - inType: the type of the message -// - msg: the typed message -// -// Returns: -// - hash: the calculated hash -func (td TypedData) GetTypedMessageHash(inType string, msg TypedMessage) (hash *big.Int) { - prim := td.Types[inType] - elements := []*big.Int{prim.Encoding} - - for _, def := range prim.Definitions { - if def.Type == "felt" { - fmtDefinitions := msg.FmtDefinitionEncoding(def.Name) - elements = append(elements, fmtDefinitions...) - continue - } - - innerElements := []*big.Int{} - encType := td.Types[def.Type] - innerElements = append(innerElements, encType.Encoding) - fmtDefinitions := msg.FmtDefinitionEncoding(def.Name) - innerElements = append(innerElements, fmtDefinitions...) - innerElements = append(innerElements, big.NewInt(int64(len(innerElements)))) - - innerHash := curve.HashPedersenElements(innerElements) - elements = append(elements, innerHash) - } - - return curve.ComputeHashOnElements(elements) -} - -// GetTypeHash returns the hash of the given type. -// -// Parameters: -// - inType: the type to hash -// Returns: -// - ret: the hash of the given type -// - err: any error if any -func (td TypedData) GetTypeHash(inType string) (ret *big.Int, err error) { - enc, err := td.EncodeType(inType) - if err != nil { - return ret, err - } - return utils.GetSelectorFromName(enc), nil -} - -// EncodeType encodes the given inType using the TypedData struct. -// -// Parameters: -// - inType: the type to encode -// Returns: -// - enc: the encoded type -// - err: any error if any -func (td TypedData) EncodeType(inType string) (enc string, err error) { - var typeDefs TypeDef - var ok bool - if typeDefs, ok = td.Types[inType]; !ok { - return enc, fmt.Errorf("can't parse type %s from types %v", inType, td.Types) - } - var buf bytes.Buffer - customTypes := make(map[string]TypeDef) - buf.WriteString(inType) - buf.WriteString("(") - for i, def := range typeDefs.Definitions { - if def.Type != "felt" { - var customTypeDef TypeDef - if customTypeDef, ok = td.Types[def.Type]; !ok { - return enc, fmt.Errorf("can't parse type %s from types %v", def.Type, td.Types) - } - customTypes[def.Type] = customTypeDef - } - buf.WriteString(fmt.Sprintf("%s:%s", def.Name, def.Type)) - if i != (len(typeDefs.Definitions) - 1) { - buf.WriteString(",") - } - } - buf.WriteString(")") - - for customTypeName, customType := range customTypes { - buf.WriteString(fmt.Sprintf("%s(", customTypeName)) - for i, def := range customType.Definitions { - buf.WriteString(fmt.Sprintf("%s:%s", def.Name, def.Type)) - if i != (len(customType.Definitions) - 1) { - buf.WriteString(",") - } - } - buf.WriteString(")") - } - return buf.String(), nil -} diff --git a/typed2/typed_test.go b/typed2/typed_test.go deleted file mode 100644 index 0c0e7a1c..00000000 --- a/typed2/typed_test.go +++ /dev/null @@ -1,280 +0,0 @@ -package typed2 - -import ( - "fmt" - "math/big" - "testing" - - "github.com/NethermindEth/starknet.go/utils" - "github.com/stretchr/testify/require" -) - -type Mail struct { - From Person - To Person - Contents string -} - -type Person struct { - Name string - Wallet string -} - -// FmtDefinitionEncoding formats the encoding for the given field in the Mail struct. -// -// Parameters: -// - field: the field to format the encoding for -// Returns: -// - fmtEnc: a slice of big integers -func (mail Mail) FmtDefinitionEncoding(field string) (fmtEnc []*big.Int) { - if field == "from" { - fmtEnc = append(fmtEnc, utils.UTF8StrToBig(mail.From.Name)) - fmtEnc = append(fmtEnc, utils.HexToBN(mail.From.Wallet)) - } else if field == "to" { - fmtEnc = append(fmtEnc, utils.UTF8StrToBig(mail.To.Name)) - fmtEnc = append(fmtEnc, utils.HexToBN(mail.To.Wallet)) - } else if field == "contents" { - fmtEnc = append(fmtEnc, utils.UTF8StrToBig(mail.Contents)) - } - return fmtEnc -} - -// MockTypedData generates a TypedData object for testing purposes. -// It creates example types and initializes a Domain object. Then it uses the example types and the domain to create a new TypedData object. -// The function returns the generated TypedData object. -// -// Parameters: -// -// none -// -// Returns: -// - ttd: the generated TypedData object -func MockTypedData() (ttd TypedData, err error) { - exampleTypes := make(map[string]TypeDef) - domDefs := []Definition{{"name", "felt"}, {"version", "felt"}, {"chainId", "felt"}} - exampleTypes["StarkNetDomain"] = TypeDef{Definitions: domDefs} - mailDefs := []Definition{{"from", "Person"}, {"to", "Person"}, {"contents", "felt"}} - exampleTypes["Mail"] = TypeDef{Definitions: mailDefs} - persDefs := []Definition{{"name", "felt"}, {"wallet", "felt"}} - exampleTypes["Person"] = TypeDef{Definitions: persDefs} - - dm := Domain{ - Name: "StarkNet Mail", - Version: "1", - ChainId: "1", - } - - ttd, err = NewTypedData(exampleTypes, "Mail", dm) - if err != nil { - return TypedData{}, err - } - return ttd, err -} - -// TestGeneral_GetMessageHash tests the GetMessageHash function. -// -// It creates a mock TypedData and sets up a test case for hashing a mail message. -// The mail message contains information about the sender and recipient, as well as the contents of the message. -// The function then calls the GetMessageHash function with the necessary parameters to calculate the message hash. -// If an error occurs during the hashing process, an error is reported using the t.Errorf function. -// The expected hash value is compared with the actual hash value returned by the function. -// If the values do not match, an error is reported using the t.Errorf function. -// -// Parameters: -// - t: a testing.T object that provides methods for testing functions -// Returns: -// - None -func TestGeneral_GetMessageHash(t *testing.T) { - ttd, err := MockTypedData() - require.NoError(t, err) - - mail := Mail{ - From: Person{ - Name: "Cow", - Wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", - }, - To: Person{ - Name: "Bob", - Wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", - }, - Contents: "Hello, Bob!", - } - - hash := ttd.GetMessageHash(utils.HexToBN("0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826"), mail) - - exp := "0x6fcff244f63e38b9d88b9e3378d44757710d1b244282b435cb472053c8d78d0" - require.Equal(t, exp, utils.BigToHex(hash)) -} - -// BenchmarkGetMessageHash is a benchmark function for testing the GetMessageHash function. -// -// It tests the performance of the GetMessageHash function by running it with different input sizes. -// The input size is determined by the bit length of the address parameter, which is converted from -// a hexadecimal string to a big integer using the HexToBN function from the utils package. -// -// Parameters: -// - b: a testing.B object that provides methods for benchmarking the function -// Returns: -// -// none -func BenchmarkGetMessageHash(b *testing.B) { - ttd, err := MockTypedData() - require.NoError(b, err) - - mail := Mail{ - From: Person{ - Name: "Cow", - Wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", - }, - To: Person{ - Name: "Bob", - Wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", - }, - Contents: "Hello, Bob!", - } - addr := utils.HexToBN("0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826") - b.Run(fmt.Sprintf("input_size_%d", addr.BitLen()), func(b *testing.B) { - result := ttd.GetMessageHash(addr, mail) - require.NotEmpty(b, result) - }) -} - -// TestGeneral_GetDomainHash tests the GetDomainHash function. -// It creates a mock TypedData object and generates the hash of a typed message using the Starknet domain and curve. -// If there is an error during the hashing process, it logs the error. -// It then compares the generated hash with the expected hash and logs an error if they do not match. -// -// Parameters: -// - t: a testing.T object that provides methods for testing functions -// Returns: -// -// none -func TestGeneral_GetDomainHash(t *testing.T) { - ttd, err := MockTypedData() - require.NoError(t, err) - - hash := ttd.GetTypedMessageHash("StarkNetDomain", ttd.Domain) - - exp := "0x54833b121883a3e3aebff48ec08a962f5742e5f7b973469c1f8f4f55d470b07" - require.Equal(t, exp, utils.BigToHex(hash)) -} - -// TestGeneral_GetTypedMessageHash is a unit test for the GetTypedMessageHash function -// equivalent of get struct hash. -// -// It tests the generation of a typed message hash for a given mail object using a specific curve. -// The function expects the mail object to have a "From" field of type Person, a "To" field of type Person, -// and a "Contents" field of type string. It returns the generated hash as a byte array and an error object. -// -// Parameters: -// - t: a testing.T object that provides methods for testing functions -// Returns: -// -// none -func TestGeneral_GetTypedMessageHash(t *testing.T) { - ttd, err := MockTypedData() - require.NoError(t, err) - - mail := Mail{ - From: Person{ - Name: "Cow", - Wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", - }, - To: Person{ - Name: "Bob", - Wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", - }, - Contents: "Hello, Bob!", - } - - hash := ttd.GetTypedMessageHash("Mail", mail) - - exp := "0x4758f1ed5e7503120c228cbcaba626f61514559e9ef5ed653b0b885e0f38aec" - require.Equal(t, exp, utils.BigToHex(hash)) -} - -// TestGeneral_GetTypeHash tests the GetTypeHash function. -// -// It tests the GetTypeHash function by calling it with different input values -// and comparing the result with expected values. It also checks that the -// encoding of the types matches the expected values. -// -// Parameters: -// - t: The testing.T object used for reporting test failures and logging test output -// Returns: -// -// none -func TestGeneral_GetTypeHash(t *testing.T) { - require := require.New(t) - - ttd, err := MockTypedData() - require.NoError(err) - - hash, err := ttd.GetTypeHash("StarkNetDomain") - require.NoError(err) - - exp := "0x1bfc207425a47a5dfa1a50a4f5241203f50624ca5fdf5e18755765416b8e288" - require.Equal(exp, utils.BigToHex(hash)) - - enc := ttd.Types["StarkNetDomain"] - require.Equal(exp, utils.BigToHex(enc.Encoding)) - - pHash, err := ttd.GetTypeHash("Person") - require.NoError(err) - - exp = "0x2896dbe4b96a67110f454c01e5336edc5bbc3635537efd690f122f4809cc855" - require.Equal(exp, utils.BigToHex(pHash)) - - enc = ttd.Types["Person"] - require.Equal(exp, utils.BigToHex(enc.Encoding)) -} - -// TestGeneral_GetSelectorFromName tests the GetSelectorFromName function. -// -// It checks if the GetSelectorFromName function returns the expected values -// for different input names. -// The expected values are hard-coded and compared against the actual values. -// If any of the actual values do not match the expected values, an error is -// reported. -// -// Parameters: -// - t: The testing.T object used for reporting test failures and logging test output -// Returns: -// -// none -func TestGeneral_GetSelectorFromName(t *testing.T) { - sel1 := utils.BigToHex(utils.GetSelectorFromName("initialize")) - sel2 := utils.BigToHex(utils.GetSelectorFromName("mint")) - sel3 := utils.BigToHex(utils.GetSelectorFromName("test")) - - exp1 := "0x79dc0da7c54b95f10aa182ad0a46400db63156920adb65eca2654c0945a463" - exp2 := "0x2f0b3c5710379609eb5495f1ecd348cb28167711b73609fe565a72734550354" - exp3 := "0x22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658" - - if sel1 != exp1 || sel2 != exp2 || sel3 != exp3 { - t.Errorf("invalid Keccak256 encoding: %v %v %v\n", sel1, sel2, sel3) - } -} - -// TestGeneral_EncodeType tests the EncodeType function. -// -// It creates a mock typed data and calls the EncodeType method with the -// parameter "Mail". It checks if the returned encoding matches the expected -// encoding. If there is an error during the encoding process, it fails the -// test. -// -// Parameters: -// - t: The testing.T object used for reporting test failures and logging test output -// Returns: -// -// none -func TestGeneral_EncodeType(t *testing.T) { - ttd, err := MockTypedData() - require.NoError(t, err) - - enc, err := ttd.EncodeType("Mail") - require.NoError(t, err) - - exp := "Mail(from:Person,to:Person,contents:felt)Person(name:felt,wallet:felt)" - require.Equal(t, exp, enc) -} diff --git a/typedData/typedData_test.go b/typedData/typedData_test.go index 997bea5a..60e87929 100644 --- a/typedData/typedData_test.go +++ b/typedData/typedData_test.go @@ -12,16 +12,62 @@ import ( ) type Mail struct { - From Person - To Person - Contents string + From Person `json:"from"` + To Person `json:"to"` + Contents string `json:"contents"` } type Person struct { - Name string - Wallet string + Name string `json:"name"` + Wallet string `json:"wallet"` } +var types = []TypeDefinition{ + { + Name: "StarkNetDomain", + Parameters: []TypeParameter{ + {Name: "name", Type: "felt"}, + {Name: "version", Type: "felt"}, + {Name: "chainId", Type: "felt"}, + }, + }, + { + Name: "Mail", + Parameters: []TypeParameter{ + {Name: "from", Type: "Person"}, + {Name: "to", Type: "Person"}, + {Name: "contents", Type: "felt"}, + }, + }, + { + Name: "Person", + Parameters: []TypeParameter{ + {Name: "name", Type: "felt"}, + {Name: "wallet", Type: "felt"}, + }, + }, +} + +var dm = Domain{ + Name: "StarkNet Mail", + Version: "1", + ChainId: "1", +} + +var message = ` + { + "from": { + "name": "Cow", + "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" + }, + "to": { + "name": "Bob", + "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" + }, + "contents": "Hello, Bob!" +} +` + // FmtDefinitionEncoding formats the encoding for the given field in the Mail struct. // // Parameters: @@ -52,52 +98,6 @@ func (mail Mail) FmtDefinitionEncoding(field string) (fmtEnc []*big.Int) { // Returns: // - ttd: the generated TypedData object func MockTypedData() (ttd TypedData, err error) { - types := []TypeDefinition{ - { - Name: "StarkNetDomain", - Parameters: []TypeParameter{ - {Name: "name", Type: "felt"}, - {Name: "version", Type: "felt"}, - {Name: "chainId", Type: "felt"}, - }, - }, - { - Name: "Mail", - Parameters: []TypeParameter{ - {Name: "from", Type: "Person"}, - {Name: "to", Type: "Person"}, - {Name: "contents", Type: "felt"}, - }, - }, - { - Name: "Person", - Parameters: []TypeParameter{ - {Name: "name", Type: "felt"}, - {Name: "wallet", Type: "felt"}, - }, - }, - } - - dm := Domain{ - Name: "StarkNet Mail", - Version: "1", - ChainId: "1", - } - - message := ` - { - "from": { - "name": "Cow", - "wallet": "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" - }, - "to": { - "name": "Bob", - "wallet": "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" - }, - "contents": "Hello, Bob!" - } - ` - ttd, err = NewTypedData(types, "Mail", dm, []byte(message)) if err != nil { return TypedData{}, err @@ -114,6 +114,31 @@ func TestGeneral_Unmarshal(t *testing.T) { require.NoError(t, err) } +func TestGeneral_CreateMessageWithTypes(t *testing.T) { + ttd1, err := NewTypedData(types, "Mail", dm, []byte(message)) + require.NoError(t, err) + + mail := Mail{ + From: Person{ + Name: "Cow", + Wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826", + }, + To: Person{ + Name: "Bob", + Wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB", + }, + Contents: "Hello, Bob!", + } + + bytes, err := json.Marshal(mail) + require.NoError(t, err) + + ttd2, err := NewTypedData(types, "Mail", dm, bytes) + require.NoError(t, err) + + require.EqualValues(t, ttd1, ttd2) +} + // TestGeneral_GetMessageHash tests the GetMessageHash function. // // It creates a mock TypedData and sets up a test case for hashing a mail message.