canopy-network/canopy · error

duplicate bls public key

Error message

duplicate bls public key

What it means

NewMultiBLSFromPublicKey returns "duplicate bls public key" when the decoded MultiPublicKey contains the same public key bytes more than once. Duplicate signers would double-count a single party toward the threshold, so decoding rejects them.

Source

Thrown at lib/crypto/bls.go:347

		for i := 1; i < len(mpk.PublicKeys); i++ {
			if bytes.Compare(mpk.PublicKeys[i-1], mpk.PublicKeys[i]) >= 0 {
				return nil, errInvalidPK
			}
		}
	}
	// Reject unused bits because kyber ignores them during verification.
	if remainder := len(mpk.PublicKeys) % 8; remainder != 0 {
		paddingMask := byte(0xff << remainder)
		if mpk.Bitmap[len(mpk.Bitmap)-1]&paddingMask != 0 {
			return nil, errInvalidPK
		}
	}
	var points []kyber.Point
	seen := make(map[string]struct{}, len(mpk.PublicKeys))
	// convert to a kyber.point
	for _, key := range mpk.PublicKeys {
		if _, exists := seen[string(key)]; exists {
			return nil, errors.New("duplicate bls public key")
		}
		seen[string(key)] = struct{}{}
		point, err := BytesToBLS12381Point(key)
		if err != nil {
			return nil, err
		}
		points = append(points, point)
	}
	mask, err := sign.NewMask(newBLSSuite(), points, nil)
	if err != nil {
		return nil, err
	}
	if err = mask.SetMask(mpk.Bitmap); err != nil {
		return nil, err
	}
	key := newBLSMultiPublicKey(mask, mpk.Threshold)
	// Reject semantically equivalent protobuf encodings that would produce different transaction hashes.
	if !bytes.Equal(publicKey, key.Bytes()) {

View on GitHub (pinned to ee8197d91d)

Solutions

  1. Deduplicate the key list (by raw bytes) before constructing/serializing the MultiPublicKey
  2. Remove the duplicate entry from the serialized key material
  3. Add a pre-check that compares unique key count to threshold

Example fix

// before
mpk.PublicKeys = append(mpk.PublicKeys, dupKey) // dupKey already present
// after
if !slices.Contains(mpk.PublicKeys, newKey) {
	mpk.PublicKeys = append(mpk.PublicKeys, newKey)
}
Defensive patterns

Strategy: validation

Validate before calling

seen := map[string]struct{}{}
for _, k := range keys {
	if _, dup := seen[string(k)]; dup { return errors.New("duplicate signer key") }
	seen[string(k)] = struct{}{}
}
mpk, err := crypto.NewMultiBLSFromPublicKey(serialized)

Try / catch

mpk, err := crypto.NewMultiBLSFromPublicKey(publicKey)
if err != nil {
	return nil, fmt.Errorf("multisig key decode: %w", err)
}

Prevention

When it happens

Trigger: Deserializing a MultiPublicKey whose PublicKeys list contains two identical byte slices; building a key by appending the same signer twice then serializing.

Common situations: Hand-rolled key assembly where a signer was added twice; merging signer lists without deduplication; config edits that copy/pasted a key entry.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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