diff --git a/consensus/pow.go b/consensus/pow.go new file mode 100644 index 0000000..76053f6 --- /dev/null +++ b/consensus/pow.go @@ -0,0 +1,55 @@ +// Copyright (c) 2017-2026 Lethean (https://lt.hn) +// +// Licensed under the European Union Public Licence (EUPL) version 1.2. +// SPDX-License-Identifier: EUPL-1.2 + +package consensus + +import ( + "encoding/binary" + "math/big" + + "forge.lthn.ai/core/go-blockchain/crypto" + "forge.lthn.ai/core/go-blockchain/types" +) + +// maxTarget is 2^256, used for difficulty comparison. +var maxTarget = new(big.Int).Lsh(big.NewInt(1), 256) + +// CheckDifficulty returns true if hash meets the given difficulty target. +// The hash (interpreted as a 256-bit little-endian number) must be less +// than maxTarget / difficulty. +func CheckDifficulty(hash types.Hash, difficulty uint64) bool { + if difficulty == 0 { + return true + } + + // Convert hash to big.Int (little-endian as per CryptoNote convention). + // Reverse to big-endian for big.Int. + var be [32]byte + for i := 0; i < 32; i++ { + be[i] = hash[31-i] + } + hashInt := new(big.Int).SetBytes(be[:]) + + target := new(big.Int).Div(maxTarget, new(big.Int).SetUint64(difficulty)) + + return hashInt.Cmp(target) < 0 +} + +// CheckPoWHash computes the RandomX hash of a block header hash + nonce +// and checks it against the difficulty target. +func CheckPoWHash(headerHash types.Hash, nonce, difficulty uint64) (bool, error) { + // Build input: header_hash (32 bytes) || nonce (8 bytes LE). + var input [40]byte + copy(input[:32], headerHash[:]) + binary.LittleEndian.PutUint64(input[32:], nonce) + + key := []byte("LetheanRandomXv1") + powHash, err := crypto.RandomXHash(key, input[:]) + if err != nil { + return false, err + } + + return CheckDifficulty(types.Hash(powHash), difficulty), nil +} diff --git a/consensus/pow_test.go b/consensus/pow_test.go new file mode 100644 index 0000000..dc361f6 --- /dev/null +++ b/consensus/pow_test.go @@ -0,0 +1,25 @@ +//go:build !integration + +package consensus + +import ( + "testing" + + "forge.lthn.ai/core/go-blockchain/types" + "github.com/stretchr/testify/assert" +) + +func TestCheckDifficulty_Good(t *testing.T) { + // A zero hash meets any difficulty. + hash := types.Hash{} + assert.True(t, CheckDifficulty(hash, 1)) +} + +func TestCheckDifficulty_Bad(t *testing.T) { + // Max hash (all 0xFF) should fail high difficulty. + hash := types.Hash{} + for i := range hash { + hash[i] = 0xFF + } + assert.False(t, CheckDifficulty(hash, ^uint64(0))) +}