FiloSottile/age · error
invalid tag recipient public key: %v
Error message
invalid tag recipient public key: %v
What it means
After the length check, NewClassicRecipient decodes the bytes as a compressed P-256 point via nistec.NewP256Point().SetBytes. This error reports a point-decoding failure: the bytes have the right size but do not represent a point on the P-256 curve (bad prefix byte or invalid coordinates).
Source
Thrown at tag/tag.go:76
return nil, fmt.Errorf("malformed recipient %q: %v", s, err)
}
return r, nil
default:
return nil, fmt.Errorf("malformed recipient %q: invalid type %q", s, t)
}
}
const compressedPointSize = 1 + 32
const uncompressedPointSize = 1 + 32 + 32
// NewClassicRecipient returns a new P-256 [Recipient] from a raw public key.
func NewClassicRecipient(publicKey []byte) (*Recipient, error) {
if len(publicKey) != compressedPointSize {
return nil, fmt.Errorf("invalid tag recipient public key size %d", len(publicKey))
}
p, err := nistec.NewP256Point().SetBytes(publicKey)
if err != nil {
return nil, fmt.Errorf("invalid tag recipient public key: %v", err)
}
k, err := hpke.DHKEM(ecdh.P256()).NewPublicKey(p.Bytes())
if err != nil {
return nil, fmt.Errorf("invalid tag recipient public key: %v", err)
}
return &Recipient{k}, nil
}
// NewHybridRecipient returns a new hybrid P-256 + ML-KEM-768 [Recipient] from
// raw concatenated public keys.
func NewHybridRecipient(publicKey []byte) (*Recipient, error) {
k, err := hpke.MLKEM768P256().NewPublicKey(publicKey)
if err != nil {
return nil, fmt.Errorf("invalid tagpq recipient public key: %v", err)
}
return &Recipient{k}, nil
}
View on GitHub (pinned to b74dce4cdb)
Solutions
- Check the wrapped %v for the exact SetBytes failure.
- Verify the key really is a P-256 (NIST) public key, not X25519/Ed25519 or another curve.
- Ensure the first byte is the SEC1 compressed prefix 0x02 or 0x03.
- Re-export the key from its source in compressed SEC1 form rather than hand-assembling bytes.
Defensive patterns
Strategy: validation
Validate before calling
if len(publicKey) == 33 && (publicKey[0] == 0x02 || publicKey[0] == 0x03) {
if _, err := tag.NewClassicRecipient(publicKey); err != nil {
return fmt.Errorf("not a valid P-256 point: %w", err)
}
} Type guard
func isCompressedP256Point(b []byte) bool {
if len(b) != 33 || (b[0] != 0x02 && b[0] != 0x03) {
return false
}
_, err := nistec.NewP256Point().SetBytes(b)
return err == nil
} Prevention
- Confirm key provenance: P-256 only, never X25519/Ed25519 material.
- Validate points with nistec.SetBytes before handing keys to constructors.
- Regenerate keys from trusted generators rather than assembling bytes manually.
When it happens
Trigger: tag.NewClassicRecipient with a 33-byte slice whose first byte is not 0x02/0x03, or whose x-coordinate has no matching y on the curve.
Common situations: Random/corrupted bytes passed as a key; endian or format confusion (e.g. Ed25519 or Curve25519 keys fed as P-256); manually assembled key material.
Related errors
- invalid tag recipient public key size %d
- failed to decrypt and authenticate final chunk: %w
- failed to decrypt and authenticate chunk at offset %d: %w
- wrong ecdh Curve
- failed to create hybrid public key: %v
AI-assisted analysis of FiloSottile/age@b74dce4cdb (2026-08-31).
Data as JSON: /api/errors/d5a770312942cae2.
Report an issue: GitHub.