sign: use fresh randomness as BIP340 auxiliary data #36409

pull fametrano wants to merge 1 commits into bitcoin:master from fametrano:sign-bip340-aux-rand changing 4 files +17 −4
  1. fametrano commented at 7:04 AM on October 2, 2026: contributor

    Bitcoin Core signs Taproot inputs with all-zero BIP340 auxiliary data. BIP340 recommends fresh randomness there, as protection against fault injection and side-channel attacks. I made CreateSchnorrSig pass GetRandHash() instead, unless SignOptions::aux_rand sets a fixed value.

    In the unit tests, only script_tests/bip341_keypath_test_vectors depended on the zero value: the BIP341 vectors were made with zero auxiliary data. It now builds the signature creator with aux_rand set to zero and compares its signature byte for byte with the vectors, then signs twice with default options and checks that the two signatures differ. If CreateSchnorrSig ignores aux_rand, the vector check fails for all 7 key path inputs. The signing-related functional tests pass unchanged. The script_sign fuzz target can now reach GetRandHash(), so it seeds the RNG like the other signing targets.

    I chose GetRandHash() over GetStrongRandBytes(). Both draw from the same RNG, which is seeded with OS entropy at startup, so the output is unpredictable; GetRandHash() skips the OS call GetStrongRandBytes() makes on each draw. BIP340 says any non-repeating value increases protection against fault injection, and that no security property other than side-channel resistance depends on the quality of this randomness. In unit tests GetRandHash() follows the test seed, so the tests stay reproducible; GetStrongRandBytes() does not.

    Default signing is no longer deterministic. SignOptions::aux_rand sets a fixed auxiliary value where one is needed, as in the vector test; Yudis-bit suggested it.

    Fixes #31883.

    Made with my usual tools: a computer, the Internet and an LLM. The mistakes, as usual, are all mine.

  2. DrahtBot commented at 7:04 AM on October 2, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    External sites

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    ACK Yudis-bit

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #36122 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/36122.svg"></sub> (BIP460: CISA for Taproot key path spends by fjahr)
    • #32857 <sub><img src="https://drahtbot.space/ack_count/bitcoin/bitcoin/32857.svg"></sub> (wallet: allow skipping script paths by Sjors)

    If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  3. DrahtBot added the label CI failed on Oct 2, 2026
  4. Yudis-bit commented at 4:33 PM on October 3, 2026: none

    Tested on be2bc19726.

    I think GetRandHash() is definitely the right choice here. For BIP340, nonce generation already binds the secret key with the message via tagged hashes, so auxiliary randomness is purely for fault injection and side-channel mitigation rather than baseline entropy. Skipping the GetStrongRandBytes() OS syscall keeps transaction signing fast, and crucially keeps fuzzing and unit tests deterministic under SeedRandomStateForTest(SeedRand::ZEROS).

    Regarding your question about SignOptions: I'd definitely lean towards adding std::optional<uint256> aux_rand there.

    Notice what happens in src/test/script_tests.cpp: because MutableTransactionSignatureCreator doesn't accept an aux value, the test has to bypass the creator entirely and call key.SignSchnorr(...) directly. But the whole point of that test case in script_tests.cpp was to test MutableTransactionSignatureCreator's end-to-end handling of the BIP-341 witness generation against the official vectors.

    If SignOptions has:

    struct SignOptions {
        int sighash_type{SIGHASH_DEFAULT};
        std::optional<uint256> aux_rand{std::nullopt};
    };
    

    and in MutableTransactionSignatureCreator::CreateSchnorrSig:

    const uint256 aux_rand = m_options.aux_rand.value_or(GetRandHash());
    if (!key.SignSchnorr(*hash, sig, merkle_root, aux_rand)) return false;
    

    Then default transaction signing keeps fresh randomness, but script_tests can still test the creator against the exact BIP-341 vector:

    MutableTransactionSignatureCreator creator_zero_aux(tx, txinpos, utxos[txinpos].nValue, &txdata, {.sighash_type = hashtype, .aux_rand = uint256{}});
    std::vector<unsigned char> zero_aux_sig;
    BOOST_CHECK(creator_zero_aux.CreateSchnorrSig(provider, zero_aux_sig, pubkey, nullptr, &merkle_root, SigVersion::TAPROOT));
    BOOST_CHECK_EQUAL(HexStr(zero_aux_sig), expected);
    

    Verified this locally with bip341_wallet_vectors.json. All 7 keypath test vector inputs reproduce the expected witness signature directly through the creator, alongside the two differing non-deterministic signatures under default options.

  5. sign: use fresh randomness as BIP340 auxiliary data
    MutableTransactionSignatureCreator::CreateSchnorrSig passed all-zero
    auxiliary data to SignSchnorr. BIP340 recommends fresh randomness there,
    for protection against fault injection and side-channel attacks. Pass
    GetRandHash() instead, unless the new SignOptions::aux_rand sets a
    fixed value.
    
    The BIP341 key path vectors were generated with zero auxiliary data.
    The test now sets aux_rand to zero to compare the creator's signature
    with the vectors, and checks that two signatures made with the default
    options differ.
    
    The script_sign fuzz target can now reach GetRandHash() through
    SignTransaction, so it seeds the global RNG like the other signing
    targets.
    1f274eae23
  6. fametrano force-pushed on Oct 3, 2026
  7. fametrano commented at 6:31 PM on October 3, 2026: contributor

    Done in 1f274eae23, as you suggested: SignOptions::aux_rand, defaulting to GetRandHash(). The vector test goes through the creator again with aux_rand = uint256{}, and two default-options signatures are checked to differ. With aux_rand ignored, the vector check fails for all 7 inputs. PR description updated. Thanks.

  8. DrahtBot removed the label CI failed on Oct 3, 2026
  9. Yudis-bit commented at 11:23 PM on October 3, 2026: none

    ACK 1f274eae23

    Re-reviewed the commit. Cleanly addresses the test vector coupling by keeping SignOptions::aux_rand optional while defaulting to GetRandHash(). Verified that bip341_keypath_test_vectors reproduces the BIP-341 witness with zero aux and confirms divergence under default options, with script_sign fuzzing seeded deterministically.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-10-11 09:51 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me