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

stdaddr: Introduce package for standard addrs. #2610

Merged
merged 13 commits into from Mar 12, 2021

Conversation

davecgh
Copy link
Member

@davecgh davecgh commented Mar 5, 2021

The current code for handling standard addresses implemented in dcrutil was written many years ago prior a wide variety of changes and several new features added by Decred. As a result, it entirely lacks support for some features and supports others in a roundabout and non-intuitive way.

Specifically, it does not support or provide a clean path to enable support for different script versions and the way they are handled in stake transactions is entirely non-intuitive.

Further, back when the original address code was implemented, it was necessary to implement script creation in the txscript package which led to the current design of providing methods such as txscript.PayTo{AddrScript,SStx,SStxChange}, and others, which need to type assert the specific concrete types of addresses in order to produce the necessary scripts. This, unfortunately, effectively negates the use of an interface to support generic addresses because it means callers, such as dcrwallet, are not able to implement their own types without somewhat invisibly breaking the script creation.

Finally, the aforementioned blending of the address code into txscript has led to confusion regarding what is considered standard and what is considered consensus which has tripped up several contributors over the years.

This aims to resolve all of the aforementioned issues by introducing a new package named stdaddr which entirely reworks the way addresses are handled.

It is split up into a series of commits to help ease the review process such that the initial generic infrastructure is introduced first, followed by the overall version 0 address decoding logic, then each supported version 0 address, followed by an example, and finally what I hope is a fairly complete README.md that describes the architecture. Comprehensive tests are included.

For the time being, the package is introduced into the internal staging area for initial review.

The following provides an overview of some of the key features of the new design:

  • Supports versioned addresses
  • Produces scripts directly via methods implemented on underlying types
  • Provides direct support for creation of the scripts necessary for the staking system
  • Uses a capabilities-based approach via interfaces so callers can cleanly and generically determine under what circumstances addresses can be used
  • Allows callers to create their own concrete address types without worrying about breaking the existing ones
  • Clearly denotes that addresses are a standardized construction that must not be used directly in consensus code

Finally, although the primary goal here is not optimization, this also significantly reduces the execution time and number of allocations needed to produce the payment scripts as can be seen in the following benchmarks:

Name old time/op new time/op delta
p2sh 207ns ± 1% 39ns ± 1% -81.24% (p=0.008 n=5+5)
p2pkh-ecdsa-secp256k1 219ns ± 2% 45ns ± 8% -79.44% (p=0.008 n=5+5)
p2pkh-ed25519 235ns ± 2% 42ns ± 4% -82.30% (p=0.008 n=5+5)
p2pkh-schnorr-secp256k1 232ns ± 1% 42ns ± 3% -81.99% (p=0.008 n=5+5)
p2pk-ecdsa-secp256k1 276ns ± 1% 47ns ± 2% -83.10% (p=0.008 n=5+5)
p2pk-ed25519 665ns ± 6% 47ns ± 1% -92.98% (p=0.008 n=5+5)
p2pk-schnorr-secp256k1 283ns ± 1% 47ns ± 4% -83.47% (p=0.008 n=5+5)
Name old alloc/op new alloc/op delta
p2sh 512B ± 0% 24B ± 0% -95.31% (p=0.008 n=5+5)
p2pkh-ecdsa-secp256k1 512B ± 0% 32B ± 0% -93.75% (p=0.008 n=5+5)
p2pkh-ed25519 512B ± 0% 32B ± 0% -93.75% (p=0.008 n=5+5)
p2pkh-schnorr-secp256k1 512B ± 0% 32B ± 0% -93.75% (p=0.008 n=5+5)
p2pk-ecdsa-secp256k1 560B ± 0% 48B ± 0% -91.43% (p=0.008 n=5+5)
p2pk-ed25519 640B ± 0% 48B ± 0% -92.50% (p=0.008 n=5+5)
p2pk-schnorr-secp256k1 560B ± 0% 48B ± 0% -91.43% (p=0.008 n=5+5)

@davecgh davecgh added this to the 1.7.0 milestone Mar 5, 2021
@jrick
Copy link
Member

jrick commented Mar 5, 2021

This looks great.

May i suggest naming the package stdaddr? A little shorter and unlikely to collide with any variable names because those would probably read stdAddr when following the usual camel case style.

@davecgh
Copy link
Member Author

davecgh commented Mar 5, 2021

This looks great.

May i suggest naming the package stdaddr? A little shorter and unlikely to collide with any variable names because those would probably read stdAddr when following the usual camel case style.

That sounds good to me. We also need to decide on where to put it. I just put it under internal/staging for now, but it will need to be exposed.

internal/staging/stdaddress/README.md Outdated Show resolved Hide resolved
internal/staging/stdaddress/README.md Outdated Show resolved Hide resolved
internal/staging/stdaddress/README.md Outdated Show resolved Hide resolved
internal/staging/stdaddress/address.go Outdated Show resolved Hide resolved
internal/staging/stdaddress/address.go Outdated Show resolved Hide resolved
internal/staging/stdaddress/address.go Outdated Show resolved Hide resolved
internal/staging/stdaddress/address.go Outdated Show resolved Hide resolved
Copy link
Contributor

@matthawkins90 matthawkins90 left a comment

Choose a reason for hiding this comment

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

I left two in-line comments, and then I have these three notes from my Readme review. I realize I'm not exactly the technical target audience for this package, but I'm trying to follow along for my own understanding. Here are my comments:

  • You don't really define script in the Readme. I realize it's not really in the scope of this readme, but you asked for advice for how to make this primer more clear. Perhaps just some quick definitions or references would be helpful. I'm nominally aware of Bitcoin's Script, and aware that it uses opcodes for super concise and byte-efficient commands. That's the extent of my knowledge.
    • Is Decred's script the same as Bitcoin's, just with different opcodes?
    • You don't really talk about opcodes until Address Use in the Staking System
    • You use script instead of Script. The capitalization seems like it might be more clear that you're referring to the Script system.
  • I don't know what Hash160 is, even after reading the blurb about RIPEMD-160 hashes. It would be nice to link to a reference.
  • I don't know what you mean by Version 0 addresses. I don't know much about address versioning, unless you're talking about things like Segwit or Taproot.

Also, in the PR commit message, Paragraph 2 has a typo:
"Specifically, it does not support or provide a clean path to enable support for different script versions and it the way they are handled in stake transactions is entirely non-intuitive."

Otherwise, it reads very well, and the motivation and improvement is very clear! Thank you.

internal/staging/stdaddress/README.md Outdated Show resolved Hide resolved
internal/staging/stdaddress/README.md Outdated Show resolved Hide resolved
@davecgh davecgh force-pushed the stdaddress_introduce branch 2 times, most recently from 5ed1fc4 to 06c4018 Compare March 6, 2021 03:04
@davecgh
Copy link
Member Author

davecgh commented Mar 6, 2021

I've updated this to rename the package to the recommended stdaddr and updated all references in the code, commits, and documentation accordingly.

@davecgh davecgh changed the title stdaddress: Introduce package for standard addrs. stdaddr: Introduce package for standard addrs. Mar 6, 2021
@davecgh
Copy link
Member Author

davecgh commented Mar 6, 2021

@matthawkins90 Thanks for the feedback. It's always good to get a fresh pair of eyes!

I'll comment inline, as your points are all valid, but, I did want to point out that, as you note, this is really aimed at developers who already have some background knowledge about the underlying concepts. If this were in a developer guide, I would put this type of information after sections which have already described how the transaction scripting language works which includes opcodes, as well as the notion of standard scripts.

You don't really define script in the Readme...

I'll add a link to the txscript package which should help clarify this I think.

Is Decred's script the same as Bitcoin's, just with different opcodes?

Yes.

You don't really talk about opcodes until Address Use in the Staking System

Right. The underlying transaction scripts are really just a collection of opcodes. There is some assumed knowledge here. Again, I think having an initial link to the txscript package near the top will help with this since it provides the prerequisite knowledge.

You use script instead of Script. The capitalization seems like it might be more clear that you're referring to the Script system.

Not sure I follow you here. Is there a specific instance you can point out? As far as I can tell, all of the references to scripts are indeed talking about scripts, whereas references to the scripting system specifically say scripting system. Maybe I missed some cases?

I don't know what Hash160 is, even after reading the blurb about RIPEMD-160 hashes. It would be nice to link to a reference.

Good point. I'll add a link to the ripemd160 package and clarify that Hash160 is shorthand for RIPEMD-160(BLAKE256(data)).

I don't know what you mean by Version 0 addresses. I don't know much about address versioning, unless you're talking about things like Segwit or Taproot.

That is likely because there currently is no real notion of versioned addresses since there is only the existing addresses (hence version 0). The plan in the future is to introduce a new version with a different encoding scheme. Also, perhaps more importantly, since addresses really just unpack to underlying scripts that enforce a set of constraints, and all software working with an address has to be able to produce scripts that are valid for a given script version, they effectively have a one-to-one correspondence with the underlying script version.

It seems like it would be worthwhile to add a blurb about address versioning in the primer.

Also, in the PR commit message, Paragraph 2 has a typo:

Fixed.

@davecgh
Copy link
Member Author

davecgh commented Mar 6, 2021

@matthawkins90 I believe I have addressed all of you feedback. See the diff of changes here.

@matthawkins90
Copy link
Contributor

matthawkins90 commented Mar 6, 2021

Thanks for the updates. Reading now!
Meanwhile, Let me clarify what I meant by

You use script instead of Script.

Not sure I follow you here. Is there a specific instance you can point out?

Sorry for the confusion. This is incredibly nitpicky and inconsequential, so please let me know if this simply doesn't matter.
Here are my definitions:

  1. Capitalized Script: the proper noun for the language system
  2. Lowercase script: a stack of opcodes for a transaction.
  3. txscript: dcrd's packaged implementation for Script.

The main phrase I'm getting hung up on is "script version".
I can't tell if you're talking about the Script language version, or if you intend to mean that the script of opcodes themselves can be versioned without versioning the Script.

So, examples:

  • Line 20 - The script version of the aforementioned script
    Should probably read:
    - The Script version of the aforementioned script
    right?

  • Line 23: It is also worth keeping in mind that the script version may be implicit
    Should be Script?

  • Line 99: payment scripts that correspond to the same script version the original creator
    Should be Script version?

  • Line 104

  • Line 105

  • Line 110
    ...
    I can do these in-line, but I want to make sure I'm not misunderstanding or wasting your time. This was simply something I noticed. Thanks for pointing me to the txscript package and answering my other questions!

@davecgh davecgh force-pushed the stdaddress_introduce branch 2 times, most recently from 371ed3c to 43e7034 Compare March 6, 2021 22:01
@davecgh
Copy link
Member Author

davecgh commented Mar 6, 2021

I've updated the generic decoding logic to implement a length check for determining if an address is probably a version 0 base58 address as well as slightly modified it a bit to be a bit more efficient when a new version is added as well as introduced some additional tests to cover the differences.

See Changes.

@davecgh
Copy link
Member Author

davecgh commented Mar 7, 2021

@matthawkins90 Thanks for the clarification. I'll think on it a bit as I understand from your description why you find it confusing, though I'm not really sure that Script correctly refers to the scripting language because the current incarnation is named txscript. All scripts are accompanied by a script version (aka the scripting language version) that identifies the exact language and semantics the script must use. Currently, the only supported version is 0, but that is going to change in the future.

Copy link
Member

@dnldd dnldd left a comment

Choose a reason for hiding this comment

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

Nicely done with great documentation 👍

Copy link
Member

@rstaudt2 rstaudt2 left a comment

Choose a reason for hiding this comment

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

This looks great to me. The code is much easier to follow than the existing script creation code in txscript and it makes sense to cleanly split out standard script creation from txscript. The tests are excellent and make it straightforward to review the code for correctness as well. Just noticed a few typos.

internal/staging/stdaddress/README.md Outdated Show resolved Hide resolved
internal/staging/stdaddress/README.md Outdated Show resolved Hide resolved
internal/staging/stdaddr/address_test.go Outdated Show resolved Hide resolved
internal/staging/stdaddr/address_test.go Outdated Show resolved Hide resolved
internal/staging/stdaddr/example_test.go Outdated Show resolved Hide resolved
internal/staging/stdaddr/bench_test.go Outdated Show resolved Hide resolved
internal/staging/stdaddr/address_test.go Outdated Show resolved Hide resolved
internal/staging/stdaddr/addressv0.go Show resolved Hide resolved
internal/staging/stdaddr/addressv0.go Show resolved Hide resolved
@davecgh
Copy link
Member Author

davecgh commented Mar 8, 2021

@matthawkins90 After thinking about it a bit, I decided to go with "scripting language version" throughout which should hopefully address your points. I also changed the wording from until absolutely necessary to until the funds are redeemed which hopefully resolves that point of confusion. Changes here.

@jholdstock
Copy link
Member

Just reviewed the README. As somebody with a low-medium level of familiarity of the topic, it reads well and goes into a good level of depth. ACK.

Copy link
Member

@matheusd matheusd left a comment

Choose a reason for hiding this comment

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

Excellent work! I also ran run_tests.sh in every commit to assert the correct behavior.

Only noted two points for clarification.

internal/staging/stdaddr/address_test.go Show resolved Hide resolved
internal/staging/stdaddr/addressv0.go Outdated Show resolved Hide resolved
@matthawkins90
Copy link
Contributor

@davecgh, really great work! Thanks for working to make this more approachable and understandable for people.

internal/staging/stdaddr/addressv0.go Outdated Show resolved Hide resolved
internal/staging/stdaddr/addressv0.go Outdated Show resolved Hide resolved
internal/staging/stdaddr/addressv0.go Outdated Show resolved Hide resolved
internal/staging/stdaddr/addressv0.go Outdated Show resolved Hide resolved
internal/staging/stdaddr/addressv0.go Outdated Show resolved Hide resolved
internal/staging/stdaddr/addressv0.go Outdated Show resolved Hide resolved
@davecgh davecgh force-pushed the stdaddress_introduce branch 2 times, most recently from edff0c1 to 3dabd6b Compare March 8, 2021 19:15
Copy link
Member

@matheusd matheusd left a comment

Choose a reason for hiding this comment

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

🎉

Copy link
Member

@JoeGruffins JoeGruffins left a comment

Choose a reason for hiding this comment

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

Amazing.

Copy link
Member

@JoeGruffins JoeGruffins left a comment

Choose a reason for hiding this comment

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

This probably sounds wierd, but I can't get the example test to print out with go test --run=ExampleDecodeAddress -v unless I add another fmt.Println() somewhere else in the file. Not sure what it is.

edit: Oh, I see. It's not a test. Nevermind.

edit2: I cannot find much documentation on "package_test". Is it possible to have that print to stdout?

@davecgh
Copy link
Member Author

davecgh commented Mar 10, 2021

This probably sounds wierd, but I can't get the example test to print out with go test --run=ExampleDecodeAddress -v unless I add another fmt.Println() somewhere else in the file. Not sure what it is.

It's a testable example. go test compiles, runs, and captures the output and then compares that output against the comments to ensure the example actually produces the output it claims to. That way they don't go stale as the code changes.

@JoeGruffins
Copy link
Member

I see! I don't know how I missed this part of golang testing. I see that the output must match what is under // Output: Thank you.

This corrects the generic public key address constructor to pass the
actual serialized ed25519 public key to specialized constructor instead
of incorrectly including the initial byte that identifies the signature
suite and y bit oddness.
The current code for handling standard addresses implemented in dcrutil
was written many years ago prior a wide variety of changes and several
new features added by Decred.  As a result, it entirely lacks support
for some features and supports others in a roundabout and non-intuitive
way.

Specifically, it does not support or provide a clean path to enable
support for different script versions and the way they are handled in
stake transactions is entirely non-intuitive.

Further, back when the original address code was implemented, it was
necessary to implement script creation in the txscript package which led
to the current design of providing methods such as
txscript.PayTo{AddrScript,SStx,SStxChange}, and others, which need to
type assert the specific concrete types of addresses in order to produce
the necessary scripts.  This, unfortunately, effectively negates the use
of an interface to support generic addresses because it means callers,
such as dcrwallet, are not able to implement their own types without
somewhat invisibly breaking the script creation.

Finally, the aforementioned blending of the address code into txscript
has led to confusion regarding what is considered standard and what is
considered consensus which has tripped up several contributors over the
years.

This is part of a series of commits that aims to resolve all of the
aforementioned issues by introducing a new package named stdaddr which
entirely reworks the way addresses are handled.

For the time being, the package is introduced into the internal staging
area for initial review.

The following provides an overview of some of the key features of the
new design:

- Supports versioned addresses
- Produces scripts directly via methods implemented on underlying types
- Provides direct support for creation of the scripts necessary for the
  staking system
- Uses a capabilities-based approach via interfaces so callers can
  cleanly and generically determine under what circumstances addresses
  can be used
- Allows callers to create their own concrete address types without
  worrying about breaking the existing ones
- Clearly denotes that addresses are a standardized construction that
  must not be used directly in consensus code

In order to help ease the review process, this commit only contains the
overall generic infrastructure without adding support for any specific
address types.  Each supported version 0 address type will be added in
future commits.
This adds the overall infrastracture for decoding version 0 addresses
along with associated testing infrastructure.

Note that, in order to help ease the review process, this only adds the
relevant infrastructure and does not yet support any version 0 address
types.  Each supported version 0 address type will be added in future
commits.

This is part of a series of commits to fully implement the stdaddr
package.
This adds a new type to fully support version 0
pay-to-pubkey-ecdsa-secp256k1 addresses along with associated tests.

This is part of a series of commits to fully implement the stdaddr
package.
This adds a new type to fully support version 0 pay-to-pubkey-ed25519
addresses along with associated tests.

This is part of a series of commits to fully implement the stdaddr
package.
This adds a new type to fully support version 0
pay-to-pubkey-schnorr-secp256k1 addresses along with associated tests.

This is part of a series of commits to fully implement the stdaddr
package.
This adds a new type to fully support version 0
pay-to-pubkey-hash-ecdsa-secp256k1 addresses along with associated
tests.

This is part of a series of commits to fully implement the stdaddr
package.
This adds a new type to fully support version 0
pay-to-pubkey-hash-ed25519 addresses along with associated tests.

This is part of a series of commits to fully implement the stdaddr
package.
This adds a new type to fully support version 0
pay-to-pubkey-hash-schnorr-secp256k1 addresses along with associated
tests.

This is part of a series of commits to fully implement the stdaddr
package.
This adds a new type to fully support version 0 pay-to-script-hash
addresses along with associated tests.

This is part of a series of commits to fully implement the stdaddr
package.
This adds benchmarks for decoding and generating the payment script for
each supported version 0 address type.

This is part of a series of commits to fully implement the stdaddr
package.
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.

None yet

8 participants