From cac0b49e3661db01590572124eba6ce5ca6ad9bc Mon Sep 17 00:00:00 2001 From: Bilal Rafique Date: Thu, 4 Sep 2025 22:39:29 +0500 Subject: [PATCH] Refactor signature utils --- pkg/cascade/signature.go | 134 ++++++++++++++++++++++++++++++++ tests/system/go.mod | 9 ++- tests/system/go.sum | 10 +-- tests/system/signature_utils.go | 127 ++---------------------------- 4 files changed, 152 insertions(+), 128 deletions(-) create mode 100644 pkg/cascade/signature.go diff --git a/pkg/cascade/signature.go b/pkg/cascade/signature.go new file mode 100644 index 00000000..4cb83cf1 --- /dev/null +++ b/pkg/cascade/signature.go @@ -0,0 +1,134 @@ +package cascade + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "io" + + "github.com/LumeraProtocol/supernode/v2/pkg/codec" + "github.com/LumeraProtocol/supernode/v2/pkg/keyring" + "github.com/LumeraProtocol/supernode/v2/pkg/utils" + "github.com/cosmos/btcutil/base58" + cosmoskeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" + "lukechampine.com/blake3" +) + +// CreateLayoutSignature creates the cascade signature format for a given layout file. +// It returns the signature format and index file IDs needed for CASCADE action. +func CreateLayoutSignature(metadataFile codec.Layout, kr cosmoskeyring.Keyring, userKeyName string, ic uint32, maxFiles uint32) (signatureFormat string, indexFileIDs []string, err error) { + // Step 1: Convert metadata to JSON then base64 + me, err := json.Marshal(metadataFile) + if err != nil { + return "", nil, fmt.Errorf("failed to marshal metadata: %w", err) + } + layoutBase64 := base64.StdEncoding.EncodeToString(me) + + // Step 2: Sign the layout data + layoutSignature, err := keyring.SignBytes(kr, userKeyName, []byte(layoutBase64)) + if err != nil { + return "", nil, fmt.Errorf("failed to sign layout: %w", err) + } + layoutSignatureB64 := base64.StdEncoding.EncodeToString(layoutSignature) + + // Step 3: Generate redundant layout file IDs + layoutIDs := GenerateLayoutIDsBatch(layoutBase64, layoutSignatureB64, ic, maxFiles) + + // Step 4: Create index file containing layout references + indexFile := map[string]interface{}{ + "layout_ids": layoutIDs, + "layout_signature": layoutSignatureB64, + } + + // Step 5: Sign the index file + indexFileJSON, err := json.Marshal(indexFile) + if err != nil { + return "", nil, fmt.Errorf("failed to marshal index file: %w", err) + } + indexFileBase64 := base64.StdEncoding.EncodeToString(indexFileJSON) + + creatorSignature, err := keyring.SignBytes(kr, userKeyName, []byte(indexFileBase64)) + if err != nil { + return "", nil, fmt.Errorf("failed to sign index file: %w", err) + } + creatorSignatureB64 := base64.StdEncoding.EncodeToString(creatorSignature) + + // Step 6: Create final signature format + signatureFormat = fmt.Sprintf("%s.%s", indexFileBase64, creatorSignatureB64) + + // Step 7: Generate final index file IDs for submission + indexFileIDs = GenerateIndexIDsBatch(signatureFormat, ic, maxFiles) + + return signatureFormat, indexFileIDs, nil +} + +// GenerateLayoutIDsBatch generates layout IDs using the process: +// combine data -> add counter -> compress -> hash -> Base58 encode +func GenerateLayoutIDsBatch(layoutBase64, layoutSignatureB64 string, ic, maxFiles uint32) []string { + layoutWithSig := fmt.Sprintf("%s.%s", layoutBase64, layoutSignatureB64) + layoutIDs := make([]string, maxFiles) + + var buffer bytes.Buffer + buffer.Grow(len(layoutWithSig) + 10) + + for i := uint32(0); i < maxFiles; i++ { + // Build unique content with counter + buffer.Reset() + buffer.WriteString(layoutWithSig) + buffer.WriteByte('.') + buffer.WriteString(fmt.Sprintf("%d", ic+i)) + + // Compress for efficiency + compressedData, err := utils.ZstdCompress(buffer.Bytes()) + if err != nil { + continue + } + + // Hash for uniqueness + hash, err := utils.Blake3Hash(compressedData) + if err != nil { + continue + } + + // Base58 encode for readable ID + layoutIDs[i] = base58.Encode(hash) + } + + return layoutIDs +} + +// GenerateIndexIDsBatch generates index file IDs using same process as layout IDs +func GenerateIndexIDsBatch(signatureFormat string, ic, maxFiles uint32) []string { + indexFileIDs := make([]string, maxFiles) + + var buffer bytes.Buffer + buffer.Grow(len(signatureFormat) + 10) + + for i := uint32(0); i < maxFiles; i++ { + buffer.Reset() + buffer.WriteString(signatureFormat) + buffer.WriteByte('.') + buffer.WriteString(fmt.Sprintf("%d", ic+i)) + + compressedData, err := utils.ZstdCompress(buffer.Bytes()) + if err != nil { + continue + } + hash, err := utils.Blake3Hash(compressedData) + if err != nil { + continue + } + indexFileIDs[i] = base58.Encode(hash) + } + return indexFileIDs +} + +// ComputeBlake3Hash computes Blake3 hash of the given message +func ComputeBlake3Hash(msg []byte) ([]byte, error) { + hasher := blake3.New(32, nil) + if _, err := io.Copy(hasher, bytes.NewReader(msg)); err != nil { + return nil, err + } + return hasher.Sum(nil), nil +} \ No newline at end of file diff --git a/tests/system/go.mod b/tests/system/go.mod index 00960f9e..15b8212d 100644 --- a/tests/system/go.mod +++ b/tests/system/go.mod @@ -30,12 +30,10 @@ require ( github.com/LumeraProtocol/lumera v1.7.0 github.com/LumeraProtocol/supernode/v2 v2.0.0-00010101000000-000000000000 github.com/cometbft/cometbft v0.38.17 - github.com/cosmos/btcutil v1.0.5 github.com/tidwall/gjson v1.14.2 github.com/tidwall/sjson v1.2.5 golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 gopkg.in/yaml.v3 v3.0.1 - lukechampine.com/blake3 v1.4.0 ) require ( @@ -61,6 +59,7 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.5 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect github.com/cockroachdb/errors v1.11.3 // indirect github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect @@ -68,6 +67,7 @@ require ( github.com/cockroachdb/redact v1.1.6 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/cometbft/cometbft-db v0.14.1 // indirect + github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/cosmos-db v1.1.1 // indirect github.com/cosmos/go-bip39 v1.0.0 // indirect github.com/cosmos/ics23/go v0.11.0 // indirect @@ -170,6 +170,7 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect google.golang.org/protobuf v1.36.6 // indirect gotest.tools/v3 v3.5.2 // indirect + lukechampine.com/blake3 v1.4.0 // indirect nhooyr.io/websocket v1.8.10 // indirect pgregory.net/rapid v1.2.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect @@ -177,6 +178,10 @@ require ( replace ( github.com/99designs/keyring => github.com/cosmos/keyring v1.2.0 + + // Fix bytedance/sonic compatibility + github.com/bytedance/sonic => github.com/bytedance/sonic v1.12.3 + github.com/bytedance/sonic/loader => github.com/bytedance/sonic/loader v0.2.1 // dgrijalva/jwt-go is deprecated and doesn't receive security updates. // See: https://github.com/cosmos/cosmos-sdk/issues/13134 github.com/dgrijalva/jwt-go => github.com/golang-jwt/jwt/v4 v4.4.2 diff --git a/tests/system/go.sum b/tests/system/go.sum index afece699..9ff0158a 100644 --- a/tests/system/go.sum +++ b/tests/system/go.sum @@ -126,11 +126,10 @@ github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce h1:YtWJF7RHm2pY github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce/go.mod h1:0DVlHczLPewLcPGEIeUEzfOJhqGPQ0mJJRDBtD307+o= github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= -github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= -github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= -github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/bytedance/sonic v1.12.3 h1:W2MGa7RCU1QTeYRTPE3+88mVC0yXmsRQRChiyVocVjU= +github.com/bytedance/sonic v1.12.3/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= +github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= +github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= @@ -149,6 +148,7 @@ github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= diff --git a/tests/system/signature_utils.go b/tests/system/signature_utils.go index 61a93125..977c674c 100644 --- a/tests/system/signature_utils.go +++ b/tests/system/signature_utils.go @@ -1,132 +1,17 @@ package system import ( - "bytes" - "encoding/base64" - "encoding/json" - "fmt" - "io" - + "github.com/LumeraProtocol/supernode/v2/pkg/cascade" "github.com/LumeraProtocol/supernode/v2/pkg/codec" - "github.com/LumeraProtocol/supernode/v2/pkg/keyring" - "github.com/LumeraProtocol/supernode/v2/pkg/utils" - "github.com/cosmos/btcutil/base58" cosmoskeyring "github.com/cosmos/cosmos-sdk/crypto/keyring" - "lukechampine.com/blake3" ) +// createCascadeLayoutSignature is a wrapper for the common cascade signature function func createCascadeLayoutSignature(metadataFile codec.Layout, kr cosmoskeyring.Keyring, userKeyName string, ic uint32, maxFiles uint32) (signatureFormat string, indexFileIDs []string, err error) { - - // Step 1: Convert metadata to JSON then base64 - me, err := json.Marshal(metadataFile) - if err != nil { - return "", nil, fmt.Errorf("failed to marshal metadata: %w", err) - } - layoutBase64 := base64.StdEncoding.EncodeToString(me) - - // Step 2: Sign the layout data - layoutSignature, err := keyring.SignBytes(kr, userKeyName, []byte(layoutBase64)) - if err != nil { - return "", nil, fmt.Errorf("failed to sign layout: %w", err) - } - layoutSignatureB64 := base64.StdEncoding.EncodeToString(layoutSignature) - - // Step 3: Generate redundant layout file IDs - layoutIDs := generateLayoutIDsBatch(layoutBase64, layoutSignatureB64, ic, maxFiles) - - // Step 4: Create index file containing layout references - indexFile := map[string]interface{}{ - "layout_ids": layoutIDs, - "layout_signature": layoutSignatureB64, - } - - // Step 5: Sign the index file - indexFileJSON, err := json.Marshal(indexFile) - if err != nil { - return "", nil, fmt.Errorf("failed to marshal index file: %w", err) - } - indexFileBase64 := base64.StdEncoding.EncodeToString(indexFileJSON) - - creatorSignature, err := keyring.SignBytes(kr, userKeyName, []byte(indexFileBase64)) - if err != nil { - return "", nil, fmt.Errorf("failed to sign index file: %w", err) - } - creatorSignatureB64 := base64.StdEncoding.EncodeToString(creatorSignature) - - // Step 6: Create final signature format - signatureFormat = fmt.Sprintf("%s.%s", indexFileBase64, creatorSignatureB64) - - // Step 7: Generate final index file IDs for submission - indexFileIDs = generateIndexIDsBatch(signatureFormat, ic, maxFiles) - - return signatureFormat, indexFileIDs, nil -} - -// Process: combine data -> add counter -> compress -> hash -> Base58 encode -func generateLayoutIDsBatch(layoutBase64, layoutSignatureB64 string, ic, maxFiles uint32) []string { - layoutWithSig := fmt.Sprintf("%s.%s", layoutBase64, layoutSignatureB64) - layoutIDs := make([]string, maxFiles) - - var buffer bytes.Buffer - buffer.Grow(len(layoutWithSig) + 10) - - for i := uint32(0); i < maxFiles; i++ { - // Build unique content with counter - buffer.Reset() - buffer.WriteString(layoutWithSig) - buffer.WriteByte('.') - buffer.WriteString(fmt.Sprintf("%d", ic+i)) - - // Compress for efficiency - compressedData, err := utils.ZstdCompress(buffer.Bytes()) - if err != nil { - continue - } - - // Hash for uniqueness - hash, err := utils.Blake3Hash(compressedData) - if err != nil { - continue - } - - // Base58 encode for readable ID - layoutIDs[i] = base58.Encode(hash) - } - - return layoutIDs + return cascade.CreateLayoutSignature(metadataFile, kr, userKeyName, ic, maxFiles) } -// generateIndexIDsBatch generates index file IDs using same process as layout IDs -func generateIndexIDsBatch(signatureFormat string, ic, maxFiles uint32) []string { - indexFileIDs := make([]string, maxFiles) - - var buffer bytes.Buffer - buffer.Grow(len(signatureFormat) + 10) - - for i := uint32(0); i < maxFiles; i++ { - buffer.Reset() - buffer.WriteString(signatureFormat) - buffer.WriteByte('.') - buffer.WriteString(fmt.Sprintf("%d", ic+i)) - - compressedData, err := utils.ZstdCompress(buffer.Bytes()) - if err != nil { - continue - } - hash, err := utils.Blake3Hash(compressedData) - if err != nil { - continue - } - indexFileIDs[i] = base58.Encode(hash) - } - return indexFileIDs -} - -// ComputeBlake3Hash computes Blake3 hash of the given message +// ComputeBlake3Hash is a wrapper for the common Blake3 hash function func ComputeBlake3Hash(msg []byte) ([]byte, error) { - hasher := blake3.New(32, nil) - if _, err := io.Copy(hasher, bytes.NewReader(msg)); err != nil { - return nil, err - } - return hasher.Sum(nil), nil -} + return cascade.ComputeBlake3Hash(msg) +} \ No newline at end of file