From 321292e9b3cee5e716ac52e2a13cc8657602adc8 Mon Sep 17 00:00:00 2001 From: kevin Heifner Date: Thu, 17 Sep 2026 16:41:02 -0500 Subject: [PATCH 1/5] chain: stop validating producer keys on the proposed schedule path set_proposed_producers_common validated every key in every authority. The system contract rebuilds the producer schedule from its own tables inside onblock, and update_ranked_producers writes last_producer_schedule_update before proposing -- a rejection rolls that write back with the transaction, so the 120-slot gate re-fires on the next block and fails again. One producer holding a key that cannot sign froze schedule updates permanently. Neither key check decides anything here. block_state::verify_signee rejects any key recovered from a block signature that is not K1 or R1, so a producer holding another type can never sign with it. A key that decodes to no curve point is recovered from no signature at all, so it never matches in keys_satisfy_and_relevant. A producer holding either burns its rounds; the chain keeps publishing schedules. Upstream dropped the same checks from the authority format for the same reason, and kept them on the legacy producer_key format only to avoid a consensus change on an already-live intrinsic. That constraint does not apply here, so both formats are lenient and there is one rule rather than two that differ by packing format. r1::public_key_shim::valid() could not answer for the case that matters: the r1::public_key constructor throws when the point does not decode, so the predicate raised a bare fc::exception past the assert meant to catch it. It returns false now, independent of the schedule path. The K1 shim, whose constructor only copies, is documented as the all-zero test it actually is. The K1/R1 rule had no coverage on the signing side, where it now lives exclusively. A block re-signed with an EM key covers it. --- .../include/sysio/chain/proposer_policy.hpp | 8 +- libraries/chain/webassembly/privileged.cpp | 25 ++-- .../libfc/include/fc/crypto/elliptic.hpp | 5 + .../libfc/include/fc/crypto/elliptic_r1.hpp | 13 +- .../libfc/test/crypto/test_cypher_suites.cpp | 32 +++++ unittests/producer_schedule_tests.cpp | 125 ++++++++++++++++++ unittests/protocol_feature_tests.cpp | 41 +++--- 7 files changed, 203 insertions(+), 46 deletions(-) diff --git a/libraries/chain/include/sysio/chain/proposer_policy.hpp b/libraries/chain/include/sysio/chain/proposer_policy.hpp index 2a4a9611e8..f5df3c0784 100644 --- a/libraries/chain/include/sysio/chain/proposer_policy.hpp +++ b/libraries/chain/include/sysio/chain/proposer_policy.hpp @@ -46,11 +46,9 @@ struct proposer_policy { // Validates structural well-formedness of the policy. Single source of truth // reused by the set_proposed_producers host function and snapshot loading. - // Two things are intentionally NOT checked here and stay at the intrinsic - // call site instead: - // - account existence (requires apply_context) - // - K1/R1 key type enforcement (uses unactivated_key_type to signal that - // non-K1/R1 keys need a protocol feature; distinct from structural errors) + // Account existence is NOT checked here and stays at the intrinsic call site, + // which has the apply_context needed for it. Key type and key validity are not + // checked anywhere on this path by design -- see set_proposed_producers_common. // Throws producer_schedule_exception on violation. void validate() const { const auto& producers = proposer_schedule.producers; diff --git a/libraries/chain/webassembly/privileged.cpp b/libraries/chain/webassembly/privileged.cpp index 733f0ae101..812d8872b4 100644 --- a/libraries/chain/webassembly/privileged.cpp +++ b/libraries/chain/webassembly/privileged.cpp @@ -58,23 +58,20 @@ namespace sysio { namespace chain { namespace webassembly { SYS_THROW(wasm_execution_error, "set_proposed_producers: {}", e.top_message()); } - // Remaining checks that don't belong in proposer_policy::validate(): - // - account existence (requires apply_context) - // - K1/R1 key type enforcement (uses unactivated_key_type to convey - // that non-K1/R1 keys need a protocol feature to be activated — a - // distinct category from structural validation errors) - // - key.valid() semantics - using key_type = fc::crypto::public_key::key_type; + // Account existence is the only remaining per-producer check; it needs the apply_context, so + // it cannot live on proposer_policy::validate(). A schedule naming an account that does not + // exist is malformed, not merely unusable. + // + // Key type and key validity are deliberately NOT checked, on either packing format. Both are + // per-producer capability facts already enforced where they decide something: + // block_state::verify_signee rejects any key recovered from a block signature that is not K1 + // or R1, and a key that decodes to no curve point is recovered from no signature at all, so + // it never matches in keys_satisfy_and_relevant. Checking them here adds no safety and one + // failure mode: the system contract assembles a schedule from its own tables inside onblock, + // where a throw rolls the rebuild timestamp back with it and re-fires on every block after. for (const auto& p : candidate.proposer_schedule.producers) { SYS_ASSERT(context.is_account(p.producer_name), wasm_execution_error, "producer schedule includes a nonexisting account"); - std::visit([](const auto& a) { - for (const auto& kw : a.keys) { - SYS_ASSERT(kw.key.contains_type(key_type::k1, key_type::r1), unactivated_key_type, - "Unactivated key type used in proposed producer schedule"); - SYS_ASSERT(kw.key.valid(), wasm_execution_error, "producer schedule includes an invalid key"); - } - }, p.authority); } return context.control.set_proposed_producers( context.trx_context, diff --git a/libraries/libfc/include/fc/crypto/elliptic.hpp b/libraries/libfc/include/fc/crypto/elliptic.hpp index 0db858b46c..59ce1fdeff 100644 --- a/libraries/libfc/include/fc/crypto/elliptic.hpp +++ b/libraries/libfc/include/fc/crypto/elliptic.hpp @@ -116,6 +116,11 @@ namespace fc { struct public_key_shim : public crypto::shim { using crypto::shim::shim; + /// Whether the stored bytes are anything other than all zero. + /// + /// The ecc::public_key constructor taking public_key_data copies without decoding, so this + /// does NOT establish that the bytes name a point on the curve -- unlike the R1 shim's + /// valid(), which does decode. bool valid()const { return public_key(_data).valid(); } diff --git a/libraries/libfc/include/fc/crypto/elliptic_r1.hpp b/libraries/libfc/include/fc/crypto/elliptic_r1.hpp index c88c66ac33..ecb83ed408 100644 --- a/libraries/libfc/include/fc/crypto/elliptic_r1.hpp +++ b/libraries/libfc/include/fc/crypto/elliptic_r1.hpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -106,8 +107,18 @@ namespace fc { struct public_key_shim : public crypto::shim { using crypto::shim::shim; + /// Whether the stored bytes decode to a point on the curve. + /// + /// Never throws: the r1::public_key constructor raises when o2i_ECPublicKey rejects the + /// point, and every caller of this uses it as a predicate. Note the asymmetry with the K1 + /// shim of the same name, whose underlying constructor only copies -- there valid() is an + /// all-zero test and says nothing about the point. bool valid()const { - return public_key(_data).valid(); + try { + return public_key(_data).valid(); + } catch (const fc::exception&) { + return false; + } } }; diff --git a/libraries/libfc/test/crypto/test_cypher_suites.cpp b/libraries/libfc/test/crypto/test_cypher_suites.cpp index b40a751cc8..b8cfd73796 100644 --- a/libraries/libfc/test/crypto/test_cypher_suites.cpp +++ b/libraries/libfc/test/crypto/test_cypher_suites.cpp @@ -9,6 +9,9 @@ #include #include +#include +#include + using namespace fc::crypto; using namespace fc; @@ -142,6 +145,35 @@ BOOST_AUTO_TEST_CASE(test_r1_recyle) try { BOOST_CHECK_EQUAL(pub.to_string({}), recycled_pub.to_string({})); } FC_LOG_AND_RETHROW(); +BOOST_AUTO_TEST_CASE(test_public_key_valid_never_throws) try { + // valid() is a predicate, and callers use it as one -- the legacy producer schedule format + // asserts on it. R1 answers by decoding the point, and the r1::public_key constructor RAISES + // when the point does not decode, so the shim has to absorb that rather than propagate it. + r1::public_key_data undecodable{}; + undecodable[0] = 0x02; // compressed point prefix + std::fill(undecodable.begin() + 1, undecodable.end(), '\xff'); // x above the field prime + + public_key bad_r1{public_key::storage_type{std::in_place_index<1>, r1::public_key_shim{undecodable}}}; + BOOST_CHECK_NO_THROW(bad_r1.valid()); + BOOST_CHECK(!bad_r1.valid()); + + // All-zero is rejected on both curves. This is the shape a producer that registered without + // setting a signing key ends up holding. + BOOST_CHECK(!public_key{}.valid()); // default storage is a zero K1 key + public_key zero_r1{public_key::storage_type{std::in_place_index<1>, r1::public_key_shim{}}}; + BOOST_CHECK(!zero_r1.valid()); + + // Real keys of both types are valid. + BOOST_CHECK(private_key::generate().get_public_key().valid()); + BOOST_CHECK(private_key::generate(private_key::key_type::r1).get_public_key().valid()); + + // K1 validity is ONLY an all-zero test: ecc::public_key's public_key_data constructor copies + // without decoding, so the same bytes R1 rejects pass here. Asserted so the asymmetry is + // recorded rather than rediscovered as a bug. + public_key bad_k1{public_key::storage_type{std::in_place_index<0>, ecc::public_key_shim{undecodable}}}; + BOOST_CHECK(bad_k1.valid()); +} FC_LOG_AND_RETHROW(); + BOOST_AUTO_TEST_CASE(test_em) try { auto key = fc::crypto::private_key::generate(private_key::key_type::em); auto pub = key.get_public_key(); diff --git a/unittests/producer_schedule_tests.cpp b/unittests/producer_schedule_tests.cpp index f0b89a1a13..c19197e645 100644 --- a/unittests/producer_schedule_tests.cpp +++ b/unittests/producer_schedule_tests.cpp @@ -5,12 +5,41 @@ #include +#include +#include + #include "fork_test_utilities.hpp" using namespace sysio::testing; using namespace sysio::chain; using mvo = fc::mutable_variant_object; +namespace { + +/// A key no block signature can ever produce: a well-formed compressed-point prefix over an x +/// coordinate above the R1 field prime. o2i_ECPublicKey rejects it, so R1 key validity answers +/// false for it -- and, until the shim absorbed the decode failure, threw instead. +public_key_type undecodable_r1_key() { + fc::crypto::r1::public_key_data data{}; + data[0] = 0x02; + std::fill( data.begin() + 1, data.end(), '\xff' ); + return public_key_type{ fc::crypto::public_key::storage_type{ std::in_place_index<1>, + fc::crypto::r1::public_key_shim{ data } } }; +} + +/// The all-zero K1 key -- what a producer that registered without setting a signing key holds, +/// and what the chain's own K1 validity test rejects. +public_key_type zero_k1_key() { return public_key_type{}; } + +/// A WebAuthn key: well-formed, and of a type the chain rejects when it recovers a key from a +/// block signature, so a producer holding one could never sign. +public_key_type webauthn_key() { + return public_key_type::from_string( + "PUB_WA_WdCPfafVNxVMiW5ybdNs83oWjenQXvSt1F49fg9mv7qrCiRwHj5b38U3ponCFWxQTkDsMC" ); +} + +} // namespace + BOOST_AUTO_TEST_SUITE(producer_schedule_tests) BOOST_AUTO_TEST_CASE(verify_producers) try { @@ -340,4 +369,100 @@ BOOST_AUTO_TEST_CASE( extra_signatures_test ) try { } FC_LOG_AND_RETHROW() +BOOST_AUTO_TEST_CASE(schedule_admits_unsignable_keys) try { + savanna_tester chain; + chain.create_accounts( {"alice"_n, "bobby"_n, "carol"_n} ); + chain.produce_block(); + + // None of these keys can be produced by recovering a key from a block signature, so none of + // these producers can sign. The schedule must still publish: the system contract rebuilds one + // from its own producer table inside onblock, and a rejection there rolls back the rebuild + // timestamp along with it, so the rebuild re-fires -- and fails again -- on every block that + // follows, permanently. + vector sch = { + producer_authority{ "alice"_n, block_signing_authority_v0{ 1, {{ undecodable_r1_key(), 1 }} } }, + producer_authority{ "bobby"_n, block_signing_authority_v0{ 1, {{ zero_k1_key(), 1 }} } }, + producer_authority{ "carol"_n, block_signing_authority_v0{ 1, {{ webauthn_key(), 1 }} } } + }; + + auto trace = chain.set_producer_schedule( sch ); + BOOST_REQUIRE( !trace->except ); + BOOST_REQUIRE( trace->receipt ); +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_CASE(legacy_format_admits_unsignable_keys) try { + savanna_tester chain; + chain.create_accounts( {"alice"_n} ); + chain.produce_block(); + + // The legacy producer_key format is lenient on the same terms. Upstream kept a key check here + // only to avoid a consensus change on an already-live intrinsic; it never established that a + // key could sign, since a curve point whose private key nobody holds passes it just the same. + for( const auto& key : { zero_k1_key(), undecodable_r1_key(), webauthn_key() } ) { + vector sched = {{ "alice"_n, key }}; + auto trace = chain.push_action( config::system_account_name, "setprodkeys"_n, + config::system_account_name, mvo()("schedule", sched) ); + BOOST_REQUIRE( !trace->except ); + BOOST_REQUIRE( trace->receipt ); + } +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_CASE(unsignable_key_never_satisfies_authority) try { + // Why admitting those keys costs nothing: the presented set is built by recovering keys from + // the block's signatures, so a key that no signature yields is never in it, and the authority + // is never satisfied. The producer burns its rounds; the chain keeps updating schedules. + block_signing_authority_v0 auth{ 1, {{ undecodable_r1_key(), 1 }, { zero_k1_key(), 1 }} }; + + std::set presented = { get_public_key("alice"_n, "bs1"), + get_public_key("bobby"_n, "bs1") }; + + auto [satisfied, relevant] = auth.keys_satisfy_and_relevant( presented ); + BOOST_CHECK( !satisfied ); + BOOST_CHECK_EQUAL( relevant, 0u ); +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_CASE( block_signed_with_non_k1_r1_key_test ) try { + savanna_tester main; + + main.create_accounts( {"alice"_n} ); + main.produce_block(); + + vector sch1 = { + producer_authority{"alice"_n, block_signing_authority_v0{1, {{get_public_key("alice"_n, "bs1"), 1}}}} + }; + main.set_producer_schedule( sch1 ); + main.block_signing_private_keys.emplace(get_public_key("alice"_n, "bs1"), get_private_key("alice"_n, "bs1")); + + BOOST_REQUIRE( main.control->pending_block_producer() == "sysio"_n ); + main.produce_blocks(24); + BOOST_REQUIRE( main.control->pending_block_producer() == "alice"_n ); + + mutable_block_ptr b; + + // Generate a valid block, then re-sign it with a key of a type no producer may sign with. + { + tester remote(setup_policy::none); + push_blocks(main, remote); + + remote.block_signing_private_keys.emplace(get_public_key("alice"_n, "bs1"), get_private_key("alice"_n, "bs1")); + + auto valid_block = remote.produce_block(); + BOOST_REQUIRE( valid_block->producer == "alice"_n ); + + b = valid_block->clone(); + + // The block id excludes producer_signatures, so replacing them does not move it. + b->producer_signatures.clear(); + b->producer_signatures.emplace_back( + fc::crypto::private_key::generate( fc::crypto::private_key::key_type::em ).sign( b->calculate_id() ) ); + } + + // This is where the K1/R1 rule decides something, and the reason a proposed schedule does not + // need to repeat it: the key type is screened on every key recovered from a block signature. + auto sb = signed_block::create_signed_block(std::move(b)); + BOOST_REQUIRE_EXCEPTION( main.push_block(sb), unactivated_key_type, + fc_exception_message_contains("Block signed with invalid key type") ); + +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END() diff --git a/unittests/protocol_feature_tests.cpp b/unittests/protocol_feature_tests.cpp index 52c9700d2f..d90a5984d1 100644 --- a/unittests/protocol_feature_tests.cpp +++ b/unittests/protocol_feature_tests.cpp @@ -1315,32 +1315,21 @@ BOOST_AUTO_TEST_CASE( producer_keys ) { try { c.create_account("prod"_n); c.produce_block(); - { // webauthn key - vector prodsched = {{"prod"_n, public_key_type::from_string("PUB_WA_WdCPfafVNxVMiW5ybdNs83oWjenQXvSt1F49fg9mv7qrCiRwHj5b38U3ponCFWxQTkDsMC"s)}}; - BOOST_CHECK_THROW( - c.push_action(config::system_account_name, "setprodkeys"_n, config::system_account_name, fc::mutable_variant_object()("schedule", prodsched)), - sysio::chain::unactivated_key_type - ); - } - { // em key - vector prodsched = {{"prod"_n, public_key_type::from_string("0x04e68acfc0253a10620dff706b0a1b1f1f5833ea3beb3bde2250d5f271f3563606672ebc45e0b7ea2e816ecb70ca03137b1c9476eec63d4632e990020b7b6fba39"s, public_key::key_type::em)}}; - BOOST_CHECK_THROW( - c.push_action(config::system_account_name, "setprodkeys"_n, config::system_account_name, fc::mutable_variant_object()("schedule", prodsched)), - sysio::chain::unactivated_key_type - ); - } - { // ed key - vector prodsched = {{"prod"_n, public_key_type::from_string("PUB_ED_7mHKCLbBMeMF7ew5C7teVeCrk8HvZafdAvmzfoecosrk"s)}}; - BOOST_CHECK_THROW( - c.push_action(config::system_account_name, "setprodkeys"_n, config::system_account_name, fc::mutable_variant_object()("schedule", prodsched)), - sysio::chain::unactivated_key_type - ); - } - { // bls key - vector prodsched = {{"prod"_n, public_key_type::from_string("PUB_BLS_sGOyYNtpmmjfsNbQaiGJrPxeSg9sdx0nRtfhI_KnWoACXLL53FIf1HjpcN8wX0cYQyOE60NLSI9iPY8mIlT4GkiFMT3ez7j2IbBBzR0D1MthC0B_fYlgYWwjcbqCOowSaH48KA"s)}}; - BOOST_CHECK_THROW( - c.push_action(config::system_account_name, "setprodkeys"_n, config::system_account_name, fc::mutable_variant_object()("schedule", prodsched)), - sysio::chain::unactivated_key_type + // A proposed schedule no longer screens key types. The rule lives on the signing side, where + // every key recovered from a block signature must be K1 or R1, so a producer holding one of + // these can be scheduled and simply never signs -- see producer_schedule_tests. + const std::vector unsignable_keys = { + public_key_type::from_string("PUB_WA_WdCPfafVNxVMiW5ybdNs83oWjenQXvSt1F49fg9mv7qrCiRwHj5b38U3ponCFWxQTkDsMC"s), + public_key_type::from_string("0x04e68acfc0253a10620dff706b0a1b1f1f5833ea3beb3bde2250d5f271f3563606672ebc45e0b7ea2e816ecb70ca03137b1c9476eec63d4632e990020b7b6fba39"s, public_key::key_type::em), + public_key_type::from_string("PUB_ED_7mHKCLbBMeMF7ew5C7teVeCrk8HvZafdAvmzfoecosrk"s), + public_key_type::from_string("PUB_BLS_sGOyYNtpmmjfsNbQaiGJrPxeSg9sdx0nRtfhI_KnWoACXLL53FIf1HjpcN8wX0cYQyOE60NLSI9iPY8mIlT4GkiFMT3ez7j2IbBBzR0D1MthC0B_fYlgYWwjcbqCOowSaH48KA"s) + }; + + for( const auto& key : unsignable_keys ) { + vector prodsched = {{"prod"_n, key}}; + BOOST_CHECK_NO_THROW( + c.push_action(config::system_account_name, "setprodkeys"_n, config::system_account_name, + fc::mutable_variant_object()("schedule", prodsched)) ); } From 2e8f0fa570912010dfbd58402ff09e32566b3106 Mon Sep 17 00:00:00 2001 From: kevin Heifner Date: Fri, 18 Sep 2026 11:01:21 -0500 Subject: [PATCH 2/5] system: reject a finalizer key the chain cannot accept regfinkey admitted the BLS identity point. A proof of possession cannot screen it out: bls_pop_verify computes e(-g1, sig) * e(pk, H(pk)), and both terms are 1 when the points are the identity, so an all-zero key with an all-zero proof verifies. Registered as a producer's first key it auto-activates without proposing a policy, so nothing looks at it again until the schedule rebuild does. That rebuild runs inside onblock. set_finalizers rejects the identity point there, rolling back the rebuild timestamp with the rest of the transaction, so the 120-slot gate re-fires and fails again on every block that follows. One producer registering that key stops both the producer schedule and the finalizer policy from updating, permanently. The rejection itself has to stand. The identity point is not a key that cannot vote, it is a key whose votes anyone can cast: aggregate_public_keys treats it as a no-op on the aggregate while verify_weights still counts its weight from the bitset, so a quorum certificate can claim it voted and verify against the honest signatures alone. So refuse it at regfinkey, the only action that admits new key material, along with any key that is not a point on the curve -- set_finalizers raises while deserializing one of those, before the policy is validated at all. delfinkey stays lenient so a key registered before this check can still be removed. set_finalizers reported an off-curve key as a bare fc::exception, because the bls_public_key constructor raises outside the catch that converts policy errors. It now reports like every other input error on that path. --- contracts/sysio.system/src/finalizer_key.cpp | 38 +++++++++++++++++- contracts/sysio.system/sysio.system.wasm | Bin 203610 -> 203878 bytes contracts/tests/sysio.finalizer_key_tests.cpp | 26 ++++++++++++ libraries/chain/webassembly/privileged.cpp | 15 +++++-- 4 files changed, 73 insertions(+), 6 deletions(-) diff --git a/contracts/sysio.system/src/finalizer_key.cpp b/contracts/sysio.system/src/finalizer_key.cpp index 3c381c0568..783733a40e 100644 --- a/contracts/sysio.system/src/finalizer_key.cpp +++ b/contracts/sysio.system/src/finalizer_key.cpp @@ -17,12 +17,37 @@ namespace sysiosystem { return !get_last_proposed_finalizers().empty(); } - // Validates finalizer_key in text form and returns a binary form + // Validates finalizer_key in text form and returns a binary form. + // This checks the prefix and the base64url encoding only -- it says nothing about the point. sysio::bls_g1 to_binary(const std::string& finalizer_key) { check(finalizer_key.compare(0, 7, "PUB_BLS") == 0, "finalizer key does not start with PUB_BLS: " + finalizer_key); return sysio::decode_bls_public_key_to_g1(finalizer_key); } + namespace { + + constexpr auto identity_key_error = "finalizer key must not be the identity point"; + constexpr auto invalid_key_error = "finalizer key is not a valid G1 point"; + + // The all-zero G1 encoding is the point at infinity, and a proof of possession cannot screen + // it out: the pairing bls_pop_verify computes is satisfied by it for free. It must never + // become a finalizer key -- aggregating it into a quorum certificate is a no-op on the + // aggregate public key while its weight still counts toward the threshold, so its votes + // could be cast by anyone. + bool is_identity_g1( const sysio::bls_g1& key ) { + return key == sysio::bls_g1{}; + } + + // Whether the bytes are a point on the curve. The host G1 primitives deserialize with + // validity checking and report failure, so adding the identity to the key answers this in + // one host call. A key that fails here makes set_finalizers throw while deserializing it. + bool is_valid_g1( const sysio::bls_g1& key ) { + sysio::bls_g1 sum{}; + return sysio::bls_g1_add( key, sysio::bls_g1{}, sum ) == 0; + } + + } // namespace + // Returns hash of finalizer_key in binary format static sysio::checksum256 get_finalizer_key_hash(const sysio::bls_g1& finalizer_key_binary) { return sysio::sha256(finalizer_key_binary.data(), finalizer_key_binary.size()); @@ -138,10 +163,19 @@ namespace sysiosystem { // Basic signature format check check(proof_of_possession.compare(0, 7, "SIG_BLS") == 0, "proof of possession signature does not start with SIG_BLS: " + proof_of_possession); - // Convert to binary form. The validity will be checked during conversion. + // Convert to binary form. const auto fin_key_g1 = to_binary(finalizer_key); const auto pop_g2 = sysio::decode_bls_signature_to_g2(proof_of_possession); + // A key set_finalizers would reject must not enter the table. The schedule rebuild proposes + // every active finalizer key from inside onblock, and a throw there rolls back the rebuild + // timestamp with it, so the gate re-fires and fails again on every block that follows. + // Registration is the only action that admits new key material, which makes it the one place + // this can be caught; delfinkey is deliberately left lenient so a key that predates this + // check can still be removed. + check( !is_identity_g1(fin_key_g1), identity_key_error ); + check( is_valid_g1(fin_key_g1), invalid_key_error ); + // Duplication check across all registered keys const auto idx = _finalizer_keys.get_index<"byfinkey"_n>(); const auto hash = get_finalizer_key_hash(fin_key_g1); diff --git a/contracts/sysio.system/sysio.system.wasm b/contracts/sysio.system/sysio.system.wasm index 74519831b06a4858a4f3d84e4a9ef7053c0e651b..343b8f8fdefeb8763806f96580296feaf4e5985f 100755 GIT binary patch delta 39057 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%0Dv^cHd z#@bIc*OxZ#6eGGPev@XgI8eF{$1ohXjBNjiqWLjm4~29dV6prU Dm>y>v delta 38717 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 fkb0;40`n% +#include +#include #include #include @@ -670,4 +672,28 @@ BOOST_FIXTURE_TEST_CASE(verify_controller_schedule_and_policy_test, finalizer_ke } FC_LOG_AND_RETHROW() + +// A finalizer key that no proof of possession can screen out: the pairing bls_pop_verify computes +// is e(-g1, sig) * e(pk, H(pk)), and both terms are 1 when the points are the identity. +BOOST_FIXTURE_TEST_CASE(reject_identity_finalizer_key, finalizer_key_tester) try { + add_roa_policy(NODE_DADDY, alice, "32.0000 SYS", "32.0000 SYS", "32.0000 SYS", 0, 0); + BOOST_REQUIRE_EQUAL( success(), regproducer(alice) ); + + const std::string identity_key = fc::crypto::bls::public_key::to_string(fc::crypto::bls::public_key_data{}); + const std::string identity_pop = fc::crypto::bls::signature::to_string(fc::crypto::bls::signature_data{}); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg("finalizer key must not be the identity point"), + register_finalizer_key(alice, identity_key, identity_pop) ); + + // Bytes with a valid encoding that are not a point on the curve. set_finalizers raises while + // deserializing one, before the policy is even validated. + fc::crypto::bls::public_key_data off_curve; + off_curve.fill(0xff); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("finalizer key is not a valid G1 point"), + register_finalizer_key(alice, fc::crypto::bls::public_key::to_string(off_curve), identity_pop) ); + + // An honest key is unaffected. + BOOST_REQUIRE_EQUAL( success(), register_finalizer_key(alice, key_pairs[0].pub_key, key_pairs[0].pop) ); +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END() diff --git a/libraries/chain/webassembly/privileged.cpp b/libraries/chain/webassembly/privileged.cpp index 733f0ae101..471a6c5718 100644 --- a/libraries/chain/webassembly/privileged.cpp +++ b/libraries/chain/webassembly/privileged.cpp @@ -175,10 +175,17 @@ namespace sysio { namespace chain { namespace webassembly { finpol.finalizers.reserve(abi_finpol.finalizers.size()); for (auto& f: abi_finpol.finalizers) { SYS_ASSERT(f.public_key.size() == 96, wasm_execution_error, "Invalid bls public key length"); - fc::crypto::bls::public_key pk(std::span(f.public_key.data(), 96)); - finpol.finalizers.push_back(chain::finalizer_authority{.description = std::move(f.description), - .weight = f.weight, - .public_key{pk}}); + // bls::public_key validates the point in its constructor and raises a bare fc::exception + // when the bytes are not on the curve -- which lands outside the catch below. Report it + // the way every other input error on this path is reported. + try { + finpol.finalizers.push_back(chain::finalizer_authority{.description = std::move(f.description), + .weight = f.weight, + .public_key{fc::crypto::bls::public_key( + std::span(f.public_key.data(), 96))}}); + } catch (const fc::exception& e) { + SYS_THROW(wasm_execution_error, "set_finalizers: invalid bls public key: {}", e.top_message()); + } } // Structural validation is factored into finalizer_policy::validate() so the From a446baded26c5a799f97fa4caca4e69264c9b7ff Mon Sep 17 00:00:00 2001 From: kevin Heifner Date: Fri, 18 Sep 2026 14:36:55 -0500 Subject: [PATCH 3/5] bls: reject a deserialized shim with no payload fc reflects the BLS shims' shared_ptr member and packs a presence flag ahead of it, so a false flag unpacks to a null pointer. valid(), to_string(), serialize() and unwrapped() all dereference it unconditionally, terminating the process rather than raising. Both constructors allocate, so deserialization is the only way to reach that state. public_key_shim matters here because dropping the key-type check on the proposed schedule path lets a BLS key reach it, and a one-key authority passes proposer_policy::validate untouched -- the first insertion into the uniqueness set compares nothing -- so the null survives to whatever logs or compares the schedule. signature_shim has the same representation and sits in the signature variant used for transaction signatures, which arrive from RPC and peers. Both reject an absent payload in reflector_init, which fc calls after unpacking a reflected type, turning it into a controlled exception where the bytes are read. --- .../include/fc/crypto/bls_private_key.hpp | 18 +++++- .../libfc/test/crypto/test_cypher_suites.cpp | 30 ++++++++++ unittests/producer_schedule_tests.cpp | 55 +++++++++++++++++++ 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/libraries/libfc/include/fc/crypto/bls_private_key.hpp b/libraries/libfc/include/fc/crypto/bls_private_key.hpp index 1a0cac3e40..82b4e8caaa 100644 --- a/libraries/libfc/include/fc/crypto/bls_private_key.hpp +++ b/libraries/libfc/include/fc/crypto/bls_private_key.hpp @@ -67,7 +67,7 @@ class private_key { /** * @brief Shim class for BLS public key operations */ -struct public_key_shim { +struct public_key_shim : fc::reflect_init { using data_type = public_key_data; /** @brief Checks if the public key is valid */ @@ -97,13 +97,24 @@ struct public_key_shim { return *this; } + /** + * Reject a deserialized shim whose payload is absent. + * + * The reflected member is the shared_ptr itself and fc packs a presence flag ahead of it, so a + * false flag unpacks to a null pointer that valid(), to_string(), serialize() and unwrapped() + * would all dereference. Both constructors allocate, so deserialization is the only way to + * reach that state; fc calls this after unpacking a reflected type, which turns it into a + * controlled exception where the bytes are read. + */ + void reflector_init() { FC_ASSERT(shim_ptr, "BLS public key has no payload"); } + std::shared_ptr> shim_ptr; }; /** * @brief Shim class for BLS signature operations */ -struct signature_shim { +struct signature_shim : fc::reflect_init { using data_type = compact_signature; /** @brief Indicates if signature is recoverable */ @@ -126,6 +137,9 @@ struct signature_shim { return *this; } + /** Reject a deserialized shim whose payload is absent -- see public_key_shim::reflector_init. */ + void reflector_init() { FC_ASSERT(shim_ptr, "BLS signature has no payload"); } + /** * Not supported, throws. */ diff --git a/libraries/libfc/test/crypto/test_cypher_suites.cpp b/libraries/libfc/test/crypto/test_cypher_suites.cpp index b8cfd73796..c5d1b8e96a 100644 --- a/libraries/libfc/test/crypto/test_cypher_suites.cpp +++ b/libraries/libfc/test/crypto/test_cypher_suites.cpp @@ -9,6 +9,9 @@ #include #include +#include +#include + #include #include @@ -468,6 +471,33 @@ BOOST_AUTO_TEST_CASE(test_bls_sig_str) try { } FC_LOG_AND_RETHROW(); // --- sign_eth (shim-level): recovery round-trip with multiple messages --- +BOOST_AUTO_TEST_CASE(test_bls_absent_payload_is_rejected) try { + // The BLS shims reflect their shared_ptr, and fc packs a presence flag ahead of it, so a false + // flag unpacks to a null pointer that valid(), to_string() and serialize() all dereference. + // Deserialization has to reject it: these bytes reach the node from any peer-supplied + // signature, and a null dereference terminates the process rather than raising. + const auto unpack_absent = [](uint8_t variant_index, auto& out) { + const std::vector bytes{static_cast(variant_index), 0}; // alternative, presence = false + fc::datastream ds(bytes.data(), bytes.size()); + fc::raw::unpack(ds, out); + }; + + public_key key; + BOOST_CHECK_THROW(unpack_absent(static_cast(public_key::key_type::bls), key), fc::exception); + + signature sig; + BOOST_CHECK_THROW(unpack_absent(static_cast(signature::sig_type::bls), sig), fc::exception); + + // A present payload still round-trips, so the guard does not reject real BLS material. + const auto real = public_key::from_string( + "PUB_BLS_sGOyYNtpmmjfsNbQaiGJrPxeSg9sdx0nRtfhI_KnWoACXLL53FIf1HjpcN8wX0cYQyOE60NLSI9iPY8mIlT4GkiFMT3ez7j2IbBBzR0D1MthC0B_fYlgYWwjcbqCOowSaH48KA"); + const auto packed = fc::raw::pack(real); + fc::datastream ds(packed.data(), packed.size()); + public_key round_tripped; + BOOST_CHECK_NO_THROW(fc::raw::unpack(ds, round_tripped)); + BOOST_CHECK_EQUAL(real.to_string({}), round_tripped.to_string({})); +} FC_LOG_AND_RETHROW(); + BOOST_AUTO_TEST_CASE(test_sign_eth_recovery_roundtrip) try { auto key = fc::crypto::private_key::generate(private_key::key_type::em); auto pub = key.get_public_key(); diff --git a/unittests/producer_schedule_tests.cpp b/unittests/producer_schedule_tests.cpp index c19197e645..9dca99c50e 100644 --- a/unittests/producer_schedule_tests.cpp +++ b/unittests/producer_schedule_tests.cpp @@ -31,6 +31,30 @@ public_key_type undecodable_r1_key() { /// and what the chain's own K1 validity test rejects. public_key_type zero_k1_key() { return public_key_type{}; } +/// A BLS key whose payload is absent. fc reflects the shim's shared_ptr behind a presence flag, +/// so clearing that flag unpacks to a null pointer. Built by packing a real key and dropping the +/// payload, rather than by hand, so it stays correct if the encoding changes. +std::vector strip_bls_payload( const std::vector& packed ) { + const auto bls_index = static_cast( fc::crypto::public_key::key_type::bls ); + for( size_t i = 0; i + 1 < packed.size(); ++i ) { + if( packed[i] == bls_index && packed[i + 1] == 1 ) { + std::vector stripped( packed.begin(), packed.begin() + i + 1 ); + stripped.push_back( 0 ); // payload absent + const auto rest = i + 2 + fc::crypto::bls::public_key_data_size; + stripped.insert( stripped.end(), packed.begin() + rest, packed.end() ); + return stripped; + } + } + BOOST_FAIL( "no BLS payload found in packed schedule" ); + return {}; +} + +/// A real BLS public key, used only as a carrier for the payload-stripping above. +public_key_type bls_key() { + return public_key_type::from_string( + "PUB_BLS_sGOyYNtpmmjfsNbQaiGJrPxeSg9sdx0nRtfhI_KnWoACXLL53FIf1HjpcN8wX0cYQyOE60NLSI9iPY8mIlT4GkiFMT3ez7j2IbBBzR0D1MthC0B_fYlgYWwjcbqCOowSaH48KA" ); +} + /// A WebAuthn key: well-formed, and of a type the chain rejects when it recovers a key from a /// block signature, so a producer holding one could never sign. public_key_type webauthn_key() { @@ -407,6 +431,37 @@ BOOST_AUTO_TEST_CASE(legacy_format_admits_unsignable_keys) try { } } FC_LOG_AND_RETHROW() +BOOST_AUTO_TEST_CASE(absent_bls_payload_is_rejected_on_both_formats) try { + // The schedule path no longer screens key types, so a BLS key reaches it. Its shim holds the + // payload behind a shared_ptr that fc lets deserialize as absent, and every accessor -- the + // to_string a node performs when it logs a schedule, among them -- would dereference null. + // A one-key authority slips past proposer_policy::validate untouched, because the first + // insertion into the uniqueness set compares nothing. Deserialization has to reject it. + + // Authority format, as set_proposed_producers_ex(1) unpacks it. + vector authority_schedule = { + producer_authority{ "alice"_n, block_signing_authority_v0{ 1, {{ bls_key(), 1 }} } } + }; + const auto unpack_bytes = []( const std::vector& bytes, auto& out ) { + fc::datastream ds( bytes.data(), bytes.size() ); + fc::raw::unpack( ds, out ); + }; + + auto authority_bytes = strip_bls_payload( fc::raw::pack( authority_schedule ) ); + vector unpacked_authority; + BOOST_CHECK_THROW( unpack_bytes( authority_bytes, unpacked_authority ), fc::exception ); + + // Legacy format, as set_proposed_producers unpacks it. + vector legacy_schedule = {{ "alice"_n, bls_key() }}; + auto legacy_bytes = strip_bls_payload( fc::raw::pack( legacy_schedule ) ); + vector unpacked_legacy; + BOOST_CHECK_THROW( unpack_bytes( legacy_bytes, unpacked_legacy ), fc::exception ); + + // The unmodified bytes still round-trip, so the guard rejects only the absent payload. + BOOST_CHECK_NO_THROW( unpack_bytes( fc::raw::pack( authority_schedule ), unpacked_authority ) ); + BOOST_CHECK_NO_THROW( unpack_bytes( fc::raw::pack( legacy_schedule ), unpacked_legacy ) ); +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_CASE(unsignable_key_never_satisfies_authority) try { // Why admitting those keys costs nothing: the presented set is built by recovering keys from // the block's signatures, so a key that no signature yields is never in it, and the authority From 08b44d2c62377672e78924c40a8203247fafd09c Mon Sep 17 00:00:00 2001 From: kevin Heifner Date: Fri, 18 Sep 2026 18:06:06 -0500 Subject: [PATCH 4/5] bls: require G1 subgroup membership for finalizer keys A canonical on-curve check is not key validation. IETF's BLS KeyValidate is deserialize, reject the identity, and check r-order subgroup membership; only the first two were enforced. A small-order point such as affine (0, 2) is canonical, on the curve, and not the identity, yet its order is coprime to r, so it pairs to one against every G2 point and bls_pop_verify accepts it with an identity proof. Enforced in the two places that matter. fc::crypto::bls::public_key rejects a point outside the subgroup where the key is constructed, covering every path that deserializes BLS material. regfinkey rejects it as [r]P == identity before the key enters the table, because set_finalizers is reached from the schedule rebuild inside onblock, where throwing would stop every later rebuild. Key generation is unaffected: a public key is [sk]G, which is always in the subgroup, so no key these tools produce can fail either check. --- contracts/sysio.system/src/finalizer_key.cpp | 25 ++++++++++++++++++ contracts/sysio.system/sysio.system.wasm | Bin 205668 -> 205987 bytes contracts/tests/sysio.finalizer_key_tests.cpp | 9 +++++++ libraries/libfc/src/crypto/bls_public_key.cpp | 7 ++++- .../libfc/test/crypto/test_cypher_suites.cpp | 15 +++++++++++ 5 files changed, 55 insertions(+), 1 deletion(-) diff --git a/contracts/sysio.system/src/finalizer_key.cpp b/contracts/sysio.system/src/finalizer_key.cpp index 7fd3e9a0bd..e85eb475aa 100644 --- a/contracts/sysio.system/src/finalizer_key.cpp +++ b/contracts/sysio.system/src/finalizer_key.cpp @@ -46,6 +46,30 @@ namespace sysiosystem { return sysio::bls_g1_add( key, sysio::bls_g1{}, sum ) == 0; } + constexpr auto subgroup_key_error = "finalizer key is not in the r-order subgroup"; + + /// Order of the G1 subgroup, little-endian, as bls_g1_weighted_sum reads its scalars. + constexpr sysio::bls_scalar g1_subgroup_order = { + '\x01', '\x00', '\x00', '\x00', '\xff', '\xff', '\xff', '\xff', + '\xfe', '\x5b', '\xfe', '\xff', '\x02', '\xa4', '\xbd', '\x53', + '\x05', '\xd8', '\xa1', '\x09', '\x08', '\xd8', '\x39', '\x33', + '\x48', '\x7d', '\x9d', '\x29', '\x53', '\xa7', '\xed', '\x73' + }; + + // Whether `key` lies in the r-order subgroup, tested as [r]P == identity. + // + // Being on the curve is not enough. A small-order point such as affine (0, 2) -- on the + // curve, canonical, and not the identity -- pairs to one against any G2 point, because its + // order is coprime to r. bls_pop_verify therefore accepts it with an identity proof, and it + // would carry finality weight that anyone could cast, exactly as the identity key would. + bool is_in_g1_subgroup( const sysio::bls_g1& key ) { + const sysio::bls_g1 points[1] = { key }; + const sysio::bls_scalar scalars[1] = { g1_subgroup_order }; + sysio::bls_g1 product{}; + if( sysio::bls_g1_weighted_sum( points, scalars, 1, product ) != 0 ) return false; + return product == sysio::bls_g1{}; + } + } // namespace // Returns hash of finalizer_key in binary format @@ -180,6 +204,7 @@ namespace sysiosystem { // check can still be removed. check( !is_identity_g1(fin_key_g1), identity_key_error ); check( is_valid_g1(fin_key_g1), invalid_key_error ); + check( is_in_g1_subgroup(fin_key_g1), subgroup_key_error ); // Duplication check across all registered keys const auto idx = _finalizer_keys.get_index<"byfinkey"_n>(); diff --git a/contracts/sysio.system/sysio.system.wasm b/contracts/sysio.system/sysio.system.wasm index 1a01882850269fb1689600237ff218d5d29fccbb..ed60ed5726f95299844c79f62d51cde7a18a482d 100755 GIT binary patch delta 37986 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 delta 38331 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 diff --git a/contracts/tests/sysio.finalizer_key_tests.cpp b/contracts/tests/sysio.finalizer_key_tests.cpp index 176be5e4ba..de7fe29652 100644 --- a/contracts/tests/sysio.finalizer_key_tests.cpp +++ b/contracts/tests/sysio.finalizer_key_tests.cpp @@ -741,6 +741,15 @@ BOOST_FIXTURE_TEST_CASE(reject_identity_finalizer_key, finalizer_key_tester) try BOOST_REQUIRE_EQUAL( wasm_assert_msg("finalizer key is not a valid G1 point"), register_finalizer_key(alice, fc::crypto::bls::public_key::to_string(off_curve), identity_pop) ); + // Affine (0, 2) is canonical, on the curve, and not the identity, but its order is 3. That is + // coprime to r, so it pairs to one against any G2 point and the proof of possession above + // accepts it -- only a subgroup test rejects it. + fc::crypto::bls::public_key_data small_order{}; + small_order[48] = 2; + BOOST_REQUIRE_EQUAL( wasm_assert_msg("finalizer key is not in the r-order subgroup"), + register_finalizer_key(alice, fc::crypto::bls::public_key::to_string(small_order), + identity_pop) ); + // An honest key is unaffected. BOOST_REQUIRE_EQUAL( success(), register_finalizer_key(alice, key_pairs[0].pub_key, key_pairs[0].pop) ); } FC_LOG_AND_RETHROW() diff --git a/libraries/libfc/src/crypto/bls_public_key.cpp b/libraries/libfc/src/crypto/bls_public_key.cpp index d08ce4f7b3..c7561452b4 100644 --- a/libraries/libfc/src/crypto/bls_public_key.cpp +++ b/libraries/libfc/src/crypto/bls_public_key.cpp @@ -19,7 +19,12 @@ namespace fc::crypto::bls { std::span affine_non_montgomery_le_span = affine_non_montgomery_le; std::optional g1 = bls12_381::g1::fromAffineBytesLE(affine_non_montgomery_le_span, {.check_valid = true, .to_mont = true}); - FC_ASSERT(g1); + FC_ASSERT(g1, "BLS public key is not a canonical point on the curve"); + // check_valid establishes only that the point is canonical and on the curve. A small-order + // point such as affine (0, 2) satisfies both and still pairs to one against any G2 subgroup + // point, so a proof of possession cannot tell it from a real key -- it would carry finality + // weight that anyone could cast. Subgroup membership is a separate test. + FC_ASSERT(g1->inCorrectSubgroup(), "BLS public key is not in the r-order subgroup"); return *g1; } public_key::public_key(const public_key_data& affine_non_montgomery_le) diff --git a/libraries/libfc/test/crypto/test_cypher_suites.cpp b/libraries/libfc/test/crypto/test_cypher_suites.cpp index c5d1b8e96a..33ea3c5378 100644 --- a/libraries/libfc/test/crypto/test_cypher_suites.cpp +++ b/libraries/libfc/test/crypto/test_cypher_suites.cpp @@ -498,6 +498,21 @@ BOOST_AUTO_TEST_CASE(test_bls_absent_payload_is_rejected) try { BOOST_CHECK_EQUAL(real.to_string({}), round_tripped.to_string({})); } FC_LOG_AND_RETHROW(); +BOOST_AUTO_TEST_CASE(test_bls_small_order_point_is_rejected) try { + // Affine (0, 2): y^2 = 4 = x^3 + 4, so it is canonical and on the curve, and it is not the + // identity. Its order is 3 -- the tangent at (0, y) has slope 3x^2/2y = 0, so 2P = -P and + // 3P = O -- and 3 is coprime to r, so e(P, Q) = 1 for every G2 point Q. A proof of possession + // therefore cannot reject it, and it would carry finality weight anyone could cast. Only a + // subgroup test catches it. + fc::crypto::bls::public_key_data small_order{}; + small_order[48] = 2; // x = 0, y = 2, affine little-endian + + BOOST_CHECK_EXCEPTION(fc::crypto::bls::public_key{small_order}, fc::exception, + [](const fc::exception& e) { + return e.top_message().find("r-order subgroup") != std::string::npos; + }); +} FC_LOG_AND_RETHROW(); + BOOST_AUTO_TEST_CASE(test_sign_eth_recovery_roundtrip) try { auto key = fc::crypto::private_key::generate(private_key::key_type::em); auto pub = key.get_public_key(); From d05e18d22969cae97e6a2349e43c73e7de63b0f9 Mon Sep 17 00:00:00 2001 From: kevin Heifner Date: Fri, 18 Sep 2026 18:06:15 -0500 Subject: [PATCH 5/5] test: drive the schedule liveness regressions to publication set_producer_schedule only pushes the action. The policy is assembled, logged and diffed when the block is finalized, so the tests proved intrinsic acceptance and nothing about publication -- and that logging path is where an unusable key would have been dereferenced. Both now produce the block and assert the proposer policy diff. Adds the rank-walk regression this fix exists for. A scheduled producer re-registers with an undecodable R1 key, which registration admits because it is an R1 key and is not all zero, and the walk must still publish a schedule. It advances one block at a time and stops at the proposal: past that the policy activates, and a harness that signs every block itself cannot sign a slot belonging to a producer whose key nothing can sign with, which is the intended consequence of admitting the key rather than a chain failure. --- contracts/tests/emissions_tests.cpp | 47 +++++++++++++++++++++++++++ unittests/producer_schedule_tests.cpp | 10 ++++++ 2 files changed, 57 insertions(+) diff --git a/contracts/tests/emissions_tests.cpp b/contracts/tests/emissions_tests.cpp index c37d4fa788..2d96ee90f1 100644 --- a/contracts/tests/emissions_tests.cpp +++ b/contracts/tests/emissions_tests.cpp @@ -5764,6 +5764,53 @@ BOOST_FIXTURE_TEST_CASE( active_producers_scheduled, producer_eligibility_tester // A slashed producer (status SLASHED) is dropped from the schedule; the others // remain (4 eligible >= min_schedule_size). +// The rank walk runs inside onblock, so a schedule it cannot publish stops every future schedule +// update rather than just this one: update_ranked_producers writes last_producer_schedule_update +// before proposing, and a throw rolls that write back with the rest of the transaction. A producer +// holding a key nothing can sign with is the case that used to do it -- regproducer accepts the +// key, the walk proposes it, and set_proposed_producers rejected the whole schedule. +BOOST_FIXTURE_TEST_CASE( unsignable_producer_key_does_not_stall_rebuild, producer_eligibility_tester ) try { + auto names = setup_ranked_producers(5); + trigger_reschedule(); + for (const auto& p : names) { + BOOST_REQUIRE_MESSAGE(is_scheduled(p), "expected " << p.to_string() << " scheduled"); + } + + // One producer re-registers with a key no signature can ever yield: a well-formed compressed + // R1 point prefix over an x coordinate above the field prime. Registration admits it -- it is + // an R1 key and it is not all zero, which is as far as the contract can check without curve + // arithmetic -- so this is the case the contract guard cannot close. + fc::crypto::r1::public_key_data undecodable{}; + undecodable[0] = 0x02; + std::fill(undecodable.begin() + 1, undecodable.end(), '\xff'); + const fc::crypto::public_key unsignable_key{ + fc::crypto::public_key::storage_type{std::in_place_index<1>, + fc::crypto::r1::public_key_shim{undecodable}}}; + + const auto bad = names.back(); + BOOST_REQUIRE_EQUAL(success(), push_system_action(bad, "regproducer"_n, mvo() + ("producer", bad)("producer_key", unsignable_key)("url", "")("location", 0))); + + // A block carries a proposer policy diff only when set_proposed_producers accepted the + // schedule, so that diff is the rebuild succeeding. Advance one block at a time and stop as + // soon as it appears: continuing past it would activate a policy this producer cannot sign + // for, and the harness signs every block itself. + bool proposed = false; + size_t blocks = 0; + for (; blocks < 200 && !proposed; ++blocks) { + proposed = produce_block()->new_proposer_policy_diff.has_value(); + } + BOOST_REQUIRE_MESSAGE(proposed, + "the rank walk never published a schedule with an unsignable key registered"); + BOOST_TEST_MESSAGE("proposal landed after " << blocks << " blocks"); + + // One landing is proof enough: the stall was per block and permanent, so the row could not have + // moved at all unless onblock completed. Recovery beyond this point is not observable here -- + // the harness holds no private half for an unsignable key and cannot sign that producer's slot + // once the policy activates, which is the intended consequence of admitting the key rather than + // a chain failure. +} FC_LOG_AND_RETHROW() + BOOST_FIXTURE_TEST_CASE( slashed_producer_removed, producer_eligibility_tester ) try { auto names = setup_ranked_producers(5); trigger_reschedule(); diff --git a/unittests/producer_schedule_tests.cpp b/unittests/producer_schedule_tests.cpp index 0dc7a52b24..b449a6b614 100644 --- a/unittests/producer_schedule_tests.cpp +++ b/unittests/producer_schedule_tests.cpp @@ -435,6 +435,12 @@ BOOST_AUTO_TEST_CASE(schedule_admits_unsignable_keys) try { auto trace = chain.set_producer_schedule( sch ); BOOST_REQUIRE( !trace->except ); BOOST_REQUIRE( trace->receipt ); + + // Accepting the action is not the claim. The policy is assembled, logged and diffed when the + // block is finalized, which is where an unusable key would be dereferenced or rejected, so the + // block has to be produced and the proposal observed. + auto block = chain.produce_block(); + BOOST_REQUIRE( block->new_proposer_policy_diff ); } FC_LOG_AND_RETHROW() BOOST_AUTO_TEST_CASE(legacy_format_admits_unsignable_keys) try { @@ -451,6 +457,10 @@ BOOST_AUTO_TEST_CASE(legacy_format_admits_unsignable_keys) try { config::system_account_name, mvo()("schedule", sched) ); BOOST_REQUIRE( !trace->except ); BOOST_REQUIRE( trace->receipt ); + + // As above: the proposal is only assembled when the block is finalized. + auto block = chain.produce_block(); + BOOST_REQUIRE( block->new_proposer_policy_diff ); } } FC_LOG_AND_RETHROW()