gblsminsig

package
v0.0.0-...-63a5211 Latest Latest
Warning

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

Go to latest
Published: Feb 4, 2025 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package gblsminsig wraps github.com/supranational/blst/bindings/go to provide a gcrypto.PubKey implementation backed by BLS keys, where the BLS keys have minimized signatures.

We are not currently providing an alternate implementation with minimized keys, as signatures are expected to be transmitted and stored much more frequently than keys.

The blst dependency requires CGo, so therefore this package also requires CGo.

Two key references for correctly understanding and using BLS keys are RFC9380 (Hashing to Elliptic Curves) and the IETF draft for BLS Signatures.

See the SignatureProofScheme docs for a detailed explanation of how this package aggregates keys and signatures.

Index

Constants

This section is empty.

Variables

View Source
var DomainSeparationTag = []byte("BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_")

The domain separation tag is a requirement per RFC9380 (Hashing to Elliptic Curves). See sections 2.2.5 (domain separation), 3.1 (domain separation requirements), and 8.10 (suite ID naming conventions).

Furthermore, see also draft-irtf-cfrg-bls-signature-05, section 4.1 (ciphersuite format), as that is the actual format being followed here.

The ciphersuite ID according to the BLS signature document is:

"BLS_SIG_" || H2C_SUITE_ID || SC_TAG || "_"

And the H2C_SUITE_ID, per RFC9380 section 8.8.1, is:

BLS12381G1_XMD:SHA-256_SSWU_RO_

Which only leaves the SC_TAG value, which is "NUL" for the basic scheme.

Functions

func NewPubKey

func NewPubKey(b []byte) (gcrypto.PubKey, error)

NewPubKey decodes a compressed p2 affine point and returns the public key for it.

func Register

func Register(reg *gcrypto.Registry)

Register registers the BLS minimzed-signature key type with the given Registry.

Types

type PubKey

type PubKey blst.P2Affine

PubKey wraps a blst.P2Affine and defines methods for the gcrypto.PubKey interface.

func (PubKey) Equal

func (k PubKey) Equal(other gcrypto.PubKey) bool

Equal reports whether other is the same public key as k.

func (PubKey) PubKeyBytes

func (k PubKey) PubKeyBytes() []byte

PubKeyBytes returns the compressed bytes underlying k's P2 affine point.

func (PubKey) TypeName

func (k PubKey) TypeName() string

TypeName returns the type name for minimized-signature BLS signatures.

func (PubKey) Verify

func (k PubKey) Verify(msg, sig []byte) bool

Verify reports whether sig matches k for msg.

type SignatureProof

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

SignatureProof is an implementation of gcrypto.CommonMessageSignatureProof for the BLS keys and signatures in this package.

When extracting sparse signatures from this proof, signatures are aggregated pairwise, forming a binary tree. If signatures were free to be paired arbitrarily, then a validator could receive an aggregation of A-B and then a separate aggregation of B-C-D. Aggregating them into A-B-B-C-D is valid in general, but then you need to either have a way to indicate that B has been accounted for twice, or you need a way to recover the original signature B in order to subtract B to normalize it back to A-B-C-D.

Instead, all validators with the same view of the public keys understand how to aggregate keys and signatures in a fixed fashion. Arranging the validators such that the leftmost validators are the most likely to be online and voting the same way, allows the signatures to be more likely aggregated into a single set, thereby minimizing bandwidth during consensus gossip.

func NewSignatureProof

func NewSignatureProof(msg []byte, trustedKeys []PubKey, pubKeyHash string) (SignatureProof, error)

NewSignatureProof returns a new SignatureProof based on trustedKeys.

The pubKeyHash is sent as part of the sparse signatures, and it is meant to ensure that peers agree on the set of keys and corresponding signatures.

It may turn out that we need a pair of key hashes -- one for the real set of ordered validator keys, and another hash representing the current arrangement of keys for the proof. For instance, if a highly delegated validator has not voted in the past several blocks, that validator ought to move towards the end of the list such that its absence does not interfere with aggregating the other online validators' signatures.

func (SignatureProof) AddSignature

func (p SignatureProof) AddSignature(sig []byte, key gcrypto.PubKey) error

AddSignature adds a signature representing a single key.

This should only be called when receiving the local application's signature for a message. Otherwise, use the Merge method to combine incoming proofs with the existing one.

If the signature does not match, or if the public key was not one of the candidate keys, an error is returned.

func (SignatureProof) AsSparse

func (SignatureProof) Clone

func (SignatureProof) Derive

func (SignatureProof) HasSparseKeyID

func (p SignatureProof) HasSparseKeyID(keyID []byte) (has, valid bool)

HasSparseKeyID reports whether the full proof already contains a signature matching the given sparse key ID. If the key ID does not properly map into the set of trusted public keys, the "valid" return parameter will be false.

func (SignatureProof) Matches

func (SignatureProof) Message

func (p SignatureProof) Message() []byte

func (SignatureProof) PubKeyHash

func (p SignatureProof) PubKeyHash() []byte

func (SignatureProof) SignatureBitSet

func (p SignatureProof) SignatureBitSet(dst *bitset.BitSet)

type SignatureProofScheme

type SignatureProofScheme struct{}

