qcc/internal/auth/pow.go
2026-06-30 12:52:09 +03:00

96 lines
2.1 KiB
Go

package auth
import (
"crypto/sha256"
"encoding/binary"
"fmt"
"math/bits"
"time"
)
type PoWChallenge struct {
ServerNonce [8]byte
Timestamp int64
Bits int
}
type PoWSolution struct {
ClientNonce [8]byte
ClientPubKey [32]byte
}
func NewChallenge(bits int) *PoWChallenge {
var nonce [8]byte
binary.BigEndian.PutUint64(nonce[:], uint64(time.Now().UnixNano()>>10))
return &PoWChallenge{
ServerNonce: nonce,
Timestamp: time.Now().UnixNano(),
Bits: bits,
}
}
func (c *PoWChallenge) Verify(solution *PoWSolution, challengeTTL time.Duration) error {
if time.Since(time.Unix(0, c.Timestamp)) > challengeTTL {
return fmt.Errorf("challenge expired")
}
hashInput := make([]byte, 32)
binary.BigEndian.PutUint64(hashInput[0:8], uint64(c.Timestamp))
copy(hashInput[8:16], c.ServerNonce[:])
copy(hashInput[16:24], solution.ClientNonce[:])
copy(hashInput[24:], solution.ClientPubKey[:])
hash := sha256.Sum256(hashInput)
leadingZeros := 0
for i := 0; i < 32; i++ {
if hash[i] == 0 {
leadingZeros += 8
} else {
leadingZeros += bits.LeadingZeros8(hash[i])
break
}
}
if leadingZeros < c.Bits {
return fmt.Errorf("insufficient PoW difficulty: got %d, need %d", leadingZeros, c.Bits)
}
return nil
}
func (c *PoWChallenge) Marshal() []byte {
buf := make([]byte, 20)
copy(buf[0:8], c.ServerNonce[:])
binary.BigEndian.PutUint64(buf[8:16], uint64(c.Timestamp))
binary.BigEndian.PutUint32(buf[16:20], uint32(c.Bits))
return buf
}
func UnmarshalChallenge(data []byte) *PoWChallenge {
if len(data) < 20 {
return nil
}
c := &PoWChallenge{}
copy(c.ServerNonce[:], data[0:8])
c.Timestamp = int64(binary.BigEndian.Uint64(data[8:16]))
c.Bits = int(binary.BigEndian.Uint32(data[16:20]))
return c
}
func MarshalSolution(nonce [8]byte, pubKey [32]byte) []byte {
buf := make([]byte, 40)
copy(buf[0:8], nonce[:])
copy(buf[8:40], pubKey[:])
return buf
}
func UnmarshalSolution(data []byte) *PoWSolution {
if len(data) < 40 {
return nil
}
s := &PoWSolution{}
copy(s.ClientNonce[:], data[0:8])
copy(s.ClientPubKey[:], data[8:40])
return s
}