package captcha import ( "crypto/rand" "encoding/binary" "io" "sync" ) // siprng is PRNG based on SipHash-2-4. type siprng struct { mu sync.Mutex k0, k1, ctr uint64 } // siphash implements SipHash-2-4, accepting a uint64 as a message. func siphash(k0, k1, m uint64) uint64 { // Initialization. v0 := k0 ^ 0x736f6d6570736575 v1 := k1 ^ 0x646f72616e646f6d v2 := k0 ^ 0x6c7967656e657261 v3 := k1 ^ 0x7465646279746573 t := uint64(8) << 56 // Compression. v3 ^= m // Round 1. v0 += v1 v1 = v1<<13 | v1>>(64-13) v1 ^= v0 v0 = v0<<32 | v0>>(64-32) v2 += v3 v3 = v3<<16 | v3>>(64-16) v3 ^= v2 v0 += v3 v3 = v3<<21 | v3>>(64-21) v3 ^= v0 v2 += v1 v1 = v1<<17 | v1>>(64-17) v1 ^= v2 v2 = v2<<32 | v2>>(64-32) // Round 2. v0 += v1 v1 = v1<<13 | v1>>(64-13) v1 ^= v0 v0 = v0<<32 | v0>>(64-32) v2 += v3 v3 = v3<<16 | v3>>(64-16) v3 ^= v2 v0 += v3 v3 = v3<<21 | v3>>(64-21) v3 ^= v0 v2 += v1 v1 = v1<<17 | v1>>(64-17) v1 ^= v2 v2 = v2<<32 | v2>>(64-32) v0 ^= m // Compress last block. v3 ^= t // Round 1. v0 += v1 v1 = v1<<13 | v1>>(64-13) v1 ^= v0 v0 = v0<<32 | v0>>(64-32) v2 += v3 v3 = v3<<16 | v3>>(64-16) v3 ^= v2 v0 += v3 v3 = v3<<21 | v3>>(64-21) v3 ^= v0 v2 += v1 v1 = v1<<17 | v1>>(64-17) v1 ^= v2 v2 = v2<<32 | v2>>(64-32) // Round 2. v0 += v1 v1 = v1<<13 | v1>>(64-13) v1 ^= v0 v0 = v0<<32 | v0>>(64-32) v2 += v3 v3 = v3<<16 | v3>>(64-16) v3 ^= v2 v0 += v3 v3 = v3<<21 | v3>>(64-21) v3 ^= v0 v2 += v1 v1 = v1<<17 | v1>>(64-17) v1 ^= v2 v2 = v2<<32 | v2>>(64-32) v0 ^= t // Finalization. v2 ^= 0xff // Round 1. v0 += v1 v1 = v1<<13 | v1>>(64-13) v1 ^= v0 v0 = v0<<32 | v0>>(64-32) v2 += v3 v3 = v3<<16 | v3>>(64-16) v3 ^= v2 v0 += v3 v3 = v3<<21 | v3>>(64-21) v3 ^= v0 v2 += v1 v1 = v1<<17 | v1>>(64-17) v1 ^= v2 v2 = v2<<32 | v2>>(64-32) // Round 2. v0 += v1 v1 = v1<<13 | v1>>(64-13) v1 ^= v0 v0 = v0<<32 | v0>>(64-32) v2 += v3 v3 = v3<<16 | v3>>(64-16) v3 ^= v2 v0 += v3 v3 = v3<<21 | v3>>(64-21) v3 ^= v0 v2 += v1 v1 = v1<<17 | v1>>(64-17) v1 ^= v2 v2 = v2<<32 | v2>>(64-32) // Round 3. v0 += v1 v1 = v1<<13 | v1>>(64-13) v1 ^= v0 v0 = v0<<32 | v0>>(64-32) v2 += v3 v3 = v3<<16 | v3>>(64-16) v3 ^= v2 v0 += v3 v3 = v3<<21 | v3>>(64-21) v3 ^= v0 v2 += v1 v1 = v1<<17 | v1>>(64-17) v1 ^= v2 v2 = v2<<32 | v2>>(64-32) // Round 4. v0 += v1 v1 = v1<<13 | v1>>(64-13) v1 ^= v0 v0 = v0<<32 | v0>>(64-32) v2 += v3 v3 = v3<<16 | v3>>(64-16) v3 ^= v2 v0 += v3 v3 = v3<<21 | v3>>(64-21) v3 ^= v0 v2 += v1 v1 = v1<<17 | v1>>(64-17) v1 ^= v2 v2 = v2<<32 | v2>>(64-32) return v0 ^ v1 ^ v2 ^ v3 } // rekey sets a new PRNG key, which is read from crypto/rand. func (p *siprng) rekey() { var k [16]byte if _, err := io.ReadFull(rand.Reader, k[:]); err != nil { panic(err.Error()) } p.k0 = binary.LittleEndian.Uint64(k[0:8]) p.k1 = binary.LittleEndian.Uint64(k[8:16]) p.ctr = 1 } // Uint64 returns a new pseudorandom uint64. // It rekeys PRNG on the first call and every 64 MB of generated data. func (p *siprng) Uint64() uint64 { p.mu.Lock() if p.ctr == 0 || p.ctr > 8*1024*1024 { p.rekey() } v := siphash(p.k0, p.k1, p.ctr) p.ctr++ p.mu.Unlock() return v } func (p *siprng) Int63() int64 { return int64(p.Uint64() & 0x7fffffffffffffff) } func (p *siprng) Uint32() uint32 { return uint32(p.Uint64()) } func (p *siprng) Int31() int32 { return int32(p.Uint32() & 0x7fffffff) } func (p *siprng) Intn(n int) int { if n <= 0 { panic("invalid argument to Intn") } if n <= 1<<31-1 { return int(p.Int31n(int32(n))) } return int(p.Int63n(int64(n))) } func (p *siprng) Int63n(n int64) int64 { if n <= 0 { panic("invalid argument to Int63n") } max := int64((1 << 63) - 1 - (1<<63)%uint64(n)) v := p.Int63() for v > max { v = p.Int63() } return v % n } func (p *siprng) Int31n(n int32) int32 { if n <= 0 { panic("invalid argument to Int31n") } max := int32((1 << 31) - 1 - (1<<31)%uint32(n)) v := p.Int31() for v > max { v = p.Int31() } return v % n } func (p *siprng) Float64() float64 { return float64(p.Int63()) / (1 << 63) }