From 799082a04545ccde01d8487b6899ef7f413a06bf Mon Sep 17 00:00:00 2001
From: Rootul Patel <rootulp@gmail.com>
Date: Wed, 7 Jun 2023 15:19:16 -0400
Subject: [PATCH] fix: left pad namespace IDs

---
 cmd/celestia/rpc.go      | 68 ++++++++++++++++++++++++----------------
 cmd/celestia/rpc_test.go | 53 ++++++++++++-------------------
 share/nid.go             |  5 ++-
 3 files changed, 63 insertions(+), 63 deletions(-)

diff --git a/cmd/celestia/rpc.go b/cmd/celestia/rpc.go
index 080b43dfc3..b6ec60dcd6 100644
--- a/cmd/celestia/rpc.go
+++ b/cmd/celestia/rpc.go
@@ -18,13 +18,12 @@ import (
 	"github.com/cosmos/cosmos-sdk/types"
 	"github.com/spf13/cobra"
 
+	"github.com/celestiaorg/celestia-app/pkg/appconsts"
 	appns "github.com/celestiaorg/celestia-app/pkg/namespace"
 	apptypes "github.com/celestiaorg/celestia-app/x/blob/types"
-	"github.com/celestiaorg/nmt/namespace"
 
 	"github.com/celestiaorg/celestia-node/api/rpc/client"
 	"github.com/celestiaorg/celestia-node/blob"
-	"github.com/celestiaorg/celestia-node/share"
 	"github.com/celestiaorg/celestia-node/state"
 )
 
@@ -33,6 +32,8 @@ const (
 
 	// TODO: remove this once we add support for user specified namespace version.
 	namespaceVersion = appns.NamespaceVersionZero
+	// TODO: remove this once we add support for user specified share version.
+	shareVersion = appconsts.ShareVersionZero
 )
 
 var requestURL string
@@ -74,7 +75,7 @@ func init() {
 }
 
 var rpcCmd = &cobra.Command{
-	Use:   "rpc [namespace] [method] [params...]",
+	Use:   "rpc [namespaceID] [method] [params...]",
 	Short: "Send JSON-RPC request",
 	Args:  cobra.MinimumNArgs(2),
 	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
@@ -150,7 +151,7 @@ func parseParams(method string, params []string) []interface{} {
 				panic("Error decoding blob data: base64 string could not be decoded.")
 			}
 		}
-		parsedBlob, err := blob.NewBlob(0, namespaceVersion, nID, blobData)
+		parsedBlob, err := blob.NewBlob(shareVersion, namespaceVersion, nID, blobData)
 		if err != nil {
 			panic(fmt.Sprintf("Error creating blob: %v", err))
 		}
@@ -191,10 +192,10 @@ func parseParams(method string, params []string) []interface{} {
 			}
 		}
 		parsedParams[2] = []*apptypes.Blob{{
-			NamespaceId:      nID[1:],
+			NamespaceId:      nID,
 			Data:             blobData,
-			ShareVersion:     0,
-			NamespaceVersion: 0,
+			ShareVersion:     uint32(shareVersion),
+			NamespaceVersion: uint32(namespaceVersion),
 		}}
 		return parsedParams[:3]
 	case "Get":
