Skip to content

Commit

Permalink
Adding support for Byte array serialization (NethermindEth#583)
Browse files Browse the repository at this point in the history
* implement helper for byte-array serialisation
  • Loading branch information
krishnateja262 authored Jul 23, 2024
1 parent 20b9a51 commit 20e049b
Show file tree
Hide file tree
Showing 2 changed files with 221 additions and 9 deletions.
133 changes: 124 additions & 9 deletions utils/Felt.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package utils

import (
"encoding/hex"
"fmt"
"math/big"
"regexp"
"strings"

"github.com/NethermindEth/juno/core/felt"
)
Expand Down Expand Up @@ -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

}

Expand Down Expand Up @@ -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
}
97 changes: 97 additions & 0 deletions utils/Felt_test.go
Original file line number Diff line number Diff line change
@@ -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")
}
}

0 comments on commit 20e049b

Please sign in to comment.