forked from TrueCloudLab/frostfs-api-go
initial
This commit is contained in:
commit
1cf33e5ffd
87 changed files with 29835 additions and 0 deletions
98
hash/hash.go
Normal file
98
hash/hash.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
package hash
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/mr-tron/base58"
|
||||
"github.com/nspcc-dev/neofs-proto/internal"
|
||||
"github.com/nspcc-dev/tzhash/tz"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// HomomorphicHashSize contains size of HH.
|
||||
const HomomorphicHashSize = 64
|
||||
|
||||
// Hash is implementation of HomomorphicHash.
|
||||
type Hash [HomomorphicHashSize]byte
|
||||
|
||||
// ErrWrongDataSize raised when wrong size of bytes is passed to unmarshal HH.
|
||||
const ErrWrongDataSize = internal.Error("wrong data size")
|
||||
|
||||
var (
|
||||
_ internal.Custom = (*Hash)(nil)
|
||||
|
||||
emptyHH [HomomorphicHashSize]byte
|
||||
)
|
||||
|
||||
// Size returns size of Hash (HomomorphicHashSize).
|
||||
func (h Hash) Size() int { return HomomorphicHashSize }
|
||||
|
||||
// Empty checks that Hash is empty.
|
||||
func (h Hash) Empty() bool { return bytes.Equal(h.Bytes(), emptyHH[:]) }
|
||||
|
||||
// Reset sets current Hash to empty value.
|
||||
func (h *Hash) Reset() { *h = Hash{} }
|
||||
|
||||
// ProtoMessage method to satisfy proto.Message interface.
|
||||
func (h Hash) ProtoMessage() {}
|
||||
|
||||
// Bytes represents Hash as bytes.
|
||||
func (h Hash) Bytes() []byte {
|
||||
buf := make([]byte, HomomorphicHashSize)
|
||||
copy(buf, h[:])
|
||||
return h[:]
|
||||
}
|
||||
|
||||
// Marshal returns bytes representation of Hash.
|
||||
func (h Hash) Marshal() ([]byte, error) { return h.Bytes(), nil }
|
||||
|
||||
// MarshalTo tries to marshal Hash into passed bytes and returns count of copied bytes.
|
||||
func (h *Hash) MarshalTo(data []byte) (int, error) { return copy(data, h.Bytes()), nil }
|
||||
|
||||
// Unmarshal tries to parse bytes into valid Hash.
|
||||
func (h *Hash) Unmarshal(data []byte) error {
|
||||
if ln := len(data); ln != HomomorphicHashSize {
|
||||
return errors.Wrapf(ErrWrongDataSize, "expect=%d, actual=%d", HomomorphicHashSize, ln)
|
||||
}
|
||||
|
||||
copy((*h)[:], data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// String returns string representation of Hash.
|
||||
func (h Hash) String() string { return base58.Encode(h[:]) }
|
||||
|
||||
// Equal checks that current Hash is equal to passed Hash.
|
||||
func (h Hash) Equal(hash Hash) bool { return h == hash }
|
||||
|
||||
// Verify validates if current hash generated from passed data.
|
||||
func (h Hash) Verify(data []byte) bool { return h.Equal(Sum(data)) }
|
||||
|
||||
// Validate checks if combined hashes are equal to current Hash.
|
||||
func (h Hash) Validate(hashes []Hash) bool {
|
||||
var hashBytes = make([][]byte, 0, len(hashes))
|
||||
for i := range hashes {
|
||||
hashBytes = append(hashBytes, hashes[i].Bytes())
|
||||
}
|
||||
ok, err := tz.Validate(h.Bytes(), hashBytes)
|
||||
return err == nil && ok
|
||||
}
|
||||
|
||||
// Sum returns Tillich-Zémor checksum of data.
|
||||
func Sum(data []byte) Hash { return tz.Sum(data) }
|
||||
|
||||
// Concat combines hashes based on homomorphic property.
|
||||
func Concat(hashes []Hash) (Hash, error) {
|
||||
var (
|
||||
hash Hash
|
||||
h = make([][]byte, 0, len(hashes))
|
||||
)
|
||||
for i := range hashes {
|
||||
h = append(h, hashes[i].Bytes())
|
||||
}
|
||||
cat, err := tz.Concat(h)
|
||||
if err != nil {
|
||||
return hash, err
|
||||
}
|
||||
return hash, hash.Unmarshal(cat)
|
||||
}
|
166
hash/hash_test.go
Normal file
166
hash/hash_test.go
Normal file
|
@ -0,0 +1,166 @@
|
|||
package hash
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func Test_Sum(t *testing.T) {
|
||||
var (
|
||||
data = []byte("Hello world")
|
||||
sum = Sum(data)
|
||||
hash = []byte{0, 0, 0, 0, 1, 79, 16, 173, 134, 90, 176, 77, 114, 165, 253, 114, 0, 0, 0, 0, 0, 148,
|
||||
172, 222, 98, 248, 15, 99, 205, 129, 66, 91, 0, 0, 0, 0, 0, 138, 173, 39, 228, 231, 239, 123,
|
||||
170, 96, 186, 61, 0, 0, 0, 0, 0, 90, 69, 237, 131, 90, 161, 73, 38, 164, 185, 55}
|
||||
)
|
||||
|
||||
require.Equal(t, hash, sum.Bytes())
|
||||
}
|
||||
|
||||
func Test_Validate(t *testing.T) {
|
||||
var (
|
||||
data = []byte("Hello world")
|
||||
hash = Sum(data)
|
||||
pieces = splitData(data, 2)
|
||||
ln = len(pieces)
|
||||
hashes = make([]Hash, 0, ln)
|
||||
)
|
||||
|
||||
for i := 0; i < ln; i++ {
|
||||
hashes = append(hashes, Sum(pieces[i]))
|
||||
}
|
||||
|
||||
require.True(t, hash.Validate(hashes))
|
||||
}
|
||||
|
||||
func Test_Concat(t *testing.T) {
|
||||
var (
|
||||
data = []byte("Hello world")
|
||||
hash = Sum(data)
|
||||
pieces = splitData(data, 2)
|
||||
ln = len(pieces)
|
||||
hashes = make([]Hash, 0, ln)
|
||||
)
|
||||
|
||||
for i := 0; i < ln; i++ {
|
||||
hashes = append(hashes, Sum(pieces[i]))
|
||||
}
|
||||
|
||||
res, err := Concat(hashes)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, hash, res)
|
||||
}
|
||||
|
||||
func Test_HashChunks(t *testing.T) {
|
||||
var (
|
||||
chars = []byte("+")
|
||||
size = 1400
|
||||
data = bytes.Repeat(chars, size)
|
||||
hash = Sum(data)
|
||||
count = 150
|
||||
)
|
||||
|
||||
hashes, err := dataHashes(data, count)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hashes, count)
|
||||
|
||||
require.True(t, hash.Validate(hashes))
|
||||
|
||||
// 100 / 150 = 0
|
||||
hashes, err = dataHashes(data[:100], count)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, hashes)
|
||||
}
|
||||
|
||||
func TestXOR(t *testing.T) {
|
||||
var (
|
||||
dl = 10
|
||||
data = make([]byte, dl)
|
||||
)
|
||||
|
||||
_, err := rand.Read(data)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("XOR with <nil> salt", func(t *testing.T) {
|
||||
res := SaltXOR(data, nil)
|
||||
require.Equal(t, res, data)
|
||||
})
|
||||
|
||||
t.Run("XOR with empty salt", func(t *testing.T) {
|
||||
xorWithSalt(t, data, 0)
|
||||
})
|
||||
|
||||
t.Run("XOR with salt same data size", func(t *testing.T) {
|
||||
xorWithSalt(t, data, dl)
|
||||
})
|
||||
|
||||
t.Run("XOR with salt shorter than data aliquot", func(t *testing.T) {
|
||||
xorWithSalt(t, data, dl/2)
|
||||
})
|
||||
|
||||
t.Run("XOR with salt shorter than data aliquant", func(t *testing.T) {
|
||||
xorWithSalt(t, data, dl/3/+1)
|
||||
})
|
||||
|
||||
t.Run("XOR with salt longer than data aliquot", func(t *testing.T) {
|
||||
xorWithSalt(t, data, dl*2)
|
||||
})
|
||||
|
||||
t.Run("XOR with salt longer than data aliquant", func(t *testing.T) {
|
||||
xorWithSalt(t, data, dl*2-1)
|
||||
})
|
||||
}
|
||||
|
||||
func xorWithSalt(t *testing.T, data []byte, saltSize int) {
|
||||
var (
|
||||
direct, reverse []byte
|
||||
salt = make([]byte, saltSize)
|
||||
)
|
||||
|
||||
_, err := rand.Read(salt)
|
||||
require.NoError(t, err)
|
||||
|
||||
direct = SaltXOR(data, salt)
|
||||
require.Len(t, direct, len(data))
|
||||
|
||||
reverse = SaltXOR(direct, salt)
|
||||
require.Len(t, reverse, len(data))
|
||||
|
||||
require.Equal(t, reverse, data)
|
||||
}
|
||||
|
||||
func splitData(buf []byte, lim int) [][]byte {
|
||||
var piece []byte
|
||||
pieces := make([][]byte, 0, len(buf)/lim+1)
|
||||
for len(buf) >= lim {
|
||||
piece, buf = buf[:lim], buf[lim:]
|
||||
pieces = append(pieces, piece)
|
||||
}
|
||||
if len(buf) > 0 {
|
||||
pieces = append(pieces, buf)
|
||||
}
|
||||
return pieces
|
||||
}
|
||||
|
||||
func dataHashes(data []byte, count int) ([]Hash, error) {
|
||||
var (
|
||||
ln = len(data)
|
||||
mis = ln / count
|
||||
off = (count - 1) * mis
|
||||
hashes = make([]Hash, 0, count)
|
||||
)
|
||||
if mis == 0 {
|
||||
return nil, errors.Errorf("could not split %d bytes to %d pieces", ln, count)
|
||||
}
|
||||
|
||||
pieces := splitData(data[:off], mis)
|
||||
pieces = append(pieces, data[off:])
|
||||
for i := 0; i < count; i++ {
|
||||
hashes = append(hashes, Sum(pieces[i]))
|
||||
}
|
||||
return hashes, nil
|
||||
}
|
20
hash/hashesslice.go
Normal file
20
hash/hashesslice.go
Normal file
|
@ -0,0 +1,20 @@
|
|||
package hash
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
// HashesSlice is a collection that satisfies sort.Interface and can be
|
||||
// sorted by the routines in sort package.
|
||||
type HashesSlice []Hash
|
||||
|
||||
// -- HashesSlice -- an inner type to sort Objects
|
||||
// Len is the number of elements in the collection.
|
||||
func (hs HashesSlice) Len() int { return len(hs) }
|
||||
|
||||
// Less reports whether the element with
|
||||
// index i should be sorted before the element with index j.
|
||||
func (hs HashesSlice) Less(i, j int) bool { return bytes.Compare(hs[i].Bytes(), hs[j].Bytes()) == -1 }
|
||||
|
||||
// Swap swaps the elements with indexes i and j.
|
||||
func (hs HashesSlice) Swap(i, j int) { hs[i], hs[j] = hs[j], hs[i] }
|
17
hash/salt.go
Normal file
17
hash/salt.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package hash
|
||||
|
||||
// SaltXOR xors bits of data with salt
|
||||
// repeating salt if necessary.
|
||||
func SaltXOR(data, salt []byte) (result []byte) {
|
||||
result = make([]byte, len(data))
|
||||
ls := len(salt)
|
||||
if ls == 0 {
|
||||
copy(result, data)
|
||||
return
|
||||
}
|
||||
|
||||
for i := range result {
|
||||
result[i] = data[i] ^ salt[i%ls]
|
||||
}
|
||||
return
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue