Skip to content

chaincfg/chainhash: add strict parsing#2520

Open
starius wants to merge 1 commit intobtcsuite:masterfrom
starius:strict-chainhash-parsing-1
Open

chaincfg/chainhash: add strict parsing#2520
starius wants to merge 1 commit intobtcsuite:masterfrom
starius:strict-chainhash-parsing-1

Conversation

@starius
Copy link
Copy Markdown
Contributor

@starius starius commented Apr 8, 2026

Change Description

Add NewHashFromStrStrict and DecodeStrict for callers that must parse full txids or block hashes exactly.

Keep NewHashFromStr and Decode lenient for compatibility, but add NOTE docs steering typical parsing to the strict helpers.

Add tests for the new strict behavior and preserve coverage for the existing lenient short and odd hex behavior.

I also patched call sites of the affected functions, keeping it in a branch for now, because it depends on a sub-module version bump.

Steps to Test

cd chaincfg/chainhash
go test

Pull Request Checklist

Testing

  • Your PR passes all CI checks.
  • Tests covering the positive and negative (error paths) are included.
  • Bug fixes contain tests triggering the bug to prevent regressions.

Code Style and Documentation

📝 Please see our Contribution Guidelines for further guidance.

Copy link
Copy Markdown

@utafrali utafrali left a comment

Choose a reason for hiding this comment

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

Clean, well-structured addition. The refactor of shared logic into decodeHash is a good DRY improvement, the strict variants correctly reject anything shorter or longer than 64 chars, and the doc notes steering callers to the strict helpers are a nice touch. Minor gaps: a missing test for even-length too-short input in TestDecodeStrict, and a small avoidable allocation in DecodeStrict.

// ErrHashStrSizeMismatch describes an error that indicates the caller
// specified a hash string that does not meet the exact length required for
// strict parsing.
var ErrHashStrSizeMismatch = fmt.Errorf("hash string must be exactly %v bytes",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The error message says "bytes" but MaxHashStringSize (64) is a count of hex characters, not bytes. The word "characters" would be more precise. That said, the existing ErrHashStrSize uses the same wording, so if you want consistency just leave it, but this would be a good opportunity to use the more accurate term in the new error.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch! Thanks! Fixed.

}

// TestDecodeStrict executes tests against the DecodeStrict function.
func TestDecodeStrict(t *testing.T) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

TestDecodeStrict covers odd-length, non-hex, and too-long inputs, but there's no test for a valid even-length hex string that is simply too short (e.g., a 32-character hex string). That's likely the most common real-world mistake this function is meant to catch (someone passing an abbreviated hash). Adding a case like {"deadbeef" + strings.Repeat("0", 24), ErrHashStrSizeMismatch} (a 32-char string) would make the intent clearer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added "deadbeef" + strings.Repeat("0", 24) case

// decodeHash decodes the provided byte-reversed hexadecimal bytes into dst.
// The caller is responsible for applying any caller-specific length validation
// before invoking this helper.
func decodeHash(dst *Hash, src []byte) error {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The decodeHash helper accepts []byte so that Decode can pass its already-allocated srcBytes slice without an extra copy. However, DecodeStrict calls it with []byte(src), which allocates on every call. Since DecodeStrict only accepts exactly 64-char strings, you could declare a [MaxHashStringSize]byte on the stack and copy into it (or keep a local var b [MaxHashStringSize]byte; copy(b[:], src); return decodeHash(dst, b[:])) to avoid the heap allocation on the hot path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Go already optimizes such cases - no need to use a stack array for this.

I added a benchmark and ensured there is already no memory allocation here:

 go test -run '^$' -bench '^BenchmarkDecodeStrict$' -benchmem -count=5
goos: linux
goarch: amd64
pkg: github.com/btcsuite/btcd/chaincfg/chainhash
BenchmarkDecodeStrict-2         15530290                78.59 ns/op            0 B/op          0 allocs/op
BenchmarkDecodeStrict-2         14514126                78.90 ns/op            0 B/op          0 allocs/op
BenchmarkDecodeStrict-2         19157900                77.40 ns/op            0 B/op          0 allocs/op
BenchmarkDecodeStrict-2         14660510                78.14 ns/op            0 B/op          0 allocs/op
BenchmarkDecodeStrict-2         18853245                78.26 ns/op            0 B/op          0 allocs/op
PASS
ok      github.com/btcsuite/btcd/chaincfg/chainhash     8.575s

Add NewHashFromStrStrict and DecodeStrict for callers that must
parse full txids or block hashes exactly.

Keep NewHashFromStr and Decode lenient for compatibility, but add
NOTE docs steering typical parsing to the strict helpers.

Add tests for the new strict behavior and preserve coverage for the
existing lenient short and odd hex behavior.
@starius starius force-pushed the strict-chainhash-parsing-1 branch from 8192c02 to 5fefcc6 Compare April 9, 2026 05:45
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.

2 participants