From 03eefd72121d267e07a32b10036165b6d4b45234 Mon Sep 17 00:00:00 2001 From: pasta Date: Mon, 2 Nov 2026 17:51:53 +0000 Subject: [PATCH 1/5] pkc%test(bls): add a program that writes the BLS read corpus gen_read.cpp draws seeded inputs, each class's property decided or checked by the pinned release's BLS library, and records how that library reads each as a key or signature under both schemes. --- pkgs/pkc/contrib/README.md | 3 + pkgs/pkc/contrib/gen_read.cpp | 659 +++++++++++++++++++++++++++++++++ pkgs/pkc/corpus/bls_read.json5 | 228 ++++++++++++ 3 files changed, 890 insertions(+) create mode 100644 pkgs/pkc/contrib/gen_read.cpp create mode 100644 pkgs/pkc/corpus/bls_read.json5 diff --git a/pkgs/pkc/contrib/README.md b/pkgs/pkc/contrib/README.md index b0bee585a..fc49b6d7b 100644 --- a/pkgs/pkc/contrib/README.md +++ b/pkgs/pkc/contrib/README.md @@ -10,6 +10,7 @@ serialization and hashing, and commitment hash function, apart from fixed inputs | `bls_ies.json5` | `gen_ies.cpp` | | `bls_keygen.json5` | `gen_keygen.cpp` | | `bls_llmq_100.json5` | `gen_llmq_100.cpp` | +| `bls_read.json5` | `gen_read.cpp` | | `bls_secure_aggregate.json5` | `gen_secure_aggregate.cpp` | | `bls_serialize.json5` | `gen_serialize.cpp` | | `bls_sign.json5` | `gen_sign.cpp` | @@ -20,6 +21,8 @@ inputs, which the program checks against the commitment hash it computes. For `b legacy map's hashing and reduction steps with relic, which the library does not expose, and the library's own point anchors them. +`bls_read.json5` records how the library reads key and signature bytes under each scheme, as `gen_read.cpp` describes. + ## Regenerating `regen.py` needs Python 3.11.4 or later, git, CMake, a C compiler, a C++20 compiler, Boost and GMP, all of which the diff --git a/pkgs/pkc/contrib/gen_read.cpp b/pkgs/pkc/contrib/gen_read.cpp new file mode 100644 index 000000000..311a54716 --- /dev/null +++ b/pkgs/pkc/contrib/gen_read.cpp @@ -0,0 +1,659 @@ +/*! + * Copyright (c) 2026-present, The Dash Core developers + * SPDX-License-Identifier: MIT + * See the accompanying file LICENSE or https://opensource.org/license/MIT + */ + +// Writes bls_read.json5, how the release's BLS library reads public keys and +// signatures from bytes under each scheme. +// +// The inputs come from seeded classes of encodings. What a class holds, such +// as an x with a point on the curve, one with none or a point of the group, +// comes from the library's own reads and operations, or is checked against +// them so that the program stops when the two disagree. Relic draws +// candidates with its field operations and gives the bounds of the field and +// the group. The curve of a legacy read comes from the curve check relic's +// read runs, applied to the point the library returns. +// +// Each outcome is the library's read, with an all-zero input marked null as +// the release's key and signature types do. An input is valid when the +// library reads an element other than the identity that encodes back to it, +// and invalid otherwise. For an input the legacy scheme reads as valid, the +// corpus also gives whether the point the library reads lies on the curve. + +#include "util/common.hpp" + +#include +#include +#include + +using namespace corpus; + +namespace { +//! The seed of the stream the inputs come from. +constexpr uint64_t kSeed = 0xb511a7; +//! How many inputs each class that is not a fixed value draws. +constexpr int kSamples = 3; + +/** A byte stream of SHA-256(seed || counter) blocks. */ +class Stream +{ +public: + Bytes Take(size_t len) + { + Bytes out; + while (out.size() < len) { + if (pos_ == block_.size()) { + Refill(); + } + out.push_back(block_.at(pos_++)); + } + return out; + } + + uint8_t NonZeroByte() + { + for (;;) { + const uint8_t b = Take(1).at(0); + if (b != 0) { + return b; + } + } + } + +private: + void Refill() + { + Bytes in; + for (uint64_t v : {kSeed, counter_++}) { + for (int i = 7; i >= 0; --i) { + in.push_back(static_cast(v >> (8 * i))); + } + } + block_ = Sha256(in); + pos_ = 0; + } + + uint64_t counter_{0}; + Bytes block_; + size_t pos_{0}; +}; + +/** The field modulus and the group order, from relic. */ +struct Bounds { + Bytes p; + Bytes r; + Bytes p_minus_1; +}; + +Bytes BnBytes(const bn_t v, size_t len) +{ + Bytes out(len); + bn_write_bin(out.data(), len, v); + return out; +} + +const Bounds& GetBounds() +{ + static const Bounds bounds = [] { + bn_t p, r; + bn_null(p); + bn_null(r); + bn_new(p); + bn_new(r); + bn_read_raw(p, fp_prime_get(), RLC_FP_DIGS); + ep_curve_get_ord(r); + Bounds b{BnBytes(p, 48), BnBytes(r, 32), {}}; + bn_sub_dig(p, p, 1); + b.p_minus_1 = BnBytes(p, 48); + bn_free(p); + bn_free(r); + return b; + }(); + return bounds; +} + +/** @return @p raw with the top three bits of its first byte replaced. */ +Bytes WithFlags(Bytes raw, uint8_t flags) +{ + raw.at(0) = (raw.at(0) & 0x1f) | flags; + return raw; +} + +Bytes Cat(const Bytes& a, const Bytes& b) +{ + Bytes out(a); + out.insert(out.end(), b.begin(), b.end()); + return out; +} + +/** @return 381 random bits, as a big-endian 48-byte value. */ +Bytes Bits381(Stream& rng) +{ + return WithFlags(rng.Take(48), 0); +} + +/** @return A value below p, or at or above p when @p high is set. */ +Bytes FieldValue(Stream& rng, bool high = false) +{ + for (;;) { + Bytes v = Bits381(rng); + if ((v >= GetBounds().p) == high) { + return v; + } + } +} + +/** @return A nonzero scalar below the group order. */ +Bytes Scalar(Stream& rng) +{ + for (;;) { + Bytes v = rng.Take(32); + if (v < GetBounds().r && v != Bytes(32, 0)) { + return v; + } + } +} + +/** Stops the program when the library does not bear out @p claim. */ +void Expect(bool held, const char* claim) +{ + if (!held) { + throw std::logic_error(std::string("the library disagrees, ") + claim); + } +} + +/** + * @return Whether the library's basic read finds a point at the x that + * @p basic encodes, from the read that checks the curve but not the group. + */ +template +bool HasPoint(const Bytes& basic) +{ + try { + E::FromBytesUnchecked(bls::Bytes(basic), /*fLegacy=*/false); + return true; + } catch (const std::invalid_argument&) { + return false; + } +} + +/** @return Whether the library's checked basic read takes @p basic. */ +template +bool InGroup(const Bytes& basic) +{ + try { + E::FromBytes(bls::Bytes(basic), /*fLegacy=*/false); + return true; + } catch (const std::invalid_argument&) { + return false; + } +} + +/** @return The basic layout of a G1 x, 0x80 and the sign clear. */ +Bytes G1Basic(const Bytes& x) +{ + return WithFlags(x, 0x80); +} + +struct Fp2Bytes { + Bytes c0; + Bytes c1; +}; + +/** @return The legacy layout, c0 then c1, with the sign in bit 0x80. */ +Bytes G2Legacy(const Fp2Bytes& x, bool sign) +{ + return WithFlags(Cat(x.c0, x.c1), sign ? 0x80 : 0); +} + +/** @return The basic layout, c1 then c0, 0x80 and the sign in 0x20. */ +Bytes G2Basic(const Fp2Bytes& x, bool sign) +{ + return WithFlags(Cat(x.c1, x.c0), 0x80 | (sign ? 0x20 : 0)); +} + +/** + * @return A nonzero x below p with a point on the curve, or with none, as + * the library's basic read finds. + */ +Bytes G1X(Stream& rng, bool on_curve) +{ + for (;;) { + Bytes x = FieldValue(rng); + if (x != Bytes(48, 0) && HasPoint(G1Basic(x)) == on_curve) { + return x; + } + } +} + +/** @return An x below p with a point on the curve, or with none. */ +Fp2Bytes G2X(Stream& rng, bool on_curve) +{ + for (;;) { + Fp2Bytes x{FieldValue(rng), FieldValue(rng)}; + if (HasPoint(G2Basic(x, /*sign=*/false)) == on_curve) { + return x; + } + } +} + +/** @return The library's key for a random scalar. */ +bls::PrivateKey Key(Stream& rng) +{ + return bls::PrivateKey::FromBytes(bls::Bytes(Scalar(rng))); +} + +/** @return The x of the point the library makes from a random key. */ +Bytes G1Sub(Stream& rng) +{ + return WithFlags(Key(rng).GetG1Element().Serialize(/*fLegacy=*/false), 0); +} + +/** @return The x of the point the library makes from a random key. */ +Fp2Bytes G2Sub(Stream& rng) +{ + const Bytes raw = Key(rng).GetG2Element().Serialize(/*fLegacy=*/false); + return {Bytes(raw.begin() + 48, raw.end()), WithFlags(Bytes(raw.begin(), raw.begin() + 48), 0)}; +} + +/** + * @return A G1 x with a point on the curve outside the group, which the + * library's checked read refuses. + */ +Bytes G1NonSub(Stream& rng) +{ + Bytes x = G1X(rng, /*on_curve=*/true); + Expect(!InGroup(G1Basic(x)), "a G1 x outside the group"); + return x; +} + +/** @return A G2 x with a point on the curve outside the group. */ +Fp2Bytes G2NonSub(Stream& rng) +{ + Fp2Bytes x = G2X(rng, /*on_curve=*/true); + Expect(!InGroup(G2Basic(x, /*sign=*/false)), "a G2 x outside the group"); + return x; +} + +/** + * @return Whether the library reads a point at @p x whose y has no u term. + * The library writes the sign of that term alone, so the point and its + * negation encode alike exactly when the term is zero. + */ +bool RealY(const Fp2Bytes& x) +{ + const Bytes basic = G2Basic(x, /*sign=*/false); + bls::G2Element element; + try { + element = bls::G2Element::FromBytesUnchecked(bls::Bytes(basic), /*fLegacy=*/false); + } catch (const std::invalid_argument&) { + return false; + } + return element.Serialize(/*fLegacy=*/false) == element.Negate().Serialize(/*fLegacy=*/false); +} + +/** + * @return An x = a + b u whose points have y in Fp. Relic's field operations + * solve 3 a^2 b - b^3 = -4 for a to draw a candidate, and the program takes it + * only when the library reads a point there whose y has no u term. + */ +Fp2Bytes G2RealY(Stream& rng) +{ + for (;;) { + const Bytes b_bytes = FieldValue(rng); + if (b_bytes == Bytes(48, 0)) { + continue; + } + fp_t b, num, den, a2, a; + fp_read_bin(b, b_bytes.data(), 48); + fp_sqr(num, b); + fp_mul(num, num, b); + fp_sub_dig(num, num, 4); + fp_set_dig(den, 3); + fp_mul(den, den, b); + fp_inv(den, den); + fp_mul(a2, num, den); + if (!fp_srt(a, a2)) { + continue; + } + Fp2Bytes x{Bytes(48), b_bytes}; + fp_write_bin(x.c0.data(), 48, a); + if (RealY(x)) { + return x; + } + } +} + +using Gen = std::function; + +struct Class { + std::string name; + Gen gen; + bool fixed{false}; +}; + +Class Fixed(std::string name, const Bytes& value) +{ + return {std::move(name), [value](Stream&) { return value; }, true}; +} + +Bytes Lead(uint8_t first, size_t len) +{ + Bytes out(len, 0); + out.at(0) = first; + return out; +} + +std::vector G1Classes() +{ + auto flags = [](const Gen& make, uint8_t bits) -> Gen { + return [=](Stream& rng) { + return WithFlags(make(rng), bits); + }; + }; + const Gen sub = G1Sub; + const Gen nonsub = G1NonSub; + const Gen on = [](Stream& rng) { + return G1X(rng, /*on_curve=*/true); + }; + const Gen off = [](Stream& rng) { + return G1X(rng, /*on_curve=*/false); + }; + const Gen ge_p = [](Stream& rng) { + return FieldValue(rng, /*high=*/true); + }; + const auto& bounds = GetBounds(); + return { + Fixed("zero", Bytes(48, 0)), + Fixed("identity", Lead(0xc0, 48)), + {"inf_tail", + [](Stream& rng) { + Bytes out = Lead(0xc0, 48); + out.back() = rng.NonZeroByte(); + return out; + }}, + Fixed("inf_sign", Lead(0xe0, 48)), + {"inf_x", flags(on, 0xc0)}, + Fixed("x_eq_p_000", bounds.p), + {"x_ge_p_000", flags(ge_p, 0x00)}, + {"x_ge_p_100", flags(ge_p, 0x80)}, + {"x_ge_p_101", flags(ge_p, 0xa0)}, + {"stray_001_sub", flags(sub, 0x20)}, + {"stray_010_sub", flags(sub, 0x40)}, + {"stray_011_sub", flags(sub, 0x60)}, + {"stray_111_sub", flags(sub, 0xe0)}, + {"stray_001_off", flags(off, 0x20)}, + {"stray_010_off", flags(off, 0x40)}, + {"stray_011_off", flags(off, 0x60)}, + {"sub_000", flags(sub, 0x00)}, + {"sub_100", flags(sub, 0x80)}, + {"sub_101", flags(sub, 0xa0)}, + {"nonsub_000", flags(nonsub, 0x00)}, + {"nonsub_100", flags(nonsub, 0x80)}, + {"nonsub_101", flags(nonsub, 0xa0)}, + {"off_000", flags(off, 0x00)}, + {"off_100", flags(off, 0x80)}, + {"off_101", flags(off, 0xa0)}, + Fixed("x0_001", Lead(0x20, 48)), + Fixed("x0_010", Lead(0x40, 48)), + Fixed("x0_011", Lead(0x60, 48)), + Fixed("x0_100", Lead(0x80, 48)), + Fixed("x0_101", Lead(0xa0, 48)), + Fixed("x_max_000", bounds.p_minus_1), + Fixed("x_max_100", WithFlags(bounds.p_minus_1, 0x80)), + Fixed("x_max_101", WithFlags(bounds.p_minus_1, 0xa0)), + {"random", [](Stream& rng) { + return rng.Take(48); + }}, + }; +} + +std::vector G2Classes() +{ + using XGen = std::function; + auto legacy = [](const XGen& make, bool sign) -> Gen { + return [=](Stream& rng) { + return G2Legacy(make(rng), sign); + }; + }; + auto basic = [](const XGen& make, bool sign) -> Gen { + return [=](Stream& rng) { + return G2Basic(make(rng), sign); + }; + }; + auto flags = [](const Gen& make, uint8_t bits) -> Gen { + return [=](Stream& rng) { + return WithFlags(make(rng), bits); + }; + }; + const XGen sub = G2Sub; + const XGen nonsub = G2NonSub; + const XGen on = [](Stream& rng) { + return G2X(rng, /*on_curve=*/true); + }; + const XGen off = [](Stream& rng) { + return G2X(rng, /*on_curve=*/false); + }; + const XGen real_y = G2RealY; + const Fp2Bytes zero_x{Bytes(48, 0), Bytes(48, 0)}; + const XGen fp_x = [](Stream& rng) { + return Fp2Bytes{G1X(rng, /*on_curve=*/true), Bytes(48, 0)}; + }; + const XGen imag_x = [](Stream& rng) { + return Fp2Bytes{Bytes(48, 0), G1X(rng, /*on_curve=*/true)}; + }; + + auto tail = [](size_t at) -> Gen { + return [=](Stream& rng) { + Bytes out = Lead(0xc0, 96); + out.at(at) = rng.NonZeroByte(); + return out; + }; + }; + // One coordinate at or above p, in the first or the second half. + auto pair = [](bool first_half, uint8_t bits) -> Gen { + return [=](Stream& rng) { + const Bytes bad = FieldValue(rng, /*high=*/true); + const Bytes good = FieldValue(rng); + return WithFlags(first_half ? Cat(bad, good) : Cat(good, bad), bits); + }; + }; + // A legacy encoding off the curve, sign clear, with flags atop x.c1. + auto c1_flags = [](uint8_t bits) -> Gen { + return [=](Stream& rng) { + const Bytes raw = G2Legacy(G2X(rng, /*on_curve=*/false), /*sign=*/false); + const Bytes c1(raw.begin() + 48, raw.end()); + return Cat(Bytes(raw.begin(), raw.begin() + 48), WithFlags(c1, bits)); + }; + }; + const Gen c0_flags = [](Stream& rng) { + const Bytes raw = G2Basic(G2Sub(rng), /*sign=*/false); + const Bytes c0(raw.begin() + 48, raw.end()); + return Cat(Bytes(raw.begin(), raw.begin() + 48), WithFlags(c0, 0x20)); + }; + + std::vector out; + for (const auto& [name, make] : + {std::pair{"fp_x", fp_x}, {"imag_x", imag_x}}) { + for (int sign : {0, 1}) { + out.push_back({name + "_legacy_s" + std::to_string(sign), legacy(make, sign)}); + } + for (int sign : {0, 1}) { + out.push_back({name + "_basic_s" + std::to_string(sign), basic(make, sign)}); + } + } + std::vector rest = { + Fixed("zero", Bytes(96, 0)), + Fixed("identity", Lead(0xc0, 96)), + {"inf_tail_hi", tail(47)}, + {"inf_tail_lo", tail(95)}, + Fixed("inf_sign", Lead(0xe0, 96)), + {"inf_x", flags(basic(on, false), 0xc0)}, + {"legacy_c0_ge_p", pair(true, 0x00)}, + {"legacy_c1_ge_p", pair(false, 0x00)}, + {"basic_c1_ge_p", pair(true, 0x80)}, + {"basic_c0_ge_p", pair(false, 0x80)}, + {"basic_c0_flags", c0_flags}, + {"stray_001_sub", flags(basic(sub, false), 0x20)}, + {"stray_010_sub", flags(basic(sub, false), 0x40)}, + {"stray_011_sub", flags(basic(sub, false), 0x60)}, + {"stray_111_sub", flags(basic(sub, false), 0xe0)}, + {"stray_010_legacy", flags(legacy(sub, false), 0x40)}, + {"sub_legacy_s0", legacy(sub, false)}, + {"sub_legacy_s1", legacy(sub, true)}, + {"sub_basic_s0", basic(sub, false)}, + {"sub_basic_s1", basic(sub, true)}, + {"nonsub_legacy_s0", legacy(nonsub, false)}, + {"nonsub_legacy_s1", legacy(nonsub, true)}, + {"nonsub_basic_s0", basic(nonsub, false)}, + {"nonsub_basic_s1", basic(nonsub, true)}, + {"off_legacy_s0", legacy(off, false)}, + {"off_legacy_s1", legacy(off, true)}, + {"off_basic_s0", basic(off, false)}, + {"off_basic_s1", basic(off, true)}, + {"realy_legacy_s0", legacy(real_y, false)}, + {"realy_legacy_s1", legacy(real_y, true)}, + {"realy_basic_s0", basic(real_y, false)}, + {"realy_basic_s1", basic(real_y, true)}, + Fixed("x0_legacy_s1", G2Legacy(zero_x, true)), + Fixed("x0_basic_s0", G2Basic(zero_x, false)), + Fixed("x0_basic_s1", G2Basic(zero_x, true)), + {"random", + [](Stream& rng) { + return rng.Take(96); + }}, + {"off_legacy_c1_001", c1_flags(0x20)}, + {"off_legacy_c1_010", c1_flags(0x40)}, + {"off_legacy_c1_100", c1_flags(0x80)}, + }; + out.insert(out.end(), rest.begin(), rest.end()); + return out; +} + +/** + * @return The library's read of a key or signature. All zero is null, as the + * release's key and signature types mark it. An element other than the + * identity that encodes back to the input is valid, anything else invalid. + */ +template +const char* Read(const Bytes& in, bool legacy) +{ + if (std::all_of(in.begin(), in.end(), [](uint8_t b) { return b == 0; })) { + return "null"; + } + try { + const E element = E::FromBytes(bls::Bytes(in), legacy); + if (element == E()) { + return "invalid"; + } + return element.Serialize(legacy) == in ? "valid" : "invalid"; + } catch (...) { + return "invalid"; + } +} + +/** + * @return Whether the point the legacy scheme reads from @p raw lies on the + * curve, by the curve check relic's read runs and the legacy read does not + * act on. The library's basic read of the point must agree, unless its x is + * zero, which the basic layout cannot hold. + */ +template +const char* LegacyCurve(const Bytes& raw) +{ + const E element = E::FromBytes(bls::Bytes(raw), /*fLegacy=*/true); + bool on = false; + if constexpr (std::is_same_v) { + g1_t p; + g1_null(p); + g1_new(p); + element.ToNative(p); + on = g1_on_curve(p); + g1_free(p); + } else { + g2_t q; + g2_null(q); + g2_new(q); + element.ToNative(q); + on = g2_on_curve(q); + g2_free(q); + } + const Bytes basic = element.Serialize(/*fLegacy=*/false); + if (WithFlags(basic, 0) != Bytes(E::SIZE, 0)) { + Expect(HasPoint(basic) == on, "the curve of a legacy read"); + } + return on ? "on" : "off"; +} + +template +std::string +Section(const char* kind, const std::vector& classes, Stream& rng, const char* derive_from) +{ + std::vector> rows; + for (const auto& cls : classes) { + for (int i = 0; i < (cls.fixed ? 1 : kSamples); ++i) { + std::pair row{cls.name, cls.gen(rng)}; + // A class with few values can repeat one. + if (std::find(rows.begin(), rows.end(), row) == rows.end()) { + rows.push_back(row); + } + } + } + // The first sample of a legacy class, written by the library in the basic + // layout. + const auto first = + std::find_if(rows.begin(), rows.end(), [&](const auto& r) { return r.first == derive_from; }); + const E element = E::FromBytes(bls::Bytes(first->second), /*fLegacy=*/true); + rows.emplace_back(std::string(derive_from) + "_basic", element.Serialize(/*fLegacy=*/false)); + + std::string out = std::string(" \"") + kind + "\": [\n"; + for (size_t i = 0; i < rows.size(); ++i) { + const auto& [name, raw] = rows.at(i); + const char* legacy = Read(raw, /*legacy=*/true); + out += " {\"class\": \"" + name + "\", \"in\": \"" + Hex(raw) + "\", \"legacy\": \"" + + legacy + "\", \"basic\": \"" + Read(raw, /*legacy=*/false) + "\""; + if (std::string(legacy) == "valid") { + out += std::string(", \"curve\": \"") + LegacyCurve(raw) + "\""; + } + out += i + 1 < rows.size() ? "},\n" : "}\n"; + } + return out + " ]"; +} + +/** Writes the corpus to stdout, returning the exit code. */ +int WriteCorpus() +{ + bls::BLS::Init(); + Stream rng; + const std::string pk = Section("pk", G1Classes(), rng, /*derive_from=*/"sub_000"); + const std::string sig = + Section("sig", G2Classes(), rng, /*derive_from=*/"sub_legacy_s0"); + const int written = std::printf( + "// BLS reads of public keys and signatures under the legacy and basic schemes.\n" + "//\n" + "// Each entry gives the input class, the input and the reference\n" + "// implementation's outcome under each scheme, \"null\" for the all-zero input\n" + "// as the release's key and signature types mark it, \"valid\" when its BLS\n" + "// library reads the input as an element other than the identity that encodes\n" + "// back to the input, and \"invalid\" otherwise. An input the legacy scheme\n" + "// reads as valid also gives \"curve\", \"on\" when the x it encodes has a point\n" + "// on the curve and \"off\" when it has none, by the library's read.\n" + "//\n" + "// Inputs come from pkgs/pkc/contrib/gen_read.cpp, seed %#llx, up to %d per\n" + "// class, and outcomes from the BLS library of the release the dash\n" + "// submodule pins. Regenerate with pkgs/pkc/contrib/regen.py.\n" + "{\n%s,\n%s\n}\n", + static_cast(kSeed), + kSamples, + pk.c_str(), + sig.c_str() + ); + return written >= 0 && std::fflush(stdout) == 0 ? 0 : 1; +} +} // anonymous namespace + +int main() +{ + return Run(WriteCorpus); +} diff --git a/pkgs/pkc/corpus/bls_read.json5 b/pkgs/pkc/corpus/bls_read.json5 new file mode 100644 index 000000000..443bd5dc7 --- /dev/null +++ b/pkgs/pkc/corpus/bls_read.json5 @@ -0,0 +1,228 @@ +// BLS reads of public keys and signatures under the legacy and basic schemes. +// +// Each entry gives the input class, the input and the reference +// implementation's outcome under each scheme, "null" for the all-zero input +// as the release's key and signature types mark it, "valid" when its BLS +// library reads the input as an element other than the identity that encodes +// back to the input, and "invalid" otherwise. An input the legacy scheme +// reads as valid also gives "curve", "on" when the x it encodes has a point +// on the curve and "off" when it has none, by the library's read. +// +// Inputs come from pkgs/pkc/contrib/gen_read.cpp, seed 0xb511a7, up to 3 per +// class, and outcomes from the BLS library of the release the dash +// submodule pins. Regenerate with pkgs/pkc/contrib/regen.py. +{ + "pk": [ + {"class": "zero", "in": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "null", "basic": "null"}, + {"class": "identity", "in": "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_tail", "in": "c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006d", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_tail", "in": "c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ce", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_tail", "in": "c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003f", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_sign", "in": "e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_x", "in": "d5e73f58bddda5e1b00b54d965a8f1a2de9cf6d56daca60107b77507f7ad2e2a2bb755f66792c24c53ba56b9d1275ece", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_x", "in": "d5b114942521942cfe4c8031126de3e471e8bbd3d27b04c832620010b5dca5731dc4e58a23f99d7a275b71354befb308", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_x", "in": "d97af21105bc8ab97dc443e39bd60eedc459523c802c0801507b7b3960f43c2a7f9937aed459f6480eee0822f2c1d847", "legacy": "invalid", "basic": "invalid"}, + {"class": "x_eq_p_000", "in": "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", "legacy": "invalid", "basic": "invalid"}, + {"class": "x_ge_p_000", "in": "1ce22919dc1f3e53f6365805b53f111118c2c7204093f15ce50ef085c55f672d4e609c97bf3505e440d247219e932ca9", "legacy": "invalid", "basic": "invalid"}, + {"class": "x_ge_p_000", "in": "1b500ab39c140a530aa5effd032e6ae3f44d2056a549933f1d6bdef965e24774173fbf338fc79db56f60d797a9aa361c", "legacy": "invalid", "basic": "invalid"}, + {"class": "x_ge_p_000", "in": "1f498fa682c5ec99310c20113878d52218b28feaae7eeec9ef10fd67136de8b15fb7fc036f5d2b1ccb6a97c4c5f0c66a", "legacy": "invalid", "basic": "invalid"}, + {"class": "x_ge_p_100", "in": "9efabfb0630d04d8793fb5b51df5185eac89f3e60dbd80dcc5b989cc02ec64b6ff06a750254c1839505aa8dbb917b087", "legacy": "invalid", "basic": "invalid"}, + {"class": "x_ge_p_100", "in": "9af3da0299caf7f79f73489e7d1931eabc6c8bc9aedb046b76f597fa4534d62ee67d1e21d4a67f3a23c36c0b865d270a", "legacy": "invalid", "basic": "invalid"}, + {"class": "x_ge_p_100", "in": "9c4c5fd24f3e31d42b4f7daffcc0e486854c1299c4395a27e54bc1e9ca82e22485aba1e178f41c320959722e5cbbe6af", "legacy": "invalid", "basic": "invalid"}, + {"class": "x_ge_p_101", "in": "be4cd94b8a009b38c70e86a8084ce672084bdb1f3a9c1a57c458752d20d263f8015cc9cf3e4f03278251d0064471d939", "legacy": "invalid", "basic": "invalid"}, + {"class": "x_ge_p_101", "in": "bd2200efc9be31d3f7d9c99a5cdfd694c36a65314eb34dfebdf6ff49bc843f9387a47f1d00e9d182af38bc931552ed29", "legacy": "invalid", "basic": "invalid"}, + {"class": "x_ge_p_101", "in": "bb901186bbacaab4c66fe7617bf42023fc3ffa7d2499000f45c9e26ec4b0e3466b3512d6bf3f6892ec5dc263a9831ef0", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_001_sub", "in": "30bd55d7575772df4cb32edcc889b59ccef30f34eab942e64c5a49e940b3fa91ab5149ecdf496f27ebe33d78bbebc1c8", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_001_sub", "in": "362b3ec75df47dcca11bd205818e795451aacaec2641f7fd82da07200d387775f316221f6c9fadfc72171e6bdc8b8183", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_001_sub", "in": "2dd489e6128717fb3640cc0b241fd2661c7721b1986966ed9ec7df19e0845636863698b345395e720f903ea4721a4d9a", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_010_sub", "in": "4f7373fedc27017a8d62ffb182986e02035eab352c9c0cfdd4aa82fbe7ed40375bf247a4a92f104049acee32574d1cd3", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_010_sub", "in": "4ee331dc2615690e0b08a7d7ddf19c0af11303d8b05840b6a62b6bd49a7c8459a6aad1e93c8b2f84557eff33e883cc44", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_010_sub", "in": "443cc40fe991c0c195a5ecf5b6d38b66355b953d1873d1c82db0967a3206fbefde7518b7b401ced3052940c2138996e8", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_011_sub", "in": "62090d753a7d6dfa1620a4d95e4e5f1e5a831b37b91101d6eef25504099bfdd122e09d5ce2e3e68ad15e58ab0ee4dd24", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_011_sub", "in": "63885c3991cd2a7b55fe37cb0533347e87b0a1e6fb839b9cf8c9bbf29cd42c173f0b6854323dbe7035f36de7988ae0e2", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_011_sub", "in": "6691ce29b0dfdb846a4a0252613d8d6759b5325467738debc9cd3ca2ed46055516d0f4bb2b18db17561383a8fbb19eb0", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_111_sub", "in": "e08db4e11ca0f1286740fe4a0e47dc4a603bcf2cb18bf429ffa59efbd96cafb96ab0188626887c80b67bb7eba05ba6c4", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_111_sub", "in": "eb3074ed972be180069914a81017ca14e5547cb398a4294276f22c125e6699ebc18c0acc22850889aead22f0bb9c33d9", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_111_sub", "in": "eb01f0871ed271239bf2276b6b36defb533b7ad595b2e97e3f8ad513f502d16fb128389f70739924880ac17979a3f9c0", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_001_off", "in": "36b6f270c2cfdb82eb246bd39c70a413d509681d6498c7de39c322764a77546513499912221c282f9843ce6f53ed936c", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_001_off", "in": "398264ca611aed700e83ea177dd848d44550417b6917cef86ac6499f334f9ded1d8e67b2a9d934444896fa807aab6184", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_001_off", "in": "23bfd3f1a38903f6000bb7636990379d161941ca1e0b555d7576eecf1b1285379c80c0d55a5b04aba536c73f126ece7c", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_010_off", "in": "4d28c700db11bfb1342526fffa37e08f5650d7201435318f5b5614ef660dbd6884f6f0ed03c60190719d518d434db11d", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_010_off", "in": "5473a8715ce31fa85c140b7e4e9efceb44333a97cf0daea96a9f31e2f44c3934be4c2173a7fb27939f5dca31c4f0c131", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_010_off", "in": "420beb70bfd929070af6a738def6dbf7123f69e4e5a0454a1649035a6e888e5b8d97d03b9714aabab447cca0f86a414b", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_011_off", "in": "7042fd0bebe45ebd00ebe00beef37e8cf0cee47e2627467fd218b6231d5b38e032d8e8f308d518bd5d25edf1e6e68f11", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_011_off", "in": "78716074c83d608716db8416040e245ca52655bd858fbeddf682ccb369c97db4cf5a94da715ec6847e7f62d2fc448416", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_011_off", "in": "6d52ad1f1942cbf409e9002dd5fa61d4057b0166133e24de116216ac074fc5387ea63043b066c250ceec77f2d09266ba", "legacy": "invalid", "basic": "invalid"}, + {"class": "sub_000", "in": "0553aeb2062298a36fa0c8f99405904628da31b074dd5297fb240b72037cbf043dea2d4d631dff3baeba623580ad79a5", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "sub_000", "in": "1118cd4f8824ec29e6d9f19e9f584a2945501c588ea15bdc63a3b835e1c31ff7654e5ff12446d8556e9182fba173c46b", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "sub_000", "in": "0a6d9876157728dad635a833da9b7034faabf5c5c2f84908907289d6b392e73000496370ad3d3f48618db72c45b8fcab", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "sub_100", "in": "8d9442274d08e7b1081f5814cda5fde2f0e6a06a67d4f39abccc2684c5eefc227560ac86c53b5a57c60f8502d6a7e9e9", "legacy": "valid", "basic": "valid", "curve": "on"}, + {"class": "sub_100", "in": "8378ad9139ab215a3b393a4266df0fd44511dec5455bff0677073e8e672783fdc890a287839b748ba77723f13c0c6539", "legacy": "valid", "basic": "valid", "curve": "on"}, + {"class": "sub_100", "in": "98718766f776f692ce8c16e95d6967a345d8bd8b558fd35b25b12f82d8bfad1cd9be5b62c3ea68a0af20676e79f9b8e9", "legacy": "valid", "basic": "valid", "curve": "on"}, + {"class": "sub_101", "in": "a02690bff240480310e84737ef6976b3010b637448a41e05d162d55fefb5db45e97980da3d8f0dc470d634b6f8d5b69e", "legacy": "invalid", "basic": "valid"}, + {"class": "sub_101", "in": "aa6e474d6558934366ef9c9555cbc2c60e8e4d5caa368f614cc500a9ba2b15cf2972ce28db9dc8f40e314794015cf254", "legacy": "invalid", "basic": "valid"}, + {"class": "sub_101", "in": "b8dbbefe6ed1a6405894890b028359a000c67349d0e8a41bf3b4227093410f688cf2531f6f2bdad5ee676755c1c55e80", "legacy": "invalid", "basic": "valid"}, + {"class": "nonsub_000", "in": "0609bfb859c806aefe69e3341f6dd83911a50f8355350bc22c0248318a86277fd2cd4d616acf0fc39d866443b4bee00e", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "nonsub_000", "in": "047b8e658172635a324c02f5876e9fd25fae65298faa0f37ea973d2c6787b6e70066b8935a1505f390900aece96fc4e1", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "nonsub_000", "in": "17466a10a6ab51da4d8feff41a421e107b39ae82670ea3ea915610954b5195cc65149cc3d6bdf70b09325413c74a881d", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "nonsub_100", "in": "82643f2e299b6335c36055b5846e38a31d4d9fd914ea92b5cf1d0ea722693fb407135b8dd410ad6cb71fffb86545757f", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "nonsub_100", "in": "924a5faea4937ee177de5eb8eb66cf8895cd363801f0cfe4b98b69471b00c68a26989062374369535564e8ef973280e0", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "nonsub_100", "in": "9743fa27f04601bffe799d4bb04de6bc9eb82fa0d4c38210a2f9bbda9c7b2f686bc995482b0d1ba4da85c56f14328577", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "nonsub_101", "in": "b9f24b63a46dfdf9dd1d40e1812525f04fcfdb907f7ed8c59ec92108be41c89e346a2b4699fc47ba8ef2c0440a03ab59", "legacy": "invalid", "basic": "invalid"}, + {"class": "nonsub_101", "in": "b8de72057340b62049301ae51a10c4c30bf3edab95dc769fb9691fe4589c7eb57db4df0ec944b0665914e9079ca39d93", "legacy": "invalid", "basic": "invalid"}, + {"class": "nonsub_101", "in": "b3258b782ff873455a0fb4d5422c20fec8ac98d64089dfee7f68a0282287b48b57f118962d99346f89970713a8c0e4e3", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_000", "in": "06f695bc0b0e663b0c125904a4c8b344e5cb291e65aa4c8183cc306b7e0923977a5cdf5591d323195ea35fb9388bf051", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "off_000", "in": "08032bd7e36624abc2a8dfd00ddd3195a6e38d27e941050868a17e4fa6fd2c39df0bfff25480e8aac4126478c42782ce", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "off_000", "in": "17001842e2858e3b490f80d461499223296aea164f4078fb6e85810ab1161aae0579ffb3e0f160296a9dec54d08eccf0", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "off_100", "in": "8b986bd86ff720c2e365b402e15a318f391d36d5765bfb13f349957b44271b12bf3d3ddc6b772f0829942d5bdd4e7373", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "off_100", "in": "9947faabce3ba66926681314abd326641858a1e1a43b480be4a9490acf3bee5df2c11683367a4749684195356c20f3a4", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "off_100", "in": "8a2792a539a5bfa0abee9dabd4a72382df810980e9aa6765ee39ff4e15b721f71c0f729b27dd1bf3f2fb122f99bfdd4a", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "off_101", "in": "b548b9a225d4381845fb96ced7753e2594cbc33ea3f51a5ff0d2b6584b6758e150bc603bfad699496d9c21f33f4fdd6f", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_101", "in": "aa83bbfa4bc00851f3def087ce4bc4c3b1474b526ed99312223d2c0710fe8fab317ae964e6ff29a09eb9787fc240ede0", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_101", "in": "a46c3077aadf9f70ba8c8bf22f4180a9c197c85ca4119532d4a4f8ab11c69b53b20280f1a35e601ae32ed2f43a3855ce", "legacy": "invalid", "basic": "invalid"}, + {"class": "x0_001", "in": "200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "x0_010", "in": "400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "x0_011", "in": "600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "x0_100", "in": "800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "x0_101", "in": "a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "x_max_000", "in": "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "x_max_100", "in": "9a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "x_max_101", "in": "ba0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaaa", "legacy": "invalid", "basic": "invalid"}, + {"class": "random", "in": "190851bcb58367a57447e5831f1586fefc452016860c77b81cc41ef041568a7a48426da0813875c1c3d4754f41720462", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "random", "in": "927ad3d460a725dc64f0113a975571bb45fd56901f4b24a9d508f0f55bdac87b28a72bc7ede9e3ccedeb7bd23111b4e3", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "random", "in": "d876152116eaf94527bd60199c80eef4f53df18589e3d6a3b21a5890daac93a597524c261c93d1efedca2ebb4c36dfd7", "legacy": "invalid", "basic": "invalid"}, + {"class": "sub_000_basic", "in": "8553aeb2062298a36fa0c8f99405904628da31b074dd5297fb240b72037cbf043dea2d4d631dff3baeba623580ad79a5", "legacy": "valid", "basic": "valid", "curve": "on"} + ], + "sig": [ + {"class": "fp_x_legacy_s0", "in": "00334ab70838156cc6c2a02e10544f7c6410e31b7528d220d18e8f40f436db6445c456011de3c0075fae6c31566830c9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "fp_x_legacy_s0", "in": "14fe44e277c8dc72154488d25e1ff7017d8a295d0e6cf0425d88392c60241810557ad2c21205daa177904d9f1f7affbc000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "fp_x_legacy_s0", "in": "0f5025e94147a102f98af4b0afda19786057d01f0536470edb01445220cec7ac56f1c8906ec351138b3bd9e7f378fd3a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "fp_x_legacy_s1", "in": "824a45fdcc627b09e2c688ef4a3c796a063f0fcc8c83d7da4f04f16392140e1f7bd85e14ece5681643eae26991ea8937000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "fp_x_legacy_s1", "in": "81889b90178d96aa4c55244170c35d1d74f30fba9b43c56ce59d307abdaaa786896a536a858f6db02f319bcd45d7c516000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "fp_x_legacy_s1", "in": "8db3c0cf5a7f1a40f94bbee04592e34936b715c245d75a6d04a77aeb5e0a263b5ef2e6eb03946e9dd1d8d7e8b08615c7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "fp_x_basic_s0", "in": "80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015ff0fa1e5df3749948a20f15cdd80a7bbf38d2604f93dbb9e97de62dfb78eaeb57ac67b9858e308144d2ec443ad3444", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "fp_x_basic_s0", "in": "80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002d687c04f24280124843789990352a06dab7267efbccdcd5cf6f15c4167020ac75d0d9118642446944f04f99f608920", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "fp_x_basic_s0", "in": "80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000014c13177322cbc38c3879eb8fe4b141a383ee2b468f2a30ba8c302591bad42c5b30941447496d93158a967f41a9ca942", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "fp_x_basic_s1", "in": "a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018fcf2d4a27892a7c59cad3ec852f558e4d4d1ac01af9aadd4efa946a96877109e8b3328bd707a4afc431db672f1202", "legacy": "invalid", "basic": "invalid"}, + {"class": "fp_x_basic_s1", "in": "a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000555a117c6e3412cb19f45a655969254cc454e58d138cd8c38a98ff5ce8ebb0f895af6d28a74356b1f10788f90e93fc9", "legacy": "invalid", "basic": "invalid"}, + {"class": "fp_x_basic_s1", "in": "a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056c019764b6dbe946681713f5d3b2fcba1815e73dd5add9557253a71af4bfea5d726b453a8f83b22eada8723468bb9e", "legacy": "invalid", "basic": "invalid"}, + {"class": "imag_x_legacy_s0", "in": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000019232c8a4149e2fc8c2e49f9111f02d1c2564dd65a8f1edd4f71a7f79d45fd8590d7dbea4d04a0c40d5f452f5ceecc62", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "imag_x_legacy_s0", "in": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000011aec473b94930972a2b4d2cf68c5ebe550f77bee025c579a83e28f7ecfeb342fd6839a3a0619b6078a9f8b03d1b3dbc", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "imag_x_legacy_s0", "in": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015655ad9d34b198ebaa12acc80e9905f44415259e83fa2e1b7f86a31898b07feb6cb822c11e26bd3c7e3377d37de36b5", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "imag_x_legacy_s1", "in": "800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000086c2fa3d91f9bedda2ae70f8b36325e33e4a4bf8a216186ecd4abc80031a08ab4b53cfec9380b96646d711d120f4984", "legacy": "invalid", "basic": "invalid"}, + {"class": "imag_x_legacy_s1", "in": "800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fecfa1f5824920914c8cdfcf5b884c7a1d05cef437f0be52aecbd7806c5fe78a130061201f9244d409d219e495ee7", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "imag_x_legacy_s1", "in": "80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008ffafedd5ce9e85a728db7d3dfde2e1ea7e8b69ced9da59cebb36258825dc6872ebfb817013ff39b6366da010a77c8c", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "imag_x_basic_s0", "in": "95d0656acd020c7d03bffc888cb2e6173b67fdcbc2bded2e7913fc122b764203084b2835002e1a20e7dd6093fba38554000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "imag_x_basic_s0", "in": "928b1598856fd2c3d2e912ca59daf5181efa888f3614394dfea8e6c45bbb4b975c04b71b51dfd069cbd0b45d2e8b0b07000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "imag_x_basic_s0", "in": "9072d7fa7f570912e74b2b9c5abf620b197855effb5d1dd25e86159af81cf0f80f279c4a54b70b9f34ff57e0919b403a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "imag_x_basic_s1", "in": "ad01f7206deb3065fbd7ea94c7911649b6efa70edabb99534c297919afa2cb31ccaf6a0c44e8a3abd4989d5440a68802000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "imag_x_basic_s1", "in": "ad611ef34d850a0975c2d31b3649849d259680e7d7a415cec7125ce155e7f031c13130f07333840d8706346a598d29f2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "imag_x_basic_s1", "in": "b31a1cba2e4d7b73ac7351614304cf003f4a91e04aaff4c98150aa1016ae14bd682ffd5cadee46df690c6eb6fec2011a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "zero", "in": "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "null", "basic": "null"}, + {"class": "identity", "in": "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_tail_hi", "in": "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000071000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_tail_hi", "in": "c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_tail_hi", "in": "c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_tail_lo", "in": "c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001c", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_tail_lo", "in": "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_tail_lo", "in": "c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e2", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_sign", "in": "e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_x", "in": "d90d1465e2ef37f32ae8f11b6c105c9bf491e8a5edb88cf6d956642dfb9558573f45faad92ee4b9a9c89cac42a97dcc90849f40013eb091d42b828ea869e0bcd4f92df3d89958168d72d097afa29a58b46191a21f4686c46bce9b785d4cd0295", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_x", "in": "c0e4d76a532f5acaedaa500501b0468de1a4eb5a2d2c7911fac91d586d7f70f772312ad5a306b4295c7f6232384e8a610911c5a14e9d4244f3cc42ceb7608335c90014f99cd87d43c3b7de155feb6ddcc24aa7117d48f2f32e06d9c6815e6b69", "legacy": "invalid", "basic": "invalid"}, + {"class": "inf_x", "in": "ce35ec630e00845867f72baba3d87ad602355e358f9488c77a08c9a506a8d59f6b6ca7443461a1032935e2b4daa951b112f58dccc1cb77ddaa20d9347cf4962b319bada343aa6f804401c8d3dfb22fcac0e5d5ccbc1a78b5ae0d7974453210cf", "legacy": "invalid", "basic": "invalid"}, + {"class": "legacy_c0_ge_p", "in": "1febde5b4503b8cd71e5658191cb8677d0f59a7c3210119bc9f6a30a0c536ad546fa0122d6147f73c408b3f6a395b3661965e2c32312c6e92eb415eb2e07ce008f26f162c6c77922382561f1a874f903d026491381c6515fcbde4a07a37e97a0", "legacy": "invalid", "basic": "invalid"}, + {"class": "legacy_c0_ge_p", "in": "1fa8c097c7d314029bcd239b5053ad6fb4e87869f8b05b9f17faba5d8749f868c7e9ca1da78de34e77301b7f0d651e3c05ebd1e378d213c56fceb6f25dd2ee843e1e23f537147679f5feb85efa1fb510dd505edff2d8f7f46aae7f4c15e3d6c4", "legacy": "invalid", "basic": "invalid"}, + {"class": "legacy_c0_ge_p", "in": "1a841e439d8e673c53d96629c67d09e162dd256b04ba25ce24b507d4079e307c1faf8c7ff7379669b4d0e611bf6aefe10c355e2d25561ac4621614da5a1896e5d9519665d975e8f0729f55db0a3d82e60e86aed32adaef606cfcc20510ca851c", "legacy": "invalid", "basic": "invalid"}, + {"class": "legacy_c1_ge_p", "in": "15b90626fedde542ce2edaae6bf29179cf20e5da9df747de79a79de405b4f18d30c37f69f57ad39e1439f94059bf17a01e4e2d575100676feb40bf95e8368a79132057b37724d15c562d87233c1602933266ead215b1c369fabb9422f1dc9449", "legacy": "invalid", "basic": "invalid"}, + {"class": "legacy_c1_ge_p", "in": "1637e00ff3dc0bf7344f85a5ac044d813e5cd3b6b43f2285f0b62cd18a01ac8594d4571ddf63932d2dbbb19930c718021e5518706bb2f2b3b90a85a006fdce758b2de6dd12193f6643c756235cfed7e22a0b423d9ac7f45eba3dc2ff0e992643", "legacy": "invalid", "basic": "invalid"}, + {"class": "legacy_c1_ge_p", "in": "03e03853b638eb8a829bf67c0a1326f0e0bbee33f7252db298f14fd08647e532c3277ce0857775d4b7947eeb2408ac921dd5c04a85bbf9d1c0a0f08607677deacaa999a32281e9f719f93c232d3407c56f7a7037fb3cdf5759b6ac047f9602b1", "legacy": "invalid", "basic": "invalid"}, + {"class": "basic_c1_ge_p", "in": "9d892bcbbaae90a2082e4e1292c126f07d20ad1da3b88ab07aa05e22063c10cf98a13b611b04e597e9a8bf830005a778084207e77f82bdd22f4e76d24098ec1518651465f207b3bbbaf9721e28a260c2a3268e141a8b67d555f060cd9b939536", "legacy": "invalid", "basic": "invalid"}, + {"class": "basic_c1_ge_p", "in": "9b12f80aa6d2f3919a4c34fe7c35e67ba62c6280348ccc7db5eec89e46f9ddb4894181ba19d71f858bb40863faf072cc03aec6bf6ddb843981cf758fc745fa4b85514a4288d7173c919f8e0d2160903e3382a03b01f7a67153c72fdd78d33cf6", "legacy": "invalid", "basic": "invalid"}, + {"class": "basic_c1_ge_p", "in": "9e2c0355ff31b5adaaf62fe05021c67f5128a8a739d7c0537aec506900ae95fa34ecfa3ef57684270846b1576710a028017eead5e85887b12cafbcf3d9c221fddfe11ffbf7934fb4cf2ae72ae801458dab7141d1ef93f7ddc757e4b4d6254756", "legacy": "invalid", "basic": "invalid"}, + {"class": "basic_c0_ge_p", "in": "9106eff05ad51a4f81f83a708936c3c0274648a29251a5e4db91fa1b2484ced44e9f9ffb5db504c32f2cf369d7e2a8e01c086d6f0e394e4b07bd8334996c79adbc6025a73a780fcbd42bdc7f9cb5ae45fee5ae3ea89992490ce65cdc991f0e78", "legacy": "invalid", "basic": "invalid"}, + {"class": "basic_c0_ge_p", "in": "9076f4935367639ff38061fea5faed277b97b8ae3320392aae030c079f8f7add0b328c57168a87c6f555661ab3be27471d962547da99190f15b974401b929f0965d6f3bf62fc94a9cab3ab42f2d4e24d45bee49c33d20676c80d6baee147dad3", "legacy": "invalid", "basic": "invalid"}, + {"class": "basic_c0_ge_p", "in": "895db43c3b2da1077722bfab257d4df616287d1d1927b537e266aa80767ee22fe566293136c1eba1a027471ef73c9b5b1d38fde56c368a334801d43031cba7b60193713acb61609e2640c3ef0086f7c0744a88273b334811bc92d95eb7e32ca6", "legacy": "invalid", "basic": "invalid"}, + {"class": "basic_c0_flags", "in": "85a5abb4936a289647b3a130833650951efa55e66619489d58f0571850ea89e6ba0775dbc812454587bb1a30a9bde74437e339c245c64c3aff198e2454bd430da2af3681ddaba7f29677c0fbf3ab15c5c9fecca0183865679b7cfda3271b18f6", "legacy": "invalid", "basic": "invalid"}, + {"class": "basic_c0_flags", "in": "965a375660be497d52cd3f7838548a5b5032e8bb6b01fac37d414d7cfdd94be2d948a002d70aed844c88ff057f2462f735eb750d38ffed22091f2f4e1422441af699707690200d48728c419497c4af5476e19d0ff4911cb47dce02f8a669b2e3", "legacy": "invalid", "basic": "invalid"}, + {"class": "basic_c0_flags", "in": "82d090d684213cdba8f23405915ce9b392043cab05ed74b8f6c7d52652641c92099fab7a757f9b9f0c1408999ab309382577d41fb231549a998bea0cb1b7b9f99f69cb7579e30aa437df8f50ace3c5c29d330e6ac0496172ae6f52a009ebaf25", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_001_sub", "in": "26a75f07c953190dc61acc5522ce09e81bc5b6997cb50598ad6efe1ed9bd725e1e3d3fcf04c0d18207cfd48eea65a8020c6720fa380acecd1657b3a54e55673c90d5d3f16a645a4255bbc76be727536ebe8565d63a014998cede5c65c9c504d6", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_001_sub", "in": "24b8403256c27226d78b2cbd65638c04eaee90d6f44a91e7ef2c4c1d5fe03610c9a6dfde7dbde6a167451eee1a08ac3e18da3db3620ab1c3056d0eecbe35adc1d59812e959fb1cff97e378f7903396807678712e2201a7b63de846ffe5a8cb92", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_001_sub", "in": "2cf587f82a49f5bfac2c2e0d8b9d9e76e62ad5fd872d1edfb60bfe01358c03ac1f15bbc800352fa2998e91153bbe9b880182315c950e3ce2473606d488c51db8a7e01f85f6c192db5d70d8b62a76be5506b2d069e81e4d945c8890763015dea7", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_010_sub", "in": "50a64bbee359b0a37bca4a24aa238df6e7ed25f879fc100d2a5af5f138b0dae5bdebdd7f6f832ece71804231b7ecad75124ebfe1cd30b3a9b51e9ee69411ae01f15bc8029062e2e703641a25575db522a5268de35bd2ce9dd4504d7175989452", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_010_sub", "in": "53362e29428b0660bb117d37c700c26485ee9c9cbf6b9712e7b1e447a88bdafcdcb269063d48d0e7bcbbb8d4a8b47c37069a276a94f2e239cd71e16ab411110dac6de0ed98ce75b2b0cb8624e231ae35dfc75f0a01530a9db7748c87ee9c9f91", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_010_sub", "in": "4c180bc4e18c971bf083e43f10b30a7d5d0bb41656358341ad76e3b5cb12f22b2091f2b7f477cb634cf8c0436ef35f930a9ba2e85ac902fb1556701e42f33082f85dc2ee5030d7e5c3a5a8a69b1b6a50fedfc640d95cb631bc63793cfb639dfd", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_011_sub", "in": "63da05ceed26a84e948047accb5ace1d7f1ebff1da7cdd97c5a8e346b1645139d94184b7d70fced2b7ed5bdec3ab606f033568c8da1811b8de2b712709d360b0c0736fd02bef0fc314c4d9e985d88f025f2f4c2ca26fc7c527f153b1777021fc", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_011_sub", "in": "73d31c47beee2770b8e3818b2bc273d531bf7f3270e9219fd1fb18dbd9652fd5b231581677c90542ef9fcfc36b0380a0084e62b1a7bbc867682daadb96cc9e8dcb5a1fef455d1a84c20ef9b3cc6d3a7ec8f729cfdaa74f3f7e70423fecc02b26", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_011_sub", "in": "79887b89d388ae11a6e3207c13fb0f362465bca2bed5eb0c4b08d8ebef9cc6a44909eb915bca42ebb0cadf806f41b74210f6c8ab3968ce0bacae7d2694f509f198347a7e5f00702cb4798d470bf0ecf515d2845a364d2812478fb254950d5db5", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_111_sub", "in": "f76440dfb7ac693ccc48358568a1f879813285685d0264bb714ccb21cc1d607147d09ec6a46fc75bfd07b1d2087e558f0adc78ed9b554607818629880a9667f1cae29d6885b8a21e85e5aeffdc88259a98cf0103ab74f640effe4a4bf7ee7d46", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_111_sub", "in": "f347f1597c4107f7b7ed4f115520ac21eb1d2a8e99c41d6e4b5f8a5c726aaa6efba0a010b46ef263ab80c92f7af6f27710b5f4a9a86af03549a2cb88dac711f250cf98ab73fb7a57d0f363426d9fd3c42fde610b431207fd15800b55997a6bc0", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_111_sub", "in": "f6afd4f13c3cf6d55e584d2c0cc14b87bb0b2ca441f381369f155c5891fd8cbb51d78a15dd32c3b994b69dc2a61f8d2c14d4ac63398c9da18f31e3aa7b44dae382f3644255594573cf022dad4da56c76bd512e8a1a2802c7c4b1640d8ab4fa54", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_010_legacy", "in": "44c56e950429706f4c29b4490c6c2d18a9da67a29fd7bc76a12f3a937dd22a94a0e39a6de06d1824ebb24bd6d910a6d305b2294e3b16342f58e34bfb88b795c016014efd384e34682ad14e2593e6f4c9a15121e3b94d24d924f2271bf251dac7", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_010_legacy", "in": "53d493b007850840aa6748471cd89da3c02e0b2b46bcb328c3a99816d2148b44d9ce32cb82d26951f8f04e919e4baab60f07dcca492d997794ec71a0334af626df742482a848cc4cbfe1a0965713ec43c799284d2cdd4b41b16c60deded72a36", "legacy": "invalid", "basic": "invalid"}, + {"class": "stray_010_legacy", "in": "45ee01a470aba6ccee30fcc132ca64760a0ab96f9a1267597978d22be9758866bb8198c42be82604608bb522f648b767003b7ad0d2a081a235bb05294534dcb9a2ff1aea35cfd01fcbd5363cbe3334b941ea402378c57c1841bc4b29150e6570", "legacy": "invalid", "basic": "invalid"}, + {"class": "sub_legacy_s0", "in": "1889decf195954148ad09648cea2e35a5a19f8628bd698ce878dbc03003eb898a0995484b5b31a845bb8ec681599252816d6fca5ec918d0ee3f7c140ee173febf78c023d1aadd594804d4e96ef416d77d7786500fcc34de689f490f24b5b1ba2", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "sub_legacy_s0", "in": "0da61bb4de276d01d3100aa5da5332e74b7c3ca0051a06ed06d4cebd7111120a8717a2f38a0e9c1649fb829620a883dd18ca60a4f5e47b365e2a5286620dc73639ab1a8e3d4534640bda258e9a002e5526737bece44939a0becbad50bf062e5a", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "sub_legacy_s0", "in": "0fbb64a77b437dd89bcbb68491142238f5b4ea30fe480de02cb0eb0ce9f32a0e471815d696438f2df217cfe11ca9443d19a22909e6bc7aec089a77b96377cd7093c3cdd26da8a5cffafa0e9f365b88cf9c4944d7b200210154568d8f1397e32e", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "sub_legacy_s1", "in": "84bcf0b5a4f61acf8dfa83d5150ad8f99d0e191333542b3c61ab40b9b35c05e5c20dc374867b9d2c3f7b046ed72ada2a042bc49cf2fb325f7a3df12e7c64f00ca33c9821baf6db09a585439788ab140786f992b4e91b7cc71f79ff473515c18a", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "sub_legacy_s1", "in": "963141588db27c02e44426dd0b26ed1d1cfa32bf48cb4eccd5e6d60a787b156c93d2cb03935fb9b24d5a76dbd46b821e17745e7a8c9b9644dc71a466bfd94c3cb520917b1ed77cc042f8871e8c5255dcae6d543d33d82a7f0dffeee5c8756683", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "sub_legacy_s1", "in": "8cb70960a44d3fbd619a0d430ee4ddecfdc3f06279f579d48dd009bb60122f7bcde1b644b29e41c6f20f2da1b407aed311e6b96e15af37b931beb2613eba3805f11ea02cc5bb76cfb9d16f81f5a2467927a47cb6dce7118cb4799fea7cd5c717", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "sub_basic_s0", "in": "9208885dbb05f3af0d0f2f58122edfc6ceab74495c21971fb01a3ea6dc515fcd7784cd5c85393bd0ef13dac82da3a85816bdb6faa0ad12db49313bea7c598833e7c643f2db59fb71e629f2c27a69ea74c2a5d1301b43ed385c045388d5ff3333", "legacy": "invalid", "basic": "valid"}, + {"class": "sub_basic_s0", "in": "96dd1cb5c489bfce1b22490e2f8d79dad0ab1add791a0890bb9aec98c9ef5b8595faccd7e7852f18072ed46f06f74e58128076bdaac45f8bcf93a5fa217e20f7cc75052c446366eebab4618adbca290cb2747c7310b6014243ae9936db9da33c", "legacy": "invalid", "basic": "valid"}, + {"class": "sub_basic_s0", "in": "8fee90f838ca1951e59fdaf54d372cc6b0430078653866fb02ece50b41aedb225735f6fc1e1ccd63e19f53087275edf3087d3755fd272bb148cfe3d94410f96f4b86853c3ee83fc2c0f3347b98813534f017d59b02239b38fc843b50be48cca3", "legacy": "invalid", "basic": "valid"}, + {"class": "sub_basic_s1", "in": "aaf51e9450fccb746ec19f7dac06836cb3fe346266b365d997ea788dc91f545f320d45177b4d8dab43c7df57ce9c69a112d0a79035ff942fa7f2ec8e5e768ea8a32d18e2f39f52289ec8aaa7f144052abb3010964af9a221dc09d8f1ae335472", "legacy": "invalid", "basic": "valid"}, + {"class": "sub_basic_s1", "in": "b29e097a48fc7cad84f5e097a5464cb00ab6a0284095819bc6f6940d9eb08e2fc4f83cc1cf2869157e9c9529527577dc0e57581a6d9dc5f7395fca345110082e7ad9b27d2ee5e5b58c4539b5001ef1fb4c16a72d766a80171646e73a8a232f59", "legacy": "invalid", "basic": "valid"}, + {"class": "sub_basic_s1", "in": "b777c7f245968fe18e57807fa39c5c9e6cff465e4ff1849a4feac706f2044ab297c0187af48fe5c63a2bcdbeb27926960c2987e85b3810154c0d9d1a227120bbcba84c137d7e66c2ed33e910f1f9021b16fcf3c5bdee64c1fae1102c82896603", "legacy": "invalid", "basic": "valid"}, + {"class": "nonsub_legacy_s0", "in": "1260119f5317edb6a0b0c47bc2602b56b2c6d64dd8733a0c853bfc236490ee7d0f0287dc2c2ba9feb5214556dbcf04e018d036ad8ccb97d95f198d5b4aa9e47d506239a97d87260d9c7df585677a06c64631b03faa6da47a95b1af6233fe9989", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "nonsub_legacy_s0", "in": "06619fb972f1c12594e538a03f5cf45afb5adcc8a45eb1c4bd4162394447f27bbe77a393344a98c3662f789d380a878a03bfa1a7ae3c279b1cfa540a4e61e881275f388b36107b7cc0bb2d3072a0aab6ab6f4faec9438248270a5ccd98f202b8", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "nonsub_legacy_s0", "in": "080da208c6a3c893cc2e7ccd73059976cb072a24556a0992d3ecf4de4fe0efd2205c7d712ef0dd6be8494ae723f867fc089fa1c5bdfb7bf1475e0bf8a7846dbc540f0c84bad863e31c8533d9830559d12fcf0f0ea6db94c6f51d79eec8b023e8", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "nonsub_legacy_s1", "in": "8cb90e9a349e67fef3c559ee35bae8b576984e3480f3b52afaa72bd821864dff680b3f3e3e37ab90d7d86cd5c5e352d20881a0d79ebc2b0f2166672f4b791fc5ffa4cf76fd64a1c3e5ed49d9a7cb9f1028dfd1c7ea177f4f9827bf06a1413e5f", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "nonsub_legacy_s1", "in": "93a9aff8e8936ec11759d8c172466962e7cded4a8235e9cb292a06cab4652101327786fc176663559137c1b6e2892dfe13f89369a57e5b26de7a480dce61443643c7b76c7a091ec3f374c81cf4eef149ea3ff25de77e3b1ed58e140a0131efdf", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "nonsub_legacy_s1", "in": "94b0632af1e9b373ea050e12a9809b2e5e37f9e7df8777886871a43b6835ed8a26b828b7095c36cfa5146e9943433e3410f2455af7ebafb9205beb1835518a0303641a375822e60a5cd9ee03b3067dc23ddeb07536fcfd5532a6da93a1c85bd6", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "nonsub_basic_s0", "in": "987cef5637c3220030cc9a534d923aae66ee282f7aed8b76ece5bf79dbf9821245661391c2550d1eb761c6c3d54c46eb005774ad6e8b5fa39e4fccf7f598ec60da409179caa60e79e7a23e86b149cddd92f6634ef7a7361e6d71488dd052cace", "legacy": "invalid", "basic": "invalid"}, + {"class": "nonsub_basic_s0", "in": "8d2df998477197a7ec1df04da23462ea1b0ca7875f481fb6e3fe5f0cd48e15128d94350f1027083100af0d7fd60e779607b44ae7e47b019fd126fa6539e1541b5a2d77fe165e12b5d9a16efb53d0ff9ff71eb3e6ee1829df3c9c6159787fe621", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "nonsub_basic_s0", "in": "8f12c2ab96b59f6939a2eb7cf79c49b182e02d539c86de252b88af749453ae7e58ec133618209246c1b8c48312f8157c010b2c8830ee537d15f95d358ca8cac027018e66e2e37f6264cfb55bc37ed12ac219af19fab9799733937b1b98e62b23", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "nonsub_basic_s1", "in": "b712eea366ea7d74f22786e2dbb1a2ba2712abc8dfd6e77ef52871c57f676a4461e087c6a18ce2221c3b8a4bfdbc93da1200c8c9aa3157cce630361e8a7875a774ed8b70c3c978e28b9c566a793a3be32bca8b1fae6d1a46b9141aea60f5f1c6", "legacy": "invalid", "basic": "invalid"}, + {"class": "nonsub_basic_s1", "in": "a48d9befa4baad7c5cf7eea7ea906441bb7a3626a126caf067c59f0de5dc99ec1f3f3e33364a8c3cd8db5a4c78ffc5d70b3c3366a8518b439edee58b38e39ec369edb801f995c8b3e135ae1aaffd19ed0e9dd96be018f5c0db0b8f854e813ca8", "legacy": "invalid", "basic": "invalid"}, + {"class": "nonsub_basic_s1", "in": "a82693e5f2f8658abea471c409a957caec379b949e36eef95d0614b6af82571ae6ef745afe32718564611cdf5a35a2f30ad9b936aae660fffa70fe80aa2ea306c8ab77b2e363612cb977da650a6a3ff171161c5e5ac66bea3213f3206e44eb43", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_legacy_s0", "in": "0097926406fa8040c0069d1b53b861b1df87e8e4c38b9b9418dd80a2fb113fdf1878624f7f624d512d56cd267360453e0e6992f1207908245f455d4d0bd6fcf0d7bb962c5f1913d4c18b8deb8c4670d233493576e8ad4ddc308fea94f8a109d7", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "off_legacy_s0", "in": "16a0406b1facaacac7c4e8814203f569593166a5c1bbba47a91994dc35fe8bb3154e099e7e739c7f4d5a360c4c98c9c515dc90380d9ae7ea0af37b0f8b141a95f5293d83b7361333ee559749c2b20f9bf1cadaa7c250f276180e29cf3119bb59", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "off_legacy_s0", "in": "078a74078932ccef5b05c9c96f6849c137c374cae355dfe8587638c6c17b3ac510dabc2640d9fa9b0f4d8cde5a2c2f6b182c53f7389cd163af0949cc8e85f517f6d79926d5a06f4d44db8ca30217670ab0276b3c22432fd11409ec2142d5d1c3", "legacy": "valid", "basic": "invalid", "curve": "off"}, + {"class": "off_legacy_s1", "in": "82e3e81344da2bdffd123acda174ee35518053e1e527e91442bdabde6ccfdc2d261619f08919a3a83dbe60a9c6844ebb006454b12b2afbf0dee0902a1e0d2f88c113ceee2bc24cac747755260137cfe3674e89c80b129d8ea4383c581e05fee9", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_legacy_s1", "in": "82050f4cf0877735032151ea65e31ceba96f786aa37d1163a9adcb1a3860146952fc75d5da785a9e7ff96aa948a0e9de127dba5068ff22e297062301fec5ba07c3330bc024a8300f92efb59fcec9a804d7cb9a9c381a753582bd7f02fc2ba1b2", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_legacy_s1", "in": "8210f0a46afe8605afd42df7b59a0f2e7e99ffa883677c08e43415d0343b6620676d1a75d7cadb43f138ea7ab79a6a9e09b8766491281bf0f39d781774ef372264e8931e80e3fa85998251194f6b84bf97257740f9bf3d902144604e934cf314", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_basic_s0", "in": "93d7af3323953b92fa27896972215db199c971bf70c68a6d2fcf6585a0e6f4efa538713a97e5110d34adf305471b305f0223b8ec48930bb5c1a4eef786d033bc6f6197724958631fd8eee5e1287b52ce8977d3c055f329c1d4a5556ad795a854", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_basic_s0", "in": "839b5dffb0d6a287f08fe39ae726ad4d78e112554102bb8c9b2b60423acbcb3b97f238a39fb4c8a97a81025cc9fc121407c2d26975613cbc4bac9b7ff4a8d478ddf04488b2e16ca4f331cea0aefa870bce227572840bc9ed2c796ff94e2b7385", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_basic_s0", "in": "98ebb99085bb310b7bc517eb48efc067a1438d2bc5ef9a799ff13c5d5e2be1f54bbe61103539fe5271b5b142ac396bee05579fc3d3f5d41d2ff1c2822dc13459448655b84dc66248fc0a046d2969b0d9c787571461f3348b31ef1f0b7db12fe1", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "off_basic_s1", "in": "ab204ec06d4b97f74773144930c74387e6bf8c335470a30c832998fc4a91e3098273ad488e20fa1fe6e1b0cdd9bea4600951e0f15eec1984c6d7275f1dda26613c2b0ead001b5e3bf637c18dbb63142d86cba71e403395216156ee1da0bc7fcf", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_basic_s1", "in": "a545256ce334ae60fa30f930f3ef1d24558bc5f32f2bab82b445350c8ee25c0c46775a08ca726ab143aef00043610c7917db0c7871e4e4cdbeb167fbb8ffb076be3d3ddd7ec56a5bd9fd4972ca190fd442061dd92035acda65537a14b577c723", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_basic_s1", "in": "b058c7331aa5e66a5504935838e3db6697c70997a10869cf20cafe831234c47592c548a96b9a44502bc71c78d19a01f40b191ca3e87dad8c435741c4304400e3b83e9aaedb09054e25e5bd67c462da4261957da3a58e6c935772313549f49c1d", "legacy": "invalid", "basic": "invalid"}, + {"class": "realy_legacy_s0", "in": "126ff2be526328789db89c0b16f230281eeabe27539c107ba75c76f2795a923c33b8707a6eebbafaee1d518186f630200c45744d13467a4c642511e55c60e92e93756372db6011bd3098d80af2294c47312ac75ddcad60e83cc3dfc5e9a83dfd", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "realy_legacy_s0", "in": "101b06a4dc3a097a5fbf1368c6c60a84369d03dbc0cb3683b910bff3718c95809347147f9464bb11da11449bf8db919a0aa56c87b0ac66541caea89969365384c7fb713a46218c975eaa55cd872a844285c3037b9797f9cbde6da5207c94c856", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "realy_legacy_s0", "in": "03e0cd26d487d186f2b1e05a0b9253bb8cf306b6650c8a3068c53a6894d6620a5d7a8cbd78738d04a3084bab110a597009e299af06d363d2ef1dfea71ffb163a783d871b5bc6921a81e91a23f6124adc9b392f4d90b277105ad3336080bd8b06", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "realy_legacy_s1", "in": "8ccac8f68d691301ae01e3252ee8a1700984c226c3c6076c05757aabadbe57a2ac2ed01f86325dcd2abd4812a25c8f250e5e03ceb1e17d6780449e26070f032f455931906aea87e8d061791109f6a3f375419df3705d7de9186b1a7e8479c817", "legacy": "invalid", "basic": "invalid"}, + {"class": "realy_legacy_s1", "in": "82707d31d110571a881f5eeea4cee3bbdac1c239e4eb50ab62b17a593d753c7027cc1680051313f47bed8424053a84db09953e63574f21d7a7425c8bfdfa94cec2413539d43adc179ea9ea4a88ca057f4ccb426f696eab659b3b0a263425caa3", "legacy": "invalid", "basic": "invalid"}, + {"class": "realy_legacy_s1", "in": "8565b0f3926ea7cc14687adf0f4e7d568d7746330b1f63963b16823af9dfba5f3b282c9dcadc381863d3287ab23b3b050576b0c6c136f25bff9e9fe146f76c99eacba08139df67b6beec52fd8028153f9abfc1bbfc9531bea8bb950e9eb1847a", "legacy": "invalid", "basic": "invalid"}, + {"class": "realy_basic_s0", "in": "95bc24957481c92ae6a933f777a2bcc75bc7287cf0c7c21698e6af79681797fcaca96ba9b509aca394a303600c1fe725103855821bdb50963ee4e5b21420912fa3a60864f8864877b79a7ac40419449448f821dd78d745082b502ca462ea4fb2", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "realy_basic_s0", "in": "8f7b49e1c1622673669dbb07b73fe9245aebc5e0eeefe755c4e521626f6686bdb86afe51ab78724f85256efd1acde49e040945a98b3d79ee16237b5931c9f72c177554b21275e1293f8531e8adb14765699f053de2988ed67ae4d92effcc7b8e", "legacy": "valid", "basic": "invalid", "curve": "on"}, + {"class": "realy_basic_s0", "in": "8f3a16fa00107580b71ff45dd7e1a53670a09981d9377e469daeaf913109272cd6aee5cdde7184045970dbd37587c4ad0214709fcd869a56191c6719d3f93a843c5e97eac859590ad181015778408f46db2a6fe46994470ffabd7fb08cd1d4c1", "legacy": "invalid", "basic": "invalid"}, + {"class": "realy_basic_s1", "in": "aaa8401a75e35d25bd241b566b6b6d5fa80a83dc63846da83268304eec88d8b0ba9d36f4cc336460b718c23f8925785e12593d3071047fe4ed1cb5c1788bb01238fa947bc4e4d1f0f39d9eaef2a03aaf106bdc67cb87be45a64d743c96cce37f", "legacy": "invalid", "basic": "invalid"}, + {"class": "realy_basic_s1", "in": "a482b4dee01ca2dec319126f0ac25172476ee357adf5e574d3ac9115d5587f9c5092dbb83d97965d87be6cadc8e3ed7f17b0a381c60cf7946ba84a4fae0f1baecff8dfd10af5aa3f5b2eee3599f55a7e9eb2b7da5b0fc1dbed43dbe430b41a36", "legacy": "invalid", "basic": "invalid"}, + {"class": "realy_basic_s1", "in": "a46954c9bbd9724392a3c458f41c80050cde9e5feed365e59972657677e13bbd9e000b3697ba62670ba85422ded165dd126d97dbb5e35f31111e7fe67049f350f894d938f8e862cf5449f754b75ef5fe77a268d5eab3b534f40e9ac5bec7421d", "legacy": "invalid", "basic": "invalid"}, + {"class": "x0_legacy_s1", "in": "800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "x0_basic_s0", "in": "800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "x0_basic_s1", "in": "a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "legacy": "invalid", "basic": "invalid"}, + {"class": "random", "in": "eadf783660651607fe2fcd90d497d2e9434cc807b8f47be7efc9dcfbfd8792228b94ba3d3091ad904d1af312dfafea6674f71da1c960f91a3c5c76d91e0367534a2e08b770918c1c1c0b852cca2b62d30e16e86f7ccb631fc30fc41bd30e185f", "legacy": "invalid", "basic": "invalid"}, + {"class": "random", "in": "6caebf1f2525251b10843647a16530bf2e97ea386029d532abf447528bb1d732a62fc3f2243fcb78081c5b8c3429b0bbffda5cf5176e6b99780b976cc79d6d58a4f362d28fed7c6f817583084a6d9d8bfb359de002f67de0c93b22e2d4fcd089", "legacy": "invalid", "basic": "invalid"}, + {"class": "random", "in": "47b9d1a2418a3865972c61c42a2e434c2bb8372a7c4781f4271968a36e52ddd0b76fc59ce4e93affb9e99f6ce31699456d2c7ab60d987e0df4353371bcb85e967cdf827afa2918bdc580b4040230118942bbed1c1f7e70c8e3584ca5f2069cb6", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_legacy_c1_001", "in": "15a469c936cdbfa3a2f4c7f8d00a4543acd2928c3b5569ce7a1425bac607c5860738de05d4c426fe18ca0511d3ea884b3237686b9c7f9cc171d8cd266ca35a3f17ab50d4de0aba2f4b06f04dd2de68761034fbe869a240cd17b6eb9e6d8a2e35", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_legacy_c1_001", "in": "11fe16fe5f4deafc25def2327e26a6fe1e4775af020ff21817181a014ad1266f79ce7ff09449ea85d689d63b35ea458c235116696fc9bd14816b6c496a238c5a4506bfb4a1df08ebb7e61e0cfe28047cb25f4667564947c44450592bbcd63346", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_legacy_c1_001", "in": "131fb1cb71b22e7cd54266ff20d81c76c4a94036d34aa9aa58c568c8489c44a189cea9a1e93d33a2c2810e9626ae7ea525ae379e38ffb81f767449b41054dcfb27e3a0cdca22a3e8a4246720540e7530e7ab58e0b337f9151b1063fdb01fd51c", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_legacy_c1_010", "in": "0ae37def08eedc998c76035171344f1e232ee503bedf82d16a568938a6eb35d30d338373d3dea2e85300757a31ea08e750c2bf34e050f24a4cf7e1149721789827935a0e482374f9e8a54301bf613514e27efcef7ad88884a0588b38793c7ec5", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_legacy_c1_010", "in": "04a799786bfa9712ba422f162ff4927baa86ee6ff4d3620462c30017775335fcced2e78f3667d27dd03440ac76289ece52ad060fef0bdf8c6dcf3d6d0f49316eaf08a28960e78bebbdeec3b692a7d678e629d034335a7b253b69fc6d48e0c163", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_legacy_c1_010", "in": "00acbfa4714d6ecb3e19f8335ed6e2889d46e4f7471cfadb12199643a92ed3d625c1c7bb89f7bdc21067b0755d355db84169f5c3ccc40a9790e3b16458bca0eb8cf2c2c3414a0e4484b7b302c865ed8d722f82a80d49c0559d8cda8bc0934abc", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_legacy_c1_100", "in": "0c4c1176e67971337d56d3564d168168bfcec8099951d163f7196bd59f408eeecd15f5396b9bfacf27c8ba864021729089db37dc739f7280ea8e71c03724cd056b3215d8c5e649bd1cc54b543050c876c5a631e2913cfd88f6076becd3201a7c", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_legacy_c1_100", "in": "171f750cd892918586c67bd27c8808264a1d4d123c758f0c060c207bd2ea13a687a1cb218a12e832b61466703a03af9c83f4c0d16b8765a8ec993365b0d36151e4b4452b4cc82b7c016f409333676b0d775970f862ae025045c6a5d9f4e4e3e7", "legacy": "invalid", "basic": "invalid"}, + {"class": "off_legacy_c1_100", "in": "09dd5ce5913892fd0315ef00c77370363815d6b211380eae032f3e5c6ebe6bf9d39a07d359bc988db1c97feb74baed2f94e11e6b195b2d60ae72ca71002abe6c1492b3fc0f6aec089a6753e3fb039bfe938c7ca4b4956f34ccac8c450c1613f8", "legacy": "invalid", "basic": "invalid"}, + {"class": "sub_legacy_s0_basic", "in": "96d6fca5ec918d0ee3f7c140ee173febf78c023d1aadd594804d4e96ef416d77d7786500fcc34de689f490f24b5b1ba21889decf195954148ad09648cea2e35a5a19f8628bd698ce878dbc03003eb898a0995484b5b31a845bb8ec6815992528", "legacy": "invalid", "basic": "valid"} + ] +} From 6bff16d5403faf15d1358bb800dceb0af5813ad5 Mon Sep 17 00:00:00 2001 From: pasta Date: Sat, 31 Oct 2026 20:59:54 +0000 Subject: [PATCH 2/5] pkc%fix(bls): read legacy G1 `x = 0` with the sign bit set The reference implementation reads `x = 0` with the sign bit set, (0, -2), under the legacy scheme. blst's decompression writes the point out but reports an error, so keep the point it wrote. --- pkgs/pkc/src/bls/blst_ffi.rs | 19 +++++++++++++++ pkgs/pkc/src/bls/public_ops.rs | 28 ++++++++++++++++++++++ pkgs/pkc/src/bls/scheme_chia.rs | 42 ++++++++++++++++++++++----------- pkgs/pkc/src/bls/scheme_ietf.rs | 12 +++++++++- pkgs/pkc/src/bls/scheme_ops.rs | 9 ++++++- 5 files changed, 94 insertions(+), 16 deletions(-) diff --git a/pkgs/pkc/src/bls/blst_ffi.rs b/pkgs/pkc/src/bls/blst_ffi.rs index 0a56ac873..1453f41b9 100644 --- a/pkgs/pkc/src/bls/blst_ffi.rs +++ b/pkgs/pkc/src/bls/blst_ffi.rs @@ -424,6 +424,25 @@ impl G1Affine { Ok(Self(aff)) } + /// Uncompress a 48-byte G1 point as [`uncompress`](Self::uncompress) does, + /// adding only `x = 0` with the sign bit set, (0, -2), which blst writes out + /// and then reports as an error. + /// + /// # Errors + /// + /// Returns the blst error code when the bytes are not a compressed point + /// blst accepts, other than (0, -2). + pub(crate) fn uncompress_on_curve(bytes: &[u8; 48]) -> Result { + let mut aff = blst_p1_affine::default(); + let rc = unsafe { blst_p1_uncompress(&mut aff, bytes.as_ptr()) }; + let x_is_zero = bytes[0] & 0x1f == 0 && bytes[1..].iter().all(|&b| b == 0); + match rc { + BLST_ERROR::BLST_SUCCESS => Ok(Self(aff)), + BLST_ERROR::BLST_POINT_NOT_IN_GROUP if x_is_zero && bytes[0] & 0x20 != 0 => Ok(Self(aff)), + rc => Err(rc), + } + } + /// Deserialize a 96-byte uncompressed G1 point. /// /// # Errors diff --git a/pkgs/pkc/src/bls/public_ops.rs b/pkgs/pkc/src/bls/public_ops.rs index 2c48487ec..53ca43f96 100644 --- a/pkgs/pkc/src/bls/public_ops.rs +++ b/pkgs/pkc/src/bls/public_ops.rs @@ -533,6 +533,34 @@ mod tests { assertion(encoded); } + /// `x = 0` with the sign bit set, (0, -2), reads under the legacy scheme as + /// a key that encodes back to the same bytes, as in the reference + /// implementation. + #[rstest] + fn chia_reads_x_zero_with_sign_bit() { + let mut encoded = [0u8; 48]; + encoded[0] = 0x80; + + let pk = BlsPublicKey::::from_bytes(&encoded).unwrap(); + assert_eq!(pk.to_bytes(), encoded); + assert!(BlsPkBytes::from_bytes(encoded).is_canonical::()); + } + + /// With `x = 0` and the sign bit clear, stray bits 5-6 mask down to the null + /// encoding, so the bytes are neither a key nor canonical. + #[rstest] + #[case::bit_five(0x20)] + #[case::bit_six(0x40)] + #[case::bits_five_six(0x60)] + fn chia_x_zero_with_stray_bits_is_not_a_key(#[case] byte0: u8) { + let mut encoded = [0u8; 48]; + encoded[0] = byte0; + + let bytes = BlsPkBytes::from_bytes(encoded); + assert!(bytes.to::().is_err()); + assert!(!bytes.is_canonical::()); + } + /// The same G1 point encodes differently under the two schemes, and each /// encoding must round-trip through the wrapper of its own scheme. #[rstest] diff --git a/pkgs/pkc/src/bls/scheme_chia.rs b/pkgs/pkc/src/bls/scheme_chia.rs index 25afd0ec1..343306e9f 100644 --- a/pkgs/pkc/src/bls/scheme_chia.rs +++ b/pkgs/pkc/src/bls/scheme_chia.rs @@ -73,35 +73,35 @@ impl BlsScheme for BlsScChia { /// No prime-order subgroup check is performed on the legacy path, for /// backwards compatibility: checking here would reject keys the legacy /// format accepts. + /// + /// `x = 0` with the sign bit set decodes to (0, -2), which blst's + /// decompression alone refuses. fn pk_from_bytes(b: &[u8; 48]) -> Result { // Reject the all-zero encoding and the infinity marker. if b.iter().all(|&byte| byte == 0) || b[0] & 0xc0 == 0xc0 { return Err(BlsError::InvalidPublicKey); } - let sign = (b[0] >> 7) & 1; - let mut ietf = *b; - // Only bit 7 is the legacy sign flag; normalize away stray high bits - // rather than rejecting, to stay bit-for-bit compatible on the wire. - ietf[0] &= 0x1f; - ietf[0] |= 0x80; // compression - if sign == 1 { - ietf[0] |= 0x20; // sign - } - - G1Affine::uncompress(&ietf).map_err(|_| BlsError::InvalidPublicKey) + G1Affine::uncompress_on_curve(&Self::pk_ietf_bytes(b)).map_err(|_| BlsError::InvalidPublicKey) } /// Encode a G1 point in the legacy 48-byte format. fn pk_to_bytes(pk: &Self::InnerPk) -> [u8; 48] { - let ietf = pk.compress(); + Self::g1_bytes(pk) + } + + /// Encode a G1 point in the legacy 48-byte layout, the IETF compressed form + /// with the sign at bit 7 and no compression bit, or the IETF encoding of + /// infinity unchanged. + fn g1_bytes(point: &G1Affine) -> [u8; 48] { + let ietf = point.compress(); if ietf[0] & 0xc0 == 0xc0 { return ietf; // infinity is the same in both formats } - // IETF: bit 7 = compression, bit 5 = sign. - // Legacy: bit 7 = sign, no compression indicator. + // IETF sets bit 7 for compression and bit 5 for the sign. Legacy sets + // bit 7 for the sign and has no compression bit. let sign = (ietf[0] >> 5) & 1; let mut legacy = ietf; legacy[0] &= 0x1f; @@ -111,6 +111,20 @@ impl BlsScheme for BlsScChia { legacy } + /// The IETF compressed form of a legacy G1 encoding of a point other than + /// infinity. + fn pk_ietf_bytes(b: &[u8; 48]) -> [u8; 48] { + let mut ietf = *b; + // Only bit 7 is the legacy sign flag; normalize away stray high bits + // rather than rejecting, to stay bit-for-bit compatible on the wire. + ietf[0] &= 0x1f; + ietf[0] |= 0x80; // compression + if b[0] & 0x80 != 0 { + ietf[0] |= 0x20; // sign + } + ietf + } + /// The legacy public key is already an affine G1 point. fn pk_to_g1(pk: &Self::InnerPk) -> Result { Ok(pk.to_projective()) diff --git a/pkgs/pkc/src/bls/scheme_ietf.rs b/pkgs/pkc/src/bls/scheme_ietf.rs index 0da6afbbe..3410c3fed 100644 --- a/pkgs/pkc/src/bls/scheme_ietf.rs +++ b/pkgs/pkc/src/bls/scheme_ietf.rs @@ -73,6 +73,16 @@ impl BlsScheme for BlsScIetf { pk.compress() } + /// Compress the G1 point to 48 bytes. + fn g1_bytes(point: &G1Affine) -> [u8; 48] { + point.compress() + } + + /// The bytes are already in the IETF compressed form. + fn pk_ietf_bytes(b: &[u8; 48]) -> [u8; 48] { + *b + } + /// Lift the blst public key into a projective G1 point. fn pk_to_g1(pk: &Self::InnerPk) -> Result { let aff = G1Affine::deserialize(&pk.serialize()).map_err(|_| BlsError::InvalidPublicKey)?; @@ -81,7 +91,7 @@ impl BlsScheme for BlsScIetf { /// Re-encode the projective point and parse it back through `validate`. fn g1_to_pk(point: G1) -> Result { - Self::pk_from_bytes(&point.to_affine().compress()) + Self::pk_from_bytes(&Self::g1_bytes(&point.to_affine())) } /// Uncompress the standard encoding without re-running `validate`. diff --git a/pkgs/pkc/src/bls/scheme_ops.rs b/pkgs/pkc/src/bls/scheme_ops.rs index 599d93010..01af825c9 100644 --- a/pkgs/pkc/src/bls/scheme_ops.rs +++ b/pkgs/pkc/src/bls/scheme_ops.rs @@ -8,7 +8,7 @@ use super::blst_ffi; use super::error::BlsError; -use super::group::{Point, G1, G2}; +use super::group::{G1Affine, Point, G1, G2}; use super::scalar::{Fr, FR_BITS}; use super::schemes::BlsSchemeId; use super::{BlsDhBytes, BlsShareId}; @@ -84,6 +84,13 @@ pub trait BlsScheme: BlsSchemeId + Sealed + Sized { /// Serialize a public key to its 48-byte encoding. fn pk_to_bytes(pk: &Self::InnerPk) -> [u8; 48]; + /// Encode a G1 point in the scheme's 48-byte layout. + fn g1_bytes(point: &G1Affine) -> [u8; 48]; + + /// The IETF compressed form of `b`, a G1 encoding of a point other than + /// infinity in the scheme's layout, with the same `x` and sign. + fn pk_ietf_bytes(b: &[u8; 48]) -> [u8; 48]; + /// Lift a public key to a projective G1 point. /// /// # Errors From ac2355f0e3eb46c2efff009dc46c9119d6774e43 Mon Sep 17 00:00:00 2001 From: pasta Date: Sat, 31 Oct 2026 21:00:03 +0000 Subject: [PATCH 3/5] pkc%fix(bls): accept legacy encodings off the curve in `is_canonical` The reference implementation accepts a legacy encoding of an `x` with no point on the curve, for signatures only with the sign bit clear. `to` has no point to return for these bytes and still refuses them. --- pkgs/pkc/src/bls/public_ops.rs | 27 ++++++++-- pkgs/pkc/src/bls/scheme_chia.rs | 90 +++++++++++++++++++++++++-------- pkgs/pkc/src/bls/scheme_ietf.rs | 24 ++++++++- pkgs/pkc/src/bls/scheme_ops.rs | 17 ++++++- pkgs/pkc/src/bls/sig_basic.rs | 35 ++++++++++++- pkgs/pkc/src/bls/tests.rs | 51 +++++++++++++++++++ 6 files changed, 214 insertions(+), 30 deletions(-) diff --git a/pkgs/pkc/src/bls/public_ops.rs b/pkgs/pkc/src/bls/public_ops.rs index 53ca43f96..602bad71b 100644 --- a/pkgs/pkc/src/bls/public_ops.rs +++ b/pkgs/pkc/src/bls/public_ops.rs @@ -199,15 +199,19 @@ impl BlsPkBytes { /// # Errors /// /// Returns `InvalidPublicKey` when the bytes are not a valid point under `S`. + /// That includes legacy encodings of an `x` with no point on the curve, + /// which [`is_canonical`](Self::is_canonical) accepts; there is no point to + /// return for them. pub fn to(&self) -> Result, BlsError> { BlsPublicKey::try_from(self) } - /// Whether `S` decodes the bytes to a key that re-encodes to them. + /// Whether `S` reads the bytes as a key that re-encodes to them, or under + /// the legacy scheme as an `x` with no point on the curve. /// /// Null bytes pass, so pair with [`is_null`](Self::is_null) to require a key. pub fn is_canonical(&self) -> bool { - self.is_null() || self.to::().is_ok_and(|pk| pk.to_bytes() == *self.as_bytes()) + self.is_null() || S::is_canonical_pk(self.as_bytes()) } } @@ -224,8 +228,8 @@ type_cvrt!(for[S: BlsScheme] TryFrom for BlsPublicKey, BlsError, |point| mod tests { use super::*; use crate::bls::tests::{ - ietf_g1_encoding, ser_pairs, SerType, G1_OFF_SUBGROUP_CHIA, G1_OFF_SUBGROUP_IETF, G1_X_EQ_PRIME_CHIA, - G1_X_GE_PRIME_CHIA, G1_X_MAX_CHIA, MSG_8BADFOOD, RSEED, + chia_pk_off_curve, ietf_g1_encoding, ser_pairs, SerType, G1_OFF_SUBGROUP_CHIA, G1_OFF_SUBGROUP_IETF, + G1_X_EQ_PRIME_CHIA, G1_X_GE_PRIME_CHIA, G1_X_MAX_CHIA, MSG_8BADFOOD, RSEED, }; use crate::bls::{BlsScChia, BlsScIetf, BlsSecretKey, BlsSignature}; @@ -561,6 +565,21 @@ mod tests { assert!(!bytes.is_canonical::()); } + /// The reference implementation accepts a legacy encoding of an `x` with no + /// point on the curve, so `is_canonical` does too, while `to` has no point + /// to return. + #[rstest] + fn chia_off_curve_x_is_canonical_but_not_a_key() { + // p - 1, with no point on the curve. + let mut encoded = G1_X_EQ_PRIME_CHIA; + encoded[47] -= 1; + assert!(chia_pk_off_curve(&encoded)); + + let bytes = BlsPkBytes::from_bytes(encoded); + assert!(bytes.is_canonical::()); + assert!(bytes.to::().is_err()); + } + /// The same G1 point encodes differently under the two schemes, and each /// encoding must round-trip through the wrapper of its own scheme. #[rstest] diff --git a/pkgs/pkc/src/bls/scheme_chia.rs b/pkgs/pkc/src/bls/scheme_chia.rs index 343306e9f..265bcd87f 100644 --- a/pkgs/pkc/src/bls/scheme_chia.rs +++ b/pkgs/pkc/src/bls/scheme_chia.rs @@ -17,7 +17,7 @@ use super::schemes::BlsScChia; use crate::prelude::*; use crate::sealed::Sealed; -use blst::min_pk; +use blst::{min_pk, BLST_ERROR}; use sha2::{Digest, Sha256}; use zeroize::{Zeroize, Zeroizing}; @@ -125,6 +125,25 @@ impl BlsScheme for BlsScChia { ietf } + /// Decode `b` and require it to encode back to the same bytes, or accept an + /// `x` with no point on the curve, below the field prime and with bits 5-6 + /// clear, under either sign. + /// + /// [`pk_from_bytes`](Self::pk_from_bytes) has no point to return for these + /// encodings. + fn is_canonical_pk(b: &[u8; 48]) -> bool { + match Self::pk_from_bytes(b) { + Ok(pk) => Self::pk_to_bytes(&pk) == *b, + Err(_) => { + b[0] & 0x60 == 0 + && matches!( + G1Affine::uncompress(&Self::pk_ietf_bytes(b)), + Err(BLST_ERROR::BLST_POINT_NOT_ON_CURVE) + ) + } + } + } + /// The legacy public key is already an affine G1 point. fn pk_to_g1(pk: &Self::InnerPk) -> Result { Ok(pk.to_projective()) @@ -161,21 +180,8 @@ impl BlsScheme for BlsScChia { return Err(BlsError::InvalidSignature); } - let mut x_c0 = [0u8; 48]; - x_c0.copy_from_slice(&b[..48]); - // Clear only the sign bit: stray bits 5-6 make `x.c0 >= p`, rejected by - // the decompression range check like any out-of-range coordinate. - x_c0[0] &= 0x7f; - let x_c1 = &b[48..96]; - - let mut ietf = [0u8; 96]; - ietf[..48].copy_from_slice(x_c1); - ietf[48..96].copy_from_slice(&x_c0); - - ietf[0] |= 0x80; // compression - // Decompress with sign=0, then negate y if needed. - let out = G2Affine::uncompress(&ietf).map_err(|_| BlsError::InvalidSignature)?; + let out = G2Affine::uncompress(&Self::sig_ietf_x_bytes(b)).map_err(|_| BlsError::InvalidSignature)?; let decompressed_sign = y_c1_is_larger(&out.y().c1_bendian()); if (sign == 1) != decompressed_sign { @@ -186,21 +192,27 @@ impl BlsScheme for BlsScChia { } /// Encode a G2 point in the legacy 96-byte format. - /// - /// Uses the uncompressed 192-byte intermediate to sidestep sign-bit - /// convention differences: blst lays out `[x.c1, x.c0, y.c1, y.c0]`, - /// legacy `[x.c0, x.c1]` with the sign at byte\[0\] bit 7. fn sig_to_bytes(sig: &Self::InnerSig) -> [u8; 96] { - // Take blst's own infinity flag rather than testing the buffer: its + Self::g2_bytes(sig) + } + + /// Encode a G2 point in the legacy 96-byte layout, `[x.c0, x.c1]` with the + /// sign at bit 7 of byte 0, set when `y.c1 > (p-1)/2`, or `0xc0` and zeros + /// for infinity. + /// + /// The encoding reads the uncompressed 192-byte form, which blst lays out + /// as `[x.c1, x.c0, y.c1, y.c0]`. + fn g2_bytes(point: &G2Affine) -> [u8; 96] { + // Take blst's own infinity flag rather than testing the buffer. Its // uncompressed form sets bit 6 of byte 0 and zeroes the rest, so an // all-zero test never fires and the swizzle would relocate that flag. - if sig.is_inf() { + if point.is_inf() { let mut out = [0u8; 96]; out[0] = 0xc0; return out; } - let uncomp = sig.serialize(); + let uncomp = point.serialize(); let x_c1 = &uncomp[0..48]; let x_c0 = &uncomp[48..96]; let y_c1 = &uncomp[96..144]; @@ -216,6 +228,40 @@ impl BlsScheme for BlsScChia { legacy } + /// The `x` of a legacy G2 encoding of a point other than infinity, in the + /// IETF compressed layout with the sign bit clear. + fn sig_ietf_x_bytes(b: &[u8; 96]) -> [u8; 96] { + let mut ietf = [0u8; 96]; + ietf[..48].copy_from_slice(&b[48..96]); + ietf[48..96].copy_from_slice(&b[..48]); + // Clear only the sign bit. Stray bits 5-6 make `x.c0 >= p`, which the + // decompression range check rejects like any out-of-range coordinate. + ietf[48] &= 0x7f; + ietf[0] |= 0x80; // compression + ietf + } + + /// Decode `b` and require it to encode back to the same bytes, or accept an + /// `x` with no point on the curve, both halves below the field prime, when + /// the sign bit and the top three bits of `x.c1` are clear. + /// + /// [`sig_from_bytes`](Self::sig_from_bytes) has no point to return for these + /// encodings. + fn is_canonical_sig(b: &[u8; 96]) -> bool { + match Self::sig_from_bytes(b) { + Ok(sig) => Self::sig_to_bytes(&sig) == *b, + Err(_) => { + b.iter().any(|&byte| byte != 0) + && b[0] & 0x80 == 0 + && b[48] & 0xe0 == 0 + && matches!( + G2Affine::uncompress(&Self::sig_ietf_x_bytes(b)), + Err(BLST_ERROR::BLST_POINT_NOT_ON_CURVE) + ) + } + } + } + /// The legacy signature is already an affine G2 point. fn sig_to_g2(sig: &Self::InnerSig) -> Result { Ok(sig.to_projective()) diff --git a/pkgs/pkc/src/bls/scheme_ietf.rs b/pkgs/pkc/src/bls/scheme_ietf.rs index 3410c3fed..a41db8cc3 100644 --- a/pkgs/pkc/src/bls/scheme_ietf.rs +++ b/pkgs/pkc/src/bls/scheme_ietf.rs @@ -83,6 +83,11 @@ impl BlsScheme for BlsScIetf { *b } + /// Decode `b` and require it to encode back to the same bytes. + fn is_canonical_pk(b: &[u8; 48]) -> bool { + Self::pk_from_bytes(b).is_ok_and(|pk| Self::pk_to_bytes(&pk) == *b) + } + /// Lift the blst public key into a projective G1 point. fn pk_to_g1(pk: &Self::InnerPk) -> Result { let aff = G1Affine::deserialize(&pk.serialize()).map_err(|_| BlsError::InvalidPublicKey)?; @@ -117,6 +122,23 @@ impl BlsScheme for BlsScIetf { sig.compress() } + /// Compress the G2 point to 96 bytes. + fn g2_bytes(point: &G2Affine) -> [u8; 96] { + point.compress() + } + + /// The `x` of the encoding, with the sign bit, bit 5 of byte 0, clear. + fn sig_ietf_x_bytes(b: &[u8; 96]) -> [u8; 96] { + let mut ietf = *b; + ietf[0] &= !0x20; + ietf + } + + /// Decode `b` and require it to encode back to the same bytes. + fn is_canonical_sig(b: &[u8; 96]) -> bool { + Self::sig_from_bytes(b).is_ok_and(|sig| Self::sig_to_bytes(&sig) == *b) + } + /// Lift the blst signature into a projective G2 point. fn sig_to_g2(sig: &Self::InnerSig) -> Result { let aff = G2Affine::deserialize(&sig.serialize()).map_err(|_| BlsError::InvalidSignature)?; @@ -125,7 +147,7 @@ impl BlsScheme for BlsScIetf { /// Re-encode the projective point and parse it back through `validate`. fn g2_to_sig(point: G2) -> Result { - Self::sig_from_bytes(&point.to_affine().compress()) + Self::sig_from_bytes(&Self::g2_bytes(&point.to_affine())) } /// Sign with the basic-scheme DST. diff --git a/pkgs/pkc/src/bls/scheme_ops.rs b/pkgs/pkc/src/bls/scheme_ops.rs index 01af825c9..7f90b45f0 100644 --- a/pkgs/pkc/src/bls/scheme_ops.rs +++ b/pkgs/pkc/src/bls/scheme_ops.rs @@ -8,7 +8,7 @@ use super::blst_ffi; use super::error::BlsError; -use super::group::{G1Affine, Point, G1, G2}; +use super::group::{G1Affine, G2Affine, Point, G1, G2}; use super::scalar::{Fr, FR_BITS}; use super::schemes::BlsSchemeId; use super::{BlsDhBytes, BlsShareId}; @@ -91,6 +91,9 @@ pub trait BlsScheme: BlsSchemeId + Sealed + Sized { /// infinity in the scheme's layout, with the same `x` and sign. fn pk_ietf_bytes(b: &[u8; 48]) -> [u8; 48]; + /// Whether the scheme reads `b` as a canonical public key encoding. + fn is_canonical_pk(b: &[u8; 48]) -> bool; + /// Lift a public key to a projective G1 point. /// /// # Errors @@ -115,6 +118,18 @@ pub trait BlsScheme: BlsSchemeId + Sealed + Sized { /// Serialize a signature to its 96-byte encoding. fn sig_to_bytes(sig: &Self::InnerSig) -> [u8; 96]; + /// Encode a G2 point in the scheme's 96-byte layout. + fn g2_bytes(point: &G2Affine) -> [u8; 96]; + + /// The `x` of `b`, a G2 encoding of a point other than infinity in the + /// scheme's layout, in the IETF compressed layout with the sign bit clear. + /// + /// It need not be the IETF encoding of the point `b` encodes. + fn sig_ietf_x_bytes(b: &[u8; 96]) -> [u8; 96]; + + /// Whether the scheme reads `b` as a canonical signature encoding. + fn is_canonical_sig(b: &[u8; 96]) -> bool; + /// Lift a signature to a projective G2 point. /// /// # Errors diff --git a/pkgs/pkc/src/bls/sig_basic.rs b/pkgs/pkc/src/bls/sig_basic.rs index b9aafd9b8..884983a99 100644 --- a/pkgs/pkc/src/bls/sig_basic.rs +++ b/pkgs/pkc/src/bls/sig_basic.rs @@ -115,16 +115,21 @@ impl BlsSigBytes { /// # Errors /// /// Returns `InvalidSignature` when the bytes are not a valid point under `S`. + /// That includes legacy encodings of an `x` with no point on the curve, + /// which [`is_canonical`](Self::is_canonical) accepts; there is no point to + /// return for them. pub fn to(&self) -> Result, BlsError> { BlsSignature::try_from(self) } - /// Whether `S` decodes the bytes to a signature that re-encodes to them. + /// Whether `S` reads the bytes as a signature that re-encodes to them, or + /// under the legacy scheme as an `x` with no point on the curve and the sign + /// bit clear. /// /// Null bytes pass, so pair with [`is_null`](Self::is_null) to require a /// signature. pub fn is_canonical(&self) -> bool { - self.is_null() || self.to::().is_ok_and(|sig| sig.to_bytes() == *self.as_bytes()) + self.is_null() || S::is_canonical_sig(self.as_bytes()) } } @@ -250,6 +255,32 @@ mod tests { assertion(); } + /// A legacy encoding of an `x` with no point on the curve is canonical, while + /// `to` has no point to return. With the sign bit set or a flag bit on `x.c1` + /// the same `x` is not canonical. + #[rstest] + fn chia_off_curve_x_is_canonical_but_not_a_signature() { + let encoded: [u8; 96] = arr_from_hex(concat!( + "0097926406fa8040c0069d1b53b861b1df87e8e4c38b9b9418dd80a2fb113fdf1878624f7f624d512d56cd267360453e", + "0e6992f1207908245f455d4d0bd6fcf0d7bb962c5f1913d4c18b8deb8c4670d233493576e8ad4ddc308fea94f8a109d7" + )); + let bytes = BlsSigBytes::from_bytes(encoded); + assert!(bytes.is_canonical::()); + assert!(bytes.to::().is_err()); + + let mut signed = encoded; + signed[0] |= 0x80; + assert!(!BlsSigBytes::from_bytes(signed).is_canonical::()); + for flag in [0x20, 0x40, 0x80] { + let mut flagged = encoded; + flagged[48] |= flag; + assert!( + !BlsSigBytes::from_bytes(flagged).is_canonical::(), + "{flag:#x}" + ); + } + } + fn assert_sig_roundtrip() { let sk = BlsSecretKey::::from_ikm(&RSEED[0]).unwrap(); let bytes = sk.sign_digest(&MSG_DEADBEEF).to_bytes(); diff --git a/pkgs/pkc/src/bls/tests.rs b/pkgs/pkc/src/bls/tests.rs index 5122561fc..4e9035afb 100644 --- a/pkgs/pkc/src/bls/tests.rs +++ b/pkgs/pkc/src/bls/tests.rs @@ -86,6 +86,11 @@ pub const fn ietf_g1_encoding(mut chia: [u8; 48]) -> [u8; 48] { cfg_if! { if #[cfg(test)] { + use super::group::G1Affine; + use super::scheme_ops::BlsScheme; + use super::schemes::BlsScChia; + + use blst::BLST_ERROR; use dash_dev::Corpus; use core::fmt; @@ -119,6 +124,52 @@ cfg_if! { legacy.into_iter().zip(ietf).collect() } + /// Whether the legacy G1 encoding `b` holds an `x` with no point on the + /// curve. + pub fn chia_pk_off_curve(b: &[u8; 48]) -> bool { + matches!( + G1Affine::uncompress(&BlsScChia::pk_ietf_bytes(b)), + Err(BLST_ERROR::BLST_POINT_NOT_ON_CURVE) + ) + } + + #[expect(clippy::unwrap_used, reason = "test code")] + mod layouts { + use super::*; + use crate::bls::group::G2Affine; + use crate::bls::schemes::BlsScIetf; + + use rstest::rstest; + + /// A point other than infinity in the scheme's layout, taken back to the + /// IETF compressed layout, is the point's IETF encoding for G1, and its + /// `x` with the sign bit clear for G2. + fn assert_layouts_reach_ietf() { + let mut signs = [[false; 2]; 2]; + for seed in &RSEED { + let sk = BlsScIetf::sk_from_ikm(seed).unwrap(); + let g1 = BlsScIetf::derive_pk(&sk).compress(); + let g2 = BlsScIetf::sign(&sk, &MSG_DEADBEEF).compress(); + let point = G1Affine::uncompress(&g1).unwrap(); + assert_eq!(S::pk_ietf_bytes(&S::g1_bytes(&point)), g1); + let point = G2Affine::uncompress(&g2).unwrap(); + let mut unsigned = g2; + unsigned[0] &= !0x20; + assert_eq!(S::sig_ietf_x_bytes(&S::g2_bytes(&point)), unsigned); + signs[0][usize::from(g1[0] & 0x20 != 0)] = true; + signs[1][usize::from(g2[0] & 0x20 != 0)] = true; + } + assert_eq!(signs, [[true; 2]; 2], "the seeds cover both signs"); + } + + #[rstest] + #[case::chia(assert_layouts_reach_ietf::)] + #[case::ietf(assert_layouts_reach_ietf::)] + fn layouts_reach_the_ietf_form(#[case] check: fn()) { + check(); + } + } + mod builders { use super::*; From 137d32938e5976fee2f34bb6a1ae5b75ec1ecd31 Mon Sep 17 00:00:00 2001 From: pasta Date: Sun, 1 Nov 2026 10:37:47 +0000 Subject: [PATCH 4/5] pkc%test(bls): check BLS reads against the reference outcomes Each `bls_read` input must read as in the reference implementation: `is_canonical` follows its outcome, and a valid input decodes and encodes back to itself unless the corpus marks its `x` off the curve. --- pkgs/pkc/src/bls/public_ops.rs | 41 +++++++++++++++++++++++++++++-- pkgs/pkc/src/bls/sig_basic.rs | 45 ++++++++++++++++++++++++++++++++-- pkgs/pkc/src/bls/tests.rs | 42 +++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 4 deletions(-) diff --git a/pkgs/pkc/src/bls/public_ops.rs b/pkgs/pkc/src/bls/public_ops.rs index 602bad71b..c5f9fbb4b 100644 --- a/pkgs/pkc/src/bls/public_ops.rs +++ b/pkgs/pkc/src/bls/public_ops.rs @@ -228,8 +228,8 @@ type_cvrt!(for[S: BlsScheme] TryFrom for BlsPublicKey, BlsError, |point| mod tests { use super::*; use crate::bls::tests::{ - chia_pk_off_curve, ietf_g1_encoding, ser_pairs, SerType, G1_OFF_SUBGROUP_CHIA, G1_OFF_SUBGROUP_IETF, - G1_X_EQ_PRIME_CHIA, G1_X_GE_PRIME_CHIA, G1_X_MAX_CHIA, MSG_8BADFOOD, RSEED, + chia_pk_off_curve, ietf_g1_encoding, read_vectors, ser_pairs, Curve, ReadOutcome, SerType, G1_OFF_SUBGROUP_CHIA, + G1_OFF_SUBGROUP_IETF, G1_X_EQ_PRIME_CHIA, G1_X_GE_PRIME_CHIA, G1_X_MAX_CHIA, MSG_8BADFOOD, RSEED, }; use crate::bls::{BlsScChia, BlsScIetf, BlsSecretKey, BlsSignature}; @@ -580,6 +580,43 @@ mod tests { assert!(bytes.to::().is_err()); } + /// Every `bls_read` input reads as in the reference implementation. + /// + /// `is_canonical` follows its outcome. A valid input decodes to a key that + /// encodes back to it, except a legacy encoding whose `x` the corpus marks + /// off the curve, which does not decode. + fn assert_pk_reads(legacy: bool) { + for v in read_vectors(SerType::PublicKey) { + let label = format!("{} {}", v.class, v.input); + let bytes = BlsPkBytes::from_bytes(arr_from_hex(&v.input)); + let outcome = v.outcome(legacy); + + assert_eq!(v.curve.is_some(), v.outcome(true) == ReadOutcome::Valid, "{label}"); + assert_eq!(bytes.is_null(), outcome == ReadOutcome::Null, "{label}"); + assert_eq!( + S::is_canonical_pk(bytes.as_bytes()), + outcome == ReadOutcome::Valid, + "{label}" + ); + assert_eq!(bytes.is_canonical::(), outcome != ReadOutcome::Invalid, "{label}"); + if outcome == ReadOutcome::Valid { + let decoded = bytes.to::(); + if legacy && v.curve == Some(Curve::Off) { + assert!(decoded.is_err(), "{label}"); + } else { + assert_eq!(decoded.map(|pk| pk.to_bytes()).ok(), Some(*bytes.as_bytes()), "{label}"); + } + } + } + } + + #[rstest] + #[case::chia(assert_pk_reads::, true)] + #[case::ietf(assert_pk_reads::, false)] + fn pk_reads_match_reference(#[case] assertion: fn(bool), #[case] legacy: bool) { + assertion(legacy); + } + /// The same G1 point encodes differently under the two schemes, and each /// encoding must round-trip through the wrapper of its own scheme. #[rstest] diff --git a/pkgs/pkc/src/bls/sig_basic.rs b/pkgs/pkc/src/bls/sig_basic.rs index 884983a99..c0d421131 100644 --- a/pkgs/pkc/src/bls/sig_basic.rs +++ b/pkgs/pkc/src/bls/sig_basic.rs @@ -148,8 +148,8 @@ mod tests { use crate::bls::public_ops::BlsPublicKey; use crate::bls::secret_ops::BlsSecretKey; use crate::bls::tests::{ - ser_pairs, test_ikm, test_msg, SerType, G2_OFF_SUBGROUP_CHIA, G2_OFF_SUBGROUP_IETF, MSG_8BADFOOD, MSG_DEADBEEF, - RSEED, + read_vectors, ser_pairs, test_ikm, test_msg, Curve, ReadOutcome, SerType, G2_OFF_SUBGROUP_CHIA, + G2_OFF_SUBGROUP_IETF, MSG_8BADFOOD, MSG_DEADBEEF, RSEED, }; use crate::bls::{BlsScChia, BlsScIetf, BlsSigId}; use crate::prelude::*; @@ -281,6 +281,47 @@ mod tests { } } + /// Every `bls_read` input reads as in the reference implementation. + /// + /// `is_canonical` follows its outcome. A valid input decodes to a signature + /// that encodes back to it, except a legacy encoding whose `x` the corpus + /// marks off the curve, which does not decode. + fn assert_sig_reads(legacy: bool) { + for v in read_vectors(SerType::Signature) { + let label = format!("{} {}", v.class, v.input); + let bytes = BlsSigBytes::from_bytes(arr_from_hex(&v.input)); + let outcome = v.outcome(legacy); + + assert_eq!(v.curve.is_some(), v.outcome(true) == ReadOutcome::Valid, "{label}"); + assert_eq!(bytes.is_null(), outcome == ReadOutcome::Null, "{label}"); + assert_eq!( + S::is_canonical_sig(bytes.as_bytes()), + outcome == ReadOutcome::Valid, + "{label}" + ); + assert_eq!(bytes.is_canonical::(), outcome != ReadOutcome::Invalid, "{label}"); + if outcome == ReadOutcome::Valid { + let decoded = bytes.to::(); + if legacy && v.curve == Some(Curve::Off) { + assert!(decoded.is_err(), "{label}"); + } else { + assert_eq!( + decoded.map(|sig| sig.to_bytes()).ok(), + Some(*bytes.as_bytes()), + "{label}" + ); + } + } + } + } + + #[rstest] + #[case::chia(assert_sig_reads::, true)] + #[case::ietf(assert_sig_reads::, false)] + fn sig_reads_match_reference(#[case] assertion: fn(bool), #[case] legacy: bool) { + assertion(legacy); + } + fn assert_sig_roundtrip() { let sk = BlsSecretKey::::from_ikm(&RSEED[0]).unwrap(); let bytes = sk.sign_digest(&MSG_DEADBEEF).to_bytes(); diff --git a/pkgs/pkc/src/bls/tests.rs b/pkgs/pkc/src/bls/tests.rs index 4e9035afb..42ca3b0e6 100644 --- a/pkgs/pkc/src/bls/tests.rs +++ b/pkgs/pkc/src/bls/tests.rs @@ -124,6 +124,48 @@ cfg_if! { legacy.into_iter().zip(ietf).collect() } + /// The reference implementation's outcome for one `bls_read` input. + #[derive(Clone, Copy, Debug, Eq, PartialEq, ::serde::Deserialize)] + #[serde(rename_all = "lowercase")] + pub enum ReadOutcome { + Null, + Valid, + Invalid, + } + + /// Whether the `x` of an input the legacy scheme reads as valid has a + /// point on the curve. + #[derive(Clone, Copy, Debug, Eq, PartialEq, ::serde::Deserialize)] + #[serde(rename_all = "lowercase")] + pub enum Curve { + On, + Off, + } + + /// One `bls_read` input, its outcome under each scheme and, when the + /// legacy scheme reads it as valid, whether its `x` has a curve point. + #[derive(Debug, ::serde::Deserialize)] + pub struct ReadVector { + pub class: String, + #[serde(rename = "in")] + pub input: String, + legacy: ReadOutcome, + basic: ReadOutcome, + #[serde(default)] + pub curve: Option, + } + + impl ReadVector { + pub fn outcome(&self, legacy: bool) -> ReadOutcome { + if legacy { self.legacy } else { self.basic } + } + } + + /// The `bls_read` vectors of one kind. + pub fn read_vectors(kind: SerType) -> Vec { + Corpus::open(env!("CARGO_MANIFEST_DIR"), "bls_read").vectors(&kind.to_string()) + } + /// Whether the legacy G1 encoding `b` holds an `x` with no point on the /// curve. pub fn chia_pk_off_curve(b: &[u8; 48]) -> bool { From 1ebd2c2936c6d16bcf0f0725204b318d6ee96e54 Mon Sep 17 00:00:00 2001 From: pasta Date: Sat, 31 Oct 2026 21:00:19 +0000 Subject: [PATCH 5/5] pkc%doc: update changelog for the legacy BLS reads --- pkgs/pkc/CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pkgs/pkc/CHANGELOG.md b/pkgs/pkc/CHANGELOG.md index a1ca2b9aa..4cd526b9a 100644 --- a/pkgs/pkc/CHANGELOG.md +++ b/pkgs/pkc/CHANGELOG.md @@ -7,6 +7,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), ## [Unreleased] +### Added + +- `BlsScheme::is_canonical_pk` and `BlsScheme::is_canonical_sig`, each scheme's rule for `is_canonical::()`. +- `BlsScheme::g1_bytes` and `BlsScheme::g2_bytes`, each scheme's byte layout of a point; `BlsScheme::pk_ietf_bytes`, + the IETF compressed form of a G1 encoding in that layout, and `BlsScheme::sig_ietf_x_bytes`, the `x` of a G2 + encoding in the IETF compressed layout with the sign bit clear. + +### Changed + +- `BlsPkBytes::to::()` and `BlsPublicKey::::from_bytes` read `x = 0` with the sign bit set, + (0, -2), as the reference implementation does. +- `is_canonical::()` on `BlsPkBytes` and `BlsSigBytes` accepts legacy encodings of an `x` with no point on + the curve, as the reference implementation does; for signatures, only with the sign bit clear. `to::()` + still refuses them, having no point to return. + ## [0.2.0-beta] - 2026-10-20 ### Added