rust-bitcoin / rust-bitcoin/rust-miniscript

`master_fingerprint()` docs promise `0x00000000` for no-origin keys, but PSBT helpers export synthetic fingerprints instead

Open
#998 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
426
Forks
200
Avg merge
7d 17h
Merged PRs (30d)
8

Description

Summary

The public docs for master_fingerprint() say no-origin descriptor keys should use:

0x00000000 if none

However, for raw descriptor public keys with no origin information, the current implementation computes a nonzero synthetic fingerprint from the key material. The public PSBT descriptor-update helpers then serialize that synthetic (fingerprint, empty_path) pair into PSBT origin metadata maps:

  • bip32_derivation
  • tap_key_origins

The first minimal repro below shows the non-Taproot bip32_derivation path. A Taproot companion variant further below shows the same synthetic-origin behavior in tap_key_origins.

Verified locally on commit:

6131095c8ee99ba9a91ee004b1d387dd8418766a

Doc / implementation contradiction

The contradiction appears to be literal.

The public docs for master_fingerprint() say the fingerprint should be:

0x00000000 if none

That wording appears on both the DescriptorPublicKey and DefiniteDescriptorKey wrappers.

But for raw single keys without origin information, the current implementation instead derives a nonzero fingerprint from the pubkey bytes and then exports that value with an empty derivation path.

Minimal repro

use std::str::FromStr;

use miniscript::bitcoin::psbt;
use miniscript::descriptor::DefiniteDescriptorKey;
use miniscript::{Descriptor, PsbtInputExt};

fn main() {
    let desc = Descriptor::<DefiniteDescriptorKey>::from_str(
        "wpkh(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)",
    )
    .unwrap();

    let mut input = psbt::Input::default();
    input.update_with_descriptor_unchecked(&desc).unwrap();

    println!("{:?}", input.bip32_derivation);
}

Taproot companion variant

Also locally confirmed:

use std::str::FromStr;

use miniscript::bitcoin::psbt;
use miniscript::descriptor::DefiniteDescriptorKey;
use miniscript::{Descriptor, PsbtOutputExt};

fn main() {
    let desc = Descriptor::<DefiniteDescriptorKey>::from_str(
        "tr(79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)",
    )
    .unwrap();

    let mut output = psbt::Output::default();
    output.update_with_descriptor_unchecked(&desc).unwrap();

    println!("{:?}", output.tap_key_origins);
}

Observed behavior

Locally:

  • the no-origin case does not use the documented 00000000 if none behavior
  • bip32_derivation is populated even though the descriptor key had no origin
  • the exported fingerprint is synthetic and nonzero rather than the documented 00000000 if none

Concrete local output from the existing repro family:

wpkh_bip32_len=1
wpkh_fp=751e76e8
wpkh_path= (empty path)

I also confirmed the same family on Taproot helpers:

tr_tap_key_origins_len=1
tr_fp=f678d9b7
tr_path= (empty path)

So both non-Taproot and Taproot PSBT helper paths export nonzero synthetic fingerprints with an empty derivation path for raw no-origin descriptor keys.

Why this seems to happen

The current raw-key path appears to:

  1. treat a raw DescriptorPublicKey::Single as a definite key
  2. derive a synthetic master_fingerprint() from the raw pubkey bytes
  3. pair it with an empty derivation path
  4. emit that pair into PSBT key-origin metadata maps

Why this matters

Applications using the public PSBT update helpers can emit invented origin/provenance metadata for raw descriptor keys whose descriptor supplied no origin at all.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing DescriptorPublicKey and DefiniteDescriptorKey::master_fingerprint, then follow update_with_descriptor_unchecked for both PsbtInputExt and PsbtOutputExt. Run the supplied non-Taproot and Taproot reproductions first; done means raw keys without origin information no longer export synthetic fingerprint and empty-path pairs in bip32_derivation or tap_key_origins, while the documented no-origin behavior is preserved.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
cryptography
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.