aboutsummaryrefslogtreecommitdiff
path: root/dashboard
diff options
context:
space:
mode:
Diffstat (limited to 'dashboard')
-rw-r--r--dashboard/dashboard.go132
-rw-r--r--dashboard/inmemory.go187
2 files changed, 319 insertions, 0 deletions
diff --git a/dashboard/dashboard.go b/dashboard/dashboard.go
new file mode 100644
index 0000000..9b6dedd
--- /dev/null
+++ b/dashboard/dashboard.go
@@ -0,0 +1,132 @@
+package dashboard
+
+import (
+ "bytes"
+ "crypto/ed25519"
+ "encoding/base32"
+ "encoding/json"
+
+ "kesim.org/seal"
+ "kesim.org/seal/nizk/commit"
+)
+
+type Dashboard interface {
+ NewAuction(description *seal.SignedDescription) (auctionId string, e error)
+ Auctions(state AuctionState) map[string]*seal.SignedDescription
+ Join(auctionId string, commitment SignedCommitment, bidderId ed25519.PublicKey) error
+ Commitments(auctionId string) (bidders map[string]SignedCommitment, e error)
+ Publish(auctionId string, round uint8, message SignedMessage, bidderId ed25519.PublicKey) error
+ Messages(auctionId string, round uint8) (messages map[string]SignedMessage, e error)
+ Cancel(auctionId string, reason SignedMessage, bidderId ed25519.PublicKey) error
+ Result(auctionId string) (price uint64, winner ed25519.PublicKey, e error)
+}
+
+type SignedMessage struct {
+ Message []byte
+ Signature []byte
+}
+
+type SignedCommitment struct {
+ *commit.Commitment
+ Signature []byte
+}
+
+type Error int
+
+const (
+ ErrUnknownError Error = iota
+
+ ErrSellerIncorrectSignature
+ ErrSellerAuctionExists
+
+ ErrAuctionUnknown
+ ErrAuctionNotStarted
+ ErrAuctionNotReady
+ ErrAuctionNotRunning
+ ErrAuctionFinished
+ ErrAuctionCanceled
+ ErrAuctionTimeout
+
+ ErrBidderUnknown
+ ErrBidderTimeout
+ ErrBidderEmptyMessage
+ ErrBidderIncorrectSignature
+ ErrBidderWrongRound
+ ErrBidderVerificationFailed
+)
+
+func (d Error) Error() string {
+ switch d {
+ case ErrUnknownError:
+ return "unknown error"
+
+ case ErrSellerIncorrectSignature:
+ return "incorrect seller signature"
+ case ErrSellerAuctionExists:
+ return "auction with that id already exists"
+
+ case ErrAuctionUnknown:
+ return "unknown auction"
+ case ErrAuctionNotStarted:
+ return "auction not started yet"
+ case ErrAuctionNotReady:
+ return "auction not ready"
+ case ErrAuctionNotRunning:
+ return "auction is not running"
+ case ErrAuctionFinished:
+ return "auction is finished"
+ case ErrAuctionTimeout:
+ return "action has timed out"
+ case ErrAuctionCanceled:
+ return "auction has been canceled"
+
+ case ErrBidderUnknown:
+ return "bidder is unknown"
+ case ErrBidderTimeout:
+ return "bidder has timed out"
+ case ErrBidderEmptyMessage:
+ return "empty message"
+ case ErrBidderIncorrectSignature:
+ return "incorrect signature from bidder"
+ case ErrBidderWrongRound:
+ return "wrong round"
+ case ErrBidderVerificationFailed:
+ return "round data couldn't be verified"
+
+ default:
+ panic("should not happen")
+ }
+}
+
+type AuctionState int
+
+const (
+ AuctionStateUnknown AuctionState = iota
+
+ AuctionStateReady
+ AuctionStateRunning
+ AuctionStateFinished
+ AuctionStateCanceled
+ AuctionStateTimeout
+
+ AuctionStateLAST
+)
+
+func (s *SignedMessage) Verify(pubkey ed25519.PublicKey) bool {
+ return ed25519.Verify(pubkey, s.Message, s.Signature)
+}
+
+func (s *SignedCommitment) Verify(pubkey ed25519.PublicKey) bool {
+ buf := &bytes.Buffer{}
+ e := json.NewEncoder(buf).Encode(s.Commitment)
+ if e != nil {
+ // TODO: log message?
+ return false
+ }
+
+ return ed25519.Verify(pubkey, buf.Bytes(), s.Signature)
+}
+
+func Pub2String(pubkey ed25519.PublicKey) string {
+ return base32.StdEncoding.EncodeToString(pubkey)
+} \ No newline at end of file
diff --git a/dashboard/inmemory.go b/dashboard/inmemory.go
new file mode 100644
index 0000000..82412cb
--- /dev/null
+++ b/dashboard/inmemory.go
@@ -0,0 +1,187 @@
+package dashboard
+
+import (
+ "crypto/ed25519"
+ "fmt"
+ "sync"
+
+ "kesim.org/seal"
+)
+
+type inmemory struct {
+ sync.RWMutex
+
+ auctions map[string]*auction
+}
+
+func NewInMemoryDashboard() *inmemory {
+ return &inmemory{}
+}
+
+type auction struct {
+ sync.RWMutex
+
+ description *seal.SignedDescription
+ state AuctionState
+ round uint8 // which bit has been completed
+
+ commitments map[string]SignedCommitment
+ messages map[string][]SignedMessage // per-bidder and per-round messages, pre-allocated.
+}
+
+func (m *inmemory) NewAuction(description *seal.SignedDescription) (auctionId string, e error) {
+ if ok, e := description.Verify(); e != nil {
+ return "", e
+ } else if !ok {
+ return "", ErrSellerIncorrectSignature
+ }
+
+ auctionId, e = description.Hash()
+ if e != nil {
+ return "", e
+ }
+
+ auction := &auction{
+ description: description,
+ state: AuctionStateReady,
+ messages: make(map[string][]SignedMessage),
+ }
+
+ m.Lock()
+ defer m.Unlock()
+
+ if _, exists := m.auctions[auctionId]; exists {
+ return "", ErrSellerAuctionExists
+ }
+ m.auctions[auctionId] = auction
+
+ return auctionId, e
+
+}
+
+func (m *inmemory) Auctions(state AuctionState) map[string]*seal.SignedDescription {
+ if state == AuctionStateUnknown || state >= AuctionStateLAST {
+ return nil
+ }
+
+ m.RLock()
+ defer m.RUnlock()
+
+ r := make(map[string]*seal.SignedDescription)
+ for id, auction := range m.auctions {
+ if auction.state == state {
+ r[id] = auction.description
+ }
+ }
+ return r
+}
+
+func (m *inmemory) getAuction(auctionId string) (*auction, error) {
+ m.RLock()
+ defer m.RUnlock()
+
+ auction, exists := m.auctions[auctionId]
+ if !exists {
+ return nil, ErrAuctionUnknown
+
+ }
+
+ return auction, nil
+}
+
+func (m *inmemory) Join(auctionId string,
+ commitment SignedCommitment,
+ pubkey ed25519.PublicKey) error {
+
+ // Check signature first
+ if !commitment.Verify(pubkey) {
+ return ErrBidderIncorrectSignature
+ }
+
+ bidderId := Pub2String(pubkey)
+
+ auction, e := m.getAuction(auctionId)
+ if e != nil {
+ return e
+ }
+ // We allow overwriting
+ auction.Lock()
+ defer auction.Unlock()
+ if auction.state != AuctionStateReady {
+ return ErrAuctionNotReady
+ }
+ auction.commitments[bidderId] = commitment
+ auction.messages[bidderId] = make([]SignedMessage, auction.description.BitLength)
+
+ return nil
+}
+
+func (m *inmemory) Commitments(auctionId string) (bidders map[string]SignedCommitment, e error) {
+ auction, e := m.getAuction(auctionId)
+ if e != nil {
+ return nil, e
+ }
+
+ auction.RLock()
+ defer auction.RUnlock()
+
+ bidders = make(map[string]SignedCommitment)
+ for id, commitment := range auction.commitments {
+ bidders[id] = commitment
+ }
+ return bidders, nil
+}
+
+func (m *inmemory) Publish(auctionId string, round uint8, message SignedMessage, pubkey ed25519.PublicKey) error {
+ if len(message.Message) == 0 {
+ return ErrBidderEmptyMessage
+ }
+
+ // check signature first
+ if !message.Verify(pubkey) {
+ return ErrBidderIncorrectSignature
+ }
+
+ bidderId := Pub2String(pubkey)
+
+ auction, e := m.getAuction(auctionId)
+ if e != nil {
+ return e
+ }
+
+ auction.Lock()
+ defer auction.Unlock()
+
+ if auction.state == AuctionStateFinished {
+ return ErrAuctionFinished
+ } else if auction.state != AuctionStateRunning {
+ return ErrAuctionNotRunning
+ } else if auction.round != round {
+ return ErrBidderWrongRound
+ }
+
+ messages, ok := auction.messages[bidderId]
+ if !ok {
+ return ErrBidderUnknown
+ }
+ messages[auction.round] = message
+
+ completed := true
+
+ for _, messages := range auction.messages {
+ completed = completed && len(messages[auction.round].Message) > 0
+ }
+
+ if completed {
+ auction.round += 1
+ if auction.round == auction.description.BitLength {
+ auction.state = AuctionStateFinished
+ }
+ }
+
+ return nil
+}
+
+func (m *inmemory) Messages(auctionId string, round uint8) (messages map[string]SignedMessage, e error) {
+ return nil, fmt.Errorf("inmemory.Messages not implemented")
+} \ No newline at end of file