Summary
FalconPrivateKeyParameters does not expose a way to recover the corresponding FalconPublicKeyParameters (the polynomial h) from an already-encoded private key (f ‖ g ‖ F). Mathematically h = g · f⁻¹ mod (q, xⁿ+1) is fully determined by (f, g), but the FalconNIST.crypto_sign_keypair flow that computes h is internal and only runs during fresh key generation.
Reproduction
BC version: 1.79 (also reproduced on bcprov-jdk18on:1.78.1)
// Round-trip a Falcon-512 private key through its encoded form and try to
// reconstruct the keypair without the public key.
FalconKeyPairGenerator gen = new FalconKeyPairGenerator();
gen.init(new FalconKeyGenerationParameters(new SecureRandom(), FalconParameters.falcon_512));
AsymmetricCipherKeyPair kp = gen.generateKeyPair();
byte[] encodedSk = ((FalconPrivateKeyParameters) kp.getPrivate()).getEncoded();
// encodedSk layout for Falcon-512: f(384) || g(384) || F(512) — no h, no G.
// There is no public API to get back to FalconPublicKeyParameters from encodedSk.
// FalconPrivateKeyParameters has no getPublicKeyParameters() / derivePublicKey().
// Reconstructing via `new FalconPrivateKeyParameters(params, f, g, F, new byte[0])`
// gives a valid signing key but no way to obtain h.
Why this matters
- Wallets / HSMs: a stored Falcon private key should be sufficient to recover its address. Today every consumer must persist
(sk, pk) side-by-side or persist the 48-byte keygen seed and re-run keygen on every load, which is significantly slower than a one-shot g · f⁻¹ and is awkward when the seed was never retained (imported keys, HSM-extracted keys, etc.).
- Symmetry with other PQC algorithms in BC:
MLDSAPrivateKeyParameters.getPublicKeyParameters() and SLHDSAPrivateKeyParameters.getPublicKeyParameters() already exist; Falcon is the odd one out.
- NIST FN-DSA (FIPS 206 draft) is converging on Falcon as SLH-DSA's sibling; downstream libraries are starting to assume "private key implies public key".
Suggested API
Either of these would resolve it; preferably both:
public class FalconPrivateKeyParameters extends FalconKeyParameters {
/** Recover the public key (h = g · f⁻¹ mod q) corresponding to this private key. */
public FalconPublicKeyParameters getPublicKeyParameters();
}
…and/or a stateless helper alongside the existing FalconKeyPairGenerator:
public class FalconKeyPairGenerator {
public static FalconPublicKeyParameters derivePublicKey(FalconPrivateKeyParameters sk);
}
The math is already implemented inside FalconNIST (the compute_public step of crypto_sign_keypair); exposing it just requires lifting it out of the keygen-only path and feeding it the (f, g) decoded from the existing encoded private key.
Workaround
Currently consumers must either (a) store the 896-byte encoded h next to the 1280-byte private key, or (b) persist the 48-byte SHAKE256 keygen seed and re-derive the entire keypair on load. We're currently doing (a) and would happily switch to a getPublicKeyParameters() once available.
Happy to send a PR if the maintainers agree on the shape — please confirm whether getPublicKeyParameters() on the parameters class is the preferred location, or whether you'd rather keep this on the generator.
Summary
FalconPrivateKeyParametersdoes not expose a way to recover the correspondingFalconPublicKeyParameters(the polynomialh) from an already-encoded private key (f ‖ g ‖ F). Mathematicallyh = g · f⁻¹ mod (q, xⁿ+1)is fully determined by(f, g), but theFalconNIST.crypto_sign_keypairflow that computeshis internal and only runs during fresh key generation.Reproduction
BC version: 1.79 (also reproduced on
bcprov-jdk18on:1.78.1)Why this matters
(sk, pk)side-by-side or persist the 48-byte keygen seed and re-run keygen on every load, which is significantly slower than a one-shotg · f⁻¹and is awkward when the seed was never retained (imported keys, HSM-extracted keys, etc.).MLDSAPrivateKeyParameters.getPublicKeyParameters()andSLHDSAPrivateKeyParameters.getPublicKeyParameters()already exist; Falcon is the odd one out.Suggested API
Either of these would resolve it; preferably both:
…and/or a stateless helper alongside the existing
FalconKeyPairGenerator:The math is already implemented inside
FalconNIST(thecompute_publicstep ofcrypto_sign_keypair); exposing it just requires lifting it out of the keygen-only path and feeding it the(f, g)decoded from the existing encoded private key.Workaround
Currently consumers must either (a) store the 896-byte encoded
hnext to the 1280-byte private key, or (b) persist the 48-byte SHAKE256 keygen seed and re-derive the entire keypair on load. We're currently doing (a) and would happily switch to agetPublicKeyParameters()once available.Happy to send a PR if the maintainers agree on the shape — please confirm whether
getPublicKeyParameters()on the parameters class is the preferred location, or whether you'd rather keep this on the generator.