refactor(hash): add hash package and alias hashing for backwards compat

The hash package is the canonical implementation.
The hashing package now re-exports from hash for backwards compatibility.

New code should import github.com/luxfi/crypto/hash directly.
This commit is contained in:
Zach Kelling
2025-12-26 17:02:15 -08:00
parent 898b7f84c0
commit f41ac722b2
6 changed files with 937 additions and 90 deletions
+121
View File
@@ -0,0 +1,121 @@
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package blake3 provides Blake3 hash functions for cryptographic operations.
// This is extracted from the threshold package to provide a centralized
// implementation for all Lux projects.
package blake3
import (
"encoding/binary"
"io"
"math/big"
"github.com/zeebo/blake3"
)
// DigestLength is the standard output length for Blake3 hashes
const DigestLength = 64 // 512 bits
// Digest represents a Blake3 hash output
type Digest [DigestLength]byte
// Hasher wraps blake3.Hasher to provide a consistent interface
type Hasher struct {
h *blake3.Hasher
}
// New creates a new Blake3 hasher
func New() *Hasher {
return &Hasher{h: blake3.New()}
}
// NewWithDomain creates a new Blake3 hasher with a domain separator
func NewWithDomain(domain string) *Hasher {
h := &Hasher{h: blake3.New()}
h.WriteString(domain)
return h
}
// Write adds data to the hash
func (h *Hasher) Write(p []byte) (n int, err error) {
return h.h.Write(p)
}
// WriteString adds a string to the hash
func (h *Hasher) WriteString(s string) (n int, err error) {
return h.h.WriteString(s)
}
// WriteUint32 adds a uint32 to the hash in big-endian format
func (h *Hasher) WriteUint32(v uint32) {
var buf [4]byte
binary.BigEndian.PutUint32(buf[:], v)
h.h.Write(buf[:])
}
// WriteUint64 adds a uint64 to the hash in big-endian format
func (h *Hasher) WriteUint64(v uint64) {
var buf [8]byte
binary.BigEndian.PutUint64(buf[:], v)
h.h.Write(buf[:])
}
// WriteBigInt adds a big.Int to the hash
func (h *Hasher) WriteBigInt(n *big.Int) {
if n == nil {
h.WriteUint32(0)
return
}
bytes := n.Bytes()
h.WriteUint32(uint32(len(bytes)))
h.h.Write(bytes)
}
// Sum returns the hash digest
func (h *Hasher) Sum(b []byte) []byte {
return h.h.Sum(b)
}
// Digest returns a fixed-size digest
func (h *Hasher) Digest() Digest {
var d Digest
h.h.Digest().Read(d[:])
return d
}
// Reader returns an io.Reader for extended output
func (h *Hasher) Reader() io.Reader {
return h.h.Digest()
}
// Clone creates a copy of the hasher
func (h *Hasher) Clone() *Hasher {
return &Hasher{h: h.h.Clone()}
}
// Reset resets the hasher to its initial state
func (h *Hasher) Reset() {
h.h.Reset()
}
// HashBytes hashes a byte slice and returns a digest
func HashBytes(data []byte) Digest {
h := New()
h.Write(data)
return h.Digest()
}
// HashString hashes a string and returns a digest
func HashString(s string) Digest {
h := New()
h.WriteString(s)
return h.Digest()
}
// HashWithDomain hashes data with a domain separator
func HashWithDomain(domain string, data []byte) Digest {
h := NewWithDomain(domain)
h.Write(data)
return h.Digest()
}
+382
View File
@@ -0,0 +1,382 @@
package blake3
import (
"bytes"
"encoding/hex"
"math/big"
"strings"
"testing"
)
func TestNew(t *testing.T) {
h := New()
if h == nil {
t.Fatal("New() returned nil")
}
if h.h == nil {
t.Fatal("Internal hasher is nil")
}
}
func TestNewWithDomain(t *testing.T) {
domain := "test-domain"
h1 := NewWithDomain(domain)
h2 := NewWithDomain(domain)
data := []byte("test data")
h1.Write(data)
h2.Write(data)
d1 := h1.Digest()
d2 := h2.Digest()
if !bytes.Equal(d1[:], d2[:]) {
t.Error("Same domain should produce same hash")
}
// Different domain should produce different hash
h3 := NewWithDomain("different-domain")
h3.Write(data)
d3 := h3.Digest()
if bytes.Equal(d1[:], d3[:]) {
t.Error("Different domain should produce different hash")
}
}
func TestHashBytes(t *testing.T) {
// Test with known test vectors
testCases := []struct {
input []byte
expected string // First 64 bytes of hash
}{
{
[]byte(""),
"af1349b9f5f9a1a6a0404dea36dcc9499bcb25c9adc112b7cc9a93cae41f3262e00f03e7b69af26b7faaf09fcd333050338ddfe085b8cc869ca98b206c08243a",
},
{
[]byte("hello"),
"ea8f163db38682925e4491c5e58d4bb3506ef8c14eb78a86e908c5624a67200fe992405f0d785b599a2e3387f6d34d01faccfeb22fb697ef3fd53541241a338c",
},
}
for _, tc := range testCases {
hash := HashBytes(tc.input)
hashStr := hex.EncodeToString(hash[:])
if hashStr != tc.expected {
t.Errorf("HashBytes(%q) = %s, want %s", tc.input, hashStr, tc.expected)
}
}
// Test consistency
data := []byte("test data")
h1 := HashBytes(data)
h2 := HashBytes(data)
if !bytes.Equal(h1[:], h2[:]) {
t.Error("Same input should produce same hash")
}
}
func TestHashString(t *testing.T) {
// Test consistency
s := "test string"
h1 := HashString(s)
h2 := HashString(s)
if !bytes.Equal(h1[:], h2[:]) {
t.Error("Same string should produce same hash")
}
// Compare with HashBytes
h3 := HashBytes([]byte(s))
if !bytes.Equal(h1[:], h3[:]) {
t.Error("HashString should match HashBytes for same content")
}
// Different strings produce different hashes
h4 := HashString("different string")
if bytes.Equal(h1[:], h4[:]) {
t.Error("Different strings should produce different hashes")
}
}
func TestHashWithDomain(t *testing.T) {
data := []byte("test data")
domain1 := "domain1"
domain2 := "domain2"
h1 := HashWithDomain(domain1, data)
h2 := HashWithDomain(domain1, data)
h3 := HashWithDomain(domain2, data)
// Same domain and data should produce same hash
if !bytes.Equal(h1[:], h2[:]) {
t.Error("Same domain and data should produce same hash")
}
// Different domain should produce different hash
if bytes.Equal(h1[:], h3[:]) {
t.Error("Different domain should produce different hash")
}
// Should differ from hash without domain
h4 := HashBytes(data)
if bytes.Equal(h1[:], h4[:]) {
t.Error("Hash with domain should differ from hash without domain")
}
}
func TestWriteMethods(t *testing.T) {
h := New()
// Test Write
n, err := h.Write([]byte("test"))
if err != nil {
t.Errorf("Write error: %v", err)
}
if n != 4 {
t.Errorf("Write returned %d, want 4", n)
}
// Test WriteString
n, err = h.WriteString("string")
if err != nil {
t.Errorf("WriteString error: %v", err)
}
if n != 6 {
t.Errorf("WriteString returned %d, want 6", n)
}
// Test WriteUint32
h.WriteUint32(0x12345678)
// Test WriteUint64
h.WriteUint64(0x123456789ABCDEF0)
// Test WriteBigInt
bigNum := big.NewInt(1234567890)
h.WriteBigInt(bigNum)
// Test WriteBigInt with nil
h.WriteBigInt(nil)
// Get digest to ensure it doesn't panic
_ = h.Digest()
}
func TestWriteUint32(t *testing.T) {
h1 := New()
h1.WriteUint32(0x12345678)
d1 := h1.Digest()
// Should be same as writing the bytes in big-endian
h2 := New()
h2.Write([]byte{0x12, 0x34, 0x56, 0x78})
d2 := h2.Digest()
if !bytes.Equal(d1[:], d2[:]) {
t.Error("WriteUint32 should write in big-endian format")
}
}
func TestWriteUint64(t *testing.T) {
h1 := New()
h1.WriteUint64(0x123456789ABCDEF0)
d1 := h1.Digest()
// Should be same as writing the bytes in big-endian
h2 := New()
h2.Write([]byte{0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0})
d2 := h2.Digest()
if !bytes.Equal(d1[:], d2[:]) {
t.Error("WriteUint64 should write in big-endian format")
}
}
func TestWriteBigInt(t *testing.T) {
// Test with normal big int
n := big.NewInt(1234567890)
h1 := New()
h1.WriteBigInt(n)
d1 := h1.Digest()
// Writing same number should produce same hash
h2 := New()
h2.WriteBigInt(n)
d2 := h2.Digest()
if !bytes.Equal(d1[:], d2[:]) {
t.Error("Same big.Int should produce same hash")
}
// Test with nil
h3 := New()
h3.WriteBigInt(nil)
d3 := h3.Digest()
// Nil should write a zero length prefix
h4 := New()
h4.WriteUint32(0)
d4 := h4.Digest()
if !bytes.Equal(d3[:], d4[:]) {
t.Error("WriteBigInt(nil) should write zero length")
}
// Test with zero
zero := big.NewInt(0)
h5 := New()
h5.WriteBigInt(zero)
_ = h5.Digest() // Should not panic
}
func TestSum(t *testing.T) {
h := New()
h.WriteString("test")
// Sum with nil
sum1 := h.Sum(nil)
if len(sum1) != 32 { // Default blake3 output
t.Errorf("Sum(nil) length = %d, want 32", len(sum1))
}
// Sum with existing slice
prefix := []byte("prefix")
sum2 := h.Sum(prefix)
if !bytes.HasPrefix(sum2, prefix) {
t.Error("Sum should append to provided slice")
}
if len(sum2) != len(prefix)+32 {
t.Errorf("Sum length = %d, want %d", len(sum2), len(prefix)+32)
}
}
func TestDigest(t *testing.T) {
h := New()
h.WriteString("test")
d := h.Digest()
if len(d) != DigestLength {
t.Errorf("Digest length = %d, want %d", len(d), DigestLength)
}
// Digest should be consistent
h2 := New()
h2.WriteString("test")
d2 := h2.Digest()
if !bytes.Equal(d[:], d2[:]) {
t.Error("Same input should produce same digest")
}
}
func TestReader(t *testing.T) {
h := New()
h.WriteString("test")
reader := h.Reader()
if reader == nil {
t.Fatal("Reader() returned nil")
}
// Read some bytes
buf := make([]byte, 100)
n, err := reader.Read(buf)
if err != nil {
t.Errorf("Reader.Read error: %v", err)
}
if n != 100 {
t.Errorf("Reader.Read returned %d, want 100", n)
}
}
func TestClone(t *testing.T) {
h1 := New()
h1.WriteString("test")
h2 := h1.Clone()
if h2 == nil {
t.Fatal("Clone() returned nil")
}
// Both should produce same digest at this point
d1 := h1.Digest()
d2 := h2.Digest()
if !bytes.Equal(d1[:], d2[:]) {
t.Error("Clone should produce same digest")
}
// Writing to one shouldn't affect the other
h1.WriteString("more")
d1New := h1.Digest()
d2New := h2.Digest()
if bytes.Equal(d1New[:], d2New[:]) {
t.Error("Writing to original should not affect clone")
}
}
func TestReset(t *testing.T) {
h := New()
h.WriteString("test")
d1 := h.Digest()
h.Reset()
h.WriteString("test")
d2 := h.Digest()
if !bytes.Equal(d1[:], d2[:]) {
t.Error("Reset should restore initial state")
}
// After reset, different input should produce different hash
h.Reset()
h.WriteString("different")
d3 := h.Digest()
if bytes.Equal(d1[:], d3[:]) {
t.Error("After reset, different input should produce different hash")
}
}
func TestDigestLength(t *testing.T) {
if DigestLength != 64 {
t.Errorf("DigestLength = %d, want 64", DigestLength)
}
}
func BenchmarkHashBytes(b *testing.B) {
data := make([]byte, 1024)
for i := range data {
data[i] = byte(i % 256)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = HashBytes(data)
}
}
func BenchmarkHashString(b *testing.B) {
data := strings.Repeat("benchmark", 128)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = HashString(data)
}
}
func BenchmarkWriteBigInt(b *testing.B) {
n := new(big.Int)
n.SetString("123456789012345678901234567890123456789012345678901234567890", 10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
h := New()
h.WriteBigInt(n)
_ = h.Digest()
}
}
+103
View File
@@ -0,0 +1,103 @@
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package hash
import (
"crypto/sha256"
"errors"
"fmt"
"io"
// This file generates addresses from public keys with ripemd160. Though ripemd160 is not
// generally recommended for use, the small size of the public key input is considered harder to
// attack than larger payloads.
//
// Bitcoin similarly uses ripemd160 to generate addresses from public keys.
//
// Reference: https://online.tugraz.at/tug_online/voe_main2.getvolltext?pCurrPk=17675
"golang.org/x/crypto/ripemd160" //nolint:gosec
)
const (
HashLen = sha256.Size
AddrLen = ripemd160.Size
)
var ErrInvalidHashLen = errors.New("invalid hash length")
// Hash256 A 256 bit long hash value.
type Hash256 = [HashLen]byte
// Hash160 A 160 bit long hash value.
type Hash160 = [ripemd160.Size]byte
// ComputeHash256Array computes a cryptographically strong 256 bit hash of the
// input byte slice.
func ComputeHash256Array(buf []byte) Hash256 {
return sha256.Sum256(buf)
}
// ComputeHash256 computes a cryptographically strong 256 bit hash of the input
// byte slice.
func ComputeHash256(buf []byte) []byte {
arr := ComputeHash256Array(buf)
return arr[:]
}
// ComputeHash160Array computes a cryptographically strong 160 bit hash of the
// input byte slice.
func ComputeHash160Array(buf []byte) Hash160 {
h, err := ToHash160(ComputeHash160(buf))
if err != nil {
panic(err)
}
return h
}
// ComputeHash160 computes a cryptographically strong 160 bit hash of the input
// byte slice.
func ComputeHash160(buf []byte) []byte {
// See the comment on the ripemd160 import as to why the risk of use is
// considered acceptable.
ripe := ripemd160.New() //nolint:gosec
_, err := io.Writer(ripe).Write(buf)
if err != nil {
panic(err)
}
return ripe.Sum(nil)
}
// Checksum creates a checksum of [length] bytes from the 256 bit hash of the
// byte slice.
//
// Returns: the lower [length] bytes of the hash
// Panics if length > 32.
func Checksum(bytes []byte, length int) []byte {
hash := ComputeHash256Array(bytes)
return hash[len(hash)-length:]
}
func ToHash256(bytes []byte) (Hash256, error) {
hash := Hash256{}
if bytesLen := len(bytes); bytesLen != HashLen {
return hash, fmt.Errorf("%w: expected 32 bytes but got %d", ErrInvalidHashLen, bytesLen)
}
copy(hash[:], bytes)
return hash, nil
}
func ToHash160(bytes []byte) (Hash160, error) {
hash := Hash160{}
if bytesLen := len(bytes); bytesLen != ripemd160.Size {
return hash, fmt.Errorf("%w: expected 20 bytes but got %d", ErrInvalidHashLen, bytesLen)
}
copy(hash[:], bytes)
return hash, nil
}
// PubkeyBytesToAddress converts a public key to an address using
// Bitcoin-style SHA256+RIPEMD160 hash.
func PubkeyBytesToAddress(key []byte) []byte {
return ComputeHash160(ComputeHash256(key))
}
+299
View File
@@ -0,0 +1,299 @@
package hash
import (
"bytes"
"encoding/hex"
"testing"
)
func TestComputeHash256(t *testing.T) {
// Test with known vectors
testCases := []struct {
name string
input []byte
expected string
}{
{
"empty",
[]byte(""),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
},
{
"abc",
[]byte("abc"),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
},
{
"fox",
[]byte("The quick brown fox jumps over the lazy dog"),
"d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Test ComputeHash256
hash := ComputeHash256(tc.input)
hashStr := hex.EncodeToString(hash)
if hashStr != tc.expected {
t.Errorf("ComputeHash256(%q) = %s, want %s", tc.input, hashStr, tc.expected)
}
// Test ComputeHash256Array
hashArray := ComputeHash256Array(tc.input)
hashArrayStr := hex.EncodeToString(hashArray[:])
if hashArrayStr != tc.expected {
t.Errorf("ComputeHash256Array(%q) = %s, want %s", tc.input, hashArrayStr, tc.expected)
}
// Verify slice and array produce same result
if !bytes.Equal(hash, hashArray[:]) {
t.Error("ComputeHash256 and ComputeHash256Array should produce same result")
}
})
}
}
func TestComputeHash160(t *testing.T) {
testCases := []struct {
name string
input []byte
expected string
}{
{
"empty",
[]byte(""),
"9c1185a5c5e9fc54612808977ee8f548b2258d31",
},
{
"abc",
[]byte("abc"),
"8eb208f7e05d987a9b044a8e98c6b087f15a0bfc",
},
{
"message digest",
[]byte("message digest"),
"5d0689ef49d2fae572b881b123a85ffa21595f36",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// Test ComputeHash160
hash := ComputeHash160(tc.input)
hashStr := hex.EncodeToString(hash)
if hashStr != tc.expected {
t.Errorf("ComputeHash160(%q) = %s, want %s", tc.input, hashStr, tc.expected)
}
// Test ComputeHash160Array
hashArray := ComputeHash160Array(tc.input)
hashArrayStr := hex.EncodeToString(hashArray[:])
if hashArrayStr != tc.expected {
t.Errorf("ComputeHash160Array(%q) = %s, want %s", tc.input, hashArrayStr, tc.expected)
}
// Verify slice and array produce same result
if !bytes.Equal(hash, hashArray[:]) {
t.Error("ComputeHash160 and ComputeHash160Array should produce same result")
}
})
}
}
func TestChecksum(t *testing.T) {
input := []byte("test input for checksum")
// Test various checksum lengths
lengths := []int{1, 4, 8, 16, 32}
for _, length := range lengths {
checksum := Checksum(input, length)
if len(checksum) != length {
t.Errorf("Checksum length should be %d, got %d", length, len(checksum))
}
// Verify checksum is last 'length' bytes of hash
fullHash := ComputeHash256Array(input)
expected := fullHash[len(fullHash)-length:]
if !bytes.Equal(checksum, expected) {
t.Errorf("Checksum should be last %d bytes of hash", length)
}
}
// Test that same input produces same checksum
checksum1 := Checksum(input, 4)
checksum2 := Checksum(input, 4)
if !bytes.Equal(checksum1, checksum2) {
t.Error("Same input should produce same checksum")
}
// Test that different input produces different checksum
input2 := []byte("different input")
checksum3 := Checksum(input2, 4)
if bytes.Equal(checksum1, checksum3) {
t.Error("Different input should produce different checksum")
}
}
func TestToHash256(t *testing.T) {
// Test valid conversion
validBytes := make([]byte, HashLen)
for i := range validBytes {
validBytes[i] = byte(i)
}
hash, err := ToHash256(validBytes)
if err != nil {
t.Errorf("ToHash256 with valid bytes should not error: %v", err)
}
if !bytes.Equal(hash[:], validBytes) {
t.Error("ToHash256 should copy bytes correctly")
}
// Test invalid lengths
invalidLengths := []int{0, 1, 31, 33, 100}
for _, length := range invalidLengths {
invalidBytes := make([]byte, length)
_, err := ToHash256(invalidBytes)
if err == nil {
t.Errorf("ToHash256 should error with %d bytes", length)
}
if err != nil && err.Error() != ErrInvalidHashLen.Error() && !bytes.Contains([]byte(err.Error()), []byte("invalid hash length")) {
t.Errorf("Expected ErrInvalidHashLen, got: %v", err)
}
}
}
func TestToHash160(t *testing.T) {
// Test valid conversion
validBytes := make([]byte, AddrLen)
for i := range validBytes {
validBytes[i] = byte(i)
}
hash, err := ToHash160(validBytes)
if err != nil {
t.Errorf("ToHash160 with valid bytes should not error: %v", err)
}
if !bytes.Equal(hash[:], validBytes) {
t.Error("ToHash160 should copy bytes correctly")
}
// Test invalid lengths
invalidLengths := []int{0, 1, 19, 21, 100}
for _, length := range invalidLengths {
invalidBytes := make([]byte, length)
_, err := ToHash160(invalidBytes)
if err == nil {
t.Errorf("ToHash160 should error with %d bytes", length)
}
if err != nil && err.Error() != ErrInvalidHashLen.Error() && !bytes.Contains([]byte(err.Error()), []byte("invalid hash length")) {
t.Errorf("Expected ErrInvalidHashLen, got: %v", err)
}
}
}
func TestPubkeyBytesToAddress(t *testing.T) {
// Test that address generation is consistent
pubkey := []byte("test public key")
addr1 := PubkeyBytesToAddress(pubkey)
addr2 := PubkeyBytesToAddress(pubkey)
if !bytes.Equal(addr1, addr2) {
t.Error("Same pubkey should produce same address")
}
// Test that different pubkeys produce different addresses
pubkey2 := []byte("different public key")
addr3 := PubkeyBytesToAddress(pubkey2)
if bytes.Equal(addr1, addr3) {
t.Error("Different pubkeys should produce different addresses")
}
// Test that address is 20 bytes (ripemd160 size)
if len(addr1) != AddrLen {
t.Errorf("Address should be %d bytes, got %d", AddrLen, len(addr1))
}
// Test empty pubkey
emptyAddr := PubkeyBytesToAddress([]byte{})
if len(emptyAddr) != AddrLen {
t.Errorf("Empty pubkey should still produce %d byte address", AddrLen)
}
}
func TestHashConstants(t *testing.T) {
// Verify constants match expected values
if HashLen != 32 {
t.Errorf("HashLen should be 32, got %d", HashLen)
}
if AddrLen != 20 {
t.Errorf("AddrLen should be 20, got %d", AddrLen)
}
}
func BenchmarkComputeHash256(b *testing.B) {
input := make([]byte, 1024)
for i := range input {
input[i] = byte(i % 256)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ComputeHash256(input)
}
}
func BenchmarkComputeHash256Array(b *testing.B) {
input := make([]byte, 1024)
for i := range input {
input[i] = byte(i % 256)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ComputeHash256Array(input)
}
}
func BenchmarkComputeHash160(b *testing.B) {
input := make([]byte, 1024)
for i := range input {
input[i] = byte(i % 256)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = ComputeHash160(input)
}
}
func BenchmarkPubkeyBytesToAddress(b *testing.B) {
pubkey := make([]byte, 65) // Typical pubkey size
for i := range pubkey {
pubkey[i] = byte(i % 256)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = PubkeyBytesToAddress(pubkey)
}
}
func BenchmarkChecksum(b *testing.B) {
input := make([]byte, 1024)
for i := range input {
input[i] = byte(i % 256)
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = Checksum(input, 4)
}
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package hash
// Hasher is an interface to compute a hash value.
type Hasher interface {
// Hash takes a string and computes its hash value.
// Values must be computed deterministically.
Hash([]byte) uint64
}
+21 -90
View File
@@ -1,103 +1,34 @@
// Copyright (C) 2020-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
// Package hashing is an alias for hash package for backwards compatibility.
// New code should use github.com/luxfi/crypto/hash directly.
package hashing
import (
"crypto/sha256"
"errors"
"fmt"
"io"
// This file generates addresses from public keys with ripemd160. Though ripemd160 is not
// generally recommended for use, the small size of the public key input is considered harder to
// attack than larger payloads.
//
// Bitcoin similarly uses ripemd160 to generate addresses from public keys.
//
// Reference: https://online.tugraz.at/tug_online/voe_main2.getvolltext?pCurrPk=17675
"golang.org/x/crypto/ripemd160" //nolint:gosec
)
import "github.com/luxfi/crypto/hash"
const (
HashLen = sha256.Size
AddrLen = ripemd160.Size
HashLen = hash.HashLen
AddrLen = hash.AddrLen
)
var ErrInvalidHashLen = errors.New("invalid hash length")
// Type aliases
type Hash256 = hash.Hash256
type Hash160 = hash.Hash160
// Hash256 A 256 bit long hash value.
type Hash256 = [HashLen]byte
// Error aliases
var ErrInvalidHashLen = hash.ErrInvalidHashLen
// Hash160 A 160 bit long hash value.
type Hash160 = [ripemd160.Size]byte
// Function aliases - SHA256 (256-bit)
var ComputeHash256Array = hash.ComputeHash256Array
var ComputeHash256 = hash.ComputeHash256
var ToHash256 = hash.ToHash256
// ComputeHash256Array computes a cryptographically strong 256 bit hash of the
// input byte slice.
func ComputeHash256Array(buf []byte) Hash256 {
return sha256.Sum256(buf)
}
// Function aliases - RIPEMD160 (160-bit)
var ComputeHash160Array = hash.ComputeHash160Array
var ComputeHash160 = hash.ComputeHash160
var ToHash160 = hash.ToHash160
// ComputeHash256 computes a cryptographically strong 256 bit hash of the input
// byte slice.
func ComputeHash256(buf []byte) []byte {
arr := ComputeHash256Array(buf)
return arr[:]
}
// ComputeHash160Array computes a cryptographically strong 160 bit hash of the
// input byte slice.
func ComputeHash160Array(buf []byte) Hash160 {
h, err := ToHash160(ComputeHash160(buf))
if err != nil {
panic(err)
}
return h
}
// ComputeHash160 computes a cryptographically strong 160 bit hash of the input
// byte slice.
func ComputeHash160(buf []byte) []byte {
// See the comment on the ripemd160 import as to why the risk of use is
// considered acceptable.
ripe := ripemd160.New() //nolint:gosec
_, err := io.Writer(ripe).Write(buf)
if err != nil {
panic(err)
}
return ripe.Sum(nil)
}
// Checksum creates a checksum of [length] bytes from the 256 bit hash of the
// byte slice.
//
// Returns: the lower [length] bytes of the hash
// Panics if length > 32.
func Checksum(bytes []byte, length int) []byte {
hash := ComputeHash256Array(bytes)
return hash[len(hash)-length:]
}
func ToHash256(bytes []byte) (Hash256, error) {
hash := Hash256{}
if bytesLen := len(bytes); bytesLen != HashLen {
return hash, fmt.Errorf("%w: expected 32 bytes but got %d", ErrInvalidHashLen, bytesLen)
}
copy(hash[:], bytes)
return hash, nil
}
func ToHash160(bytes []byte) (Hash160, error) {
hash := Hash160{}
if bytesLen := len(bytes); bytesLen != ripemd160.Size {
return hash, fmt.Errorf("%w: expected 20 bytes but got %d", ErrInvalidHashLen, bytesLen)
}
copy(hash[:], bytes)
return hash, nil
}
// PubkeyBytesToAddress converts a public key to an address using
// Bitcoin-style SHA256+RIPEMD160 hash.
func PubkeyBytesToAddress(key []byte) []byte {
return ComputeHash160(ComputeHash256(key))
}
// Utility functions
var Checksum = hash.Checksum
var PubkeyBytesToAddress = hash.PubkeyBytesToAddress