Skip to content

Commit

Permalink
Faster hash arshaling methods
Browse files Browse the repository at this point in the history
  • Loading branch information
cristaloleg committed May 27, 2024
1 parent 9979590 commit 1249fe1
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 6 deletions.
27 changes: 21 additions & 6 deletions hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,25 @@ package header
import (
"encoding/hex"
"fmt"
"strings"
)

// Hash represents cryptographic hash and provides basic serialization functions.
type Hash []byte

// String implements fmt.Stringer interface.
func (h Hash) String() string {
return strings.ToUpper(hex.EncodeToString(h))
jbz := make([]byte, hex.EncodedLen(len(h)))
hex.Encode(jbz, h)
hexToUpper(jbz)
return string(jbz)
}

// MarshalJSON serializes Hash into valid JSON.
func (h Hash) MarshalJSON() ([]byte, error) {
s := strings.ToUpper(hex.EncodeToString(h))
jbz := make([]byte, len(s)+2)
jbz := make([]byte, 2+hex.EncodedLen(len(h)))
jbz[0] = '"'
copy(jbz[1:], s)
hex.Encode(jbz[1:], h)
hexToUpper(jbz)
jbz[len(jbz)-1] = '"'
return jbz, nil
}
Expand All @@ -29,10 +31,23 @@ func (h *Hash) UnmarshalJSON(data []byte) error {
if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' {
return fmt.Errorf("invalid hex string: %s", data)
}
bz2, err := hex.DecodeString(string(data[1 : len(data)-1]))

bz2 := make([]byte, hex.DecodedLen(len(data)-2))
_, err := hex.Decode(bz2, data[1:len(data)-1])
if err != nil {
return err
}
*h = bz2
return nil
}

// because we encode hex (alphabet: 0-9a-f) we can do this inplace.
func hexToUpper(b []byte) {
for i := 0; i < len(b); i++ {
c := b[i]
if 'a' <= c && c <= 'z' {
c -= 'a' - 'A'
}
b[i] = c
}
}
67 changes: 67 additions & 0 deletions hash_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package header_test

import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"testing"

"github.com/celestiaorg/go-header"
"github.com/stretchr/testify/require"
)

func TestHash(t *testing.T) {
h := randHash()

buf, err := h.MarshalJSON()
require.NoError(t, err)

var h2 header.Hash
err = h2.UnmarshalJSON(buf)
require.NoError(t, err)

require.Equal(t, h.String(), h2.String())
}

func BenchmarkHashMarshaling(b *testing.B) {
h := randHash()

golden, err := h.MarshalJSON()
require.NoError(b, err)

b.ResetTimer()

b.Run("String", func(b *testing.B) {
wantSize := hex.EncodedLen(len(h))

for i := 0; i < b.N; i++ {
ln := len(h.String())
require.Equal(b, ln, wantSize)
}
})

b.Run("Marshal", func(b *testing.B) {
for i := 0; i < b.N; i++ {
buf, err := h.MarshalJSON()
require.NoError(b, err)
require.NotZero(b, buf)
}
})

b.Run("Unmarshal", func(b *testing.B) {
var h2 header.Hash

for i := 0; i < b.N; i++ {
err := h2.UnmarshalJSON(golden)
require.NoError(b, err)
}
})
}

func randHash() header.Hash {
var buf [sha256.Size]byte
if _, err := rand.Read(buf[:]); err != nil {
panic(err)
}
return header.Hash(buf[:])
}

0 comments on commit 1249fe1

Please sign in to comment.