You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
76 lines
2.8 KiB
76 lines
2.8 KiB
// Package provider defines conditions, releases and release sources (spec §9,
|
|
// §45-§52) and the local verification every release must pass.
|
|
//
|
|
// A release is never trusted because of where it came from: the DateKeys API,
|
|
// a cache or a relay are untrusted transports (spec §3, §48, §52). A remote
|
|
// "verified: true" has no security value (spec §51).
|
|
package provider
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/drand/drand/v2/common"
|
|
|
|
datekeys "g.activething.com/go/DateKeys"
|
|
"g.activething.com/go/DateKeys/profile"
|
|
)
|
|
|
|
// Condition is the time condition of a Quicknet-style profile: a round.
|
|
type Condition struct {
|
|
Round uint64
|
|
}
|
|
|
|
// Release is the material that satisfies a condition. For drand it is the
|
|
// BLS signature of the round.
|
|
type Release struct {
|
|
Round uint64
|
|
Signature []byte
|
|
}
|
|
|
|
// ReleaseSource fetches the release of a condition. Implementations need not
|
|
// verify it; callers always do, with Verify.
|
|
//
|
|
// Errors should wrap datekeys.ErrReleaseUnavailable when the release cannot be
|
|
// obtained, for example because the round is not published yet.
|
|
type ReleaseSource interface {
|
|
Fetch(ctx context.Context, p *profile.Profile, c Condition) (Release, error)
|
|
}
|
|
|
|
// ReleaseSourceFunc adapts a function to ReleaseSource.
|
|
type ReleaseSourceFunc func(ctx context.Context, p *profile.Profile, c Condition) (Release, error)
|
|
|
|
// Fetch calls f.
|
|
func (f ReleaseSourceFunc) Fetch(ctx context.Context, p *profile.Profile, c Condition) (Release, error) {
|
|
return f(ctx, p, c)
|
|
}
|
|
|
|
// Verify checks a release locally against the pinned profile (spec §17, §51):
|
|
// the expected round, the signature length of the scheme and a valid BLS
|
|
// signature under the pinned public key. The chain hash is covered because
|
|
// the pinned public key is bound to it by profile.Validate.
|
|
func Verify(p *profile.Profile, c Condition, r Release) error {
|
|
if c.Round == 0 || c.Round > p.MaxRound() {
|
|
return fmt.Errorf("provider: round %d outside the range of %s: %w", c.Round, p.ID, datekeys.ErrDateKeyInvalid)
|
|
}
|
|
if r.Round != c.Round {
|
|
return fmt.Errorf("provider: release for round %d, expected %d: %w", r.Round, c.Round, datekeys.ErrRoundMismatch)
|
|
}
|
|
scheme, err := p.DrandScheme()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if want := scheme.SigGroup.PointLen(); len(r.Signature) != want {
|
|
return fmt.Errorf("provider: signature is %d bytes, %s uses %d: %w", len(r.Signature), scheme.Name, want, datekeys.ErrReleaseInvalid)
|
|
}
|
|
key := scheme.KeyGroup.Point()
|
|
if err := key.UnmarshalBinary(p.PublicKey); err != nil {
|
|
return fmt.Errorf("provider: pinned public key of %s: %v: %w", p.ID, err, datekeys.ErrUnknownProfile)
|
|
}
|
|
beacon := &common.Beacon{Round: r.Round, Signature: r.Signature}
|
|
if err := scheme.VerifyBeacon(beacon, key); err != nil {
|
|
return fmt.Errorf("provider: BLS signature of round %d does not verify under %s: %w", r.Round, p.ID, datekeys.ErrReleaseInvalid)
|
|
}
|
|
return nil
|
|
}
|