ugly server implementation without signature checks
[epoint] / logic / logic.go
diff --git a/logic/logic.go b/logic/logic.go
new file mode 100644 (file)
index 0000000..a9fd15b
--- /dev/null
@@ -0,0 +1,420 @@
+package logic
+
+// main transfer logic
+
+import (
+       "bytes"
+       "crypto/openpgp"
+       "epoint/document"
+       "epoint/store"
+       "fmt"
+       "time"
+)
+
+// TODO: do in docs?
+const IntLimit = 1e15
+
+var db *store.Conn
+
+func AddKeys(d []byte) (err error) {
+       entities, err := openpgp.ReadArmoredKeyRing(bytes.NewBuffer(d))
+       if err != nil {
+               return
+       }
+       // TODO: allow multiple key uploads at once?
+       if len(entities) > 100 {
+               err = fmt.Errorf("expected at most 100 keys; got %d", len(entities))
+               return
+       }
+       for _, e := range entities {
+               // TODO: various checks..
+               b := new(bytes.Buffer)
+               err = e.Serialize(b)
+               if err != nil {
+                       return
+               }
+               fpr := fmt.Sprintf("%040X", e.PrimaryKey.Fingerprint)
+               err = db.Set("key", fpr, b.Bytes())
+               if err != nil {
+                       return
+               }
+               err = db.Append("fprlist/64", fpr[len(fpr)-16:], []byte(fpr))
+               if err != nil {
+                       return
+               }
+               err = db.Append("fprlist/32", fpr[len(fpr)-8:], []byte(fpr))
+               if err != nil {
+                       return
+               }
+       }
+       return
+}
+
+func CertByDraft(draftid string) (d []byte, err error) {
+       certid, err := db.Get("certid/debit", draftid)
+       if err != nil {
+               // TODO: we have the draft but the cert is not ready
+               return
+       }
+       d, err = db.Get("cert", string(certid))
+       if err != nil {
+               // shouldn't happen, cert is not available
+               return
+       }
+       return
+}
+
+func CertByDebitCert(debitid string) (d []byte, err error) {
+       creditid, err := db.Get("certid/credit", debitid)
+       if err != nil {
+               // TODO: we have the debit cert but the credit cert is not ready
+               return
+       }
+       d, err = db.Get("cert", string(creditid))
+       if err != nil {
+               // shouldn't happen, cert is not available
+               return
+       }
+       return
+}
+
+// parse clear signed draft and verify it
+func ParseDraft(d []byte) (draft *document.Draft, draftid string, err error) {
+       iv, signed, err := document.Parse(d)
+       if err != nil {
+               return
+       }
+       draft, ok := iv.(*document.Draft)
+       if !ok {
+               err = fmt.Errorf("ParseDraft: expected a draft docuent")
+               return
+       }
+       draftid = document.Id(signed)
+/*
+       k, err := db.Get("key", draft.Drawer)
+       if err != nil {
+               return
+       }
+       kr, err := openpgp.ReadKeyRing(bytes.NewBuffer(k))
+       if err != nil {
+               // internal error: pubkey cannot be parsed
+               return
+       }
+       cleaned, err = document.Verify(signed, kr)
+       if err != nil {
+               return
+       }
+       // TODO: verify issuer
+       _, err = db.Get("key", draft.Beneficiary)
+       if err != nil {
+               return
+       }
+*/
+       // TODO: do various format checks (AuthorizedBy check etc)
+       if draft.Amount <= 0 || draft.Amount >= IntLimit {
+               err = fmt.Errorf("draft amount is invalid: %d", draft.Amount)
+               return
+       }
+       return
+}
+
+func ParseDebitCert(d []byte) (cert *document.DebitCert, certid string, err error) {
+       iv, signed, err := document.Parse(d)
+       if err != nil {
+               return
+       }
+       cert, ok := iv.(*document.DebitCert)
+       if !ok {
+               err = fmt.Errorf("ParseDebitCert: expected a debit docuent")
+               return
+       }
+/*
+       // TODO: keep our key at hand
+       k, err := db.Get("key", cert.AuthorizedBy)
+       if err != nil {
+               return
+       }
+       kr, err := openpgp.ReadKeyRing(bytes.NewBuffer(k))
+       if err != nil {
+               // internal error: pubkey cannot be parsed
+               return
+       }
+       // must clean up to make sure the hash is ok
+       cleaned, err = document.Verify(signed, kr)
+       if err != nil {
+               return
+       }
+*/
+       certid = document.Id(signed)
+       return
+}
+
+func NewDebitCert(draftid string, draft *document.Draft) (*document.DebitCert, error) {
+       cert := new(document.DebitCert)
+       cert.Holder = draft.Drawer
+       cert.Date = time.Seconds()
+       cert.Denomination = "epoint"
+       cert.Issuer = draft.Issuer
+       cert.AuthorizedBy = draft.AuthorizedBy
+       cert.Difference = -draft.Amount
+       cert.Draft = draftid
+       cert.Beneficiary = draft.Beneficiary
+
+       oid, err := db.Get("certid/last", draft.Drawer)
+       oldcertid := string(oid)
+       if err != nil {
+               // first cert: drawer is issuer
+               if draft.Drawer != draft.Issuer {
+                       return nil, fmt.Errorf("drawer must be the issuer when drawing an empty account")
+               }
+               cert.Serial = 1
+               cert.Balance = cert.Difference
+               cert.LastDebitSerial = 0
+               cert.LastCreditSerial = 0
+       } else {
+               d, err := db.Get("cert", oldcertid)
+               if err != nil {
+                       return nil, err
+               }
+               iv, _, err := document.Parse(d)
+               if err != nil {
+                       // internal error
+                       return nil, err
+               }
+               // TODO: this is a hack
+               oldcert, err := document.ToCert(iv)
+               // TODO: sanity checks? oldcert.Holder == draft.Drawer
+               cert.Serial = oldcert.Serial + 1
+               cert.Balance = oldcert.Balance + cert.Difference
+               if cert.Balance <= -IntLimit {
+                       return nil, fmt.Errorf("balance limit exceeded: %d", cert.Balance)
+               }
+               if oldcert.Balance > 0 && cert.Balance < 0 {
+                       return nil, fmt.Errorf("insufficient funds: %d", oldcert.Balance)
+               }
+               cert.LastDebitSerial = oldcert.LastDebitSerial
+               cert.LastCreditSerial = oldcert.LastCreditSerial
+               if oldcert.IsDebit {
+                       cert.LastDebitSerial = oldcert.Serial
+               } else {
+                       cert.LastCreditSerial = oldcert.Serial
+               }
+               cert.LastCert = &oldcertid
+       }
+       return cert, nil
+}
+
+func NewCreditCert(draftid string, draft *document.Draft, dcertid string, dcert *document.DebitCert) (*document.CreditCert, error) {
+       cert := new(document.CreditCert)
+       // TODO: get from old cert instead?
+       cert.Holder = dcert.Beneficiary
+       cert.Date = time.Seconds()
+       // TODO: get these from the cert holder pubkey
+       cert.Denomination = "epoint"
+       cert.Issuer = draft.Issuer
+       cert.AuthorizedBy = dcert.AuthorizedBy // TODO: draft vs dcert vs serverside decision
+       cert.Difference = -dcert.Difference
+       cert.Draft = draftid
+       cert.Drawer = dcert.Holder
+       cert.DebitCert = dcertid
+
+       oid, err := db.Get("certid/last", dcert.Beneficiary)
+       oldcertid := string(oid)
+       if err != nil {
+               // this is the first cert
+               cert.Serial = 1
+               cert.Balance = cert.Difference
+               cert.LastDebitSerial = 0
+               cert.LastCreditSerial = 0
+       } else {
+               d, err := db.Get("cert", oldcertid)
+               if err != nil {
+                       // internal error
+                       return nil, err
+               }
+               iv, _, err := document.Parse(d)
+               if err != nil {
+                       // internal error
+                       return nil, err
+               }
+               // TODO: this is a hack
+               oldcert, err := document.ToCert(iv)
+               if err != nil {
+                       // internal error
+                       return nil, err
+               }
+               cert.Serial = oldcert.Serial + 1
+               cert.Balance = oldcert.Balance + cert.Difference
+               if cert.Balance >= IntLimit {
+                       return nil, fmt.Errorf("balance limit exceeded: %d", cert.Balance)
+               }
+               cert.LastDebitSerial = oldcert.LastDebitSerial
+               cert.LastCreditSerial = oldcert.LastCreditSerial
+               if oldcert.IsDebit {
+                       cert.LastDebitSerial = oldcert.Serial
+               } else {
+                       cert.LastCreditSerial = oldcert.Serial
+               }
+               cert.LastCert = &oldcertid
+       }
+       return cert, nil
+}
+
+// TODO: draft ref
+func EvalDraft(d []byte, sk *openpgp.Entity) (r []byte, err error) {
+       draft, draftid, err := ParseDraft(d)
+       if err != nil {
+               return
+       }
+       _, err = db.Get("draft", draftid)
+       if err == nil {
+               // found
+               return CertByDraft(draftid)
+       }
+       // if draft is ok we save it
+       err = db.Set("draft", draftid, d)
+       if err != nil {
+               // internal error
+               return
+       }
+
+       // debit cert
+       cert, err := NewDebitCert(draftid, draft)
+       if err != nil {
+               return
+       }
+       r, signed, err := document.Format(cert, sk)
+       certid := document.Id(signed)
+       err = db.Set("cert", certid, r)
+       if err != nil {
+               // internal error
+               return
+       }
+       err = db.Set("certid/debit", draftid, []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       err = db.Set("certid/last", cert.Holder, []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       // TODO: append?
+       err = db.Set("certid/all", fmt.Sprintf("%s.%09d", cert.Holder, cert.Serial), []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       return
+}
+
+func EvalDebitCert(d []byte, sk *openpgp.Entity) (r []byte, err error) {
+       dcert, dcertid, err := ParseDebitCert(d)
+       if err != nil {
+               return
+       }
+       r, err = CertByDebitCert(dcertid)
+       if err == nil {
+               // found
+               return
+       }
+       // TODO: we only need the draft to know the issuer (+beneficiary)
+       // it should be in the pubkey
+       d, err = db.Get("draft", dcert.Draft)
+       if err != nil {
+               // internal error
+               return
+       }
+       iv, _, err := document.Parse(d)
+       if err != nil {
+               // internal error
+               return
+       }
+       draft, ok := iv.(*document.Draft)
+       if !ok {
+               // internal error
+               err = fmt.Errorf("EvalDebitCert: expected draft from internal db")
+               return
+       }
+
+       // credit side
+       // TODO: check pubkey etc
+       cert, err := NewCreditCert(dcert.Draft, draft, dcertid, dcert)
+       if err != nil {
+               // internal error
+               return
+       }
+       r, signed, err := document.Format(cert, sk)
+       if err != nil {
+               // internal error
+               return
+       }
+       certid := document.Id(signed)
+       err = db.Set("cert", certid, r)
+       if err != nil {
+               // internal error
+               return
+       }
+       err = db.Set("certid/credit", dcertid, []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       err = db.Set("certid/last", cert.Holder, []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       // TODO: append?
+       err = db.Set("certid/all", fmt.Sprintf("%s.%09d", cert.Holder, cert.Serial), []byte(certid))
+       if err != nil {
+               // internal error
+               return
+       }
+       return
+}
+
+func Init() (err error) {
+       db, err = store.Open("teststore")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("key")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("cert")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("draft")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("certid/credit")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("certid/debit")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("certid/last")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("certid/all")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("fprlist/64")
+       if err != nil {
+               return
+       }
+       err = db.Ensure("fprlist/32")
+       if err != nil {
+               return
+       }
+       return
+}