blst

package
v0.3.12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 30, 2024 License: Apache-2.0 Imports: 7 Imported by: 73

README

blst Lint Status

The blst package provides a Go interface to the blst BLS12-381 signature library.

Build

The build process consists of two steps, code generation followed by compilation.

./generate.py # Optional - only required if making code changes
go build
go test

The generate.py script is used to generate both min-pk and min-sig variants of the binding from a common code base. It consumes the *.tgo files along with blst_minpk_test.go and produces blst.go and blst_minsig_test.go. The .tgo files can treated as if they were .go files, including the use of gofmt and goimports. The generate script will filter out extra imports while processing and automatically run goimports on the final blst.go file.

After running generate.py, go build and go test can be run as usual. Cgo will compile cgo_server.c, which includes the required C implementation files, and cgo_assembly.S, which includes appropriate pre-generated assembly code for the platform. To compile on Windows one has to have MinGW gcc on the %PATH%.

If the test or target application crashes with an "illegal instruction" exception [after copying to an older system], rebuild with CGO_CFLAGS environment variable set to -O2 -D__BLST_PORTABLE__. Don't forget -O2!

Usage

There are two primary modes of operation that can be chosen based on type definitions in the application.

For minimal-pubkey-size operations:

type PublicKey = blst.P1Affine
type Signature = blst.P2Affine
type AggregateSignature = blst.P2Aggregate
type AggregatePublicKey = blst.P1Aggregate

For minimal-signature-size operations:

type PublicKey = blst.P2Affine
type Signature = blst.P1Affine
type AggregateSignature = blst.P1Aggregate
type AggregatePublicKey = blst.P2Aggregate

TODO - structures and possibly methods

A complete example for generating a key, signing a message, and verifying the message:

package main

import (
	"crypto/rand"
	"fmt"

	blst "github.com/supranational/blst/bindings/go"
)

type PublicKey = blst.P1Affine
type Signature = blst.P2Affine
type AggregateSignature = blst.P2Aggregate
type AggregatePublicKey = blst.P1Aggregate

func main() {
	var ikm [32]byte
	_, _ = rand.Read(ikm[:])
	sk := blst.KeyGen(ikm[:])
	pk := new(PublicKey).From(sk)

	var dst = []byte("BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_")
	msg := []byte("hello foo")
	sig := new(Signature).Sign(sk, msg, dst)

	if !sig.Verify(true, pk, true, msg, dst) {
		fmt.Println("ERROR: Invalid!")
	} else {
		fmt.Println("Valid!")
	}
}

See the tests for further examples of usage.

If you're cross-compiling, you have to set CC environment variable to the target C cross-compiler and CGO_ENABLED to 1. For example, to compile the test program for ARM:

env GOARCH=arm CC=arm-linux-gnueabi-gcc CGO_ENABLED=1 go test -c

Documentation

Index

Constants

View Source
const BLST_FP_BYTES = 384 / 8
View Source
const BLST_P1_COMPRESS_BYTES = BLST_FP_BYTES
View Source
const BLST_P1_SERIALIZE_BYTES = BLST_FP_BYTES * 2
View Source
const BLST_P2_COMPRESS_BYTES = BLST_FP_BYTES * 2
View Source
const BLST_P2_SERIALIZE_BYTES = BLST_FP_BYTES * 4
View Source
const BLST_SCALAR_BYTES = 256 / 8

Variables

This section is empty.

Functions

func CoreVerifyPkInG1 added in v0.3.11

func CoreVerifyPkInG1(pk *P1Affine, sig *P2Affine, hash_or_encode bool,
	msg Message, dst []byte, optional ...[]byte) int

func CoreVerifyPkInG2 added in v0.3.11

func CoreVerifyPkInG2(pk *P2Affine, sig *P1Affine, hash_or_encode bool,
	msg Message, dst []byte, optional ...[]byte) int

func Fp12FinalVerify added in v0.3.5

func Fp12FinalVerify(pt1 *Fp12, pt2 *Fp12) bool

func PairingAggregatePkInG1

func PairingAggregatePkInG1(ctx Pairing, PK *P1Affine, pkValidate bool,
	sig *P2Affine, sigGroupcheck bool, msg []byte,
	optional ...[]byte) int

func PairingAggregatePkInG2

func PairingAggregatePkInG2(ctx Pairing, PK *P2Affine, pkValidate bool,
	sig *P1Affine, sigGroupcheck bool, msg []byte,
	optional ...[]byte) int

func PairingCommit

func PairingCommit(ctx Pairing)

func PairingFinalVerify

func PairingFinalVerify(ctx Pairing, optional ...*Fp12) bool

func PairingMerge

func PairingMerge(ctx Pairing, ctx1 Pairing) int

func PairingMulNAggregatePkInG1 added in v0.2.0

func PairingMulNAggregatePkInG1(ctx Pairing, PK *P1Affine, pkValidate bool,
	sig *P2Affine, sigGroupcheck bool,
	rand *Scalar, randBits int, msg []byte,
	optional ...[]byte) int

func PairingMulNAggregatePkInG2 added in v0.2.0

func PairingMulNAggregatePkInG2(ctx Pairing, PK *P2Affine, pkValidate bool,
	sig *P1Affine, sigGroupcheck bool,
	rand *Scalar, randBits int, msg []byte,
	optional ...[]byte) int

func PairingRawAggregate added in v0.3.5

func PairingRawAggregate(ctx Pairing, q *P2Affine, p *P1Affine)

func PrintBytes

func PrintBytes(val []byte, name string)

func SetMaxProcs added in v0.1.1

func SetMaxProcs(max int)

func Uniq added in v0.3.3

func Uniq(msgs []Message) bool

Types

type Fp

type Fp = C.blst_fp

func (*Fp) Equals

func (e1 *Fp) Equals(e2 *Fp) bool

func (*Fp) FromBEndian added in v0.1.1

func (fp *Fp) FromBEndian(arr []byte) *Fp

func (*Fp) FromLEndian added in v0.1.1

func (fp *Fp) FromLEndian(arr []byte) *Fp

func (*Fp) ToBEndian

func (fp *Fp) ToBEndian() []byte

func (*Fp) ToLEndian

func (fp *Fp) ToLEndian() []byte

type Fp12

type Fp12 = C.blst_fp12

func Fp12MillerLoop added in v0.3.5

func Fp12MillerLoop(q *P2Affine, p *P1Affine) *Fp12

func Fp12MillerLoopN added in v0.3.11

func Fp12MillerLoopN(qs []P2Affine, ps []P1Affine) *Fp12

func Fp12One added in v0.2.0

func Fp12One() Fp12

func PairingAsFp12 added in v0.3.5

func PairingAsFp12(ctx Pairing) *Fp12

func (*Fp12) Equals added in v0.3.11

func (pt1 *Fp12) Equals(pt2 *Fp12) bool

func (*Fp12) FinalExp added in v0.3.6

func (pt *Fp12) FinalExp()

func (*Fp12) InGroup added in v0.3.6

func (pt *Fp12) InGroup() bool

func (*Fp12) MulAssign added in v0.3.6

func (pt *Fp12) MulAssign(p *Fp12)

func (*Fp12) ToBendian added in v0.3.8

func (pt *Fp12) ToBendian() []byte

type Fp2

type Fp2 = C.blst_fp2

func (*Fp2) Equals

func (e1 *Fp2) Equals(e2 *Fp2) bool

func (*Fp2) Print

func (f *Fp2) Print(name string)

type Fp6

type Fp6 = C.blst_fp6

type Message

type Message = []byte

type P1

type P1 = C.blst_p1

func EncodeToG1

func EncodeToG1(msg []byte, dst []byte,
	optional ...[]byte) *P1

func HashToG1

func HashToG1(msg []byte, dst []byte,
	optional ...[]byte) *P1

Hash

func P1AffinesAdd added in v0.3.6

func P1AffinesAdd(points []*P1Affine, optional ...int) *P1

func P1AffinesMult added in v0.3.6

func P1AffinesMult(pointsIf interface{}, scalarsIf interface{}, nbits int) *P1

func P1Generator added in v0.3.6

func P1Generator() *P1

func (*P1) Add added in v0.3.8

func (p1 *P1) Add(pointIf interface{}) *P1

func (*P1) AddAssign added in v0.3.8

func (p1 *P1) AddAssign(pointIf interface{}) *P1

func (*P1) Compress

func (p1 *P1) Compress() []byte

func (*P1) Equals added in v0.2.0

func (e1 *P1) Equals(e2 *P1) bool

func (*P1) FromAffine added in v0.3.5

func (p *P1) FromAffine(pa *P1Affine)

func (*P1) Mult added in v0.3.6

func (p1 *P1) Mult(scalarIf interface{}, optional ...int) *P1

func (*P1) MultAssign added in v0.3.8

func (p1 *P1) MultAssign(scalarIf interface{}, optional ...int) *P1

func (*P1) MultNAccumulate added in v0.3.8

func (acc *P1) MultNAccumulate(pointIf interface{}, scalarIf interface{},
	optional ...int) *P1

'acc += point * scalar', passing 'nil' for 'point' means "use the

group generator point"

func (*P1) Print

func (p *P1) Print(name string)

func (*P1) Serialize

func (p1 *P1) Serialize() []byte

func (*P1) Sub added in v0.3.11

func (p1 *P1) Sub(pointIf interface{}) *P1

func (*P1) SubAssign added in v0.3.11

func (p1 *P1) SubAssign(pointIf interface{}) *P1

func (*P1) ToAffine

func (p *P1) ToAffine() *P1Affine

type P1Affine

type P1Affine = C.blst_p1_affine

func (*P1Affine) AggregateVerify

func (sig *P1Affine) AggregateVerify(sigGroupcheck bool,
	pks []*P2Affine, pksVerify bool, msgs []Message, dst []byte,
	optional ...interface{}) bool

Aggregate verify with uncompressed signature and public keys Note that checking message uniqueness, if required, is left to the user. Not all signature schemes require it and this keeps the binding minimal and fast. Refer to the Uniq function for one method method of performing this check.

func (*P1Affine) AggregateVerifyCompressed

func (_ *P1Affine) AggregateVerifyCompressed(sig []byte, sigGroupcheck bool,
	pks [][]byte, pksVerify bool, msgs []Message, dst []byte,
	optional ...bool) bool

Aggregate verify with compressed signature and public keys Uses a dummy signature to get the correct type

func (*P1Affine) BatchUncompress added in v0.2.0

func (_ *P1Affine) BatchUncompress(in [][]byte) []*P1Affine

func (*P1Affine) Compress

func (p1 *P1Affine) Compress() []byte

func (*P1Affine) Deserialize

func (p1 *P1Affine) Deserialize(in []byte) *P1Affine

func (*P1Affine) Equals

func (e1 *P1Affine) Equals(e2 *P1Affine) bool

func (*P1Affine) FastAggregateVerify

func (sig *P1Affine) FastAggregateVerify(sigGroupcheck bool,
	pks []*P2Affine, msg Message, dst []byte,
	optional ...interface{}) bool

pks are assumed to be verified for proof of possession, which implies that they are already group-checked

func (*P1Affine) From

func (pk *P1Affine) From(s *Scalar) *P1Affine

func (*P1Affine) InG1 added in v0.2.0

func (p1 *P1Affine) InG1() bool

func (*P1Affine) KeyValidate added in v0.3.0

func (pk *P1Affine) KeyValidate() bool

func (*P1Affine) MultipleAggregateVerify added in v0.2.0

func (_ *P1Affine) MultipleAggregateVerify(sigs []*P1Affine,
	sigsGroupcheck bool, pks []*P2Affine, pksVerify bool,
	msgs []Message, dst []byte, randFn func(*Scalar), randBits int,
	optional ...interface{}) bool

func (*P1Affine) Print added in v0.1.1

func (p *P1Affine) Print(name string)

func (*P1Affine) Serialize

func (p1 *P1Affine) Serialize() []byte

P1 Serdes

func (*P1Affine) SigValidate added in v0.3.0

func (sig *P1Affine) SigValidate(sigInfcheck bool) bool

sigInfcheck, check for infinity, is a way to avoid going into resource-consuming verification. Passing 'false' is always cryptographically safe, but application might want to guard against obviously bogus individual[!] signatures.

func (*P1Affine) Sign

func (sig *P1Affine) Sign(sk *SecretKey, msg []byte, dst []byte,
	optional ...interface{}) *P1Affine

func (*P1Affine) Uncompress

func (p1 *P1Affine) Uncompress(in []byte) *P1Affine

