multi: add strict txid parsing helper

Add a dedicated helper that rejects any chainhash string unless it is
exactly 64 hex characters long, instead of relying on btcd's
lenient parser semantics.

Cover the helper with focused tests for valid, short, odd-length,
empty, and non-hex inputs so later call sites can use it as a
full-txid validation primitive.
This commit is contained in:
Boris Nagaev 2026-04-04 01:03:36 -05:00
parent 30472d0f28
commit dbd944e6ab
No known key found for this signature in database
2 changed files with 90 additions and 0 deletions

View file

@ -0,0 +1,26 @@
package chainhashutil
import (
"fmt"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// NewHashFromStrExact parses a chainhash string that must be fully specified.
func NewHashFromStrExact(hash string) (chainhash.Hash, error) {
if len(hash) != chainhash.MaxHashStringSize {
return chainhash.Hash{}, fmt.Errorf(
"invalid hash string length of %v, want %v",
len(hash), chainhash.MaxHashStringSize)
}
parsed, err := chainhash.NewHashFromStr(hash)
if err != nil {
return chainhash.Hash{}, err
}
// chainhash.NewHashFromStr uses a pointer return, but on success it
// returns a populated hash, not (nil, nil), so dereferencing here is
// safe.
return *parsed, nil
}

View file

@ -0,0 +1,64 @@
package chainhashutil
import (
"strings"
"testing"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/stretchr/testify/require"
)
// TestNewHashFromStrExact verifies that strict hash parsing rejects any
// non-fully-specified chainhash string.
func TestNewHashFromStrExact(t *testing.T) {
t.Parallel()
validHash := strings.Repeat("01", 32)
testCases := []struct {
name string
hash string
wantErr string
}{
{
name: "valid",
hash: validHash,
wantErr: "",
},
{
name: "short",
hash: validHash[:62],
wantErr: "invalid hash string length",
},
{
name: "odd length",
hash: validHash[:63],
wantErr: "invalid hash string length",
},
{
name: "empty",
hash: "",
wantErr: "invalid hash string length",
},
{
name: "non hex",
hash: strings.Repeat("z", 64),
wantErr: "invalid byte",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
t.Parallel()
hash, err := NewHashFromStrExact(testCase.hash)
if testCase.wantErr != "" {
require.ErrorContains(t, err, testCase.wantErr)
require.Equal(t, chainhash.Hash{}, hash)
} else {
require.NoError(t, err)
require.Equal(t, testCase.hash, hash.String())
}
})
}
}