@@ -229,7 +230,7 @@ func parseParams(method string, params []string) []interface{} {
 		if err != nil {
 			panic(fmt.Sprintf("Error parsing namespace ID: %v", err))
 		}
-		parsedParams[1] = []namespace.ID{nID}
+		parsedParams[1] = nID
 		return parsedParams
 	case "QueryDelegation", "QueryUnbonding", "BalanceForAddress":
 		var err error
@@ -428,32 +429,45 @@ func parseSignatureForHelpstring(methodSig reflect.StructField) string {
 	return simplifiedSignature
 }
 
-func parseNamespaceID(param string) (namespace.ID, error) {
-	var nID []byte
-	var err error
+// parseNamespaceID parses a namespace ID from a base64 or hex string. The param
+// is expected to be the user-specified portion of a v0 namespace ID (i.e. the
+// last 10 bytes) of a namespace ID.
+func parseNamespaceID(param string) (namespaceID []byte, err error) {
+	userBytes, err := decodeNamespaceID(param)
+	if err != nil {
+		return nil, err
+	}
+	if len(userBytes) > appns.NamespaceVersionZeroIDSize {
+		return nil, fmt.Errorf("namespace ID %v is too large to be a v0 namespace, want <= %v bytes", userBytes, appns.NamespaceVersionZeroIDSize)
+	}
+	// if the namespace ID is <= 10 bytes, left pad it with 0s
+	return leftPad(userBytes, appns.NamespaceVersionZeroIDSize), nil
+}
+
+// decodeNamespaceID decodes a namespace ID from a base64 or hex string
+func decodeNamespaceID(param string) (namespaceID []byte, err error) {
 	if strings.HasPrefix(param, "0x") {
 		decoded, err := hex.DecodeString(param[2:])
 		if err != nil {
 			return nil, fmt.Errorf("error decoding namespace ID: %w", err)
 		}
-		nID = decoded
-	} else {
-		// otherwise, it's just a base64 string
-		nID, err = base64.StdEncoding.DecodeString(param)
-		if err != nil {
-			return nil, fmt.Errorf("error decoding namespace ID: %w", err)
-		}
+		return decoded, nil
 	}
-	// if the namespace ID is <= 10 bytes, add v0 share + namespace prefix and zero pad
-	if len(nID) <= appns.NamespaceVersionZeroIDSize {
-		nID, err = share.NewNamespaceV0(nID)
-		if err != nil {
-			return nil, err
-		}
-	} else if len(nID) < appns.NamespaceSize {
-		return nil, fmt.Errorf("passed namespace is too large")
+	// otherwise, it's just a base64 string
+	decoded, err := base64.StdEncoding.DecodeString(param)
+	if err != nil {
+		return nil, fmt.Errorf("error decoding namespace ID: %w", err)
+	}
+	return decoded, nil
+}
+
+func leftPad(data []byte, size int) []byte {
+	if len(data) >= size {
+		return data
 	}
-	return nID, nil
+	padded := make([]byte, size)
+	copy(padded[size-len(data):], data)
+	return padded
 }
 
 func parseJSON(param string) (json.RawMessage, error) {
diff --git a/cmd/celestia/rpc_test.go b/cmd/celestia/rpc_test.go
index f16042c80d..5b8b146f21 100644
--- a/cmd/celestia/rpc_test.go
+++ b/cmd/celestia/rpc_test.go
@@ -12,56 +12,43 @@ func Test_parseNamespaceID(t *testing.T) {
 	type testCase struct {
 		name    string
 		param   string
-		want    namespace.ID
+		want    []byte
 		wantErr bool
 	}
 	testCases := []testCase{
 		{
-			name:  "8 byte hex encoded namespace ID gets right padded",
-			param: "0x0c204d39600fddd3",
-			want: namespace.ID{
-				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-				0x0, 0x0, 0x0, 0x0, 0xc, 0x20, 0x4d, 0x39, 0x60, 0xf, 0xdd, 0xd3, 0x0, 0x0,
-			},
+			name:    "8 byte hex encoded namespace ID gets left padded with 0s",
+			param:   "0x0c204d39600fddd3",
+			want:    []byte{0x0, 0x0, 0xc, 0x20, 0x4d, 0x39, 0x60, 0xf, 0xdd, 0xd3},
 			wantErr: false,
 		},
 		{
-			name:  "10 byte hex encoded namespace ID",
-			param: "0x42690c204d39600fddd3",
-			want: namespace.ID{
-				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-				0x0, 0x0, 0x0, 0x0, 0x42, 0x69, 0xc, 0x20, 0x4d, 0x39, 0x60, 0xf, 0xdd, 0xd3,
-			},
-			wantErr: false,
-		},
-		{
-			name:  "29 byte hex encoded namespace ID",
-			param: "0x0000000000000000000000000000000000000001010101010101010101",
-			want: namespace.ID{
-				0x0,                                                                                      // namespace version
-				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // v0 ID prefix
-				0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, // namespace ID
-			},
+			name:    "10 byte hex encoded namespace ID remains unchanged",
+			param:   "0x42690c204d39600fddd3",
+			want:    []byte{0x42, 0x69, 0xc, 0x20, 0x4d, 0x39, 0x60, 0xf, 0xdd, 0xd3},
 			wantErr: false,
 		},
 		{
 			name:    "11 byte hex encoded namespace ID returns error",
-			param:   "0x42690c204d39600fddd3a3",
-			want:    namespace.ID{},
+			param:   "0x1142690c204d39600fddd3",
+			want:    []byte{},
 			wantErr: true,
 		},
 		{
-			name:  "10 byte base64 encoded namespace ID",
-			param: "QmkMIE05YA/d0w==",
-			want: namespace.ID{
-				0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
-				0x0, 0x0, 0x0, 0x0, 0x42, 0x69, 0xc, 0x20, 0x4d, 0x39, 0x60, 0xf, 0xdd, 0xd3,
-			},
+			name:    "8 byte base64 encoded namespace ID gets left padded with 0s",
+			param:   "QmkMIE05YA/d0w==",
+			want:    namespace.ID{0x42, 0x69, 0xc, 0x20, 0x4d, 0x39, 0x60, 0xf, 0xdd, 0xd3},
+			wantErr: false,
+		},
+		{
+			name:    "10 byte base64 encoded namespace ID remains unchanged",
+			param:   "QmkMIE05YA/d0w==",
+			want:    namespace.ID{0x42, 0x69, 0xc, 0x20, 0x4d, 0x39, 0x60, 0xf, 0xdd, 0xd3},
 			wantErr: false,
 		},
 		{
-			name:    "not base64 or hex encoded namespace ID returns error",
-			param:   "5748493939429",
+			name:    "11 byte base64 encoded namespace ID returns error",
+			param:   "QQmkMIE05YA/d0w==",
 			want:    namespace.ID{},
 			wantErr: true,
 		},
diff --git a/share/nid.go b/share/nid.go
index dfd6a5de7e..9709e3bd2c 100644
--- a/share/nid.go
+++ b/share/nid.go
@@ -4,12 +4,11 @@ import (
 	"fmt"
 
 	appns "github.com/celestiaorg/celestia-app/pkg/namespace"
-	"github.com/celestiaorg/nmt/namespace"
 )
 
-// NewNamespaceV0 takes variable size byte slice anc creates version 0 Namespace ID.
+// NewNamespaceV0 takes variable size byte slice and creates a version 0 namespace.
 // The bytes slice must be <= 10 bytes.
-func NewNamespaceV0(subNId []byte) (namespace.ID, error) {
+func NewNamespaceV0(subNId []byte) ([]byte, error) {
 	if lnid := len(subNId); lnid > appns.NamespaceVersionZeroIDSize {
 		return nil, fmt.Errorf("namespace id must be <= %v, but it was %v bytes", appns.NamespaceVersionZeroIDSize, lnid)
 	}