| 1 | package console |
| 2 | |
| 3 | // Proof of work for Keymail federation, ported from keymail |
| 4 | // internal/pow/pow.go and the nonce rule in federation.go handleRegister. |
| 5 | // An unknown sender pays it once per recipient mailbox to register its |
| 6 | // signing key; after that, deliveries from the same key are free. |
| 7 | // |
| 8 | // nonce = SHA-256(challenge token) |
| 9 | // find counter: SHA-256(nonce || sign_pub_raw || counter_be64) has >= bits leading zero bits |
| 10 | |
| 11 | import ( |
| 12 | "context" |
| 13 | "crypto/sha256" |
| 14 | "encoding/binary" |
| 15 | "math/bits" |
| 16 | ) |
| 17 | |
| 18 | // maxFederationPowBits is the highest price the console will pay. A mailbox |
| 19 | // sets its own price, and an invite runs inside an HTTP request: 24 bits is |
| 20 | // about sixteen million hashes, a few seconds on one core. Keymail's default |
| 21 | // is 20. |
| 22 | const maxFederationPowBits = 24 |
| 23 | |
| 24 | func powLeadingZeroBits(h []byte) int { |
| 25 | n := 0 |
| 26 | for _, b := range h { |
| 27 | if b == 0 { |
| 28 | n += 8 |
| 29 | continue |
| 30 | } |
| 31 | return n + bits.LeadingZeros8(b) |
| 32 | } |
| 33 | return n |
| 34 | } |
| 35 | |
| 36 | func powVerify(nonce, signPub []byte, counter uint64, difficulty int) bool { |
| 37 | if difficulty <= 0 { |
| 38 | return true |
| 39 | } |
| 40 | h := sha256.New() |
| 41 | h.Write(nonce) |
| 42 | h.Write(signPub) |
| 43 | var c [8]byte |
| 44 | binary.BigEndian.PutUint64(c[:], counter) |
| 45 | h.Write(c[:]) |
| 46 | return powLeadingZeroBits(h.Sum(nil)) >= difficulty |
| 47 | } |
| 48 | |
| 49 | // powMine searches counters from 0 on one goroutine, checking ctx every |
| 50 | // 65536 attempts. One core is enough at the prices the console accepts. |
| 51 | func powMine(ctx context.Context, nonce, signPub []byte, difficulty int) (uint64, error) { |
| 52 | if difficulty <= 0 { |
| 53 | return 0, nil |
| 54 | } |
| 55 | prefix := make([]byte, 0, len(nonce)+len(signPub)+8) |
| 56 | prefix = append(append(prefix, nonce...), signPub...) |
| 57 | buf := append(prefix, make([]byte, 8)...) |
| 58 | n := len(prefix) |
| 59 | for counter := uint64(0); ; counter++ { |
| 60 | if counter&0xffff == 0 { |
| 61 | if err := ctx.Err(); err != nil { |
| 62 | return 0, err |
| 63 | } |
| 64 | } |
| 65 | binary.BigEndian.PutUint64(buf[n:], counter) |
| 66 | sum := sha256.Sum256(buf) |
| 67 | if powLeadingZeroBits(sum[:]) >= difficulty { |
| 68 | return counter, nil |
| 69 | } |
| 70 | } |
| 71 | } |
| 72 | |