Skip to content

Commit

Permalink
feat: add hash (#1273)
Browse files Browse the repository at this point in the history
<!-- please provide a detailed description of the changes made in this
pull request. -->

Add hash stdlib (especially adler32) and add encoding for dependency

## relate issue

#1267
  • Loading branch information
notJoon committed Oct 25, 2023
1 parent 338be19 commit f872ca7
Show file tree
Hide file tree
Showing 5 changed files with 336 additions and 2 deletions.
4 changes: 2 additions & 2 deletions gnovm/docs/go-gno-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ Additional native types:
| go/types | TBD |
| go/types/testdata | TBD |
| go/types/testdata/local | TBD |
| hash | TBD |
| hash/adler32 | TBD |
| hash | partial |
| hash/adler32 | full |
| hash/crc32 | TBD |
| hash/crc64 | TBD |
| hash/fnv | TBD |
Expand Down
54 changes: 54 additions & 0 deletions gnovm/stdlibs/encoding/encoding.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package encoding defines interfaces shared by other packages that
// convert data to and from byte-level and textual representations.
// Packages that check for these interfaces include encoding/gob,
// encoding/json, and encoding/xml. As a result, implementing an
// interface once can make a type useful in multiple encodings.
// Standard types that implement these interfaces include time.Time and net.IP.
// The interfaces come in pairs that produce and consume encoded data.
//
// Adding encoding/decoding methods to existing types may constitute a breaking change,
// as they can be used for serialization in communicating with programs
// written with different library versions.
// The policy for packages maintained by the Go project is to only allow
// the addition of marshaling functions if no existing, reasonable marshaling exists.
package encoding

// BinaryMarshaler is the interface implemented by an object that can
// marshal itself into a binary form.
//
// MarshalBinary encodes the receiver into a binary form and returns the result.
type BinaryMarshaler interface {
MarshalBinary() (data []byte, err error)
}

// BinaryUnmarshaler is the interface implemented by an object that can
// unmarshal a binary representation of itself.
//
// UnmarshalBinary must be able to decode the form generated by MarshalBinary.
// UnmarshalBinary must copy the data if it wishes to retain the data
// after returning.
type BinaryUnmarshaler interface {
UnmarshalBinary(data []byte) error
}

// TextMarshaler is the interface implemented by an object that can
// marshal itself into a textual form.
//
// MarshalText encodes the receiver into UTF-8-encoded text and returns the result.
type TextMarshaler interface {
MarshalText() (text []byte, err error)
}

// TextUnmarshaler is the interface implemented by an object that can
// unmarshal a textual representation of itself.
//
// UnmarshalText must be able to decode the form generated by MarshalText.
// UnmarshalText must copy the text if it wishes to retain the text
// after returning.
type TextUnmarshaler interface {
UnmarshalText(text []byte) error
}
135 changes: 135 additions & 0 deletions gnovm/stdlibs/hash/adler32/adler32.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package adler32 implements the Adler-32 checksum.
//
// It is defined in RFC 1950:
//
// Adler-32 is composed of two sums accumulated per byte: s1 is
// the sum of all bytes, s2 is the sum of all s1 values. Both sums
// are done modulo 65521. s1 is initialized to 1, s2 to zero. The
// Adler-32 checksum is stored as s2*65536 + s1 in most-
// significant-byte first (network) order.
package adler32

import (
"errors"
"hash"
)

const (
// mod is the largest prime that is less than 65536.
mod = 65521
// nmax is the largest n such that
// 255 * n * (n+1) / 2 + (n+1) * (mod-1) <= 2^32-1.
// It is mentioned in RFC 1950 (search for "5552").
nmax = 5552
)

// The size of an Adler-32 checksum in bytes.
const Size = 4

// digest represents the partial evaluation of a checksum.
// The low 16 bits are s1, the high 16 bits are s2.
type digest uint32

func (d *digest) Reset() { *d = 1 }

// New returns a new hash.Hash32 computing the Adler-32 checksum. Its
// Sum method will lay the value out in big-endian byte order. The
// returned Hash32 also implements encoding.BinaryMarshaler and
// encoding.BinaryUnmarshaler to marshal and unmarshal the internal
// state of the hash.
func New() hash.Hash32 {
d := new(digest)
d.Reset()
return d
}

func (d *digest) Size() int { return Size }

func (d *digest) BlockSize() int { return 4 }

const (
magic = "adl\x01"
marshaledSize = len(magic) + 4
)

func (d *digest) MarshalBinary() ([]byte, error) {
b := make([]byte, 0, marshaledSize)
b = append(b, magic...)
b = appendUint32(b, uint32(*d))
return b, nil
}

func (d *digest) UnmarshalBinary(b []byte) error {
if len(b) < len(magic) || string(b[:len(magic)]) != magic {
return errors.New("hash/adler32: invalid hash state identifier")
}
if len(b) != marshaledSize {
return errors.New("hash/adler32: invalid hash state size")
}
*d = digest(readUint32(b[len(magic):]))
return nil
}

func appendUint32(b []byte, x uint32) []byte {
a := [4]byte{
byte(x >> 24),
byte(x >> 16),
byte(x >> 8),
byte(x),
}
return append(b, a[:]...)
}

func readUint32(b []byte) uint32 {
_ = b[3]
return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
}

// Add p to the running checksum d.
func update(d digest, p []byte) digest {
s1, s2 := uint32(d&0xffff), uint32(d>>16)
for len(p) > 0 {
var q []byte
if len(p) > nmax {
p, q = p[:nmax], p[nmax:]
}
for len(p) >= 4 {
s1 += uint32(p[0])
s2 += s1
s1 += uint32(p[1])
s2 += s1
s1 += uint32(p[2])
s2 += s1
s1 += uint32(p[3])
s2 += s1
p = p[4:]
}
for _, x := range p {
s1 += uint32(x)
s2 += s1
}
s1 %= mod
s2 %= mod
p = q
}
return digest(s2<<16 | s1)
}

func (d *digest) Write(p []byte) (nn int, err error) {
*d = update(*d, p)
return len(p), nil
}

func (d *digest) Sum32() uint32 { return uint32(*d) }

func (d *digest) Sum(in []byte) []byte {
s := uint32(*d)
return append(in, byte(s>>24), byte(s>>16), byte(s>>8), byte(s))
}

// Checksum returns the Adler-32 checksum of data.
func Checksum(data []byte) uint32 { return uint32(update(1, data)) }
58 changes: 58 additions & 0 deletions gnovm/stdlibs/hash/hash.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package hash provides interfaces for hash functions.
package hash

import "io"

// Hash is the common interface implemented by all hash functions.
//
// Hash implementations in the standard library (e.g. hash/crc32 and
// crypto/sha256) implement the encoding.BinaryMarshaler and
// encoding.BinaryUnmarshaler interfaces. Marshaling a hash implementation
// allows its internal state to be saved and used for additional processing
// later, without having to re-write the data previously written to the hash.
// The hash state may contain portions of the input in its original form,
// which users are expected to handle for any possible security implications.
//
// Compatibility: Any future changes to hash or crypto packages will endeavor
// to maintain compatibility with state encoded using previous versions.
// That is, any released versions of the packages should be able to
// decode data written with any previously released version,
// subject to issues such as security fixes.
// See the Go compatibility document for background: https://golang.org/doc/go1compat
type Hash interface {
// Write (via the embedded io.Writer interface) adds more data to the running hash.
// It never returns an error.
io.Writer

// Sum appends the current hash to b and returns the resulting slice.
// It does not change the underlying hash state.
Sum(b []byte) []byte

// Reset resets the Hash to its initial state.
Reset()

// Size returns the number of bytes Sum will return.
Size() int

// BlockSize returns the hash's underlying block size.
// The Write method must be able to accept any amount
// of data, but it may operate more efficiently if all writes
// are a multiple of the block size.
BlockSize() int
}

// Hash32 is the common interface implemented by all 32-bit hash functions.
type Hash32 interface {
Hash
Sum32() uint32
}

// Hash64 is the common interface implemented by all 64-bit hash functions.
type Hash64 interface {
Hash
Sum64() uint64
}
87 changes: 87 additions & 0 deletions gnovm/stdlibs/hash/marshal_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Test that the hashes in the standard library implement
// BinaryMarshaler, BinaryUnmarshaler,
// and lock in the current representations.

package hash

import (
"bytes"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding"
"encoding/hex"
"hash"
"hash/adler32"
"testing"
)

func fromHex(s string) []byte {
b, err := hex.DecodeString(s)
if err != nil {
panic(err)
}
return b
}

var marshalTests = []struct {
name string
new func() hash.Hash
golden []byte
}{
{"adler32", func() hash.Hash { return adler32.New() }, fromHex("61646c01460a789d")},
}

func TestMarshalHash(t *testing.T) {
for _, tt := range marshalTests {
t.Run(tt.name, func(t *testing.T) {
buf := make([]byte, 256)
for i := range buf {
buf[i] = byte(i)
}

h := tt.new()
h.Write(buf[:256])
sum := h.Sum(nil)

h2 := tt.new()
h3 := tt.new()
const split = 249
for i := 0; i < split; i++ {
h2.Write(buf[i : i+1])
}
h2m, ok := h2.(encoding.BinaryMarshaler)
if !ok {
t.Fatalf("Hash does not implement MarshalBinary")
}
enc, err := h2m.MarshalBinary()
if err != nil {
t.Fatalf("MarshalBinary: %v", err)
}
if !bytes.Equal(enc, tt.golden) {
t.Errorf("MarshalBinary = %x, want %x", enc, tt.golden)
}
h3u, ok := h3.(encoding.BinaryUnmarshaler)
if !ok {
t.Fatalf("Hash does not implement UnmarshalBinary")
}
if err := h3u.UnmarshalBinary(enc); err != nil {
t.Fatalf("UnmarshalBinary: %v", err)
}
h2.Write(buf[split:])
h3.Write(buf[split:])
sum2 := h2.Sum(nil)
sum3 := h3.Sum(nil)
if !bytes.Equal(sum2, sum) {
t.Fatalf("Sum after MarshalBinary = %x, want %x", sum2, sum)
}
if !bytes.Equal(sum3, sum) {
t.Fatalf("Sum after UnmarshalBinary = %x, want %x", sum3, sum)
}
})
}
}

0 comments on commit f872ca7

Please sign in to comment.