Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add hash #1273

Merged
merged 3 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not directly related to this particular pull request, but if I remember correctly, we had discussed completely prohibiting this import at some stage.

cc @jaekwon

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's okay actualy.

"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)
}
})
}
}
Loading