func (*P1Affine) Verify

func (sig *P1Affine) Verify(sigGroupcheck bool, pk *P2Affine, pkValidate bool,
	msg Message, dst []byte,
	optional ...interface{}) bool

Single verify with decompressed pk

func (*P1Affine) VerifyCompressed

func (dummy *P1Affine) VerifyCompressed(sig []byte, sigGroupcheck bool,
	pk []byte, pkValidate bool, msg Message, dst []byte,
	optional ...bool) bool

Single verify with compressed pk Uses a dummy signature to get the correct type

type P1Affines added in v0.3.5

type P1Affines []P1Affine

func P1sToAffine added in v0.3.5

func P1sToAffine(points []*P1, optional ...int) P1Affines

func (P1Affines) Add added in v0.3.5

func (points P1Affines) Add() *P1

func (P1Affines) Mult added in v0.3.6

func (points P1Affines) Mult(scalarsIf interface{}, nbits int) *P1

type P1Aggregate

type P1Aggregate struct {
	// contains filtered or unexported fields
}

func (*P1Aggregate) Add

func (agg *P1Aggregate) Add(elmt *P1Affine, groupcheck bool) bool

func (*P1Aggregate) AddAggregate

func (agg *P1Aggregate) AddAggregate(other *P1Aggregate)

func (*P1Aggregate) Aggregate

func (agg *P1Aggregate) Aggregate(elmts []*P1Affine,
	groupcheck bool) bool

Aggregate uncompressed elements

func (*P1Aggregate) AggregateCompressed

func (agg *P1Aggregate) AggregateCompressed(elmts [][]byte,
	groupcheck bool) bool

Aggregate compressed elements

func (*P1Aggregate) ToAffine

func (agg *P1Aggregate) ToAffine() *P1Affine

type P1s added in v0.3.5

type P1s []P1

func (P1s) Add added in v0.3.5

func (points P1s) Add() *P1

func (P1s) Mult added in v0.3.6

func (points P1s) Mult(scalarsIf interface{}, nbits int) *P1

func (P1s) ToAffine added in v0.3.5

func (points P1s) ToAffine(optional ...P1Affines) P1Affines

type P2

type P2 = C.blst_p2

func EncodeToG2

func EncodeToG2(msg []byte, dst []byte,
	optional ...[]byte) *P2

func HashToG2

func HashToG2(msg []byte, dst []byte,
	optional ...[]byte) *P2

Hash

func P2AffinesAdd added in v0.3.6

func P2AffinesAdd(points []*P2Affine, optional ...int) *P2

func P2AffinesMult added in v0.3.6

func P2AffinesMult(pointsIf interface{}, scalarsIf interface{}, nbits int) *P2

func P2Generator added in v0.3.6

func P2Generator() *P2

func (*P2) Add added in v0.3.8

func (p2 *P2) Add(pointIf interface{}) *P2

func (*P2) AddAssign added in v0.3.8

func (p2 *P2) AddAssign(pointIf interface{}) *P2

func (*P2) Compress

func (p2 *P2) Compress() []byte

func (*P2) Equals added in v0.2.0

func (e1 *P2) Equals(e2 *P2) bool

func (*P2) FromAffine added in v0.3.5

func (p *P2) FromAffine(pa *P2Affine)

func (*P2) Mult added in v0.3.6

func (p2 *P2) Mult(scalarIf interface{}, optional ...int) *P2

func (*P2) MultAssign added in v0.3.8

func (p2 *P2) MultAssign(scalarIf interface{}, optional ...int) *P2

func (*P2) MultNAccumulate added in v0.3.8

func (acc *P2) MultNAccumulate(pointIf interface{}, scalarIf interface{},
	optional ...int) *P2

'acc += point * scalar', passing 'nil' for 'point' means "use the

group generator point"

func (*P2) Print

func (p *P2) Print(name string)

func (*P2) Serialize

func (p2 *P2) Serialize() []byte

func (*P2) Sub added in v0.3.11

func (p2 *P2) Sub(pointIf interface{}) *P2

func (*P2) SubAssign added in v0.3.11

func (p2 *P2) SubAssign(pointIf interface{}) *P2

func (*P2) ToAffine

func (p *P2) ToAffine() *P2Affine

type P2Affine

