package seal import ( "bytes" "crypto/ed25519" "crypto/sha512" "encoding/base32" "encoding/json" "errors" "fmt" "log/slog" "time" "kesim.org/seal/nizk" ) type Type int const ( TypHighest = iota TypSecondHighest ) type Duration time.Duration func (d *Duration) UnmarshalJSON(data []byte) error { data = bytes.Trim(data, `"`) dur, e := time.ParseDuration(string(data)) if e != nil { return e } *d = Duration(dur) return nil } func (d Duration) MarshalJSON() ([]byte, error) { return []byte((time.Duration(d)).String()), nil } // Auction describes the asset of an auction and other // relevant meta-data type Description struct { Start time.Time `json:"start"` End time.Time `json:"end"` Timeout Duration `json:"timeout"` // Timeout per round by which all responses must have arrived Bitlength uint8 `json:"bitlength"` // Length of the price encoding Currency string `json:"currency"` Type Type `json:"type"` Seller ed25519.PublicKey `json:"seller"` // Public key of the Seller } // The SignedAuction contains an Auction and the signature, // signed by the seller's public key off the SHA512 hash of // the normalized JSON-object. type SignedDescription struct { Description SellerSignature []byte } func (sd *SignedDescription) Verify() (bool, error) { // TODO: need to normalize this encoding buf := &bytes.Buffer{} e := json.NewEncoder(buf).Encode(sd.Description) if e != nil { return false, e } r := ed25519.Verify(sd.Seller, buf.Bytes(), sd.SellerSignature) return r, nil } func (d *Description) Hash() (hash string, e error) { buf := &bytes.Buffer{} e = json.NewEncoder(buf).Encode(d) if e != nil { return "", e } h := sha512.Sum512(buf.Bytes()) return base32.StdEncoding.EncodeToString(h[:]), nil } func (d *Description) validate() error { if d == nil { return fmt.Errorf("description is nil") } if d.Bitlength > MAXBITLENGTH { return fmt.Errorf("invalid BitLength in description: %d", d.Bitlength) } return nil } type Message []byte type Auction interface { Start() error Cancel() error Message(msg Message) error } type Result struct { Price uint32 Winner string Error error } type auction struct { description *Description // non nil if run by an bidder, via Join() bidder Bidder // non nil if run by an observer, via Observe() observer Observer // The commitments we received from the bidders. bidders map[string][]*nizk.Commitment // sorted list of the bidders. bidder_ids []string // Stage 1 data per round stage1 []*nizk.Stage log *slog.Logger } // Start must be called by the bidder or observer, // when they receive a start signal from the dashboard func (a *auction) Start() error { return fmt.Errorf("auction.Start not fully implemented") } func (a *auction) Cancel() error { return errors.New("Cancel not implemented") } // Message is called by the bidder's or observer's code // whenever a message came in for the auction via the dashboard // or other means of communication. func (a *auction) Message(msg Message) error { return fmt.Errorf("Auction.Received not implemented") } const MAXBITLENGTH = 64 // Bidder is the interface that the Auction engine uses to interact type Bidder interface { Broadcast(Message) error Result(Result) Start() error } func Join( bidder Bidder, description *Description, options ...Option) (Auction, error) { if bidder == nil { return nil, fmt.Errorf("missing bidder") } if e := description.validate(); e != nil { return nil, e } a := &auction{ description: description, bidder: bidder, log: slog.Default(), } for _, opt := range options { opt(a) } return a, nil } // Observer type Observer interface { Result(Result) Start() } func Observe( observer Observer, description *Description, options ...Option) (Auction, error) { if observer == nil { return nil, fmt.Errorf("missing observer") } if e := description.validate(); e != nil { return nil, e } a := &auction{ description: description, observer: observer, log: slog.Default(), } for _, opt := range options { opt(a) } return a, nil }