SignatureProofScheme is a gcrypto.SignatureProofScheme for the minimal-sized-signature BLS format.

At a high level, the most important detail to understand about BLS is that any number keys and signatures can be respectively aggregated into a single key or signature. Performing the aggregation is relatively cheap, and verifying an aggregated signature is about as expensive as verifying an unaggregated one.

Without getting deep into the math behind BLS, keys and signatures exist as points on elliptic curves, and points on those curves have the property that they can be added and subtracted. Those additions and subtractions are commutative, just like everyday addition and subtraction that we are used to. That means if we have keys A, B, C, and D, it doesn't matter if we do (A + (B + (C + D))) or (D + (A + (C + B))); the result is the same regardless of order.

However, to correctly distribute the aggregated keys and signatures during validator gossip, we do have to be particular about how we aggregate. First consider a naive case with the A, B, C, and D key example above. We will consider "A" to mean the combination of both the key and the signature belonging to A. Suppose you get one message of the aggregation (A+B) and another message of the aggregation (B+D). You are allowed to aggregate those two messages, resulting in (A+B+B+D), but this unorganized pattern is unsustainable at scale. If the next message you got was (B+C+D), you could aggregate that into (A+B+B+B+C+D+D). In fact you could verify that signature properly if you continue to track which keys and signatures were accumulated more than once, but if we require the network to track counts of how many times a pairing is present, it ends up quickly becoming a secondary and irrelevant detail to track.

So instead of allowing arbitrary aggregation during gossip, we require the aggregations to be pairwise in a tree format. If our keyset was the 8 keys, A, B, C, D, E, F, G, and H, then valid pairings include A+B, C+D, A+B+C+D, or A+B+C+D+E+F+G+H. Invalid pairings include B+C (because that would leave A, the first key, unpaired), or A+B+E+F (while A+B and E+F are independently correct, they are not a contiguous collection).

The pairwise tree format also allows us to use a single number to represent a determinstic pairing, which is much more efficient than using bitsets. For example, while numbers 0-7 indicate the independent keys A-H, then index 8 would indicate A+B, 9 would indicate C+D, all the way up to 15 indicating the full aggregation of A through H. (A binary tree with n leaves when n is a power of two then has (2*n)-1 total nodes.) Transmitting that single number is currently encoded as a 16-bit integer key ID in the sparse signature, which is significantly less data to transmit than the aggregated key, while remaining completely sufficient for any other validator to decode.

When we get to signature finalization -- that is, writing a previous commit proof in a block header -- we no longer abide by the pairwise rules, because the finalized signature is not intended to be modified further.

There are two parts to finalized signatures. First is the "main" signature: this is signing the block that is being committed. The main signature is always present. Besides the main signature, there is a collection of zero or more signatures for any other votes, which could be votes for nil or votes for a different block.

The key IDs for the finalized signatures are represented in two parts. First is a two-byte header, a big-endian uint16, representing the number of signatures aggregated to produce the finalized signature. The remaining bytes are a big-endian encoding of the combinatorial index of the present signatures from the key space. The meaning of "key space" varies slightly depending on what part of the finalized signature we are inspecting. For the main signature, the key space is the full original key set, so if there were 100 validators and we indicate 90 of them voted for the block, then that index indicates exactly which 90 keys make up that aggregation.

Then the non-main signatures follow a specific ordering. Suppose of the remaining 10, there were 6 votes for nil, and then 2 votes for some other block. We order by the number of votes descending, and in case of a tie then it goes by sign content ascending.

The combinatorial index for the 6 votes for nil are encoded as "10 choose 6" because we know that 90 of the 100 signatures were already used; then the two votes for the other block are encoded as "4 choose 2" The last two absent votes have no representation in the finalized signature. Using a reduced key space here allows us to pack signature representations into fewer bytes, as for example "4 choose 2" only has 6 possible values and "100 choose 2" has 4,950 possible values.

func (SignatureProofScheme) CanMergeFinalizedProofs

func (SignatureProofScheme) CanMergeFinalizedProofs() bool

func (SignatureProofScheme) KeyIDChecker

func (SignatureProofScheme) New

func (SignatureProofScheme) ValidateFinalizedProof

func (SignatureProofScheme) ValidateFinalizedProof(
	proof gcrypto.FinalizedCommonMessageSignatureProof,
	hashesBySignContent map[string]string,
) (
	signBitsByHash map[string]*bitset.BitSet, allSignaturesUnique bool,
)

type Signer

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

Signer satisfies gcrypto.Signer for minimized-signature BLS.

func NewSigner

func NewSigner(ikm []byte) (Signer, error)

NewSigner returns a new signer. The initial key material must be at least 32 bytes, and should be cryptographically random.

func (Signer) PubKey

func (s Signer) PubKey() gcrypto.PubKey

PubKey returns the PubKey for s (which is actually the p2 point).

func (Signer) Sign

func (s Signer) Sign(_ context.Context, input []byte) ([]byte, error)

Sign produces the signed point for the given input.

It uses the DomainSeparationTag, which must be provided to verification too. The PubKey type in this package is hardcoded to use the same DST.

Directories

Path Synopsis
internal

Jump to

Keyboard shortcuts

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