init
This commit is contained in:
226
jdenticon/hash.go
Normal file
226
jdenticon/hash.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package jdenticon
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/kevin/go-jdenticon/internal/util"
|
||||
)
|
||||
|
||||
// ComputeHash computes a SHA-1 hash for any value and returns it as a hexadecimal string.
|
||||
// This function mimics the JavaScript version's behavior for compatibility.
|
||||
func ComputeHash(value interface{}) string {
|
||||
var input string
|
||||
|
||||
// Handle different input types, converting to string like JavaScript version
|
||||
switch v := value.(type) {
|
||||
case nil:
|
||||
input = ""
|
||||
case string:
|
||||
input = v
|
||||
case []byte:
|
||||
input = string(v)
|
||||
case int:
|
||||
input = strconv.Itoa(v)
|
||||
case int64:
|
||||
input = strconv.FormatInt(v, 10)
|
||||
case float64:
|
||||
input = fmt.Sprintf("%g", v)
|
||||
default:
|
||||
// Convert to string using fmt.Sprintf for other types
|
||||
input = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
// Compute SHA-1 hash using Go's crypto/sha1 package
|
||||
h := sha1.New()
|
||||
h.Write([]byte(input))
|
||||
hash := h.Sum(nil)
|
||||
|
||||
// Convert to hexadecimal string (lowercase to match JavaScript)
|
||||
return fmt.Sprintf("%x", hash)
|
||||
}
|
||||
|
||||
// HashValue is a convenience function that wraps ComputeHash for string inputs.
|
||||
// Kept for backward compatibility.
|
||||
func HashValue(value string) string {
|
||||
return ComputeHash(value)
|
||||
}
|
||||
|
||||
// IsValidHash checks if a string is a valid hash for Jdenticon.
|
||||
// It must be a hexadecimal string with at least 11 characters.
|
||||
func IsValidHash(hashCandidate string) bool {
|
||||
return util.IsValidHash(hashCandidate)
|
||||
}
|
||||
|
||||
// isValidHash is a private wrapper for backward compatibility with existing tests
|
||||
func isValidHash(hashCandidate string) bool {
|
||||
return IsValidHash(hashCandidate)
|
||||
}
|
||||
|
||||
// parseHex extracts a value from a hex string at a specific position.
|
||||
// This function is used to deterministically extract shape and color information
|
||||
// from the hash string, matching the JavaScript implementation.
|
||||
// When octets is 0 or negative, it reads from startPosition to the end of the string.
|
||||
func parseHex(hash string, startPosition int, octets int) (int, error) {
|
||||
return util.ParseHex(hash, startPosition, octets)
|
||||
}
|
||||
|
||||
// ParseHex provides a public API that matches the JavaScript parseHex function exactly.
|
||||
// It extracts a hexadecimal value from the hash string at the specified position.
|
||||
// If octets is not provided or is <= 0, it reads from the position to the end of the string.
|
||||
// Returns 0 on error to maintain compatibility with the JavaScript implementation.
|
||||
func ParseHex(hash string, startPosition int, octets ...int) int {
|
||||
octetCount := 0
|
||||
if len(octets) > 0 {
|
||||
octetCount = octets[0]
|
||||
}
|
||||
result, err := parseHex(hash, startPosition, octetCount)
|
||||
if err != nil {
|
||||
return 0 // Maintain JavaScript compatibility: return 0 on error
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ParseHash converts a hexadecimal hash string into a byte array for further processing.
|
||||
// It validates the input hash string and handles common prefixes like "0x".
|
||||
// Returns an error if the hash contains invalid hexadecimal characters.
|
||||
func ParseHash(hash string) ([]byte, error) {
|
||||
if hash == "" {
|
||||
return nil, errors.New("hash string cannot be empty")
|
||||
}
|
||||
|
||||
// Remove "0x" prefix if present
|
||||
cleanHash := strings.TrimPrefix(hash, "0x")
|
||||
cleanHash = strings.TrimPrefix(cleanHash, "0X")
|
||||
|
||||
// Validate hash length (must be even for proper byte conversion)
|
||||
if len(cleanHash)%2 != 0 {
|
||||
return nil, errors.New("hash string must have even length")
|
||||
}
|
||||
|
||||
// Decode hex string to bytes
|
||||
bytes, err := hex.DecodeString(cleanHash)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid hexadecimal string: %w", err)
|
||||
}
|
||||
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
// ExtractInt extracts a specific number of bits from a hash byte array and converts them to an integer.
|
||||
// The index parameter specifies the starting position (negative values count from the end).
|
||||
// The bits parameter specifies how many bits to extract.
|
||||
func ExtractInt(hash []byte, index, bits int) int {
|
||||
if len(hash) == 0 || bits <= 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Handle negative indices (count from end)
|
||||
if index < 0 {
|
||||
index = len(hash) + index
|
||||
}
|
||||
|
||||
// Ensure index is within bounds
|
||||
if index < 0 || index >= len(hash) {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Calculate how many bytes we need to read
|
||||
bytesNeeded := (bits + 7) / 8 // Round up to nearest byte
|
||||
|
||||
// Ensure we don't read past the end of the array
|
||||
if index+bytesNeeded > len(hash) {
|
||||
bytesNeeded = len(hash) - index
|
||||
}
|
||||
|
||||
if bytesNeeded <= 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Extract bytes and convert to integer
|
||||
var result int
|
||||
for i := 0; i < bytesNeeded; i++ {
|
||||
if index+i < len(hash) {
|
||||
result = (result << 8) | int(hash[index+i])
|
||||
}
|
||||
}
|
||||
|
||||
// Mask to only include the requested number of bits
|
||||
if bits < 64 {
|
||||
mask := (1 << bits) - 1
|
||||
result &= mask
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ExtractFloat extracts a specific number of bits from a hash byte array and converts them to a float64 value between 0 and 1.
|
||||
// The value is normalized by dividing by the maximum possible value for the given number of bits.
|
||||
func ExtractFloat(hash []byte, index, bits int) float64 {
|
||||
if bits <= 0 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
// Extract integer value
|
||||
intValue := ExtractInt(hash, index, bits)
|
||||
|
||||
// Calculate maximum possible value for the given number of bits
|
||||
maxValue := (1 << bits) - 1
|
||||
if maxValue == 0 {
|
||||
return 0.0
|
||||
}
|
||||
|
||||
// Normalize to [0,1] range
|
||||
return float64(intValue) / float64(maxValue)
|
||||
}
|
||||
|
||||
// ExtractHue extracts the hue value from a hash string using the same algorithm as the JavaScript version.
|
||||
// This is a convenience function that extracts the last 7 characters and normalizes to [0,1] range.
|
||||
// Returns 0.0 on error to maintain compatibility with the JavaScript implementation.
|
||||
func ExtractHue(hash string) float64 {
|
||||
hueValue, err := parseHex(hash, -7, 0) // Read from -7 to end
|
||||
if err != nil {
|
||||
return 0.0 // Maintain JavaScript compatibility: return 0.0 on error
|
||||
}
|
||||
return float64(hueValue) / 0xfffffff
|
||||
}
|
||||
|
||||
// ExtractShapeIndex extracts a shape index from the hash at the specified position.
|
||||
// This is a convenience function that matches the JavaScript shape selection logic.
|
||||
// Returns 0 on error to maintain compatibility with the JavaScript implementation.
|
||||
func ExtractShapeIndex(hash string, position int) int {
|
||||
result, err := parseHex(hash, position, 1)
|
||||
if err != nil {
|
||||
return 0 // Maintain JavaScript compatibility: return 0 on error
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ExtractRotation extracts a rotation value from the hash at the specified position.
|
||||
// This is a convenience function that matches the JavaScript rotation logic.
|
||||
// Returns 0 on error to maintain compatibility with the JavaScript implementation.
|
||||
func ExtractRotation(hash string, position int) int {
|
||||
result, err := parseHex(hash, position, 1)
|
||||
if err != nil {
|
||||
return 0 // Maintain JavaScript compatibility: return 0 on error
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ExtractColorIndex extracts a color index from the hash at the specified position.
|
||||
// This is a convenience function that matches the JavaScript color selection logic.
|
||||
// Returns 0 on error to maintain compatibility with the JavaScript implementation.
|
||||
func ExtractColorIndex(hash string, position int, availableColors int) int {
|
||||
value, err := parseHex(hash, position, 1)
|
||||
if err != nil {
|
||||
return 0 // Maintain JavaScript compatibility: return 0 on error
|
||||
}
|
||||
if availableColors > 0 {
|
||||
return value % availableColors
|
||||
}
|
||||
return value
|
||||
}
|
||||
Reference in New Issue
Block a user