cleanups
[epoint] / logic / logic.go
1 package logic
2
3 // main transfer logic
4
5 import (
6         "bytes"
7         "crypto/openpgp"
8         "epoint/document"
9         "epoint/store"
10         "fmt"
11         "time"
12 )
13
14 // TODO: do in docs?
15 const IntLimit = 1e15
16
17 var db *store.Conn
18
19 func AddKeys(d []byte) (err error) {
20         entities, err := openpgp.ReadArmoredKeyRing(bytes.NewBuffer(d))
21         if err != nil {
22                 return
23         }
24         // TODO: allow multiple key uploads at once?
25         if len(entities) > 100 {
26                 err = fmt.Errorf("expected at most 100 keys; got %d", len(entities))
27                 return
28         }
29         for _, e := range entities {
30                 // TODO: various checks..
31                 b := new(bytes.Buffer)
32                 err = e.Serialize(b)
33                 if err != nil {
34                         return
35                 }
36                 fpr := fmt.Sprintf("%040X", e.PrimaryKey.Fingerprint)
37                 err = db.Set("key", fpr, b.Bytes())
38                 if err != nil {
39                         return
40                 }
41                 err = db.Append("keysby/64", fpr[len(fpr)-16:], []byte(fpr))
42                 if err != nil {
43                         return
44                 }
45                 err = db.Append("keysby/32", fpr[len(fpr)-8:], []byte(fpr))
46                 if err != nil {
47                         return
48                 }
49         }
50         return
51 }
52
53 func CertByDraft(draftid string) (d []byte, err error) {
54         certid, err := db.Get("certby/draft", draftid)
55         if err != nil {
56                 // TODO: we have the draft but the cert is not ready
57                 return
58         }
59         d, err = db.Get("cert", string(certid))
60         if err != nil {
61                 // shouldn't happen, cert is not available
62                 return
63         }
64         return
65 }
66
67 func CertByDebitCert(debitid string) (d []byte, err error) {
68         creditid, err := db.Get("certby/debit", debitid)
69         if err != nil {
70                 // TODO: we have the debit cert but the credit cert is not ready
71                 return
72         }
73         d, err = db.Get("cert", string(creditid))
74         if err != nil {
75                 // shouldn't happen, cert is not available
76                 return
77         }
78         return
79 }
80
81 // parse clear signed draft and verify it
82 func ParseDraft(d []byte) (draft *document.Draft, draftid string, err error) {
83         iv, signed, err := document.Parse(d)
84         if err != nil {
85                 return
86         }
87         draft, ok := iv.(*document.Draft)
88         if !ok {
89                 err = fmt.Errorf("ParseDraft: expected a draft docuent")
90                 return
91         }
92         draftid = document.Id(signed)
93         /*
94                 k, err := db.Get("key", draft.Drawer)
95                 if err != nil {
96                         return
97                 }
98                 kr, err := openpgp.ReadKeyRing(bytes.NewBuffer(k))
99                 if err != nil {
100                         // internal error: pubkey cannot be parsed
101                         return
102                 }
103                 cleaned, err = document.Verify(signed, kr)
104                 if err != nil {
105                         return
106                 }
107                 // TODO: verify issuer
108                 _, err = db.Get("key", draft.Beneficiary)
109                 if err != nil {
110                         return
111                 }
112         */
113         // TODO: do various format checks (AuthorizedBy check etc)
114         if draft.Amount <= 0 || draft.Amount >= IntLimit {
115                 err = fmt.Errorf("draft amount is invalid: %d", draft.Amount)
116                 return
117         }
118         return
119 }
120
121 func ParseDebitCert(d []byte) (cert *document.DebitCert, certid string, err error) {
122         iv, signed, err := document.Parse(d)
123         if err != nil {
124                 return
125         }
126         cert, ok := iv.(*document.DebitCert)
127         if !ok {
128                 err = fmt.Errorf("ParseDebitCert: expected a debit docuent")
129                 return
130         }
131         /*
132                 // TODO: keep our key at hand
133                 k, err := db.Get("key", cert.AuthorizedBy)
134                 if err != nil {
135                         return
136                 }
137                 kr, err := openpgp.ReadKeyRing(bytes.NewBuffer(k))
138                 if err != nil {
139                         // internal error: pubkey cannot be parsed
140                         return
141                 }
142                 // must clean up to make sure the hash is ok
143                 cleaned, err = document.Verify(signed, kr)
144                 if err != nil {
145                         return
146                 }
147         */
148         certid = document.Id(signed)
149         return
150 }
151
152 func NewDebitCert(draftid string, draft *document.Draft) (*document.DebitCert, error) {
153         cert := new(document.DebitCert)
154         cert.Holder = draft.Drawer
155         cert.Date = time.Seconds()
156         cert.Denomination = "epoint"
157         cert.Issuer = draft.Issuer
158         cert.AuthorizedBy = draft.AuthorizedBy
159         cert.Difference = -draft.Amount
160         cert.Draft = draftid
161         cert.Beneficiary = draft.Beneficiary
162
163         oid, err := db.Get("certby/key", draft.Drawer)
164         oldcertid := string(oid)
165         if err != nil {
166                 // first cert: drawer is issuer
167                 if draft.Drawer != draft.Issuer {
168                         return nil, fmt.Errorf("drawer must be the issuer when drawing an empty account")
169                 }
170                 cert.Serial = 1
171                 cert.Balance = cert.Difference
172                 cert.LastDebitSerial = 0
173                 cert.LastCreditSerial = 0
174         } else {
175                 d, err := db.Get("cert", oldcertid)
176                 if err != nil {
177                         return nil, err
178                 }
179                 iv, _, err := document.Parse(d)
180                 if err != nil {
181                         // internal error
182                         return nil, err
183                 }
184                 // TODO: this is a hack
185                 oldcert, err := document.ToCert(iv)
186                 // TODO: sanity checks? oldcert.Holder == draft.Drawer
187                 cert.Serial = oldcert.Serial + 1
188                 cert.Balance = oldcert.Balance + cert.Difference
189                 if cert.Balance <= -IntLimit {
190                         return nil, fmt.Errorf("balance limit exceeded: %d", cert.Balance)
191                 }
192                 if oldcert.Balance > 0 && cert.Balance < 0 {
193                         return nil, fmt.Errorf("insufficient funds: %d", oldcert.Balance)
194                 }
195                 cert.LastDebitSerial = oldcert.LastDebitSerial
196                 cert.LastCreditSerial = oldcert.LastCreditSerial
197                 if oldcert.IsDebit {
198                         cert.LastDebitSerial = oldcert.Serial
199                 } else {
200                         cert.LastCreditSerial = oldcert.Serial
201                 }
202                 cert.LastCert = &oldcertid
203         }
204         return cert, nil
205 }
206
207 func NewCreditCert(draftid string, draft *document.Draft, dcertid string, dcert *document.DebitCert) (*document.CreditCert, error) {
208         cert := new(document.CreditCert)
209         // TODO: get from old cert instead?
210         cert.Holder = dcert.Beneficiary
211         cert.Date = time.Seconds()
212         // TODO: get these from the cert holder pubkey
213         cert.Denomination = "epoint"
214         cert.Issuer = draft.Issuer
215         cert.AuthorizedBy = dcert.AuthorizedBy // TODO: draft vs dcert vs serverside decision
216         cert.Difference = -dcert.Difference
217         cert.Draft = draftid
218         cert.Drawer = dcert.Holder
219         cert.DebitCert = dcertid
220
221         oid, err := db.Get("certby/key", dcert.Beneficiary)
222         oldcertid := string(oid)
223         if err != nil {
224                 // this is the first cert
225                 cert.Serial = 1
226                 cert.Balance = cert.Difference
227                 cert.LastDebitSerial = 0
228                 cert.LastCreditSerial = 0
229         } else {
230                 d, err := db.Get("cert", oldcertid)
231                 if err != nil {
232                         // internal error
233                         return nil, err
234                 }
235                 iv, _, err := document.Parse(d)
236                 if err != nil {
237                         // internal error
238                         return nil, err
239                 }
240                 // TODO: this is a hack
241                 oldcert, err := document.ToCert(iv)
242                 if err != nil {
243                         // internal error
244                         return nil, err
245                 }
246                 cert.Serial = oldcert.Serial + 1
247                 cert.Balance = oldcert.Balance + cert.Difference
248                 if cert.Balance >= IntLimit {
249                         return nil, fmt.Errorf("balance limit exceeded: %d", cert.Balance)
250                 }
251                 cert.LastDebitSerial = oldcert.LastDebitSerial
252                 cert.LastCreditSerial = oldcert.LastCreditSerial
253                 if oldcert.IsDebit {
254                         cert.LastDebitSerial = oldcert.Serial
255                 } else {
256                         cert.LastCreditSerial = oldcert.Serial
257                 }
258                 cert.LastCert = &oldcertid
259         }
260         return cert, nil
261 }
262
263 func EvalDraft(d []byte, sk *openpgp.Entity) (r []byte, err error) {
264         draft, draftid, err := ParseDraft(d)
265         if err != nil {
266                 return
267         }
268         _, err = db.Get("draft", draftid)
269         if err == nil {
270                 // found
271                 return CertByDraft(draftid)
272         }
273         // if draft is ok we save it
274         err = db.Set("draft", draftid, d)
275         if err != nil {
276                 // internal error
277                 return
278         }
279         // TODO: db.Insert: fails if key exists
280         _, err = db.Get("draftby/nonce", draft.Nonce)
281         if err == nil {
282                 err = fmt.Errorf("draft nonce is not unique")
283                 return
284         }
285         err = db.Set("draftby/nonce", draft.Nonce, d)
286         if err != nil {
287                 // internal error
288                 return
289         }
290
291         // debit cert
292         cert, err := NewDebitCert(draftid, draft)
293         if err != nil {
294                 return
295         }
296         r, signed, err := document.Format(cert, sk)
297         certid := document.Id(signed)
298         err = db.Set("cert", certid, r)
299         if err != nil {
300                 // internal error
301                 return
302         }
303         err = db.Set("certby/draft", draftid, []byte(certid))
304         if err != nil {
305                 // internal error
306                 return
307         }
308         err = db.Set("certby/key", cert.Holder, []byte(certid))
309         if err != nil {
310                 // internal error
311                 return
312         }
313         // TODO: append?
314         err = db.Set("certby/key.serial", fmt.Sprintf("%s.%09d", cert.Holder, cert.Serial), []byte(certid))
315         if err != nil {
316                 // internal error
317                 return
318         }
319         return
320 }
321
322 func EvalDebitCert(d []byte, sk *openpgp.Entity) (r []byte, err error) {
323         dcert, dcertid, err := ParseDebitCert(d)
324         if err != nil {
325                 return
326         }
327         r, err = CertByDebitCert(dcertid)
328         if err == nil {
329                 // found
330                 return
331         }
332         // TODO: we only need the draft to know the issuer (+beneficiary)
333         // it should be in the pubkey
334         d, err = db.Get("draft", dcert.Draft)
335         if err != nil {
336                 // internal error
337                 return
338         }
339         iv, _, err := document.Parse(d)
340         if err != nil {
341                 // internal error
342                 return
343         }
344         draft, ok := iv.(*document.Draft)
345         if !ok {
346                 // internal error
347                 err = fmt.Errorf("EvalDebitCert: expected draft from internal db")
348                 return
349         }
350
351         // credit side
352         // TODO: check pubkey etc
353         cert, err := NewCreditCert(dcert.Draft, draft, dcertid, dcert)
354         if err != nil {
355                 // internal error
356                 return
357         }
358         r, signed, err := document.Format(cert, sk)
359         if err != nil {
360                 // internal error
361                 return
362         }
363         certid := document.Id(signed)
364         err = db.Set("cert", certid, r)
365         if err != nil {
366                 // internal error
367                 return
368         }
369         err = db.Set("certby/debit", dcertid, []byte(certid))
370         if err != nil {
371                 // internal error
372                 return
373         }
374         err = db.Set("certby/key", cert.Holder, []byte(certid))
375         if err != nil {
376                 // internal error
377                 return
378         }
379         // TODO: append?
380         err = db.Set("certby/key.serial", fmt.Sprintf("%s.%09d", cert.Holder, cert.Serial), []byte(certid))
381         if err != nil {
382                 // internal error
383                 return
384         }
385         return
386 }
387
388 func Init(rootdir string) (err error) {
389         db, err = store.Open(rootdir)
390         if err != nil {
391                 return
392         }
393         err = db.Ensure("key")
394         if err != nil {
395                 return
396         }
397         err = db.Ensure("cert")
398         if err != nil {
399                 return
400         }
401         err = db.Ensure("draft")
402         if err != nil {
403                 return
404         }
405         err = db.Ensure("certby/draft")
406         if err != nil {
407                 return
408         }
409         err = db.Ensure("certby/debit")
410         if err != nil {
411                 return
412         }
413         err = db.Ensure("certby/key")
414         if err != nil {
415                 return
416         }
417         err = db.Ensure("certby/key.serial")
418         if err != nil {
419                 return
420         }
421         err = db.Ensure("draftby/nonce")
422         if err != nil {
423                 return
424         }
425         err = db.Ensure("keysby/64")
426         if err != nil {
427                 return
428         }
429         err = db.Ensure("keysby/32")
430         if err != nil {
431                 return
432         }
433         return
434 }