New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Witness::p2wpkh
constructor
#2084
Conversation
push_p2wpkh
function on Witness
bitcoin/src/blockdata/witness.rs
Outdated
if pubkey.compressed { | ||
self.push_slice(&pubkey.inner.serialize()) | ||
} else { | ||
self.push_slice(&pubkey.inner.serialize_uncompressed()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Segwit v0 forbids uncompressed keys for use in signature operations.
This is a big enough foot gun that I think we should somehow prevent it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh boy, my bad, thanks man. I'll fix it up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I whipped this up in case we want to allow the footgun but at least warn the caller.
This could probably be brushed up and made prettier with a nice error variant name containing the closure with a name that makes it clear why it's a footgun.
But tbh it's probably just better to fail on uncompressed.
pub fn push_p2wpkh<'a>(
&'a mut self,
signature: &'a ecdsa::SerializedSignature,
hash_type: EcdsaSighashType,
pubkey: &'a PublicKey,
) -> Result<(), impl FnMut() + 'a> {
let mut closure = move || {
self.push_bitcoin_signature(signature, hash_type);
if pubkey.compressed {
self.push_slice(&pubkey.inner.serialize())
} else {
self.push_slice(&pubkey.inner.serialize_uncompressed())
}
};
if pubkey.compressed {
closure();
Ok(())
} else {
Err(closure)
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or didn't we add a CompressedPublicKey
type a while back (I forget)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a UncompressPubkeyError
type, and the following check
if !pubkey.compressed {
return Err(UncompressedPubkeyError);
}
I would have thought this sort of check would exist in other places but my quick greping couldn't find one. The error type is currently defined in witness
but might be better in crypto::key
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are trying to "hide" the legacy key stuff but so far haven't come up with an elegant solution.
5dac089
to
e8786c7
Compare
bitcoin/src/blockdata/witness.rs
Outdated
/// Pushes a DER-encoded ECDSA signature with a signature hash type as a new element on the | ||
/// witness, followed by the serialized public key. | ||
/// | ||
/// Also useful for spending a P2SH-P2WPKH output. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should add Errors section to docs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, thanks!
e8786c7
to
6b8fcdb
Compare
Let's just take an |
6b8fcdb looks good otherwise. |
6b8fcdb
to
1b416a8
Compare
With fresh eyes it seems much better to just take a @stevenroose you're welcome :) |
1b416a8
to
60882e8
Compare
LGTM, though I'm a little worried that the API might change again if we replace Though maybe not, I'm warming up to the idea of just re-exporting the secp pubkey types.. we'll see. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 60882e8
Well that change will trigger a bunch of API changes, right? So one more shouldn't hurt folk too much :) |
bitcoin/src/blockdata/witness.rs
Outdated
@@ -523,6 +546,21 @@ impl From<Vec<&[u8]>> for Witness { | |||
fn from(vec: Vec<&[u8]>) -> Self { Witness::from_slice(&vec) } | |||
} | |||
|
|||
/// Tried to use a legacy uncompressed pubkey in a segwit v0 transaction. | |||
#[derive(Clone, Debug, PartialEq, Eq)] | |||
pub struct UncompressedPubkeyError; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like this error is not actually used anywhere yet. Am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ouch, you are correct, it was used in an old version. Thanks, will fix.
60882e8
to
4f0daa1
Compare
Removed the stale error type. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 4f0daa1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tcharding, I believe this simplifies the code significantly. We already have a ecdsa::Signature
inside bitcoin crate.
/// - `hash_type` was used when creating the sighash signed to create `signature`.
pub fn push_p2wpkh(
&mut self,
- signature: &ecdsa::Signature,
- hash_type: EcdsaSighashType,
+ signature: &crate::ecdsa::Signature,
pubkey: &secp256k1::PublicKey,
) {
- self.push_bitcoin_signature(signature, hash_type);
+ self.push_bitcoin_signature(signature);
self.push_slice(&pubkey.serialize());
}
@@ -345,15 +344,9 @@ impl Witness {
/// this `signature` signed.
pub fn push_bitcoin_signature(
&mut self,
- signature: &ecdsa::Signature,
- hash_type: EcdsaSighashType,
+ signature: &crate::ecdsa::Signature,
) {
- let ser = signature.serialize_der();
- // Note that a maximal length ECDSA signature is 72 bytes, plus the sighash type makes 73
- let mut sig = [0; 73];
- sig[..ser.len()].copy_from_slice(&ser);
- sig[ser.len()] = hash_type as u8;
- self.push_slice(&sig[..ser.len() + 1]);
+ self.push_slice(&signature.serialize());
}
You are correct, I did know of this type but I had a reason for not using it at the time. I can't remember it right now so this message is just a response to say I'll have to come back and see if I can remember what I was thinking. Thanks for the review! |
Using your suggestion I played around with the signing code from the workshop and we get // Update the witness stack.
let signature = bitcoin::ecdsa::Signature { sig, hash_ty: EcdsaSighashType::All };
let pk = sk.public_key(&secp);
sighasher.witness_mut(input_index).unwrap().push_p2wpkh(&signature, &pk); Which seems reasonable, could Re And I think we should add to this PR /// Pushes a taproot signature, as required to do a p2tr key path spend, onto the witness, requires an allocation.
pub fn push_p2tr_key_path(&mut self, sig: &taproot::Signature) (better name?) |
The `Witness::push_bitcoin_signature` method is old and a bit stale. Bitcoin has taproot signatures now so the name is stale, also we have the `crate::ecdsa::Signature` type that holds the secp sig and the hash type so we can use that instead of having two separate parameters. Add a new, up to date, `Witness::push_ecdsa_signature` function and deprecate the `push_bitcoin_signature` one.
4f0daa1
to
df758fe
Compare
Re-did the PR after @sanket1729's review suggestion. |
Couldi/should we make this a constructor? So you'd write
? |
Oh, we could go further than that and have one constructor, used for constructing the witness of a single input spend for each of the output types. That would be super useful for documentation also [0].
We could push this even further and have methods on /// The scriptSig for sepnding a p2wpkh output is empty.
pub fn p2wpkh_script_sig() -> ScriptBuf {
ScriptBuff::EMPTY
}
/// The scriptSig for spending a p2pkh output.
pub fn p2pkh_script_sig(sig: ecdsa::Signature, pk: PublicKey) -> ScriptBuf {
ScriptBuf(v) // cat them together correctly into a vector
} Along with ones for all the other output types it would be super user friendly for newer bitcoin devs (and ones that to double check their memories). [0] Currently I have a table that has each output type and what goes in scriptPubkey, scriptSig, and witness. Having these functions would mean I could stop using that table and just look at the code. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK df758fe
Making this table implicitly obvious is a great goal but it's not clear how we'd do it and I think it's out of scope for this PR. For this PR, I simply observe that you never want to push a pubkey/signature onto an existing witness, and if you do, you're not doing a p2wpkh output. For p2wpkh, the pubkey/signature pair is the entire witness, so I think it should be a If you disagree, or agree but think it'll be a bigger project, then I'll just ACK this. |
Definitely out of scope :)
oooo, I had it in mind that the witness was the combination of the witnesses for all the inputs so we would do multiple pushes, but the cache handles the combination for us. My bad. Will change to a constructor as suggested - legend. |
df758fe
to
764ac44
Compare
Please note, now the two patches in this PR are totally unrelated and should really be two separate PRs. |
push_p2wpkh
function on Witness
Witness::p2wpkh
constructor
A constructor should not take a |
lol, face palm - its my first week of Rust. |
In order to create the witness to spend p2wpkh output one must push the signature and the pubkey, we should have a function for this.
764ac44
to
5901d35
Compare
I wonder if it would be useful for maintenance if we had the exact same code in the cookbook in
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 5901d35
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK 5901d35
In order to create the witness to spend p2wpkh output one must create a
Witness
that includes the signature and the pubkey, we should have a function for this.Notes
The PR originally added a
push_p2wphk
method, this is now instead a constrcutorWitness:p2wpkh
(after review discussion below).push_bitcoin_signature
to take anecdsa::Sigtnture
instead of anecdsa::SerializedSignature
secp256k1::PublicKey
removing the need for an error path (discussed below).