type P2Affine = C.blst_p2_affine

func (*P2Affine) AggregateVerify

func (sig *P2Affine) AggregateVerify(sigGroupcheck bool,
	pks []*P1Affine, pksVerify bool, msgs []Message, dst []byte,
	optional ...interface{}) bool

Aggregate verify with uncompressed signature and public keys Note that checking message uniqueness, if required, is left to the user. Not all signature schemes require it and this keeps the binding minimal and fast. Refer to the Uniq function for one method method of performing this check.

func (*P2Affine) AggregateVerifyCompressed

func (_ *P2Affine) AggregateVerifyCompressed(sig []byte, sigGroupcheck bool,
	pks [][]byte, pksVerify bool, msgs []Message, dst []byte,
	optional ...bool) bool

Aggregate verify with compressed signature and public keys Uses a dummy signature to get the correct type

func (*P2Affine) BatchUncompress added in v0.2.0

func (_ *P2Affine) BatchUncompress(in [][]byte) []*P2Affine

func (*P2Affine) Compress

func (p2 *P2Affine) Compress() []byte

func (*P2Affine) Deserialize

func (p2 *P2Affine) Deserialize(in []byte) *P2Affine

func (*P2Affine) Equals

func (e1 *P2Affine) Equals(e2 *P2Affine) bool

func (*P2Affine) FastAggregateVerify

func (sig *P2Affine) FastAggregateVerify(sigGroupcheck bool,
	pks []*P1Affine, msg Message, dst []byte,
	optional ...interface{}) bool

pks are assumed to be verified for proof of possession, which implies that they are already group-checked

func (*P2Affine) From

func (pk *P2Affine) From(s *Scalar) *P2Affine

func (*P2Affine) InG2 added in v0.2.0

func (p2 *P2Affine) InG2() bool

func (*P2Affine) KeyValidate added in v0.3.0

func (pk *P2Affine) KeyValidate() bool

func (*P2Affine) MultipleAggregateVerify added in v0.2.0

func (_ *P2Affine) MultipleAggregateVerify(sigs []*P2Affine,
	sigsGroupcheck bool, pks []*P1Affine, pksVerify bool,
	msgs []Message, dst []byte, randFn func(*Scalar), randBits int,
	optional ...interface{}) bool

func (*P2Affine) Print added in v0.1.1

func (p *P2Affine) Print(name string)

func (*P2Affine) Serialize

func (p2 *P2Affine) Serialize() []byte

P2 Serdes

func (*P2Affine) SigValidate added in v0.3.0

func (sig *P2Affine) SigValidate(sigInfcheck bool) bool

sigInfcheck, check for infinity, is a way to avoid going into resource-consuming verification. Passing 'false' is always cryptographically safe, but application might want to guard against obviously bogus individual[!] signatures.

func (*P2Affine) Sign

func (sig *P2Affine) Sign(sk *SecretKey, msg []byte, dst []byte,
	optional ...interface{}) *P2Affine

func (*P2Affine) Uncompress

func (p2 *P2Affine) Uncompress(in []byte) *P2Affine

func (*P2Affine) Verify

func (sig *P2Affine) Verify(sigGroupcheck bool, pk *P1Affine, pkValidate bool,
	msg Message, dst []byte,
	optional ...interface{}) bool

Single verify with decompressed pk

func (*P2Affine) VerifyCompressed

func (dummy *P2Affine) VerifyCompressed(sig []byte, sigGroupcheck bool,
	pk []byte, pkValidate bool, msg Message, dst []byte,
	optional ...bool) bool

Single verify with compressed pk Uses a dummy signature to get the correct type

type P2Affines added in v0.3.5

type P2Affines []P2Affine

func P2sToAffine added in v0.3.5

func P2sToAffine(points []*P2, optional ...int) P2Affines

func (P2Affines) Add added in v0.3.5

func (points P2Affines) Add() *P2

func (P2Affines) Mult added in v0.3.6

func (points P2Affines) Mult(scalarsIf interface{}, nbits int) *P2

type P2Aggregate

type P2Aggregate struct {
	// contains filtered or unexported fields
}

func (*P2Aggregate) Add

func (agg *P2Aggregate) Add(elmt *P2Affine, groupcheck bool) bool

func (*P2Aggregate) AddAggregate

