Repository navigation
Conversation
KyrylR
force-pushed
the
feature/pset-to-txout-ecdh-nonce
branch
from
November 4, 2026 14:03
17d8a4c to
db70ff5
Compare
KyrylR
commented
Nov 4, 2026
Comment on lines
-250
to
+254
| nonce: if self.is_partially_blinded() { | ||
| self.ecdh_pubkey | ||
| .map(|pk| confidential::Nonce::from(pk.inner)) | ||
| } else { | ||
| self.blinding_key | ||
| .map(|pk| confidential::Nonce::from(pk.inner)) | ||
| } | ||
| .unwrap_or_default(), | ||
| nonce: self | ||
| .ecdh_pubkey | ||
| .or(self.blinding_key) | ||
| .map(|pk| confidential::Nonce::from(pk.inner)) | ||
| .unwrap_or_default(), |
Contributor
Author
There was a problem hiding this comment.
I am not sure why previous code had this if statement
After I added one more condition it started looking too complicated, so I decided to simplify it and it turned out that simple rule "use the ECDH pubkey if there is one, otherwise the receiver's blinding key" can be applied here
Contributor
Author
There was a problem hiding this comment.
the ECDH pubkey becomes the nonce whenever it's set in elements core
PartiallySignedTransaction::GetUnsignedTx (
https://github.com/ElementsProject/elements/blob/2ac51cac13876bf14d3d709b07535b1868eee896/src/psbt.cpp#L183-L185) does:
if (output.m_ecdh_pubkey.IsValid() && !force_unblinded) {
txout.nNonce.vchCommitment.insert(..., output.m_ecdh_pubkey.begin(), output.m_ecdh_pubkey.end());
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR of changes
Output::from_txouton any confidentialTxOutstores the nonce asecdh_pubkeywithout a blinding key.So
from_txout(x).to_txout()dropps the nonce on the currentmaster, even for fully blinded outputs.Summary by AI
Background
Each output in an Elements transaction has a nonce field. For a blinded output, the nonce holds the sender's
ephemeral ECDH pubkey. The receiver combines it with their own blinding private key to get a shared secret. They
then use that secret to "rewind" the rangeproof and recover the value and blinding factors. With a null nonce, the
receiver can't unblind the output.
A PSET
Outputstores two related keys:blinding_key: the receiver's public blinding key. It is set before blinding and marks the output as "to beblinded".
ecdh_pubkey: the ephemeral key, set by the blinder.What was wrong
to_txout(src/pset/map/output.rs:254) picked the nonce like this:is_partially_blinded()only returns true when ablinding_keyis present. So for an output with anecdh_pubkeybutno
blinding_key, it took theelsebranch and returned the absent blinding key, giving a null nonce.That happens in two cases:
Output::from_txoutreads a confidentialTxOut's nonce intoecdh_pubkeyand sets noblinding_key. Converting it back withto_txout()silently dropped the nonce.blind_last_with_modesremovesblinding_keyfrom value-only outputs so Elements Core accepts the PSET. Without this fix, such an output'sto_txout()would have a null nonce. Meanwhileextract_tx(), which already readsecdh_pubkeydirectly, would give the correct one. The two functions would disagree about the same output.The fix
Only the third row changes. That matches
extract_txand Elements Core'sGetUnsignedTx, both of which use the ECDHpubkey whenever it's set.
The one internal caller,
blind_non_last, only converts outputs in the first row, so its behaviour is the same.Tests
to_txout_nonce(output.rs:648) checks all four rows.The one visible change to the public API is the third table row,
Output::to_txouton akey-less output with an ECDH pubkey, and the commit message says so.