package console // Proof of work for Keymail federation, ported from keymail // internal/pow/pow.go and the nonce rule in federation.go handleRegister. // An unknown sender pays it once per recipient mailbox to register its // signing key; after that, deliveries from the same key are free. // // nonce = SHA-256(challenge token) // find counter: SHA-256(nonce || sign_pub_raw || counter_be64) has >= bits leading zero bits import ( "context" "crypto/sha256" "encoding/binary" "math/bits" ) // maxFederationPowBits is the highest price the console will pay. A mailbox // sets its own price, and an invite runs inside an HTTP request: 24 bits is // about sixteen million hashes, a few seconds on one core. Keymail's default // is 20. const maxFederationPowBits = 24 func powLeadingZeroBits(h []byte) int { n := 0 for _, b := range h { if b == 0 { n += 8 continue } return n + bits.LeadingZeros8(b) } return n } func powVerify(nonce, signPub []byte, counter uint64, difficulty int) bool { if difficulty <= 0 { return true } h := sha256.New() h.Write(nonce) h.Write(signPub) var c [8]byte binary.BigEndian.PutUint64(c[:], counter) h.Write(c[:]) return powLeadingZeroBits(h.Sum(nil)) >= difficulty } // powMine searches counters from 0 on one goroutine, checking ctx every // 65536 attempts. One core is enough at the prices the console accepts. func powMine(ctx context.Context, nonce, signPub []byte, difficulty int) (uint64, error) { if difficulty <= 0 { return 0, nil } prefix := make([]byte, 0, len(nonce)+len(signPub)+8) prefix = append(append(prefix, nonce...), signPub...) buf := append(prefix, make([]byte, 8)...) n := len(prefix) for counter := uint64(0); ; counter++ { if counter&0xffff == 0 { if err := ctx.Err(); err != nil { return 0, err } } binary.BigEndian.PutUint64(buf[n:], counter) sum := sha256.Sum256(buf) if powLeadingZeroBits(sum[:]) >= difficulty { return counter, nil } } }