package neoircapi import ( "crypto/rand" "crypto/sha256" "encoding/hex" "fmt" "math/big" "time" ) const ( // bitsPerByte is the number of bits in a byte. bitsPerByte = 8 // fullByteMask is 0xFF, a mask for all bits in a byte. fullByteMask = 0xFF // counterSpace is the range for random counter seeds. counterSpace = 1 << 48 ) // MintHashcash computes a hashcash stamp with the given // difficulty (leading zero bits) and resource string. func MintHashcash(bits int, resource string) string { date := time.Now().UTC().Format("060102") prefix := fmt.Sprintf( "1:%d:%s:%s::", bits, date, resource, ) for { counter := randomCounter() stamp := prefix + counter hash := sha256.Sum256([]byte(stamp)) if hasLeadingZeroBits(hash[:], bits) { return stamp } } } // hasLeadingZeroBits checks if hash has at least numBits // leading zero bits. func hasLeadingZeroBits( hash []byte, numBits int, ) bool { fullBytes := numBits / bitsPerByte remainBits := numBits % bitsPerByte for idx := range fullBytes { if hash[idx] != 0 { return false } } if remainBits > 0 && fullBytes < len(hash) { mask := byte( fullByteMask << (bitsPerByte - remainBits), ) if hash[fullBytes]&mask != 0 { return false } } return true } // randomCounter generates a random hex counter string. func randomCounter() string { counterVal, err := rand.Int( rand.Reader, big.NewInt(counterSpace), ) if err != nil { // Fallback to timestamp-based counter on error. return fmt.Sprintf("%x", time.Now().UnixNano()) } return hex.EncodeToString(counterVal.Bytes()) }