main.go (view raw)
1package main
2
3import (
4 "bytes"
5 "crypto/rand"
6 "crypto/sha512"
7 "errors"
8 "fmt"
9 "golang.org/x/crypto/nacl/box"
10 "io"
11 mathRand "math/rand"
12)
13
14func main() {
15 numberParticipants := 4
16
17 // Generate n public/private key pairs
18 pubs, privs, err := genKeyPairs(numberParticipants)
19 if err != nil {
20 fmt.Printf("generating key pairs: %s", err.Error())
21 return
22 }
23
24 // Protocol step 1
25 // Generate a random string w
26 w := make([]byte, 8)
27 _, err = rand.Read(w)
28 if err != nil {
29 fmt.Printf("generating random w: %s", err.Error())
30 return
31 }
32 // add magic bytes to identify correct w on client side
33 w = append([]byte("li"), w...)
34 fmt.Printf("Server random string w: %x\n", w)
35
36 // For every participant, generate a random coin
37 coins := make([][]byte, numberParticipants)
38 for i := 0; i < numberParticipants; i++ {
39 c := make([]byte, 8)
40 _, err = rand.Read(c)
41 if err != nil {
42 fmt.Printf("generating random coin: %s", err.Error())
43 return
44 }
45 coins[i] = c
46 }
47
48 // Encrypt w with coin for every public key
49 encMessages, err := encrypt(pubs, w, coins)
50 if err != nil {
51 fmt.Printf("encrypt w and coin: %s\n", err.Error())
52 return
53 }
54
55 // Protocol step 2
56 // prepare client
57 c := client{
58 allPublicKeys: pubs,
59 privateKey: privs[mathRand.Intn(numberParticipants)], // Choose random participant to proof anonymity
60 encMessages: encMessages,
61 }
62
63 wDash, err := c.decrypt()
64 if err != nil {
65 fmt.Printf("client-side decrypt: %s\n", err.Error())
66 return
67 }
68 fmt.Printf("Client w': %x\n", wDash)
69
70 // Protocol step 3
71 // compare w and wDash
72 if !bytes.Equal(w, wDash) {
73 fmt.Println("Access denied")
74 return
75 }
76 fmt.Println("Access granted")
77
78 // Protocol step 4
79 // Verify server did not cheat on client
80 // insert the following code snippet to pretend a cheat
81 // coins[0] = []byte("cheat")
82 err = c.verify(coins)
83 if err != nil {
84 fmt.Printf("client verification: %s\n", err.Error())
85 return
86 }
87 fmt.Println("Verification successful")
88}
89
90type client struct {
91 allPublicKeys []*[32]byte
92 privateKey *[32]byte
93 encMessages [][]byte
94 wDash []byte
95}
96
97func genKeyPairs(n int) ([]*[32]byte, []*[32]byte, error) {
98 pubOut := make([]*[32]byte, n)
99 privOut := make([]*[32]byte, n)
100 for i := 0; i < n; i++ {
101 pub, priv, err := box.GenerateKey(rand.Reader)
102 if err != nil {
103 return nil, nil, err
104 }
105 pubOut[i] = pub
106 privOut[i] = priv
107 }
108 return pubOut, privOut, nil
109}
110
111func encrypt(pubs []*[32]byte, w []byte, coins [][]byte) ([][]byte, error) {
112 if len(pubs) != len(coins) {
113 return nil, errors.New("length public keys and coins do not match")
114 }
115
116 out := make([][]byte, len(pubs))
117 for cnt, p := range pubs {
118 enc, err := box.SealAnonymous(nil, append(w, coins[cnt]...), p, newDetermRand([]byte("deterministic")))
119 if err != nil {
120 return nil, err
121 }
122 out[cnt] = enc
123 }
124
125 return out, nil
126}
127
128func (c *client) decrypt() ([]byte, error) {
129 if len(c.allPublicKeys) != len(c.encMessages) {
130 return nil, errors.New("length of public keys and encrypted messages do not match")
131 }
132
133 for cnt, p := range c.allPublicKeys {
134 decrypted, ok := box.OpenAnonymous(nil, c.encMessages[cnt], p, c.privateKey)
135 if !ok {
136 continue
137 }
138 // Verify magic byte
139 if bytes.HasPrefix(decrypted, []byte("li")) {
140 c.wDash = decrypted[:10]
141 return decrypted[:10], nil
142 }
143 }
144
145 return nil, errors.New("decryption not possible")
146}
147
148func (c client) verify(coins [][]byte) error {
149 if len(c.allPublicKeys) != len(coins) || len(coins) != len(c.encMessages) {
150 return errors.New("length of public keys, encrypted messages and coins does not match")
151 }
152
153 for cnt, p := range c.allPublicKeys {
154 enc, err := box.SealAnonymous(nil, append(c.wDash, coins[cnt]...), p, newDetermRand([]byte("deterministic")))
155 if err != nil {
156 return err
157 }
158 if !bytes.Equal(enc, c.encMessages[cnt]) {
159 fmt.Println("CHEAT")
160 return errors.New("it appears the server cheated on us")
161 // The server could de-anonymize us by putting a different w for every encrypted message.
162 // By sending back our personalized w, the server knows our public key and hence our identity.
163 // To verify the server did not cheat, check that all encrypted messages contain the same value for w.
164 }
165 }
166 return nil
167}
168
169// As suggested by jpillora https://gist.github.com/jpillora/5a0471b246d541b984ab
170func newDetermRand(seed []byte) io.Reader {
171 return &determRand{next: seed}
172}
173
174type determRand struct {
175 next []byte
176}
177
178func (d *determRand) cycle() []byte {
179 result := sha512.Sum512(d.next)
180 d.next = result[:sha512.Size/2]
181 return result[sha512.Size/2:]
182}
183
184func (d *determRand) Read(b []byte) (int, error) {
185 n := 0
186 for n < len(b) {
187 out := d.cycle()
188 n += copy(b[n:], out)
189 }
190 return n, nil
191}