mirror of
https://github.com/luxfi/utxo.git
synced 2026-10-30 15:13:57 +00:00
171 lines
4.5 KiB
Go
171 lines
4.5 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package bls12381fx
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/luxfi/vm/components/verify"
|
|
)
|
|
|
|
const (
|
|
// PubKeyLen is the compressed G1 public key size (BLS12-381).
|
|
PubKeyLen = 48
|
|
// SigLen is the compressed G2 aggregate signature size (BLS12-381).
|
|
SigLen = 96
|
|
// AttestedHashLen is the length of the commitment being attested to.
|
|
AttestedHashLen = 32
|
|
)
|
|
|
|
var (
|
|
ErrNilOutput = errors.New("nil attestation output")
|
|
ErrInvalidAttestedHash = errors.New("attestation hash wrong length")
|
|
ErrInvalidPubKeyLen = errors.New("bls12381 pubkey wrong length")
|
|
ErrEmptyPubKeys = errors.New("attestation output must have >=1 pubkeys")
|
|
ErrThresholdZero = errors.New("attestation threshold must be >= 1")
|
|
ErrThresholdExceedsPubKeys = errors.New("threshold exceeds number of pubkeys")
|
|
ErrPubKeysNotSortedUnique = errors.New("pubkeys not sorted and unique")
|
|
|
|
ErrNilInput = errors.New("nil attestation input")
|
|
ErrSignerBitmapEmpty = errors.New("signer bitmap has no bits set")
|
|
ErrSignerBitmapSize = errors.New("signer bitmap size does not match pubkey count")
|
|
|
|
ErrNilCredential = errors.New("nil attestation credential")
|
|
ErrWrongAggSigLen = errors.New("aggregate signature wrong length")
|
|
ErrInvalidAggSig = errors.New("aggregate signature verification failed")
|
|
ErrAggregatePubKeys = errors.New("failed to aggregate pubkeys")
|
|
)
|
|
|
|
// AttestationOutput records a quorum attestation to an arbitrary 32-byte
|
|
// commitment. It is write-only once created.
|
|
type AttestationOutput struct {
|
|
verify.IsState `serialize:"-" json:"-"`
|
|
|
|
// AttestedHash is the commitment being attested to (application-defined).
|
|
AttestedHash [AttestedHashLen]byte `serialize:"true" json:"attestedHash"`
|
|
// Threshold is the minimum number of pubkeys that must contribute to the
|
|
// aggregate signature. Enforced at verification time against the signer
|
|
// bitmap popcount.
|
|
Threshold uint32 `serialize:"true" json:"threshold"`
|
|
// PubKeys are the committee pubkeys, sorted lexicographically, each
|
|
// exactly PubKeyLen bytes. Lexicographic order is consensus-enforced so
|
|
// the codec output is deterministic.
|
|
PubKeys [][]byte `serialize:"true" json:"pubKeys"`
|
|
}
|
|
|
|
func (out *AttestationOutput) Verify() error {
|
|
if out == nil {
|
|
return ErrNilOutput
|
|
}
|
|
switch {
|
|
case len(out.PubKeys) == 0:
|
|
return ErrEmptyPubKeys
|
|
case out.Threshold == 0:
|
|
return ErrThresholdZero
|
|
case out.Threshold > uint32(len(out.PubKeys)):
|
|
return ErrThresholdExceedsPubKeys
|
|
}
|
|
for i, pk := range out.PubKeys {
|
|
if len(pk) != PubKeyLen {
|
|
return ErrInvalidPubKeyLen
|
|
}
|
|
if i > 0 && !byteLess(out.PubKeys[i-1], pk) {
|
|
return ErrPubKeysNotSortedUnique
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AttestationInput references which pubkeys contributed to the aggregate.
|
|
// Bit i (little-endian) set in Signers means PubKeys[i] signed.
|
|
type AttestationInput struct {
|
|
Signers []byte `serialize:"true" json:"signers"`
|
|
}
|
|
|
|
func (in *AttestationInput) Verify() error {
|
|
if in == nil {
|
|
return ErrNilInput
|
|
}
|
|
if len(in.Signers) == 0 {
|
|
return ErrSignerBitmapEmpty
|
|
}
|
|
// Require at least one bit set.
|
|
anySet := false
|
|
for _, b := range in.Signers {
|
|
if b != 0 {
|
|
anySet = true
|
|
break
|
|
}
|
|
}
|
|
if !anySet {
|
|
return ErrSignerBitmapEmpty
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Credential carries the single aggregate BLS signature.
|
|
type Credential struct {
|
|
AggSig [SigLen]byte `serialize:"true" json:"aggregateSignature"`
|
|
}
|
|
|
|
func (cr *Credential) Verify() error {
|
|
if cr == nil {
|
|
return ErrNilCredential
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// byteLess reports whether a < b lexicographically.
|
|
func byteLess(a, b []byte) bool {
|
|
if len(a) < len(b) {
|
|
return true
|
|
}
|
|
if len(a) > len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if a[i] != b[i] {
|
|
return a[i] < b[i]
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// popcountBitmap returns the total number of set bits in b.
|
|
func popcountBitmap(b []byte) uint32 {
|
|
var n uint32
|
|
for _, by := range b {
|
|
n += popcount8(by)
|
|
}
|
|
return n
|
|
}
|
|
|
|
// popcount8 is the Brian Kernighan popcount.
|
|
func popcount8(b byte) uint32 {
|
|
var n uint32
|
|
for b != 0 {
|
|
b &= b - 1
|
|
n++
|
|
}
|
|
return n
|
|
}
|
|
|
|
// bitmapSetBits returns the indexes (ascending) whose bit is set in b.
|
|
// Bit 0 is the LSB of b[0], bit 8 is the LSB of b[1], etc. — the
|
|
// little-endian-per-byte convention.
|
|
func bitmapSetBits(b []byte, maxBits int) []int {
|
|
out := make([]int, 0, popcountBitmap(b))
|
|
for i := 0; i < maxBits; i++ {
|
|
byteIdx := i / 8
|
|
bitIdx := i % 8
|
|
if byteIdx >= len(b) {
|
|
break
|
|
}
|
|
if b[byteIdx]&(1<<uint(bitIdx)) != 0 {
|
|
out = append(out, i)
|
|
}
|
|
}
|
|
return out
|
|
}
|