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 }