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 likeValidator::public_key.