diff --git a/utils/Felt.go b/utils/Felt.go index 8d00831f..50e0357f 100644 --- a/utils/Felt.go +++ b/utils/Felt.go @@ -1,7 +1,11 @@ package utils import ( + "encoding/hex" + "fmt" "math/big" + "regexp" + "strings" "github.com/NethermindEth/juno/core/felt" ) @@ -41,15 +45,15 @@ func HexToFelt(hex string) (*felt.Felt, error) { // - error: an error if any func HexArrToFelt(hexArr []string) ([]*felt.Felt, error) { - feltArr := make([]*felt.Felt, len(hexArr)) - for i, e := range hexArr { - felt, err := HexToFelt(e) - if err != nil { - return nil, err - } - feltArr[i] = felt - } - return feltArr, nil + feltArr := make([]*felt.Felt, len(hexArr)) + for i, e := range hexArr { + felt, err := HexToFelt(e) + if err != nil { + return nil, err + } + feltArr[i] = felt + } + return feltArr, nil } @@ -87,3 +91,114 @@ func FeltArrToBigIntArr(f []*felt.Felt) []*big.Int { } return bigArr } + +// StringToByteArrFelt converts string to array of Felt objects. +// The returned array of felts will be of the format +// +// [number of felts with 31 characters in length, 31 byte felts..., pending word with max size of 30 bytes, pending words bytes size] +// +// For further explanation, refer the [article] +// +// Parameters: +// +// - s: string/bytearray to convert +// +// Returns: +// +// - []*felt.Felt: the array of felt.Felt objects +// +// - error: an error, if any +// +// [article]: https://docs.starknet.io/architecture-and-concepts/smart-contracts/serialization-of-cairo-types/#serialization_of_byte_arrays +func StringToByteArrFelt(s string) ([]*felt.Felt, error) { + const SHORT_LENGTH = 31 + exp := fmt.Sprintf(".{1,%d}", SHORT_LENGTH) + r := regexp.MustCompile(exp) + + arr := r.FindAllString(s, -1) + if len(arr) == 0 { + return []*felt.Felt{}, fmt.Errorf("invalid string no matches found, s: %s", s) + } + + hexarr := []string{} + var count, size uint64 + + for _, val := range arr { + if len(val) == SHORT_LENGTH { + count += 1 + } else { + size = uint64(len(val)) + } + hexarr = append(hexarr, "0x"+hex.EncodeToString([]byte(val))) + } + + harr, err := HexArrToFelt(hexarr) + if err != nil { + return nil, err + } + + if size == 0 { + harr = append(harr, new(felt.Felt).SetUint64(0)) + } + + harr = append(harr, new(felt.Felt).SetUint64(size)) + return append([]*felt.Felt{new(felt.Felt).SetUint64(count)}, harr...), nil +} + +// ByteArrFeltToString converts array of Felts to string. +// The input array of felts will be of the format +// +// [number of felts with 31 characters in length, 31 byte felts..., pending word with max size of 30 bytes, pending words bytes size] +// +// For further explanation, refer the [article] +// +// Parameters: +// +// - []*felt.Felt: the array of felt.Felt objects +// +// Returns: +// +// - s: string/bytearray +// +// - error: an error, if any +// +// [article]: https://docs.starknet.io/architecture-and-concepts/smart-contracts/serialization-of-cairo-types/#serialization_of_byte_arrays +func ByteArrFeltToString(arr []*felt.Felt) (string, error) { + if len(arr) < 3 { + return "", fmt.Errorf("invalid felt array, require atleast 3 elements in array") + } + + count := FeltToBigInt(arr[0]).Uint64() + var index uint64 + var res []string + for index = 0; index < count; index++ { + f := arr[1+index] + s, err := feltToString(f) + if err != nil { + return "", err + } + res = append(res, s) + } + + pendingWordLength := arr[len(arr)-1] + if pendingWordLength.IsZero() { + return strings.Join(res, ""), nil + } + + pendingWordFelt := arr[1+index] + s, err := feltToString(pendingWordFelt) + if err != nil { + return "", fmt.Errorf("invalid pending word") + } + + res = append(res, s) + return strings.Join(res, ""), nil +} + +func feltToString(f *felt.Felt) (string, error) { + b, err := hex.DecodeString(f.String()[2:]) + if err != nil { + return "", fmt.Errorf("unable to decode to string") + } + return string(b), nil +} diff --git a/utils/Felt_test.go b/utils/Felt_test.go new file mode 100644 index 00000000..db1593ab --- /dev/null +++ b/utils/Felt_test.go @@ -0,0 +1,97 @@ +package utils + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestStringToByteArrFelt(t *testing.T) { + var tests = []struct { + in string + out []string + }{ + { + in: "hello", + out: []string{"0x0", "0x68656c6c6f", "0x5"}, + }, + { + in: "Long string, more than 31 characters.", + out: []string{"0x1", "0x4c6f6e6720737472696e672c206d6f7265207468616e203331206368617261", "0x63746572732e", "0x6"}, + }, + { + in: "Blockchain secure digital asset", + out: []string{"0x1", "0x426c6f636b636861696e20736563757265206469676974616c206173736574", "0x0", "0x0"}, + }, + { + in: "Decentralized applications offer transparency and user control", + out: []string{"0x2", "0x446563656e7472616c697a6564206170706c69636174696f6e73206f666665", "0x72207472616e73706172656e637920616e64207573657220636f6e74726f6c", "0x0", "0x0"}, + }, + { + in: "12345", + out: []string{"0x0", "0x3132333435", "0x5"}, + }, + { + in: "1234567890123456789012345678901", + out: []string{"0x1", "0x31323334353637383930313233343536373839303132333435363738393031", "0x0", "0x0"}, + }, + { + in: "12345678901234567890123456789012", + out: []string{"0x1", "0x31323334353637383930313233343536373839303132333435363738393031", "0x32", "0x1"}, + }, + } + + for _, tc := range tests { + res, err := StringToByteArrFelt(tc.in) + require.NoError(t, err, "error returned from StringToByteArrFelt") + require.Len(t, res, len(tc.out), "invalid conversion: array sizes do not match") + + out, err := HexArrToFelt(tc.out) + require.NoError(t, err, "error returned from HexArrToFelt") + require.Exactly(t, out, res, "invalid conversion: values do not match") + } +} + +func TestByteArrFeltToString(t *testing.T) { + var tests = []struct { + in []string + out string + }{ + { + in: []string{"0x0", "0x68656c6c6f", "0x5"}, + out: "hello", + }, + { + in: []string{"0x1", "0x4c6f6e6720737472696e672c206d6f7265207468616e203331206368617261", "0x63746572732e", "0x6"}, + out: "Long string, more than 31 characters.", + }, + { + in: []string{"0x1", "0x426c6f636b636861696e20736563757265206469676974616c206173736574", "0x0", "0x0"}, + out: "Blockchain secure digital asset", + }, + { + in: []string{"0x2", "0x446563656e7472616c697a6564206170706c69636174696f6e73206f666665", "0x72207472616e73706172656e637920616e64207573657220636f6e74726f6c", "0x0", "0x0"}, + out: "Decentralized applications offer transparency and user control", + }, + { + in: []string{"0x0", "0x3132333435", "0x5"}, + out: "12345", + }, + { + in: []string{"0x1", "0x31323334353637383930313233343536373839303132333435363738393031", "0x0", "0x0"}, + out: "1234567890123456789012345678901", + }, + { + in: []string{"0x1", "0x31323334353637383930313233343536373839303132333435363738393031", "0x32", "0x1"}, + out: "12345678901234567890123456789012", + }, + } + + for _, tc := range tests { + in, err := HexArrToFelt(tc.in) + require.NoError(t, err, "error returned from HexArrToFelt") + res, err := ByteArrFeltToString(in) + require.NoError(t, err, "error returned from ByteArrFeltToString") + require.Equal(t, tc.out, res, "invalid conversion: output does not match") + } +}