func (agg *P2Aggregate) AddAggregate(other *P2Aggregate)

func (*P2Aggregate) Aggregate

func (agg *P2Aggregate) Aggregate(elmts []*P2Affine,
	groupcheck bool) bool

Aggregate uncompressed elements

func (*P2Aggregate) AggregateCompressed

func (agg *P2Aggregate) AggregateCompressed(elmts [][]byte,
	groupcheck bool) bool

Aggregate compressed elements

func (*P2Aggregate) ToAffine

func (agg *P2Aggregate) ToAffine() *P2Affine

type P2s added in v0.3.5

type P2s []P2

func (P2s) Add added in v0.3.5

func (points P2s) Add() *P2

func (P2s) Mult added in v0.3.6

func (points P2s) Mult(scalarsIf interface{}, nbits int) *P2

func (P2s) ToAffine added in v0.3.5

func (points P2s) ToAffine(optional ...P2Affines) P2Affines

type Pairing

type Pairing = []C.blst_pairing

func PairingCtx

func PairingCtx(hash_or_encode bool, DST []byte) Pairing

type Scalar

type Scalar = C.blst_scalar

func HashToScalar added in v0.3.6

func HashToScalar(msg []byte, dst []byte) *Scalar

func (*Scalar) Add added in v0.3.8

func (a *Scalar) Add(b *Scalar) (*Scalar, bool)

func (*Scalar) AddAssign added in v0.3.8

func (a *Scalar) AddAssign(b *Scalar) (*Scalar, bool)

func (*Scalar) Deserialize

func (s *Scalar) Deserialize(in []byte) *Scalar

func (*Scalar) Equals

func (s1 *Scalar) Equals(s2 *Scalar) bool

func (*Scalar) FromBEndian added in v0.1.1

func (fr *Scalar) FromBEndian(arr []byte) *Scalar

func (*Scalar) FromLEndian added in v0.1.1

func (fr *Scalar) FromLEndian(arr []byte) *Scalar

func (*Scalar) HashTo added in v0.3.6

func (s *Scalar) HashTo(msg []byte, dst []byte) bool

func (*Scalar) Inverse added in v0.3.8

func (a *Scalar) Inverse() *Scalar

func (*Scalar) Mul added in v0.3.8

func (a *Scalar) Mul(b *Scalar) (*Scalar, bool)

func (*Scalar) MulAssign added in v0.3.8

func (a *Scalar) MulAssign(b *Scalar) (*Scalar, bool)

These methods are inefficient because of cgo call overhead. For this reason they should be used primarily for prototyping with a goal to formulate interfaces that would process multiple scalars per cgo call.

func (*Scalar) Print

func (s *Scalar) Print(name string)

func (*Scalar) Serialize

func (s *Scalar) Serialize() []byte

Scalar serdes

func (*Scalar) Sub added in v0.3.8

func (a *Scalar) Sub(b *Scalar) (*Scalar, bool)

func (*Scalar) SubAssign added in v0.3.8

func (a *Scalar) SubAssign(b *Scalar) (*Scalar, bool)

func (*Scalar) ToBEndian

func (fr *Scalar) ToBEndian() []byte

func (*Scalar) ToLEndian

func (fr *Scalar) ToLEndian() []byte

func (*Scalar) Valid added in v0.2.0

func (s *Scalar) Valid() bool

type SecretKey

type SecretKey = Scalar

func DeriveMasterEip2333 added in v0.3.8

func DeriveMasterEip2333(ikm []byte) *SecretKey

func KeyGen

func KeyGen(ikm []byte, optional ...[]byte) *SecretKey

func KeyGenV3 added in v0.3.8

func KeyGenV3(ikm []byte, optional ...[]byte) *SecretKey

func KeyGenV45 added in v0.3.10

func KeyGenV45(ikm []byte, salt []byte, optional ...[]byte) *SecretKey

func KeyGenV5 added in v0.3.10

func KeyGenV5(ikm []byte, salt []byte, optional ...[]byte) *SecretKey

func (*SecretKey) DeriveChildEip2333 added in v0.3.8

func (master *SecretKey) DeriveChildEip2333(child_index uint32) *SecretKey

func (*SecretKey) Zeroize added in v0.3.0

func (sk *SecretKey) Zeroize()

Secret key

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL