Skip to content
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

Address validity invariant cleanups #1564

Merged
merged 4 commits into from Jan 24, 2023

Conversation

sanket1729
Copy link
Member

Fixes #1561.

Highlights:

  • Segwitv0 programs with lengths apart from 20 or 32 are invalid Address struct. Such Addresses are useless and we should not parse/create them.
  • Renamed is_standard to is_spend_standard.

@sanket1729 sanket1729 force-pushed the addr_fixes branch 3 times, most recently from 1cad205 to cc5ee00 Compare January 19, 2023 23:50
}

/// Returns a byte slice of the payload
pub fn as_bytes(&self) -> &[u8] {
match self {
Payload::ScriptHash(hash) => hash.as_ref(),
Payload::PubkeyHash(hash) => hash.as_ref(),
Payload::WitnessProgram { program, .. } => program,
Payload::WitnessProgram(prog) => prog.program(),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated: But this API is sketchy in how it is used. If anything I would expect the witness_version to get serialized in as_bytes method of the PayLoad.

Should open an issue about it.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I think I complained about this, IIRC this was introduced when implementing is_related_to_pubkey. The function shouldn't be pub.

Copy link
Collaborator

@Kixunil Kixunil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, I only want a reason to keep is_spend_standard and if yes, then better doc.

program: Vec<u8>,
},
WitnessProgram(WitnessProgram),
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I think this enum should've been [non_exhaustive]. Anyway, I like this approach.

}

/// Returns a byte slice of the payload
pub fn as_bytes(&self) -> &[u8] {
match self {
Payload::ScriptHash(hash) => hash.as_ref(),
Payload::PubkeyHash(hash) => hash.as_ref(),
Payload::WitnessProgram { program, .. } => program,
Payload::WitnessProgram(prog) => prog.program(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I think I complained about this, IIRC this was introduced when implementing is_related_to_pubkey. The function shouldn't be pub.

///
// Used internally by `new_v0_p2wpkh`, `new_v0_p2wsh`, `new_v1_p2tr`, and `new_v1_p2tr_tweaked`
// for convenience.
fn new_witness_program_unchecked(version: WitnessVersion, program: &[u8]) -> Self {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a debug_assert!?

///
/// SegWit addresses with unassigned witness versions or non-standard program sizes are
/// considered non-standard.
pub fn is_spend_standard(&self) -> bool { self.address_type().is_some() }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm correct that receiver statically knows the address is standard then I think the method is useless and we should just remove it. If there is any use then I want a warning in doc that this should not be called by the sender.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I'm correct that receiver statically knows the address is standard

This method is preciously how the receiver would know that the address they are receiving the money to is standard.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The receiver is the one who constructs the address so I would expect this to be handled by construction.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you suggesting two structures? SendingAddress and RecievingAddress. Or perhaps yet another phantom data type in Address itself. We want to support sending to unknown addresses, but don't want to receive it ourselves. I don't see how we can handle this under the current Address construction.

That being said, I don't see any utility for wallet developers who want to use this method. It maybe useful enthusiasts who scan the blockchain for non-standard spends or for block explorers. I recall somebody did a taproot spend before taproot was activated to fork off UASF nodes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you suggesting two structures?

Not really. If I write an application that uses some of the common ways of receiving then I write the code such that Address is always valid and don't need to check it. Would would I even do if it's wrong?

That being said, I don't see any utility for wallet developers who want to use this method.

Yeah, I believe we both don't for the same reason.

It maybe useful enthusiasts who scan the blockchain for non-standard spends or for block explorers.

Shouldn't the scripts be checked instead of addresses?

I recall somebody did a taproot spend before taproot was activated to fork off UASF nodes.

OT but sounds like that person was confused? I don't see how it could fork off UASF nodes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really. If I write an application that uses some of the common ways of receiving then I write the code such that Address is always valid and don't need to check it.

This is why it's a utility method and not something that we're gating constructors on or enforcing in the type system.

Would would I even do if it's wrong?

Lol assert probably. But I think it's most common to hit cases like this during transition periods...like, maybe your wallet would let you generate a Taproot address before Taproot was active, but would put a bunch of warnings on it or otherwise try to prevent you from using it until it became standard.

Shouldn't the scripts be checked instead of addresses?

This is an interesting thought. Maybe users who want to do this should need to first convert the address to a scriptpubkey, and then do a standardness check on that. But I think such an API change should wait for script tagging.

Kixunil
Kixunil previously approved these changes Jan 20, 2023
Copy link
Collaborator

@Kixunil Kixunil left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 30d251e

/// Returns a byte slice of the payload
pub fn as_bytes(&self) -> &[u8] {
/// Returns a byte slice of the inner program of the payload. Incase the payload
/// ia a script hash or pubkey hash, the a reference to the hash is returned.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nits: "is", "a reference"

20 => Some(AddressType::P2wpkh),
32 => Some(AddressType::P2wsh),
_ => None,
_ => None, // Technically unreachable, but we don't want to panic when not required.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silently ignoring bugs is not great either, debug_assert would be nice.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. TBH I'd even prefer an unconditional unreachable! panic.

/// Checks whether or not the address is following Bitcoin standardness rules when
/// *spending* from this address. This method should *NOT* called by senders. For forward
/// compatibility, the senders must send to any [`Address`]. Receivers can use this method to
/// check whether or not they can spend from this address.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like having a warning in summary but not sure if this long is appropriate. Maybe something like "Checks whether the address obeys Bitcoin standardness rules when spending from this address. *NOT to be called by senders!" And full explanation in description. Anyway, I won't block the PR because of it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed

Copy link
Member

@tcharding tcharding left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few rustdoc comment fixes.

Comment on lines 540 to 541
/// Returns a byte slice of the inner program of the payload. Incase the payload
/// ia a script hash or pubkey hash, the a reference to the hash is returned.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Returns a byte slice of the inner program of the payload. Incase the payload
/// ia a script hash or pubkey hash, the a reference to the hash is returned.
/// Returns a byte slice of the inner program of the payload. If the payload
/// is a script hash or pubkey hash, a reference to the hash is returned.

Comment on lines 1157 to 1162
/// Internal method to a witness scriptPubkey with a given version and program.
/// Does not do any checks on version or program.
///
// Used internally by `new_v0_p2wpkh`, `new_v0_p2wsh`, `new_v1_p2tr`, and `new_v1_p2tr_tweaked`
// for convenience.
fn new_witness_program_unchecked(version: WitnessVersion, program: &[u8]) -> Self {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Internal method to a witness scriptPubkey with a given version and program.
/// Does not do any checks on version or program.
///
// Used internally by `new_v0_p2wpkh`, `new_v0_p2wsh`, `new_v1_p2tr`, and `new_v1_p2tr_tweaked`
// for convenience.
fn new_witness_program_unchecked(version: WitnessVersion, program: &[u8]) -> Self {
/// Generates P2WSH-type of scriptPubkey with a given [`WitnessVersion`] and the program bytes.
///
/// Convenience method used by `new_v0_p2wpkh`, `new_v0_p2wsh`, `new_v1_p2tr`, and
/// `new_v1_p2tr_tweaked`.
fn new_witness_program_unchecked(version: WitnessVersion, program: &[u8]) -> Self {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed. But also added the Does not do any checks on version or program which I feel is relevant to the method.

}

/// Generates P2WSH-type of scriptPubkey with a given hash of the redeem script.
pub fn new_witness_program(version: WitnessVersion, program: &[u8]) -> Self {
pub fn new_witness_program(witness_program: &WitnessProgram) -> Self {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub fn new_witness_program(witness_program: &WitnessProgram) -> Self {
/// Generates P2WSH-type of scriptPubkey with a given [`WitnessProgram`].
pub fn new_witness_program(witness_program: &WitnessProgram) -> Self {

apoelstra
apoelstra previously approved these changes Jan 23, 2023
Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 30d251e

@apoelstra
Copy link
Member

@sanket1729 should I merge this or do you want to address @tcharding's nits?

@sanket1729
Copy link
Member Author

@apoelstra, force-pushing in a few mins

@sanket1729
Copy link
Member Author

sanket1729 commented Jan 23, 2023

Ready for review again.(after CI clears)

Addresses with Segwitv0 not having len 20/32 are invalid and cannot be
constructed. Also cleans up a API bug in
ScriptBuf::new_witness_prog(ver, prog) allowing prog of invalid lenghts.
It is unlikely that we will see another segwit type fork. But we never
know, best to mark Payload as [non-exhaustive]
@sanket1729
Copy link
Member Author

I have made another force push that adds details in the summary section of the doc. Looks really neat, I will use it more often in the future.

Copy link
Member

@apoelstra apoelstra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 44d3ec4

Copy link
Member

@tcharding tcharding left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 44d3ec4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Segwit Address struct allows constructing invalid Address
4 participants