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
- Deduplicate the key list (by raw bytes) before constructing/serializing the MultiPublicKey
- Remove the duplicate entry from the serialized key material
- 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
- Deduplicate signer lists before serialization
- Never append a signer without checking existence
- Validate unique signer count >= threshold when assembling keys
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
- account-auth multisig requires threshold > 0
- invalid public key
- Item too long: {len(item)} bytes (max 255)
- Invalid uint64 value: {value}
- Invalid chain_id: {self.chain_id}
AI-assisted analysis of canopy-network/canopy@ee8197d91d (2026-09-06).
Data as JSON: /api/errors/d1acae370cf64749.
Report an issue: GitHub.