Skip to main content

Module bls_pubkey

Module bls_pubkey 

Source
Expand description

Compressed-G1 BLS public key (BlsPublicKey<Bls12381G2Impl> from dashcore’s blstrs_plus). Used via #[serde(with = "crate::serialization::dashcore::bls_pubkey")].

The wrapped type comes from dashcore::blsful, which is only linked when the bls-signatures feature is on (crate::bls_signatures). Its only consumers — the core_types validator/validator-set structs — are themselves gated behind core-types, which requires bls-signatures, so gating here keeps the module from breaking builds that omit BLS (e.g. wasm-drive-verify). Local serde wrapper for BlsPublicKey<Bls12381G2Impl> that tolerates owned-string sources (serde_json::Value, platform_value::Value, and anything routed through serde’s ContentDeserializer for tagged-enum buffering).

§Why this exists

Upstream blstrs_plus 0.8.18 (src/serde_impl.rs:119) implements the human-readable deserialize path as:

if d.is_human_readable() {
    let hex_str = <&str>::deserialize(d)?;   // borrowed-only
    ...
}

<&str>::deserialize only succeeds when the deserializer’s visitor receives visit_borrowed_str — which serde_json::from_slice / serde_json::from_str provide, but serde_json::from_value, platform_value::from_value, and ContentDeserializer do not (they produce owned String). Round-tripping a BlsPublicKey through any Value representation therefore fails with "invalid type: string ..., expected a borrowed string".

This is technically a serde compatibility quirk rather than a single crate’s bug — but the leaf type is the only place to patch. See plan §10b “Common pattern: serde’s ContentDeserializer HR-quirk” for the broader narrative.

§How the workaround works

A single deserialize_any visitor accepts BOTH wire forms upstream emits: the 96-char hex string (visit_str) and the 48-byte compressed-G1 form as raw bytes or a u8 sequence (visit_bytes / visit_seq). Either way it hex/byte-decodes to the compressed-G1 representation, lifts it to G1Projective via to_curve, and wraps into PublicKey<Bls12381G2Impl> directly (the inner field is pub).

Driving it via deserialize_any (rather than branching on is_human_readable()) is what makes it robust to serde’s internal-tag Content buffer: that buffer’s is_human_readable() does not reliably match the original deserializer, so a Value (non-HR) pubkey could arrive as a byte sequence while the old HR branch expected a string — which is exactly why the ValidatorSet value round-trip test used to be #[ignore]d. Accepting both shapes in one visitor sidesteps that, and also sidesteps the upstream borrowed-only <&str>::deserialize HR path. Both serde_json and platform_value are self-describing, so deserialize_any is safe; bincode never reaches here (it goes through the separate derived Decode).

Note: BlsPublicKey<Bls12381G2Impl> carries a public key on the G1 curve (the Bls12381G2Impl name refers to where signatures live, not keys). Compressed G1 = 48 bytes = 96 hex chars.

§When to remove this

This wrapper is now self-sufficient (no behavioral dependency on an upstream fix). Once upstream blstrs_plus accepts owned strings AND a byte-sequence HR form on its own Deserialize, this wrapper and the serde(with = ...) annotations on Validator::public_key / ValidatorSetV0::threshold_public_key can simply be dropped.

Modules§

option
Option<BlsPublicKey<Bls12381G2Impl>> variant for fields like Validator::public_key.

Functions§

deserialize
serialize