96 lines
2.1 KiB
Go
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
|
|
}
|