canopy-network/canopy · error

account-auth multisig requires threshold > 0

Error message

account-auth multisig requires threshold > 0

What it means

errAccountAuthThreshold is thrown when a BLS multisig account-auth key is constructed with a threshold of 0. A multisig with threshold 0 would allow zero signatures to authorize a transaction, which is meaningless and unsafe, so NewAccountAuthMultiBLSFromPoints and NewAccountAuthMultiBLSFromPublicKey reject it up front.

Source

Thrown at lib/crypto/bls.go:267

// String() returns the hex string representation of the public key
func (b *BLS12381PublicKey) String() string {
	return hex.EncodeToString(b.Bytes())
}

var _ MultiPublicKeyI = &BLS12381MultiPublicKey{}

// BLS12381MultiPublicKey is an aggregated public key created by combining multiple BLS public keys from different signers.
// This type intentionally exposes two representations:
//   - Address(): canonical multisig account identity for the signer set
//   - Bytes()/Bitmap()/AddSigner(): order-preserving verification state where signer indices are meaningful
type BLS12381MultiPublicKey struct {
	signatures [][]byte
	mask       *sign.Mask
	scheme     *bdn.Scheme
	threshold  uint32
}

var errAccountAuthThreshold = errors.New("account-auth multisig requires threshold > 0")

// NewBLSMultiPublicKey() creates a new BLS12381MultiPublicKey reference from a kyber mask object
func newBLSMultiPublicKey(mask *sign.Mask, threshold uint32) *BLS12381MultiPublicKey {
	return &BLS12381MultiPublicKey{mask: mask, scheme: newBLSScheme(), signatures: make([][]byte, len(mask.Publics())), threshold: threshold}
}

// NewMultiBLSFromPoints() creates a multi public key from a list of G1 points on a BLS12381 curve.
// Important: this preserves caller order for backwards compatibility. Bitmap indices and AddSigner() indices refer to
// this exact order, so this constructor must not canonicalize or sort the signer list.
func NewMultiBLSFromPoints(publicKeys []kyber.Point, bitmap []byte) (MultiPublicKeyI, error) {
	mask, err := sign.NewMask(newBLSSuite(), publicKeys, nil)
	if err != nil {
		return nil, err
	}
	if bitmap != nil {
		if err = mask.SetMask(bitmap); err != nil {
			return nil, err
		}

View on GitHub (pinned to ee8197d91d)

Solutions

  1. Pass a threshold >= 1 (and <= number of public keys) when calling NewAccountAuthMultiBLSFromPoints
  2. Set the threshold field on the serialized MultiPublicKey before NewMultiBLSFromPublicKey/NewAccountAuthMultiBLSFromPublicKey
  3. Validate/fix stored key material and re-serialize it with a correct threshold

Example fix

// before
key, _ := crypto.NewAccountAuthMultiBLSFromPoints(points, bitmap, 0)
// after
threshold := uint32(2)
if threshold == 0 || threshold > uint32(len(points)) { threshold = uint32(len(points)) }
key, _ := crypto.NewAccountAuthMultiBLSFromPoints(points, bitmap, threshold)
Defensive patterns

Strategy: validation

Validate before calling

if threshold == 0 || threshold > uint32(len(publicKeys)) {
	return errors.New("threshold must be in [1, len(publicKeys)]")
}
key, err := crypto.NewAccountAuthMultiBLSFromPoints(publicKeys, bitmap, threshold)

Try / catch

key, err := crypto.NewAccountAuthMultiBLSFromPoints(points, bitmap, threshold)
if err != nil {
	return fmt.Errorf("multisig construction: %w", err)
}

Prevention

When it happens

Trigger: Calling NewAccountAuthMultiBLSFromPoints(publicKeys, bitmap, 0), or NewAccountAuthMultiBLSFromPublicKey with serialized bytes whose decoded MultiPublicKey has threshold == 0.

Common situations: Threshold field left at its zero value in config/structs, deserializing keys produced by older library versions that did not persist a threshold, or copying constructors without setting the threshold parameter.

Related errors


AI-assisted analysis of canopy-network/canopy@ee8197d91d (2026-09-06). Data as JSON: /api/errors/5c7122c66cc6e93c. Report an issue: GitHub.