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.
95 lines
4.2 KiB
95 lines
4.2 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. Callers always verify the
|
|
// release, with Verify (spec §63 step 10).
|
|
//
|
|
// A source that fetches releases over a network (a relay, the Release API or
|
|
// a cache) must also verify each response with Verify and discard the one
|
|
// that fails, and report datekeys.ErrReleaseUnavailable when no response
|
|
// passes (spec §63 step 9), as provider/drand.Client does: an invalid release
|
|
// from the network is then reported at step 9, not with the codes of step 10.
|
|
// A source that hands over a release the caller supplies directly need not
|
|
// verify it; its release gets the codes of step 10.
|
|
//
|
|
// Errors should wrap datekeys.ErrReleaseUnavailable, and no other normative
|
|
// error, when the release cannot be obtained, for example because the round
|
|
// is not published yet. capsule.Open reports any error of a source at step 9
|
|
// with ErrReleaseUnavailable as its only code, the one spec §63 gives that
|
|
// step, and keeps only the text of an error that carries another code.
|
|
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),
|
|
// in the order of spec §63 step 10: the expected round (ErrRoundMismatch),
|
|
// then the signature (ErrReleaseInvalid), which must be the canonical
|
|
// encoding of a point of the signature group of the scheme other than the
|
|
// point at infinity (spec §12.2), with the length of that group, and a valid
|
|
// BLS signature of the round under the pinned public key. The chain hash is
|
|
// covered because the pinned public key is bound to it by profile.Validate.
|
|
//
|
|
// The BLS verification of drand decodes the signature with the canonical
|
|
// decoder of kilic/bls12-381, which rejects every other encoding, and the
|
|
// point at infinity never verifies because the pinned public key is not the
|
|
// point at infinity. The error of drand is not copied.
|
|
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 is not the canonical encoding of a point of the key group: %w", p.ID, datekeys.ErrUnknownProfile)
|
|
}
|
|
beacon := &common.Beacon{Round: r.Round, Signature: r.Signature}
|
|
if err := scheme.VerifyBeacon(beacon, key); err != nil {
|
|
return fmt.Errorf("provider: the signature is not a canonical point encoding, or does not verify as the BLS signature of round %d under %s: %w", r.Round, p.ID, datekeys.ErrReleaseInvalid)
|
|
}
|
|
return nil
|
|
}
|