Hasher.go

package util

import (
	"crypto/sha256"
	"crypto/rand"
	"encoding/hex"
	"crypto/ripemd160"
	"encoding/binary"
	"fmt"
	"math"
	"strings"
	"time"
	"encoding/json"
)

const (
	HashBytes         = 32
	HexTimeBytes      = 7
	TimeHashBytes     = HexTimeBytes + HashBytes
	StatusHashBytes   = 64
	HashSize          = HashBytes * 2
	HexTimeSize       = HexTimeBytes * 2
	TimeHashSize      = TimeHashBytes * 2
	IDHashSize        = 44
	StatusPrefixSize  = 64
	StatusKeySize     = 64
	StatusHashSize    = 128
)

type Hasher struct{}

func (h *Hasher) MerkleRoot(array []string) string {
	if len(array) == 0 {
		return h.Hash("")
	}

	parent := make([]string, len(array))
	for i, item := range array {
		parent[i] = h.Hash(item)
	}

	for len(parent) > 1 {
		child := []string{}
		for i := 0; i < len(parent); i += 2 {
			if i+1 < len(parent) {
				child = append(child, h.Hash(parent[i]+parent[i+1]))
			} else {
				child = append(child, parent[i])
			}
		}
		parent = child
	}

	return parent[0]
}

func (h *Hasher) MerkleTree(array []string) map[int][]string {
	tree := map[int][]string{}
	parent := make([]string, len(array))

	for i, item := range array {
		parent[i] = h.Hash(item)
	}

	height := 0
	tree[height] = parent

	for len(parent) > 1 {
		height++
		tree[height] = []string{}

		for i := 0; i < len(parent); i += 2 {
			if i+1 < len(parent) {
				tree[height] = append(tree[height], h.Hash(parent[i]+parent[i+1]))
			} else {
				tree[height] = append(tree[height], parent[i])
			}
		}

		parent = tree[height]
	}

	return tree
}

func (h *Hasher) MerklePath(array []string) map[int][]string {
	merklePath := map[int][]string{}
	tree := h.MerkleTree(array)

	for i := 0; i < len(array); i++ {
		path := []string{}

		for height, leaf := range tree {
			o := int(i / int(math.Pow(2, float64(height))))
			way := i % int(math.Pow(2, float64(height+1)))

			if way < int(math.Pow(2, float64(height))) && o+1 < len(leaf) {
				path = append(path, "0"+leaf[o+1])
			} else if way >= int(math.Pow(2, float64(height))) && o-1 >= 0 {
				path = append(path, "1"+leaf[o-1])
			}
		}

		merklePath[i] = path
	}

	return merklePath
}

func (h *Hasher) TracePath(startHash string, path []string) string {
	root := startHash

	for _, item := range path {
		prefix := item[0:1]
		hash := item[1:]

		if prefix == "0" {
			root = h.Hash(root + hash)
		} else {
			root = h.Hash(hash + root)
		}
	}

	return root
}

func (h *Hasher) Hash(obj interface{}) string {
	str := h.String(obj)
	hash := sha256.Sum256([]byte(str))
	return hex.EncodeToString(hash[:])
}

func (h *Hasher) HashValidity(hash string) bool {
	return len(hash) == HashSize && h.IsHex(hash)
}

func (h *Hasher) ShortHash(obj interface{}) string {
	hasher := ripemd160.New()
	hasher.Write([]byte(h.Hash(obj)))
	return hex.EncodeToString(hasher.Sum(nil))
}

func (h *Hasher) Checksum(hash string) string {
	first := sha256.Sum256([]byte(hash))
	second := sha256.Sum256(first[:])
	return hex.EncodeToString(second[:4])
}

func (h *Hasher) HexTime(utime ...int64) string {
	var t int64
	if len(utime) > 0 {
		t = utime[0]
	} else {
		t = h.Utime()
	}

	return fmt.Sprintf("%0*x", HexTimeSize, t)
}

func (h *Hasher) TimeHash(obj interface{}, timestamp int64) string {
	return h.HexTime(timestamp) + h.Hash(obj)
}

func (h *Hasher) ToTimeHash(hash string) string {
	return hash[:HexTimeSize]
}

func (h *Hasher) TimeHashValidity(hash string) bool {
	return len(hash) == TimeHashSize && h.IsHex(hash)
}

func (h *Hasher) IDHash(obj interface{}) string {
	hash := h.ShortHash(obj)
	checksum := h.Checksum(hash)
	return hash + checksum
}

func (h *Hasher) IDHashValidity(id string) bool {
	if len(id) != IDHashSize {
		return false
	}

	hash := id[:len(id)-4]
	checksum := id[len(id)-4:]

	return h.Checksum(hash) == checksum
}

func (h *Hasher) FillHash(hash string) string {
	if len(hash) < StatusHashSize {
		return fmt.Sprintf("%-*s", StatusHashSize, hash)
	}
	return hash
}

func (h *Hasher) StatusHash(writer, space, attr, key string) string {
	if len(key) > StatusKeySize || !h.IsHex(key) {
		return ""
	}

	return h.StatusPrefix(writer, space, attr) + key
}

func (h *Hasher) StatusPrefix(writer, space, attr string) string {
	return h.Hash(writer + space + attr)
}

func (h *Hasher) SpaceID(writer, space string) string {
	return h.Hash([]string{writer, space})
}

func (h *Hasher) String(obj interface{}) string {
	switch v := obj.(type) {
	case string:
		return v
	case []byte:
		return string(v)
	default:
		b, _ := json.Marshal(v)
		return string(b)
	}
}

func (h *Hasher) IsHex(hexStr string) bool {
	matched, _ := regexp.MatchString("^[0-9a-fA-F]+$", hexStr)
	return matched
}

func (h *Hasher) MinimumStatusHash() string {
	return strings.Repeat("0", StatusHashSize)
}

func (h *Hasher) MaximumStatusHash() string {
	return strings.Repeat("f", StatusHashSize)
}

func (h *Hasher) Utime() int64 {
	return time.Now().UnixNano() / int64(time.Microsecond)
}