Date: 2026-01-02 Investigators: Liz Fong-Jones & Claude Status: BLOCKED - Upstream go-crypto dependency issues
Initial investigation found that Hockeypuck has most v5/v6 infrastructure in place. However, deep testing revealed the blocker is NOT in Hockeypuck, but in the go-crypto library dependency:
- Ed448 compression flag 0xA5 rejected - go-crypto only accepts 0x40, but GnuPG 2.5 v5 keys use 0xA5
- Kyber algorithm (type 8) not defined - go-crypto has no constant or parser for Kyber post-quantum encryption
These are upstream issues that must be fixed in ProtonMail/go-crypto or pgpkeys-eu/go-crypto fork.
Liz generated a new ed448/kyber OpenPGP v5 key using GnuPG 2.5 (LibrePGP) for post-quantum cryptography. Discovered that current keyservers reject v5 keys:
- keys.openpgp.org:
"OpenPGP v3 and OpenPGP v5 (LibrePGP) are not supported" - Sequoia-PGP (Hagrid backend) cannot even read v5 keys
- Ubuntu's keyserver.ubuntu.com runs Hockeypuck, which also doesn't support v5/v6 yet
- Need: PPA work requires keys on keyserver.ubuntu.com
- Workaround: Using old v4 DSA/RSA key (valid until 2030) for PPA uploads
- Goal: Get v5 key support into Hockeypuck so it can be uploaded to Ubuntu keyservers
- LibrePGP (GnuPG 2.5): Uses v5 format per RFC4880bis
- IETF RFC9580: Rejected v5, defined v6 instead
- GnuPG is "obstinately rejecting RFC9580" - no v6 support planned
- Good news: Hockeypuck maintainer intends to support BOTH v5 and v6
- ✅ #252: Storage preening thread (closed July 2025)
- ✅ #285: Purge string reversals from codebase (closed Dec 2025)
- Due date: Oct 31, 2025 (MISSED)
- Status: 9 open issues, including #247
- Description: "Target the forthcoming HKP RFC, including v6 certificate support"
After adding Liz's real v5 key as a test fixture and fixing Hockeypuck's setKeyID() function, attempted to run integration tests. Tests failed with errors from go-crypto:
Error 1: unsupported EdDSA compression: 165 (0xA5)
Error 2: public key type: 8
What GnuPG 2.5 generates:
- v5 keys use algorithm 22 (EdDSALegacy), NOT algorithm 28 (Ed448)
- Ed448 curve: OID 1.3.101.113
- Point data (456 bits / 57 bytes) starts with byte 0xA5
What go-crypto rejects:
// vendor/.../openpgp/packet/public_key.go:531-539
switch flag := pk.p.Bytes()[0]; flag {
case 0x04:
return errors.UnsupportedError(...)
case 0x40:
err = pub.UnmarshalPoint(pk.p.Bytes()) // ONLY THIS
default:
return errors.UnsupportedError(...) // REJECTS 0xA5!
}What libgcrypt (GnuPG's crypto library) accepts:
// libgcrypt/cipher/ecc-eddsa.c _gcry_ecc_eddsa_ensure_compact()
if (buf[0] == 0x04) {
// Decompress SEC1 uncompressed
} else if (buf[0] == 0x40) {
// Strip 0x40 prefix
} else {
return 0; // ✅ ACCEPT ANYTHING ELSE (including 0xA5)
}Conclusion: go-crypto is overly restrictive compared to GnuPG's libgcrypt.
v5 subkey uses: Algorithm 8 (Kyber post-quantum encryption)
go-crypto constants (packet.go:525-543):
const (
PubKeyAlgoECDH PublicKeyAlgorithm = 18
PubKeyAlgoECDSA PublicKeyAlgorithm = 19
PubKeyAlgoEdDSA PublicKeyAlgorithm = 22 // What v5 keys use
PubKeyAlgoX25519 PublicKeyAlgorithm = 25
PubKeyAlgoX448 PublicKeyAlgorithm = 26
PubKeyAlgoEd25519 PublicKeyAlgorithm = 27
PubKeyAlgoEd448 PublicKeyAlgorithm = 28
// ❌ NO ALGORITHM 8 (Kyber)
)Result: When parser encounters algorithm 8, it returns unsupported error: public key type: 8
GnuPG's view of the key:
$ gpg --list-packets testing/data/v5_ed448_kyber.asc
:public key packet:
version 5, algo 22, created 1767336170
pkey[0]: [32 bits] ed448 (1.3.101.113)
pkey[1]: [456 bits]
keyid: 8CC84EAD81F6030A
Raw bytes:
98 49 05 69 57 68 ea 16 00 00 00 3f 03 2b 65 71 01 c8 a5 c7 3b...
^v5 ^algo22 ^OID ^MPI ^0xA5
Test results with -tags v5:
V5Disabled = false
Packet 1: *packet.UnsupportedPacket
UNSUPPORTED: unsupported EdDSA compression: 165
(Was PublicKey version 5)
Packet 14: *packet.UnsupportedPacket
UNSUPPORTED: public key type: 8
(Was PublicKey version 5)
Fix #1: Relax parseEdDSA() compression check
- Change line 538 from rejecting unknown flags to accepting them (match libgcrypt)
- Allow 0xA5 and other encodings used by GnuPG 2.5
Fix #2: Add Kyber algorithm support
- Add
PubKeyAlgoKyber PublicKeyAlgorithm = 8constant - Implement
parseKyber()function (or at minimum: consume and skip gracefully)
File: vendor/github.com/ProtonMail/go-crypto/openpgp/packet/public_key.go
Lines: 531-539
Current code:
switch flag := pk.p.Bytes()[0]; flag {
case 0x04:
return errors.UnsupportedError("unsupported EdDSA compression: " + strconv.Itoa(int(flag)))
case 0x40:
err = pub.UnmarshalPoint(pk.p.Bytes())
default:
return errors.UnsupportedError("unsupported EdDSA compression: " + strconv.Itoa(int(flag)))
}Proposed fix (match libgcrypt behavior):
switch flag := pk.p.Bytes()[0]; flag {
case 0x04:
// TODO: Implement SEC1 uncompressed decompression
return errors.UnsupportedError("unsupported EdDSA compression: " + strconv.Itoa(int(flag)))
case 0x40:
// Strip 0x40 prefix byte (legacy format)
err = pub.UnmarshalPoint(pk.p.Bytes())
default:
// Assume already in compact native format (libgcrypt compatibility)
// This handles 0xA5 and other encodings used by GnuPG 2.5 v5 keys
err = pub.UnmarshalPoint(pk.p.Bytes())
}Rationale:
- Matches libgcrypt's permissive behavior
- Allows future encoding formats without code changes
- Only rejects 0x04 (truly unsupported uncompressed format)
File: vendor/github.com/ProtonMail/go-crypto/openpgp/packet/packet.go
After line 538:
const (
// ... existing constants ...
PubKeyAlgoEd448 PublicKeyAlgorithm = 28
PubKeyAlgoKyber PublicKeyAlgorithm = 8 // ML-KEM (Kyber) post-quantum
)File: vendor/github.com/ProtonMail/go-crypto/openpgp/packet/public_key.go
Add case in parse() switch:
case PubKeyAlgoKyber:
err = pk.parseKyber(r)Option A - Parse as opaque data:
func (pk *PublicKey) parseKyber(r io.Reader) (err error) {
// Read key material as opaque data (proper Kyber support requires ML-KEM library)
var buf [2]byte
if _, err = io.ReadFull(r, buf[:]); err != nil {
return
}
length := int(buf[0])<<8 | int(buf[1])
keyData := make([]byte, length)
if _, err = io.ReadFull(r, keyData); err != nil {
return
}
pk.PublicKey = keyData
return nil
}Option B - Skip gracefully (simpler):
case PubKeyAlgoKyber:
// Consume and skip Kyber subkey (not yet supported)
// Don't fail - allows primary key to be parsed
_, err = io.Copy(io.Discard, r)Immediate steps:
- Check ProtonMail/go-crypto main branch for existing fixes
- Apply local patch to test the fix works
- Submit upstream PR to pgpkeys-eu/go-crypto fork (used by Hockeypuck)
- Update Hockeypuck's go.mod after go-crypto is patched
Options for contribution:
- Option A: Local patch in Hockeypuck vendor/ (fastest, but diverges from upstream)
- Option B: PR to pgpkeys-eu/go-crypto (recommended - benefits Hockeypuck ecosystem)
- Option C: PR to ProtonMail/go-crypto (slowest, but broadest impact)
See: /home/lizf/.claude/plans/zany-crafting-petal.md for complete implementation plan
PARTIAL IMPLEMENTATION EXISTS:
-
Dependencies ready:
- ProtonMail/go-crypto v1.3 vendored (replaced with pgpkeys-eu/go-crypto fork from 2025-11-26)
- go-crypto has UpgradeToV5() and UpgradeToV6() functions
-
Code evidence of v5/v6 awareness:
// src/hockeypuck/openpgp/signature.go // Since v5 keys are permitted to make v4 sigs, we infer IssuerFpVersion==5 by heuristic. if len(s.IssuerFingerprint) == 32 && s.Version != 6 { sig.IssuerFpVersion = 5 }
// src/hockeypuck/openpgp/io.go // Write detached redacting revocations first (except for v6 keys) if node.Version < 6 { revoc, err := node.RedactingSignature() // ... }
-
Data structures ready:
PublicKey.Versionfield exists (uint8)- Populated from
packet.PublicKey.Version IssuerFpVersionfield exists in Signature struct
BLOCKERS REMAINING:
-
🔴 CRITICAL: go-crypto algorithm support (UPSTREAM DEPENDENCY)
- Ed448 compression flag 0xA5 rejected
- Kyber algorithm (type 8) not defined
- BLOCKS ALL v5 KEY PARSING
-
🟡 MEDIUM: Hockeypuck setKeyID() fix (DONE ✅)
- Fixed to handle v5/v6 64-character fingerprints
- Uses first 16 hex chars for key ID (v5/v6 spec)
- File:
openpgp/pubkey.go
-
🟢 LOW: SKS recon protocol not updated (CAN DEFER)
// src/hockeypuck/hkp/sks/recon.go "versions:34", // no v5 or 6 yet
- Only affects synchronization with other keyservers
- Local storage/retrieval can work without this
- Can be updated after ecosystem coordination
- andrewgdotcom: "This will be a big job, and I won't have the capacity to do it myself"
- Intent to support both v5 and v6 keys
- "Many commonalities between v5 and v6 keys so it won't be twice the work"
- Dependent on go-crypto v2 for v6 keys (see PR ProtonMail/go-crypto#182)
- Also dependent on HKP RFC work (#252, #285)
- Dec 2025: Issue #285 (string reversals) completed
- Nov 2025: go-crypto fork updated (pgpkeys-eu/go-crypto)
- Milestone 2.4 due date passed without completion
-
Fixed setKeyID() for v5/v6 -
openpgp/pubkey.go:191-201- Now correctly extracts first 16 hex chars for v5/v6 keys
- Validates fingerprint length (64 chars for v5/v6)
-
Added test fixture -
testing/data/v5_ed448_kyber.asc- Liz's real v5 ed448/kyber key
- Fingerprint: 8CC84EAD81F6030A3DAD7682E1A1B1460361985E971CE686465E13793CF804B3
-
Created integration test -
openpgp/io_test.goTestV5Ed448Keytest case- Currently fails due to go-crypto blockers
- V5_KEY_PARSING_BLOCKERS.md - Detailed root cause analysis
- Updated plan -
/home/lizf/.claude/plans/zany-crafting-petal.md - This file - Investigation notes with blocker section
File: vendor/github.com/ProtonMail/go-crypto/openpgp/packet/public_key.go (lines 529-554)
Problem: v5 Ed448 keys use algorithm 22 (EdDSALegacy) with native point encoding. The first byte is 0xA5 (actual key data) rather than 0x40 (compression flag). When the default case tried to call UnmarshalPoint, it failed because Ed448's UnmarshalBytePoint expects 58 bytes (57 + 1-byte prefix) but v5 keys have exactly 57 bytes.
Solution implemented:
switch flag := pk.p.Bytes()[0]; flag {
case 0x04:
return errors.UnsupportedError("unsupported EdDSA compression: " + strconv.Itoa(int(flag)))
case 0x40:
// v6 format with 0x40 prefix - UnmarshalPoint will strip it
err = pub.UnmarshalPoint(pk.p.Bytes())
default:
// Already in compact native format (libgcrypt compatibility)
// For EdDSALegacy (algo 22), the point is in native format without prefix
// Set X directly instead of calling UnmarshalPoint to avoid length check
pub.X = pk.p.Bytes()
if pub.X == nil || len(pub.X) == 0 {
return errors.StructuralError("empty EdDSA public key point")
}
}Key insight: Setting pub.X directly bypasses UnmarshalBytePoint's length validation, which expected 58 bytes but v5 keys have 57 bytes with 0xA5 as actual data, not a prefix.
File: vendor/github.com/ProtonMail/go-crypto/openpgp/packet/packet.go (line 527)
Added constant:
PubKeyAlgoKyber PublicKeyAlgorithm = 8 // ML-KEM (Kyber) post-quantum - GnuPG 2.5File: vendor/github.com/ProtonMail/go-crypto/openpgp/packet/public_key.go (lines 609-651)
Added KyberPublicKey struct:
type KyberPublicKey struct {
// OID identifies the composite algorithm (e.g., ky1024_cv448 = 1.3.101.111)
oid []byte
// ECC point (X25519 or X448) for classical ECDH
eccPoint []byte
// ML-KEM public key material (ML-KEM-768 or ML-KEM-1024)
mlkemKey []byte
}Added parser that properly parses the composite structure:
func (pk *PublicKey) parseKyber(r io.Reader) (err error) {
// Kyber (ML-KEM) support is experimental in GnuPG 2.5
// Parse the composite structure: OID + ECC point + ML-KEM key
// Note: Encryption/decryption operations not yet implemented
// Read OID identifying the composite algorithm
pk.oid = new(encoding.OID)
if _, err = pk.oid.ReadFrom(r); err != nil {
return
}
// Read ECC point (X25519 or X448)
eccMPI := new(encoding.MPI)
if _, err = eccMPI.ReadFrom(r); err != nil {
return
}
// Read ML-KEM public key material
mlkemMPI := new(encoding.MPI)
if _, err = mlkemMPI.ReadFrom(r); err != nil {
return
}
// Store parsed components
kyberKey := &KyberPublicKey{
oid: pk.oid.Bytes(),
eccPoint: eccMPI.Bytes(),
mlkemKey: mlkemMPI.Bytes(),
}
pk.PublicKey = kyberKey
return nil
}Added serialization and utility support:
- Case in
parse()switch (lines 283-286) - Case in
publicKeyMaterialLength()(lines 740-748) - properly encodes all three components - Case in
serializeWithoutHeaders()(lines 841-856) - serializes OID + ECC point + ML-KEM key - Case in
BitLength()(lines 1169-1172) - returns ML-KEM key size in bits - Updated
CanSign()(line 819) - Kyber is encryption-only, cannot sign
Test case: openpgp/io_test.go:339-351 (TestV5Ed448Key)
func (s *SamplePacketSuite) TestV5Ed448Key(c *gc.C) {
key := MustInputAscKey("v5_ed448_kyber.asc")
c.Assert(key, gc.NotNil)
c.Assert(key.Version, gc.Equals, uint8(5))
// v5 keys use SHA-256 fingerprints (64 hex chars)
c.Assert(len(key.Fingerprint), gc.Equals, 64)
c.Assert(key.Fingerprint, gc.Equals, "8cc84ead81f6030a3dad7682e1a1b1460361985e971ce686465e13793cf804b3")
// v5 key ID is first 8 bytes (16 hex chars) of fingerprint
c.Assert(key.KeyID, gc.Equals, "8cc84ead81f6030a")
// Check that we have user IDs and subkeys
c.Assert(len(key.UserIDs), gc.Equals, 3) // 3 text UIDs (photo stored separately as UserAttribute)
c.Assert(len(key.SubKeys), gc.Equals, 1) // kyber encryption subkey
}Status: ✅ PASS - Full test suite: OK: 37 passed
Parsed successfully:
- Ed448 primary key (algorithm 22, version 5)
- Kyber subkey (algorithm 8)
- All 15 packets from v5_ed448_kyber.asc
- Fingerprint: 8CC84EAD81F6030A3DAD7682E1A1B1460361985E971CE686465E13793CF804B3
- KeyID: 8cc84ead81f6030a (first 16 hex chars of fingerprint)
- 3 UserIDs (photo attribute stored separately)
- 1 Kyber subkey
- Kyber encryption/decryption not implemented - The composite structure (OID + ECC point + ML-KEM key) is properly parsed and stored in a KyberPublicKey struct, but actual encryption/decryption operations are not implemented. Full ML-KEM cryptographic operations require additional library support (e.g., FiloSottile/mlkem768).
- v5 format is experimental - GnuPG 2.5 v5 support is beta software. The format may change.
These changes maintain compatibility with existing keys:
- v6 Ed448 keys with 0x40 prefix continue to work via UnmarshalPoint
- v4 keys unaffected
- Kyber support is additive (no existing code paths modified)
✅ go-crypto PR: pgpkeys-eu/go-crypto#8
URL: pgpkeys-eu/go-crypto#8
Status: Awaiting review
Branch: lizthegrey:lizf.v5-kyber-support
Changes submitted:
- Modified
parseEdDSA()to handle native Ed448 encoding (0xA5 flag) - Added
PubKeyAlgoKyberconstant (value 8) - Added
KyberPublicKeystruct to represent composite ML-KEM + ECC keys - Added
parseKyber()function that properly parses OID + ECC point + ML-KEM key - Added Kyber cases to:
parse(),publicKeyMaterialLength(),serializeWithoutHeaders(),BitLength() - Updated
CanSign()to exclude Kyber (encryption-only algorithm)
Testing: All go-crypto tests pass (go test -tags v5 ./openpgp/...)
Files modified:
openpgp/packet/public_key.go(+87 lines)openpgp/packet/packet.go(+1 line)
Note: Submitted to pgpkeys-eu/go-crypto (Hockeypuck's fork) as interim solution. Once proven stable in Hockeypuck production, can be forwarded to ProtonMail/go-crypto upstream.
✅ Hockeypuck PR: hockeypuck/hockeypuck#429
URL: hockeypuck/hockeypuck#429
Status: Awaiting go-crypto PR merge
Branch: lizthegrey:lizf.v5-key-support
Changes submitted:
- Fixed
setKeyID()to handle v5/v6 64-character fingerprints - Added
TestV5Ed448Keytest case - Added
testing/data/v5_ed448_kyber.asctest fixture (Liz's real v5 key)
Testing: All 37 Hockeypuck tests pass
Files modified:
src/hockeypuck/openpgp/pubkey.go(setKeyID fix)src/hockeypuck/openpgp/io_test.go(test case)src/hockeypuck/testing/data/v5_ed448_kyber.asc(new fixture)
Dependency: Requires go-crypto PR to merge first, then Hockeypuck can update go.mod
URL: hockeypuck/hockeypuck#247 (comment)
Updated issue #247 with status, links to both PRs, and summary of findings.
After PR feedback from Andrew (@andrewg.com) and discussions with hko-s (@hko-s.bsky.social), the v5/Kyber support work is a dead end:
- v5 keys are politically dead - LibrePGP vs IETF schism means v5 format will not be widely adopted
- Kyber (algorithm 8) won't be merged - GnuPG's implementation diverged from IETF ML-KEM spec in "spectacular bad faith"
- v6 format is the future - RFC 9580 has 6+ interoperable implementations
- Keyserver ecosystem issues - Even if merged, Ubuntu PPAs use
sq(Sequoia) which doesn't support v5
Andrew's comment on PR #8:
"EdDSA changes appear to be a workaround for GnuPG's violation of its own (LibrePGP) specification"
"I think it would be more reliable to detect the novel format by length rather than the prefix byte"
"Did you (or claude?) find a source for the offending format change btw?"
The ONLY salvageable piece: Finding the libgcrypt source showing Ed448 encoding behavior
Finding: GnuPG's libgcrypt produces Ed448 public keys WITHOUT the 0x40 prefix byte, violating OpenPGP specification expectations.
- libgcrypt version: 1.11.2
- Path:
/home/lizf/gpg-2.5/libgcrypt20-1.11.2/ - Key files:
cipher/ecc-eddsa.c- EdDSA encoding/decoding functionscipher/ecc.c- Key generation calling codecipher/ecc-curves.c- Curve definitions
1. Ed448 uses SAFECURVE dialect (cipher/ecc-curves.c:182-183):
"Ed448", 448, 1,
MPI_EC_EDWARDS, ECC_DIALECT_SAFECURVE,2. Key generation encoding logic (cipher/ecc.c:775-778):
rc = _gcry_ecc_eddsa_encodepoint (ec->Q, ec, Gx, Gy,
(ec->dialect != ECC_DIALECT_SAFECURVE
&& !!(flags & PUBKEY_FLAG_COMP)),
&encpk, &encpklen);→ The with_prefix parameter = (ec->dialect != ECC_DIALECT_SAFECURVE && !!(flags & PUBKEY_FLAG_COMP))
→ For Ed448 (SAFECURVE): with_prefix = 0 (FALSE)
3. Encoding function behavior (cipher/ecc-eddsa.c:92-112 in eddsa_encode_x_y):
int off = with_prefix? 1:0;
// ...
if (off)
rawmpi[0] = 0x40; // Only adds 0x40 if with_prefix=1
// ...
*r_buflen = rawmpilen + off;→ When with_prefix=0: NO 0x40 prefix added, length = 57 bytes
RESULT: libgcrypt produces 57-byte Ed448 public keys with NO 0x40 prefix
Decoding function (cipher/ecc-eddsa.c:372-489 in _gcry_ecc_eddsa_decodepoint):
Lines 404-435 - Handle SEC1 uncompressed (0x04):
if (rawmpilen > 1 && (rawmpilen%2))
{
if (buf[0] == 0x04) {
// Extract x and y, compress to EdDSA format
}
}Lines 437-444 - Handle 0x40 prefix (libgcrypt extension):
/* Check whether the public key has been prefixed with a 0x40
byte to explicitly indicate compressed format using a SEC1
alike prefix byte. This is a Libgcrypt extension. */
if (buf[0] == 0x40)
{
rawmpilen--;
buf++; // Strip the prefix
}Lines 447-452 - Accept everything else as-is:
reverse_buffer (buf, b); /* Process as little-endian */RESULT: libgcrypt accepts BOTH:
- 57-byte unprefixed format (what it produces for SAFECURVE/Ed448)
- 58-byte 0x40-prefixed format (strips prefix, compatible with v6)
- Any other first byte values (treats entire buffer as native EdDSA point)
-
OpenPGP v6 specification (RFC 9580): Requires 0x40 prefix for EdDSA points (58 bytes for Ed448)
-
libgcrypt behavior: Produces 57-byte unprefixed Ed448 points because Ed448 uses
ECC_DIALECT_SAFECURVE -
The mismatch:
- GnuPG 2.5 v5 keys use libgcrypt's native 57-byte format (no prefix)
- OpenPGP v6 expects 58-byte format (0x40 + 57 bytes)
- libgcrypt's decoder is permissive and accepts both formats
- Other OpenPGP implementations expect strict compliance with the spec
Option 1: Fix libgcrypt/GnuPG (Upstream) Change Ed448 encoding to always add 0x40 prefix for OpenPGP keys.
- Pros: Fixes root cause, makes GnuPG spec-compliant
- Cons: Requires GnuPG cooperation, backward compatibility issues
Option 2: Length-Based Detection in go-crypto (Andrew's suggestion) Detect format by length rather than prefix byte:
- 57 bytes → Treat as unprefixed Ed448 (libgcrypt native format)
- 58 bytes starting with 0x40 → Strip prefix and process
- 58 bytes starting with other → Error (invalid)
- Pros: Pragmatic workaround, backward compatible, explicit
- Cons: Doesn't fix the root cause, accepts non-compliant keys
Option 3: Hybrid Approach Accept length-based detection in go-crypto BUT file bug report with GnuPG.
- Most realistic given the political situation
The libgcrypt source code evidence is documented. Andrew and hko-s can now decide whether to:
- Push for upstream GnuPG fix
- Accept length-based detection in go-crypto
- Hybrid approach (workaround + upstream bug report)
All v5/Kyber-specific work should be abandoned. Only the Ed448 encoding fix is worth pursuing.
- pgpkeys-eu maintainer to review and merge PR #8
- Address any feedback or changes requested
- After go-crypto PR merges, update Hockeypuck's go.mod to reference new commit
- This can be done by the maintainer or as a follow-up commit to PR #429
- Once go-crypto dependency is updated, PR #429 can be merged
- v5 key support will be complete end-to-end
- Upload Liz's v5 key to Hockeypuck instance
- Verify storage, retrieval, and SKS recon (after updating "versions:34")
src/hockeypuck/openpgp/pubkey.go- Key parsing/storagesrc/hockeypuck/openpgp/signature.go- Already has v5 handlingsrc/hockeypuck/openpgp/io.go- Already has v6 awarenesssrc/hockeypuck/hkp/sks/recon.go- SKS sync protocol ("versions:34")go.mod- Dependencies (go-crypto v1.3 via pgpkeys-eu fork)
- Issue #247: hockeypuck/hockeypuck#247
- Liz's key transition: https://gist.github.com/lizthegrey/2cc04c3719c2df1627c793f15bc4faa1
- Bluesky update: https://bsky.app/profile/lizthegrey.com/post/3mbhzpjhbsj2f
- ProtonMail go-crypto PR: ProtonMail/go-crypto#182
- HKP RFC draft: https://datatracker.ietf.org/doc/html/draft-gallagher-openpgp-hkp