diff options
-rw-r--r-- | dashboard/dashboard.go | 132 | ||||
-rw-r--r-- | dashboard/inmemory.go | 187 |
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 |