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  | 
