Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 11 additions & 6 deletions pkg/narinfo/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,24 @@ import (
"regexp"
)

// narInfoHashPattern defines the valid characters for a Nix32 encoded hash.
// Nix32 uses a 32-character alphabet excluding 'e', 'o', 'u', and 't'.
// Valid characters: 0-9, a-d, f-n, p-s, v-z
// Hashes must be exactly 32 characters long.
const HashPattern = `[0-9a-df-np-sv-z]{32}`

var (
// ErrInvalidHash is returned if the hash is invalid.
ErrInvalidHash = errors.New("invalid narinfo hash")

// narInfoHashPattern defines the valid characters for a narinfo hash.
//nolint:gochecknoglobals // This is used in other regexes to ensure they validate the same thing.
narInfoHashPattern = `[a-z0-9]+`

// hashRegexp is used to validate hashes.
hashRegexp = regexp.MustCompile(`^` + narInfoHashPattern + `$`)
hashRegexp = regexp.MustCompile(`^` + HashPattern + `$`)
)

// ValidateHash validates the given hash.
// ValidateHash validates the given hash according to Nix32 encoding requirements.
// A valid hash must:
// - Be exactly 32 characters long
// - Contain only characters from the Nix32 alphabet ('0'-'9', 'a'-'z' excluding 'e', 'o', 'u', 't').
func ValidateHash(hash string) error {
if !hashRegexp.MatchString(hash) {
return ErrInvalidHash
Expand Down
131 changes: 131 additions & 0 deletions pkg/narinfo/hash_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package narinfo_test

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/kalbasit/ncps/pkg/narinfo"
)

func TestValidateHash(t *testing.T) {
t.Parallel()

tests := []struct {
name string
hash string
shouldErr bool
}{
// Valid Nix32 hashes (32 characters from the allowed alphabet)
{
name: "valid hash with all allowed characters",
hash: "n5glp21rsz314qssw9fbvfswgy3kc68f",
shouldErr: false,
},
{
name: "valid hash with numbers",
hash: "01234567890123456789012345678901",
shouldErr: false,
},
{
name: "valid hash with mixed characters",
hash: "abcdfghijklmnpqrsvwxyzabcdfghijk",
shouldErr: false,
},

// Invalid: contains forbidden letters (e, o, u, t)
{
name: "invalid hash contains 'e'",
hash: "n5glp21rsz314qssw9fbvfswgy3kc68e",
shouldErr: true,
},
{
name: "invalid hash contains 'o'",
hash: "n5glp21rsz314qssw9fbvfswgy3kc68o",
shouldErr: true,
},
{
name: "invalid hash contains 'u'",
hash: "n5glp21rsz314qssw9fbvfswgy3kc68u",
shouldErr: true,
},
{
name: "invalid hash contains 't'",
hash: "n5glp21rsz314qssw9fbvfswgy3kc68t",
shouldErr: true,
},

// Invalid: contains uppercase letters
{
name: "invalid hash contains uppercase",
hash: "N5glp21rsz314qssw9fbvfswgy3kc68f",
shouldErr: true,
},
{
name: "invalid hash all uppercase",
hash: "N5GLP21RSZ314QSSW9FBVFSWGY3KC68F",
shouldErr: true,
},

// Invalid: contains special characters
{
name: "invalid hash with exclamation mark",
hash: "n5glp21rsz314qssw9fbvfswgy3kc68!",
shouldErr: true,
},
{
name: "invalid hash with hyphen",
hash: "n5glp21rsz314qssw9fbvfswgy3kc-8f",
shouldErr: true,
},
{
name: "invalid hash with underscore",
hash: "n5glp21rsz314qssw9fbvfswgy3kc_8f",
shouldErr: true,
},
{
name: "invalid hash with space",
hash: "n5glp21rsz314qssw9fbvfswgy3kc 8f",
shouldErr: true,
},

// Invalid: wrong length
{
name: "invalid hash too short",
hash: "n5glp21rsz314qssw9fbvfswgy3kc68",
shouldErr: true,
},
{
name: "invalid hash too long",
hash: "n5glp21rsz314qssw9fbvfswgy3kc68ff",
shouldErr: true,
},

// Invalid: empty string
{
name: "invalid hash empty string",
hash: "",
shouldErr: true,
},

// Invalid: only one character
{
name: "invalid hash single character",
hash: "a",
shouldErr: true,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()

err := narinfo.ValidateHash(test.hash)
if test.shouldErr {
assert.ErrorIs(t, err, narinfo.ErrInvalidHash)
} else {
assert.NoError(t, err)
}
})
}
}
4 changes: 2 additions & 2 deletions pkg/ncps/migrate_narinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -839,8 +839,8 @@ func testMigrateNarInfoLargeNarInfo(factory migrationFactory) func(*testing.T) {

const numSignatures = 50

hash := "largenarinfo1234567890abcdef1234567890abcdef"
narHash := "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
hash := "0a90gw9sdyz3680wfncd5xf0qg6zh27w"
narHash := "024wilh5y46xqqjnwp159s13kgvsh8zfr6g6znb8ix2vlyf61rwp"

// Build references string
var referencesBuilder strings.Builder
Expand Down
Loading