From 3c6b6fa34c2c8e7c46085933c4a9da9a7e314d63 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Mon, 7 Oct 2024 13:45:56 -0700 Subject: [PATCH 01/14] - initial code - basic workflows --- .github/workflows/codeql.yml | 57 +++++++++++++++++ .github/workflows/golangci.yml | 27 +++++++++ .github/workflows/test.yml | 68 +++++++++++++++++++++ .gitignore | 4 ++ go.mod | 17 ++++++ go.sum | 14 +++++ pkg/encryption/aes.go | 108 +++++++++++++++++++++++++++++++++ pkg/encryption/aes_test.go | 75 +++++++++++++++++++++++ 8 files changed, 370 insertions(+) create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/golangci.yml create mode 100644 .github/workflows/test.yml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pkg/encryption/aes.go create mode 100644 pkg/encryption/aes_test.go diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..247ea4f --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,57 @@ +name: "CodeQL" + +on: + pull_request: + paths: + - "**.go" + push: + branches: + - main + - release/** + paths: + - "**.go" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: 1.21 + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: "go" + queries: crypto-com/cosmos-sdk-codeql@main,security-and-quality + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/golangci.yml b/.github/workflows/golangci.yml new file mode 100644 index 0000000..5ff8e0f --- /dev/null +++ b/.github/workflows/golangci.yml @@ -0,0 +1,27 @@ +name: golangci-lint +on: + push: + tags: + - v* + branches: + - main + pull_request: +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + # pull-requests: read +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v3 + with: + go-version: 1.21 + - uses: actions/checkout@v3 + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version + version: v1.60.1 + args: --timeout 10m0s diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..5bf7644 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,68 @@ +name: Unit Tests + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + pull-requests: write + +concurrency: + group: ci-${{ github.ref }}-tests + cancel-in-progress: true + +jobs: + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 + with: + go-version: 1.21 + - uses: technote-space/get-diff-action@v6.1.0 + with: + PATTERNS: | + **/**.go + go.mod + go.sum + - name: Get data from Go build cache + uses: actions/cache@v3 + with: + path: | + ~/go/pkg/mod + ~/.cache/golangci-lint + ~/.cache/go-build + key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} + - name: Run unit tests + run: go test -mod=readonly -race -timeout 5m -tags='ledger test_ledger_mock' + + unit-test-check: + name: Unit Test Check + runs-on: ubuntu-latest + needs: tests + if: always() + steps: + - name: Get workflow conclusion + id: workflow_conclusion + uses: nick-fields/retry@v2 + with: + max_attempts: 2 + retry_on: error + timeout_seconds: 30 + command: | + jobs=$(curl https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs) + job_statuses=$(echo "$jobs" | jq -r '.jobs[] | .conclusion') + + for status in $job_statuses + do + echo "Status: $status" + if [[ "$status" == "failure" ]]; then + echo "Some or all tests have failed!" + exit 1 + fi + done + + echo "All tests have passed!" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6f72f89..f06ed35 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,7 @@ go.work.sum # env file .env +/.idea/modules.xml +/.idea/sei-cryptography.iml +/.idea/vcs.xml +/.idea/.gitignore diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..200c5cd --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module github.com/sei-protocol/sei-cryptography + +go 1.22 + +toolchain go1.22.0 + +require ( + github.com/ethereum/go-ethereum v1.14.11 + github.com/stretchr/testify v1.9.0 + golang.org/x/crypto v0.28.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..41cff02 --- /dev/null +++ b/go.sum @@ -0,0 +1,14 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ethereum/go-ethereum v1.14.11 h1:8nFDCUUE67rPc6AKxFj7JKaOa2W/W1Rse3oS6LvvxEY= +github.com/ethereum/go-ethereum v1.14.11/go.mod h1:+l/fr42Mma+xBnhefL/+z11/hcmJ2egl+ScIVPjhc7E= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/encryption/aes.go b/pkg/encryption/aes.go new file mode 100644 index 0000000..5a8803c --- /dev/null +++ b/pkg/encryption/aes.go @@ -0,0 +1,108 @@ +package encryption + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/binary" + "fmt" + "golang.org/x/crypto/hkdf" + "io" +) + +func GetAesKey(privKey ecdsa.PrivateKey, denom string) ([]byte, error) { + // Convert the ECDSA private key to bytes + privKeyBytes := privKey.D.Bytes() + + // Use a SHA-256 hash of the denom string as the salt + salt := sha256.Sum256([]byte(denom)) + + // Create an HKDF reader using SHA-256 + hkdf := hkdf.New(sha256.New, privKeyBytes, salt[:], []byte("aes key derivation")) + + // Allocate a 32-byte array for the AES key + aesKey := make([]byte, 32) + + _, err := io.ReadFull(hkdf, aesKey[:]) + if err != nil { + return []byte{}, err + } + + return aesKey, nil +} + +// Key must be a len 32 byte array for AES-256 +func EncryptAESGCM(value uint64, key []byte) (string, error) { + // Create a GCM cipher mode instance + aesgcm, err := getCipher(key) + if err != nil { + return "", err + } + + // Generate a nonce + nonce := make([]byte, aesgcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return "", err + } + + plaintext := make([]byte, 8) + // Convert the integer to []byte using BigEndian or LittleEndian + binary.BigEndian.PutUint64(plaintext, value) + + // Encrypt the data + ciphertext := aesgcm.Seal(nonce, nonce, plaintext, nil) + + // Encode to Base64 for storage or transmission + return base64.StdEncoding.EncodeToString(ciphertext), nil +} + +// Key must be a len 32 byte array for AES-256 +func DecryptAESGCM(ciphertextBase64 string, key []byte) (uint64, error) { + // Decode the Base64-encoded ciphertext + ciphertext, err := base64.StdEncoding.DecodeString(ciphertextBase64) + if err != nil { + return 0, err + } + + // Create a GCM cipher mode instance + aesgcm, err := getCipher(key) + if err != nil { + return 0, err + } + + // Extract the nonce + nonceSize := aesgcm.NonceSize() + if len(ciphertext) < nonceSize { + return 0, fmt.Errorf("ciphertext too short") + } + nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] + + // Decrypt the data + plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return 0, err + } + + value := binary.BigEndian.Uint64(plaintext) + + return value, nil +} + +// Creates the cipher from the key. Key must be a len 32 byte array for AES-256 +func getCipher(key []byte) (cipher.AEAD, error) { + // Create a new AES cipher + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // Create a GCM cipher mode instance + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + return aesgcm, nil +} diff --git a/pkg/encryption/aes_test.go b/pkg/encryption/aes_test.go new file mode 100644 index 0000000..81dfb0f --- /dev/null +++ b/pkg/encryption/aes_test.go @@ -0,0 +1,75 @@ +package encryption + +import ( + "testing" + + "crypto/ecdsa" + "crypto/rand" + "github.com/ethereum/go-ethereum/crypto/secp256k1" + "github.com/stretchr/testify/require" +) + +// GenerateKey generates a new private key we can use for testing. +func GenerateKey() (*ecdsa.PrivateKey, error) { + return ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) +} + +func TestAESKeyGeneration(t *testing.T) { + privateKey, err := GenerateKey() + require.Nil(t, err, "Should not have error here") + + denom := "factory/sei1239081236470/testToken" + aesPK, err := GetAesKey(*privateKey, denom) + require.Nil(t, err, "Should not have error here") + + // Test that aesPK is deterministically generated + aesPKAgain, err := GetAesKey(*privateKey, denom) + require.Equal(t, aesPK, aesPKAgain, "PK should be deterministically generated") + + // Test that changing the salt should generate a different key + altDenom := "factory/sei1239081236470/testToken1" + aesPKDiffSalt, err := GetAesKey(*privateKey, altDenom) + require.NotEqual(t, aesPK, aesPKDiffSalt, "PK should different for different salt") + + // Test same thing for salt of same length + altDenom = "factory/sei1239081236470/testTokeN" + aesPKDiffSalt, err = GetAesKey(*privateKey, altDenom) + require.NotEqual(t, aesPK, aesPKDiffSalt, "PK should different for different salt") + + // Test that different privateKey should generate different PK + altPrivateKey, err := GenerateKey() + require.Nil(t, err, "Should not have error here") + aesPKDiffPK, err := GetAesKey(*altPrivateKey, altDenom) + require.NotEqual(t, aesPK, aesPKDiffPK, "PK should different for different ESDCA Private Key") +} + +func TestAesEncryptionDecryption(t *testing.T) { + key := []byte("examplekey12345678901234567890ab") // 32 bytes for AES-256 + anotherKey := []byte("randomkey12345678901234567890abc") // 32 bytes for AES-256 + + value := uint64(3023) + + // Encrypt the plaintext + encrypted, err := EncryptAESGCM(value, key) + require.Nil(t, err, "Should have no error encrypting") + + // Decrypt the ciphertext + decrypted, err := DecryptAESGCM(encrypted, key) + require.Nil(t, err, "Should have no error decrypting") + require.Equal(t, value, decrypted) + + // Encrypt the plaintext again. This should produce a different ciphertext. + encryptedAgain, err := EncryptAESGCM(value, key) + require.Nil(t, err, "Should have no error encrypting") + require.NotEqual(t, encrypted, encryptedAgain) + + // Encrypt the plaintext again using a different key. This should produce a different ciphertext. + encryptedAgain, err = EncryptAESGCM(value, anotherKey) + require.Nil(t, err, "Should have no error encrypting") + require.NotEqual(t, encrypted, encryptedAgain) + + // Test that decryption with the wrong key will yield an error. + decryptedWrongly, err := DecryptAESGCM(encryptedAgain, key) + require.Empty(t, decryptedWrongly) + require.Error(t, err, "Should have an error decrypting") +} From 6086fa73d0282cb567f9efe6fa5b9ce34b1fb876 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Mon, 7 Oct 2024 14:15:02 -0700 Subject: [PATCH 02/14] - fix unit tests path - address linting issues --- .github/workflows/test.yml | 4 ++-- pkg/encryption/aes_test.go | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5bf7644..421f023 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,7 +37,7 @@ jobs: ~/.cache/go-build key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - name: Run unit tests - run: go test -mod=readonly -race -timeout 5m -tags='ledger test_ledger_mock' + run: go test -mod=readonly -race -timeout 5m ./... unit-test-check: name: Unit Test Check @@ -65,4 +65,4 @@ jobs: fi done - echo "All tests have passed!" \ No newline at end of file + echo "All tests have passed!" diff --git a/pkg/encryption/aes_test.go b/pkg/encryption/aes_test.go index 81dfb0f..872846e 100644 --- a/pkg/encryption/aes_test.go +++ b/pkg/encryption/aes_test.go @@ -24,22 +24,26 @@ func TestAESKeyGeneration(t *testing.T) { // Test that aesPK is deterministically generated aesPKAgain, err := GetAesKey(*privateKey, denom) + require.Nil(t, err, "Should not have error here") require.Equal(t, aesPK, aesPKAgain, "PK should be deterministically generated") // Test that changing the salt should generate a different key altDenom := "factory/sei1239081236470/testToken1" aesPKDiffSalt, err := GetAesKey(*privateKey, altDenom) + require.Nil(t, err, "Should not have error here") require.NotEqual(t, aesPK, aesPKDiffSalt, "PK should different for different salt") // Test same thing for salt of same length altDenom = "factory/sei1239081236470/testTokeN" aesPKDiffSalt, err = GetAesKey(*privateKey, altDenom) + require.Nil(t, err, "Should not have error here") require.NotEqual(t, aesPK, aesPKDiffSalt, "PK should different for different salt") // Test that different privateKey should generate different PK altPrivateKey, err := GenerateKey() require.Nil(t, err, "Should not have error here") aesPKDiffPK, err := GetAesKey(*altPrivateKey, altDenom) + require.Nil(t, err, "Should not have error here") require.NotEqual(t, aesPK, aesPKDiffPK, "PK should different for different ESDCA Private Key") } From 4d9f8e2c554c401aeb8e5a0a1f650dc6a0fccc39 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Mon, 7 Oct 2024 17:43:12 -0700 Subject: [PATCH 03/14] - update tests - update readme --- README.md | 5 +- pkg/encryption/aes.go | 20 ++- pkg/encryption/aes_test.go | 264 ++++++++++++++++++++++++++++--------- 3 files changed, 223 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 427cba9..b2f32da 100644 --- a/README.md +++ b/README.md @@ -1 +1,4 @@ -# sei-cryptography \ No newline at end of file +# Sei Cryptography + +## Description +A library for encryption and zero knowledge proofs. \ No newline at end of file diff --git a/pkg/encryption/aes.go b/pkg/encryption/aes.go index 5a8803c..85ec57e 100644 --- a/pkg/encryption/aes.go +++ b/pkg/encryption/aes.go @@ -9,11 +9,25 @@ import ( "encoding/base64" "encoding/binary" "fmt" - "golang.org/x/crypto/hkdf" + "github.com/ethereum/go-ethereum/crypto/secp256k1" "io" + + "golang.org/x/crypto/hkdf" ) -func GetAesKey(privKey ecdsa.PrivateKey, denom string) ([]byte, error) { +func GenerateKey() (*ecdsa.PrivateKey, error) { + return ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) +} + +// GetAesKey derives a 32-byte AES key using the provided ECDSA private key and denomination string. +// It employs HKDF with SHA-256, using the ECDSA private key bytes and a SHA-256 hash of the denom as salt. +func GetAESKey(privKey ecdsa.PrivateKey, denom string) ([]byte, error) { + if privKey.D == nil { + return nil, fmt.Errorf("private key D is nil") + } + if len(denom) == 0 { + return nil, fmt.Errorf("denom is empty") + } // Convert the ECDSA private key to bytes privKeyBytes := privKey.D.Bytes() @@ -28,7 +42,7 @@ func GetAesKey(privKey ecdsa.PrivateKey, denom string) ([]byte, error) { _, err := io.ReadFull(hkdf, aesKey[:]) if err != nil { - return []byte{}, err + return nil, err } return aesKey, nil diff --git a/pkg/encryption/aes_test.go b/pkg/encryption/aes_test.go index 872846e..073b255 100644 --- a/pkg/encryption/aes_test.go +++ b/pkg/encryption/aes_test.go @@ -1,79 +1,219 @@ package encryption import ( + "crypto/ecdsa" "testing" - "crypto/ecdsa" - "crypto/rand" - "github.com/ethereum/go-ethereum/crypto/secp256k1" "github.com/stretchr/testify/require" ) -// GenerateKey generates a new private key we can use for testing. -func GenerateKey() (*ecdsa.PrivateKey, error) { - return ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) -} - -func TestAESKeyGeneration(t *testing.T) { - privateKey, err := GenerateKey() - require.Nil(t, err, "Should not have error here") - - denom := "factory/sei1239081236470/testToken" - aesPK, err := GetAesKey(*privateKey, denom) - require.Nil(t, err, "Should not have error here") - - // Test that aesPK is deterministically generated - aesPKAgain, err := GetAesKey(*privateKey, denom) - require.Nil(t, err, "Should not have error here") - require.Equal(t, aesPK, aesPKAgain, "PK should be deterministically generated") - - // Test that changing the salt should generate a different key - altDenom := "factory/sei1239081236470/testToken1" - aesPKDiffSalt, err := GetAesKey(*privateKey, altDenom) - require.Nil(t, err, "Should not have error here") - require.NotEqual(t, aesPK, aesPKDiffSalt, "PK should different for different salt") - - // Test same thing for salt of same length - altDenom = "factory/sei1239081236470/testTokeN" - aesPKDiffSalt, err = GetAesKey(*privateKey, altDenom) - require.Nil(t, err, "Should not have error here") - require.NotEqual(t, aesPK, aesPKDiffSalt, "PK should different for different salt") - - // Test that different privateKey should generate different PK - altPrivateKey, err := GenerateKey() - require.Nil(t, err, "Should not have error here") - aesPKDiffPK, err := GetAesKey(*altPrivateKey, altDenom) - require.Nil(t, err, "Should not have error here") - require.NotEqual(t, aesPK, aesPKDiffPK, "PK should different for different ESDCA Private Key") +func TestGetAESKey(t *testing.T) { + tests := []struct { + name string + privateKey *ecdsa.PrivateKey + denom string + expectEqual bool + anotherKey *ecdsa.PrivateKey + anotherDenom string + }{ + { + name: "Deterministic Key Generation", + privateKey: generateTestKey(t), + denom: "factory/sei1239081236470/testToken", + expectEqual: true, + }, + { + name: "Different Denom (Salt) Generates Different Key", + privateKey: generateTestKey(t), + denom: "factory/sei1239081236470/testToken", + anotherDenom: "factory/sei1239081236470/testToken1", + expectEqual: false, + }, + { + name: "Different Denom (Salt) of same length Generates Different Key", + privateKey: generateTestKey(t), + denom: "factory/sei1239081236470/testToken1", + anotherDenom: "factory/sei1239081236470/testToken2", + expectEqual: false, + }, + { + name: "Different PrivateKey Generates Different Key", + privateKey: generateTestKey(t), + denom: "factory/sei1239081236470/testTokenN", + anotherKey: generateTestKey(t), + expectEqual: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + aesPK, err := GetAESKey(*tt.privateKey, tt.denom) + require.Nil(t, err, "Should not have error here") + + if tt.anotherKey != nil { + aesPKDiff, err := GetAESKey(*tt.anotherKey, tt.denom) + require.Nil(t, err) + require.NotEqual(t, aesPK, aesPKDiff, "PK should be different for different private keys") + } else if tt.anotherDenom != "" { + aesPKDiff, err := GetAESKey(*tt.privateKey, tt.anotherDenom) + require.Nil(t, err) + require.NotEqual(t, aesPK, aesPKDiff, "PK should be different for different salts") + } else { + + aesPKAgain, err := GetAESKey(*tt.privateKey, tt.denom) + require.Nil(t, err, "Should not have error here") + if tt.expectEqual { + require.Equal(t, aesPK, aesPKAgain, "PK should be deterministically generated") + } else { + require.NotEqual(t, aesPK, aesPKAgain, "PK should be different for different denoms") + } + } + }) + } } -func TestAesEncryptionDecryption(t *testing.T) { - key := []byte("examplekey12345678901234567890ab") // 32 bytes for AES-256 - anotherKey := []byte("randomkey12345678901234567890abc") // 32 bytes for AES-256 +func TestGetAESKey_InvalidInput(t *testing.T) { + // Nil private key + _, err := GetAESKey(*new(ecdsa.PrivateKey), "valid/denom") + require.Error(t, err, "Should return error for nil private key") - value := uint64(3023) + invalidPrivateKey := &ecdsa.PrivateKey{ /* Invalid key data */ } + _, err = GetAESKey(*invalidPrivateKey, "valid/denom") + require.Error(t, err, "Should return error for invalid private key") - // Encrypt the plaintext - encrypted, err := EncryptAESGCM(value, key) - require.Nil(t, err, "Should have no error encrypting") + validPrivateKey := generateTestKey(t) + _, err = GetAESKey(*validPrivateKey, "") + require.Error(t, err, "Should not allow empty denom(salt)") +} - // Decrypt the ciphertext - decrypted, err := DecryptAESGCM(encrypted, key) - require.Nil(t, err, "Should have no error decrypting") - require.Equal(t, value, decrypted) +func TestAESEncryptionDecryption(t *testing.T) { + tests := []struct { + name string + key []byte + anotherKey []byte + value uint64 + expectError bool + decryptWithKey []byte + encryptAgain bool + }{ + { + name: "Successful Encryption and Decryption", + key: []byte("examplekey12345678901234567890ab"), // 32 bytes for AES-256 + value: 3023, + expectError: false, + }, + { + name: "Encryption Yields Different Ciphertext If Encrypted Again", + key: []byte("examplekey12345678901234567890ab"), + value: 3023, + encryptAgain: true, + expectError: false, + }, + { + name: "Different Key Produces Different Ciphertext", + key: []byte("examplekey12345678901234567890ab"), + anotherKey: []byte("randomkey12345678901234567890abc"), // 32 bytes for AES-256 + value: 3023, + expectError: false, + }, + { + name: "Decryption with Wrong Key", + key: []byte("examplekey12345678901234567890ab"), + value: 3023, + expectError: true, + decryptWithKey: []byte("wrongkey12345678901234567890ab"), + }, + { + name: "Edge Case: Zero Value", + key: []byte("examplekey12345678901234567890ab"), + value: 0, + expectError: false, + }, + { + name: "Edge Case: Maximum Uint64", + key: []byte("examplekey12345678901234567890ab"), + value: ^uint64(0), + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + encrypted, err := EncryptAESGCM(tt.value, tt.key) + if tt.expectError && err != nil { + // Expected error during encryption + require.Error(t, err) + return + } + require.Nil(t, err, "Should have no error encrypting") + + if tt.anotherKey != nil { + encryptedAgain, err := EncryptAESGCM(tt.value, tt.anotherKey) + require.Nil(t, err, "Should have no error encrypting with another key") + require.NotEqual(t, encrypted, encryptedAgain, "Ciphertext should differ on encryption with different key") + } + + if tt.decryptWithKey != nil { + _, err := DecryptAESGCM(encrypted, tt.decryptWithKey) + require.Error(t, err, "Should have an error decrypting with wrong key") + return + } + + decrypted, err := DecryptAESGCM(encrypted, tt.key) + if tt.expectError { + require.Empty(t, decrypted) + require.Error(t, err, "Should have an error decrypting with wrong key") + } else { + require.Nil(t, err, "Should have no error decrypting") + require.Equal(t, tt.value, decrypted) + } + + // Additional checks for encryption consistency + if tt.encryptAgain { + encryptedAgain, err := EncryptAESGCM(tt.value, tt.key) + require.Nil(t, err, "Should have no error encrypting again") + require.NotEqual(t, encrypted, encryptedAgain, "Ciphertext should differ on re-encryption") + } + }) + } +} - // Encrypt the plaintext again. This should produce a different ciphertext. - encryptedAgain, err := EncryptAESGCM(value, key) - require.Nil(t, err, "Should have no error encrypting") - require.NotEqual(t, encrypted, encryptedAgain) +func TestEncryptAESGCM_InvalidKeyLength(t *testing.T) { + invalidKeys := [][]byte{ + {}, // Empty key + []byte("shortkey"), // Too short + []byte("thiskeyiswaytoolongforaesgcm"), // Too long + } + + value := uint64(1234) + + for _, key := range invalidKeys { + t.Run("InvalidKeyLength", func(t *testing.T) { + _, err := EncryptAESGCM(value, key) + require.Error(t, err, "Should return error for invalid key length") + }) + } +} - // Encrypt the plaintext again using a different key. This should produce a different ciphertext. - encryptedAgain, err = EncryptAESGCM(value, anotherKey) - require.Nil(t, err, "Should have no error encrypting") - require.NotEqual(t, encrypted, encryptedAgain) +func TestDecryptAESGCM_InvalidCiphertext(t *testing.T) { + key := []byte("examplekey12345678901234567890ab") + invalidCiphertexts := [][]byte{ + {}, // Empty ciphertext + []byte("invalidciphertext"), + } + + for _, ct := range invalidCiphertexts { + t.Run("InvalidCiphertext", func(t *testing.T) { + decrypted, err := DecryptAESGCM(string(ct), key) + require.Empty(t, decrypted) + require.Error(t, err, "Should return error for invalid ciphertext") + }) + } +} - // Test that decryption with the wrong key will yield an error. - decryptedWrongly, err := DecryptAESGCM(encryptedAgain, key) - require.Empty(t, decryptedWrongly) - require.Error(t, err, "Should have an error decrypting") +// Helper function to generate a test private key +func generateTestKey(t *testing.T) *ecdsa.PrivateKey { + privateKey, err := GenerateKey() + require.Nil(t, err, "Failed to generate private key") + return privateKey } From fca4d71fd8e2176afb3ed17cec0a1775c4e0b940 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 8 Oct 2024 12:02:54 -0700 Subject: [PATCH 04/14] Twisted ElGamal --- go.mod | 16 +- go.sum | 82 +++++++++- pkg/encryption/elgamal/common.go | 67 ++++++++ pkg/encryption/elgamal/encryption.go | 176 +++++++++++++++++++++ pkg/encryption/elgamal/encryption_test.go | 180 ++++++++++++++++++++++ pkg/encryption/elgamal/operations.go | 154 ++++++++++++++++++ pkg/encryption/elgamal/types.go | 69 +++++++++ 7 files changed, 734 insertions(+), 10 deletions(-) create mode 100644 pkg/encryption/elgamal/common.go create mode 100644 pkg/encryption/elgamal/encryption.go create mode 100644 pkg/encryption/elgamal/encryption_test.go create mode 100644 pkg/encryption/elgamal/operations.go create mode 100644 pkg/encryption/elgamal/types.go diff --git a/go.mod b/go.mod index 200c5cd..b34a293 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,23 @@ module github.com/sei-protocol/sei-cryptography -go 1.22 - -toolchain go1.22.0 +go 1.21 require ( - github.com/ethereum/go-ethereum v1.14.11 + github.com/bwesterb/go-ristretto v1.2.3 + github.com/coinbase/kryptology v1.8.0 + github.com/ethereum/go-ethereum v1.10.26 github.com/stretchr/testify v1.9.0 - golang.org/x/crypto v0.28.0 + golang.org/x/crypto v0.27.0 ) require ( + filippo.io/edwards25519 v1.0.0-rc.1 // indirect + github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401 // indirect + github.com/consensys/gnark-crypto v0.5.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/pretty v0.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.25.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 41cff02..1c49698 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,86 @@ +filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmGfU= +filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ= +github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401 h1:0tjUthKCaF8zwF9Qg7lfnep0xdo4n8WiFUfQPaMHX6g= +github.com/btcsuite/btcd v0.21.0-beta.0.20201114000516-e9c7a5ac6401/go.mod h1:Sv4JPQ3/M+teHz9Bo5jBpkNcP0x6r7rdihlNL/7tTAs= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/goleveldb v1.0.0/go.mod h1:QiK9vBlgftBg6rWQIj6wFzbPfRjiykIEhBH4obrXJ/I= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/coinbase/kryptology v1.8.0 h1:Aoq4gdTsJhSU3lNWsD5BWmFSz2pE0GlmrljaOxepdYY= +github.com/coinbase/kryptology v1.8.0/go.mod h1:RYXOAPdzOGUe3qlSFkMGn58i3xUA8hmxYHksuq+8ciI= +github.com/consensys/bavard v0.1.8-0.20210915155054-088da2f7f54a/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.5.3 h1:4xLFGZR3NWEH2zy+YzvzHicpToQR8FXFbfLNvpGB+rE= +github.com/consensys/gnark-crypto v0.5.3/go.mod h1:hOdPlWQV1gDLp7faZVeg8Y0iEPFaOUnCc4XeCCk96p0= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/ethereum/go-ethereum v1.14.11 h1:8nFDCUUE67rPc6AKxFj7JKaOa2W/W1Rse3oS6LvvxEY= -github.com/ethereum/go-ethereum v1.14.11/go.mod h1:+l/fr42Mma+xBnhefL/+z11/hcmJ2egl+ScIVPjhc7E= +github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= +github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= +github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420205809-ac73e9fd8988/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/pkg/encryption/elgamal/common.go b/pkg/encryption/elgamal/common.go new file mode 100644 index 0000000..fc2278d --- /dev/null +++ b/pkg/encryption/elgamal/common.go @@ -0,0 +1,67 @@ +package elgamal + +import ( + "crypto/ecdsa" + "crypto/sha256" + "github.com/coinbase/kryptology/pkg/core/curves" + "golang.org/x/crypto/hkdf" + "io" +) + +// H is a random point on the eliptic curve that is unrelated to G. +const H_STRING = "gPt25pi0eDphSiXWu0BIeIvyVATCtwhslTqfqvNhW2c" + +func (teg TwistedElGamal) KeyGen(privateKey ecdsa.PrivateKey, denom string) (*KeyPair, error) { + // Fixed base point H + H := teg.GetH() + + s, err := getPrivateKey(privateKey, denom) + if err != nil { + return nil, err + } + + // Compute the public key P = s^(-1) * H + sInv, _ := s.Invert() + + P := H.Mul(sInv) // P = s^(-1) * H + + return &KeyPair{ + PublicKey: P, + PrivateKey: s, + }, nil +} + +func (teg TwistedElGamal) GetG() curves.Point { + return curves.Point.Generator(teg.curve.Point) +} + +func (teg TwistedElGamal) GetH() curves.Point { + bytes := []byte(H_STRING) + return teg.curve.Point.Hash(bytes) +} + +func getPrivateKey(privateKey ecdsa.PrivateKey, denom string) (curves.Scalar, error) { + // Convert the ECDSA private key to bytes + privKeyBytes := privateKey.D.Bytes() + + // Hash the denom to get a salt. + salt := sha256.Sum256([]byte(denom)) + + // Create an HKDF reader using SHA-256 + hkdf := hkdf.New(sha256.New, privKeyBytes, salt[:], []byte("elgamal scalar derivation")) + + // Generate 64 bytes of randomness from HKDF output + var scalarBytes [64]byte + _, err := io.ReadFull(hkdf, scalarBytes[:]) + if err != nil { + return nil, err + } + + // Initialize the scalar (private key) using the generated bytes + s, err := curves.ED25519().Scalar.SetBytesWide(scalarBytes[:]) + if err != nil { + return nil, err + } + + return s, nil +} diff --git a/pkg/encryption/elgamal/encryption.go b/pkg/encryption/elgamal/encryption.go new file mode 100644 index 0000000..5ab77de --- /dev/null +++ b/pkg/encryption/elgamal/encryption.go @@ -0,0 +1,176 @@ +package elgamal + +import ( + crand "crypto/rand" + "fmt" + "github.com/bwesterb/go-ristretto" + "github.com/coinbase/kryptology/pkg/core/curves" + "math/big" +) + +type TwistedElGamal struct { + curve *curves.Curve + mapping map[string]int + + // Notes the sizes decrypted so far. This helps us know if the values we need are already in the map. + maxMapping map[MaxBits]bool +} + +func NewTwistedElgamal(curve *curves.Curve) *TwistedElGamal { + var s ristretto.Point + s.SetZero() + mapping := make(map[string]int) + maxMapping := make(map[MaxBits]bool) + mapping[s.String()] = 0 + + return &TwistedElGamal{ + curve: curve, + maxMapping: maxMapping, + mapping: mapping, + } +} + +func (teg TwistedElGamal) Encrypt(pk curves.Point, message uint64) (*Ciphertext, curves.Scalar, error) { + // Fixed base points G and H + H := teg.GetH() + G := teg.GetG() + + // Convert message x (big.Int) to a scalar on the elliptic curve + bigIntMessage := new(big.Int).SetUint64(message) + x, _ := teg.curve.Scalar.SetBigInt(bigIntMessage) + + // Generate a random scalar r + randomFactor := curves.ED25519().Scalar.Random(crand.Reader) + + // Compute the Pedersen commitment: C = r * H + x * G + rH := H.Mul(randomFactor) // r * H + xG := G.Mul(x) // x * G + C := rH.Add(xG) // C = r * H + x * G + + // Compute the decryption handle: D = r * P + D := pk.Mul(randomFactor) // D = r * P + ciphertext := Ciphertext{ + C: C, + D: D, + } + + return &ciphertext, randomFactor, nil +} + +func (teg TwistedElGamal) EncryptWithRand(pk curves.Point, message uint64, randomFactor curves.Scalar) (*Ciphertext, curves.Scalar, error) { + // Fixed base points G and H + H := teg.GetH() + G := teg.GetG() + + // Convert message x (big.Int) to a scalar on the elliptic curve + bigIntMessage := new(big.Int).SetUint64(message) + x, _ := teg.curve.Scalar.SetBigInt(bigIntMessage) + + // Compute the Pedersen commitment: C = r * H + x * G + rH := H.Mul(randomFactor) // r * H + xG := G.Mul(x) // x * G + C := rH.Add(xG) // C = r * H + x * G + + // Compute the decryption handle: D = r * P + D := pk.Mul(randomFactor) // D = r * P + ciphertext := Ciphertext{ + C: C, + D: D, + } + + return &ciphertext, randomFactor, nil +} + +// Decrypt decrypts the ciphertext ct using the private key sk = s. +// MaxBits denotes the maximum size of the decrypted message. The lower this can be set, the faster we can decrypt the message. +func (teg TwistedElGamal) Decrypt(sk curves.Scalar, ct *Ciphertext, maxBits MaxBits) (*int, error) { + G := teg.GetG() + + // Compute s * D + sD := ct.D.Mul(sk) + + // Compute C - s * D + var result = ct.C.Sub(sD) + + // Now, we need to solve for x from x * G = result. + // There's no direct method in go-ristretto for solving this, so in practice, + // this step requires a brute force or precomputation to retrieve x. + // For simplicity, let's assume we know the range of x and can brute force it. + + // We split x into x_lo and x_hi, the bottom and top 16 bits of x, such that x*G = (x_lo * G) + (x_hi * 2^(maxBits/2) * G) + // First construct the mapping of x_hi * G * 2^(maxBits/2) : x_hi * 2^(maxBits/2) for all values of x_hi in the range of 2^(maxBits/2). + if _, ok := teg.maxMapping[maxBits]; !ok { + teg.updateIterMap(maxBits) + } + + // Then iterate over all values of x_lo. + // If for some value x_lo, (x * G) - (x_lo * G) exists in the map above, then we satisfy the equation above and x = x_lo + 2^(maxBits/2) * x_hi + iMax := 1<<(uint(maxBits)/2) - 1 + + for i := 0; i < iMax; i++ { + // Attempt to brute-force x. + // We test all values of x_lo in (x * G) - x_lo * G + xValue := new(big.Int).SetUint64(uint64(i)) + x, _ := curves.ED25519().Scalar.SetBigInt(xValue) + + xLoG := G.Mul(x) + test := result.Sub(xLoG) + + compressedKey := getCompressedKeyString(test) + if xHiMultiplied, ok := teg.mapping[compressedKey]; ok { + // If the xHi value found is larger than 2^maxBits, it is not valid. + if xHiMultiplied > (1 << maxBits) { + continue + } + xComputed := xHiMultiplied + i + return &xComputed, nil + } + } + + return nil, fmt.Errorf("could not find x") +} + +// Helper function to create large maps used by the decryption funciton. +// Constructs the mapping of x_hi * 2^(maxBits/2) * G : x_hi * (maxBits/2) for all values of x_hi. +// This table can then be used to find x_lo. +func (teg TwistedElGamal) updateIterMap(maxBits MaxBits) { + G := teg.GetG() + + shift := uint(maxBits) / 2 + for i := 0; i < 1< MaxBits48 { + return nil, fmt.Errorf("maxBits must be at most 48, provided (%d)", maxBits) + } + values := []MaxBits{MaxBits16, MaxBits32, MaxBits40, MaxBits48} + for _, bits := range values { + if bits > maxBits { + return nil, fmt.Errorf("Failed to find value") + } + + res, err := teg.Decrypt(sk, ct, bits) + if err == nil { + return res, nil + } + } + + return nil, fmt.Errorf("Failed to find value") +} + +func getCompressedKeyString(key curves.Point) string { + compressedKey := key.ToAffineCompressed() + return string(compressedKey) +} diff --git a/pkg/encryption/elgamal/encryption_test.go b/pkg/encryption/elgamal/encryption_test.go new file mode 100644 index 0000000..1ac4089 --- /dev/null +++ b/pkg/encryption/elgamal/encryption_test.go @@ -0,0 +1,180 @@ +package elgamal + +import ( + "crypto/ecdsa" + "crypto/rand" + "github.com/coinbase/kryptology/pkg/core/curves" + "github.com/ethereum/go-ethereum/crypto/secp256k1" + "github.com/stretchr/testify/require" + "math" + "testing" +) + +func GenerateKey() (*ecdsa.PrivateKey, error) { + return ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) +} + +func TestKeyGeneration(t *testing.T) { + privateKey, err := GenerateKey() + require.Nil(t, err) + + ed25519 := curves.ED25519() + eg := NewTwistedElgamal(ed25519) + + denom := "factory/sei1239081236470/testToken" + keyPair, err := eg.KeyGen(*privateKey, denom) + require.Nil(t, err) + + // Test that keyPair is deterministically generated + keyPairAgain, err := eg.KeyGen(*privateKey, denom) + require.Nil(t, err) + require.Equal(t, keyPair, keyPairAgain, "PK should be deterministically generated") + + // Test that changing the salt should generate a different key + altDenom := "factory/sei1239081236470/testToken1" + keyPairDiffSalt, err := eg.KeyGen(*privateKey, altDenom) + require.Nil(t, err) + require.NotEqual(t, keyPair, keyPairDiffSalt, "PK should be different for different salt") + + // Test same thing for salt of same length + altDenom = "factory/sei1239081236470/testTokeN" + keyPairDiffSalt, err = eg.KeyGen(*privateKey, altDenom) + require.Nil(t, err) + require.NotEqual(t, keyPair, keyPairDiffSalt, "PK should be different for different salt") + + // Test that different privateKey should generate different PK + altPrivateKey, err := GenerateKey() + require.Nil(t, err) + keyPairDiffPK, err := eg.KeyGen(*altPrivateKey, altDenom) + require.Nil(t, err) + require.NotEqual(t, keyPair, keyPairDiffPK, "PK should be different for different ESDCA Private Key") +} + +func TestEncryptionDecryption(t *testing.T) { + privateKey, _ := GenerateKey() + altPrivateKey, _ := GenerateKey() + + ed25519 := curves.ED25519() + eg := NewTwistedElgamal(ed25519) + + denom := "factory/sei1239081236470/testToken" + + keys, _ := eg.KeyGen(*privateKey, denom) + altKeys, _ := eg.KeyGen(*altPrivateKey, denom) + + // Happy Path + value := 108 + ciphertext, _, err := eg.Encrypt(keys.PublicKey, uint64(value)) + require.Nil(t, err, "Should have no error while encrypting") + + decrypted, err := eg.Decrypt(keys.PrivateKey, ciphertext, MaxBits16) + require.Nil(t, err, "Should have no error while decrypting") + require.Equal(t, value, *decrypted, "Should have the same value") + + decrypted, err = eg.Decrypt(keys.PrivateKey, ciphertext, MaxBits32) + require.Nil(t, err, "Should have no error while decrypting") + require.Equal(t, value, *decrypted, "Should have the same value") + + // Using a different private key to decrypt should yield an error. + decryptedWrongly, err := eg.Decrypt(altKeys.PrivateKey, ciphertext, MaxBits32) + require.Nil(t, decryptedWrongly) + require.Error(t, err, "Should be unable to decrypt using the wrong private key") + + // Test overflow behavior + ciphertextOverflow, _, err := eg.Encrypt(keys.PublicKey, math.MaxUint64) + require.Nil(t, err, "Should have no error while encrypting") + decryptedOverflow, err := eg.Decrypt(keys.PrivateKey, ciphertextOverflow, MaxBits32) + require.Nil(t, decryptedOverflow) + require.Error(t, err, "Should be unable to decrypt the invalid overflow value") +} + +// Due to the size of 48 bit numbers, this test takes a really long time (~1hr) to run. +func Test48BitEncryptionDecryption(t *testing.T) { + privateKey, err := GenerateKey() + require.Nil(t, err) + denom := "factory/sei1239081236470/testToken" + + ed25519 := curves.ED25519() + eg := NewTwistedElgamal(ed25519) + keys, _ := eg.KeyGen(*privateKey, denom) + + // First decrypt a 32 bit number (sets up the decryptor for a later test) + value := 108092 + ciphertext, _, err := eg.Encrypt(keys.PublicKey, uint64(value)) + require.Nil(t, err, "Should have no error while encrypting") + + decrypted, err := eg.DecryptLargeNumber(keys.PrivateKey, ciphertext, MaxBits48) + require.Nil(t, err, "Should have no error while decrypting") + require.Equal(t, value, *decrypted, "Should have the same value") + + // Create a large <48 bit number to be encrypted. + largeValue := 1 << 39 + largeCiphertext, _, err := eg.Encrypt(keys.PublicKey, uint64(largeValue)) + require.Nil(t, err, "Should have no error while encrypting") + + largeDecrypted, err := eg.DecryptLargeNumber(keys.PrivateKey, largeCiphertext, MaxBits48) + require.Nil(t, err, "Should have no error while decrypting") + require.Equal(t, largeValue, *largeDecrypted, "Should have the same value") + + // Attempting to decrypt with the wrong max bits set should yield an error. + decryptedWrongly, err := eg.DecryptLargeNumber(keys.PrivateKey, largeCiphertext, MaxBits32) + require.Nil(t, decryptedWrongly) + require.Error(t, err, "Should be unable to decrypt using the wrong maxBits") + + // Decrypting the 48 bit value should not corrupt the map for 32 bit values. + decrypted, err = eg.DecryptLargeNumber(keys.PrivateKey, ciphertext, MaxBits32) + require.Nil(t, err, "Should still have no error while decrypting") + require.Equal(t, value, *decrypted, "Should still have the same value") +} + +func TestAddCiphertext(t *testing.T) { + privateKey, _ := GenerateKey() + altPrivateKey, _ := GenerateKey() + + denom := "factory/sei1239081236470/testToken" + + ed25519 := curves.ED25519() + eg := NewTwistedElgamal(ed25519) + + keys, _ := eg.KeyGen(*privateKey, denom) + altKeys, _ := eg.KeyGen(*altPrivateKey, denom) + + // Happy Path + value1 := 30842 + ciphertext1, _, err := eg.Encrypt(keys.PublicKey, uint64(value1)) + require.Nil(t, err, "Should have no error while encrypting") + + value2 := 1901 + ciphertext2, _, err := eg.Encrypt(keys.PublicKey, uint64(value2)) + require.Nil(t, err, "Should have no error while encrypting") + + ciphertextSum, err := AddCiphertext(ciphertext1, ciphertext2) + require.Nil(t, err, "Should have no error while adding ciphertexts") + + decrypted, err := eg.Decrypt(keys.PrivateKey, ciphertextSum, MaxBits32) + require.Nil(t, err, "Should have no error while decrypting") + require.NotNil(t, decrypted) + require.Equal(t, value1+value2, *decrypted, "Decrypted sum should be correct") + + // Test that the add operation is commutative by adding in the other order. + ciphertextSumInv, err := AddCiphertext(ciphertext2, ciphertext1) + require.Nil(t, err, "Should have no error while adding ciphertexts") + require.Equal(t, ciphertextSum, ciphertextSumInv, "Summation is a deterministic operation. Both ciphertexts should be the same") + + // Test addition of 2 ciphertexts encoded with different public keys. + ciphertext2alt, _, err := eg.Encrypt(altKeys.PublicKey, uint64(value2)) + require.Nil(t, err, "Should have no error while encrypting") + + // Even though ciphertexts were encoded using different keys, addition doesn't throw an error. + // However, the resulting ciphertext is unlikely to be decodable. + ciphertextSum, err = AddCiphertext(ciphertext1, ciphertext2alt) + require.Nil(t, err, "Even though ciphertexts were encoded using different keys, addition doesn't throw an error.") + + decrypted, err = eg.Decrypt(keys.PrivateKey, ciphertextSum, MaxBits32) + require.Nil(t, decrypted) + require.Error(t, err, "Ciphertext should be undecodable using either private key") + + decrypted, err = eg.Decrypt(altKeys.PrivateKey, ciphertextSum, MaxBits32) + require.Nil(t, decrypted) + require.Error(t, err, "Ciphertext should be undecodable using either private key") +} diff --git a/pkg/encryption/elgamal/operations.go b/pkg/encryption/elgamal/operations.go new file mode 100644 index 0000000..05f3f25 --- /dev/null +++ b/pkg/encryption/elgamal/operations.go @@ -0,0 +1,154 @@ +package elgamal + +import ( + "fmt" + "github.com/coinbase/kryptology/pkg/core/curves" + "math/big" +) + +// AddScalar Adds a scalar value to some ciphertext +// For some Ciphertext that encodes x, (C = rH + xG, D = rP), we can just add amount*G to C. +// The new Ciphertext C' will encode C' = rH + xG + amountG = rH + (x+amount)G. +// We do not need to touch D, since the randomness has not changed. +func (teg TwistedElGamal) AddScalar(ciphertext *Ciphertext, amount uint64) (*Ciphertext, error) { + G := teg.GetG() + // Create a scalar from the amount. + bigIntAmount := new(big.Int).SetUint64(amount) + scalarAmount, err := curves.ED25519().Scalar.SetBigInt(bigIntAmount) + if err != nil { + return nil, err + } + + // Multiply the amount by G + amountG := G.Mul(scalarAmount) + + newC := ciphertext.C.Add(amountG) + return &Ciphertext{ + C: newC, + D: ciphertext.D, + }, nil +} + +// SubScalar Subtracts a scalar value to some ciphertext +// For some Ciphertext that encodes x, (C = rH + xG, D = rP), we can just sub amount*G to C. +// The new Ciphertext C' will encode C' = rH + xG - amountG = rH + (x-amount)G. +// We do not need to touch D, since the randomness has not changed. +func (teg TwistedElGamal) SubScalar(ciphertext *Ciphertext, amount uint64) (*Ciphertext, error) { + G := teg.GetG() + // Create a scalar from the amount. + bigIntAmount := new(big.Int).SetUint64(amount) + scalarAmount, err := curves.ED25519().Scalar.SetBigInt(bigIntAmount) + if err != nil { + return nil, err + } + + // Multiply the amount by G + amountG := G.Mul(scalarAmount) + + newC := ciphertext.C.Sub(amountG) + return &Ciphertext{ + C: newC, + D: ciphertext.D, + }, nil +} + +// AddCiphertext Add takes two ciphertexts and returns the ciphertext of their sum. +func AddCiphertext(ct1 *Ciphertext, ct2 *Ciphertext) (*Ciphertext, error) { + // cSum = C1 + C2 + cSum := ct1.C.Add(ct2.C) + + // Dsum = D1 + D2 + dSum := ct1.D.Add(ct2.D) + + return &Ciphertext{ + C: cSum, + D: dSum, + }, nil +} + +// SubtractCiphertext Subtract takes two ciphertexts and returns the ciphertext of their difference. +func SubtractCiphertext(ct1 *Ciphertext, ct2 *Ciphertext) (*Ciphertext, error) { + // Cdiff = C1 - C2 + cDiff := ct1.C.Sub(ct2.C) + + // Ddiff = D1 - D2 + dDiff := ct1.D.Sub(ct2.D) + + return &Ciphertext{ + C: cDiff, + D: dDiff, + }, nil +} + +// ScalarMultCiphertext Multiply takes a ciphertext ct and returns the ciphertext of their ct * factor. +func ScalarMultCiphertext(ct *Ciphertext, factor uint64) (*Ciphertext, error) { + scalarValue := new(big.Int).SetUint64(factor) + factorScalar, _ := curves.ED25519().Scalar.SetBigInt(scalarValue) + + // Cmul = C * Factor + cMul := ct.C.Mul(factorScalar) + + // Dmul = D * Factor + dMul := ct.D.Mul(factorScalar) + + return &Ciphertext{ + C: cMul, + D: dMul, + }, nil +} + +// Additional Functions + +// AddWithLoHi performs the operation: left_ciphertext + (right_ciphertext_lo + 2^16 * right_ciphertext_hi) +func AddWithLoHi(leftCiphertext, rightCiphertextLo, rightCiphertextHi *Ciphertext) (*Ciphertext, error) { + // Step 1: Define shift_scalar as 2^16 (which is 65536) + shiftScalar := 1 << 16 + + // Step 2: Shift rightCiphertextHi by multiplying by shift_scalar + shiftedRightCiphertextHi, err := ScalarMultCiphertext(rightCiphertextHi, uint64(shiftScalar)) + if err != nil { + return nil, fmt.Errorf("failed to shift rightCiphertextHi: %v", err) + } + + // Step 3: Add rightCiphertextLo and shiftedRightCiphertextHi + combinedRightCiphertext, err := AddCiphertext(rightCiphertextLo, shiftedRightCiphertextHi) + if err != nil { + return nil, fmt.Errorf("failed to combine right_ciphertext_lo and shifted_right_ciphertext_hi: %v", err) + } + + // Step 4: Add the result to leftCiphertext + finalCiphertext, err := AddCiphertext(leftCiphertext, combinedRightCiphertext) + if err != nil { + return nil, fmt.Errorf("failed to add combined_right_ciphertext to left_ciphertext: %v", err) + } + + // Return the final ciphertext + return finalCiphertext, nil +} + +// SubWithLoHi performs the operation: left_ciphertext - (right_ciphertext_lo + 2^16 * right_ciphertext_hi) +func SubWithLoHi(leftCiphertext, rightCiphertextLo, rightCiphertextHi *Ciphertext) (*Ciphertext, error) { + // Step 1: Define shift_scalar as 2^16 (which is 65536) + shiftScalar := 1 << 16 + + // Step 2: Shift rightCiphertextHi by multiplying by shift_scalar + shiftedRightCiphertextHi, err := ScalarMultCiphertext(rightCiphertextHi, uint64(shiftScalar)) + if err != nil { + return nil, fmt.Errorf("failed to shift rightCiphertextHi: %v", err) + } + + // Step 3: Add rightCiphertextLo and shiftedRightCiphertextHi + combinedRightCiphertext, err := AddCiphertext(rightCiphertextLo, shiftedRightCiphertextHi) + if err != nil { + return nil, fmt.Errorf("failed to combine right_ciphertext_lo and shifted_right_ciphertext_hi: %v", err) + } + + // Step 4: Subtract the result from leftCiphertext + finalCiphertext, err := SubtractCiphertext(leftCiphertext, combinedRightCiphertext) + if err != nil { + return nil, fmt.Errorf("failed to subtract combined_right_ciphertext from left_ciphertext: %v", err) + } + + // Return the final ciphertext + return finalCiphertext, nil +} diff --git a/pkg/encryption/elgamal/types.go b/pkg/encryption/elgamal/types.go new file mode 100644 index 0000000..e6cb627 --- /dev/null +++ b/pkg/encryption/elgamal/types.go @@ -0,0 +1,69 @@ +package elgamal + +import ( + "encoding/json" + "github.com/coinbase/kryptology/pkg/core/curves" +) + +// KeyPair represents a public-private key pair. +type KeyPair struct { + PublicKey curves.Point + PrivateKey curves.Scalar +} + +// Ciphertext represents the ciphertext of a message. +type Ciphertext struct { + C curves.Point `json:"c,omitempty"` + D curves.Point `json:"d,omitempty"` +} + +// Custom type for maxBits +type MaxBits uint + +// Define allowed values for maxBits using the custom type +const ( + MaxBits16 MaxBits = 16 + MaxBits32 MaxBits = 32 + MaxBits40 MaxBits = 40 + MaxBits48 MaxBits = 48 +) + +// MarshalJSON for Ciphertext +func (c Ciphertext) MarshalJSON() ([]byte, error) { + // Serialize the points to a format you prefer + return json.Marshal(map[string]interface{}{ + "c": c.C.ToAffineCompressed(), // Assuming `ToAffineCompressed` returns a byte slice + "d": c.D.ToAffineCompressed(), + }) +} + +// UnmarshalJSON for Ciphertext +func (c *Ciphertext) UnmarshalJSON(data []byte) error { + // Create a temporary structure to decode JSON + var temp struct { + C []byte `json:"c"` + D []byte `json:"d"` + } + + // Unmarshal into the temp structure + if err := json.Unmarshal(data, &temp); err != nil { + return err + } + + // Convert the byte arrays back into curve points + // Assuming `FromCompressed` is a method to parse compressed points + pointC, err := curves.ED25519().Point.FromAffineCompressed(temp.C) + if err != nil { + return err + } + pointD, err := curves.ED25519().Point.FromAffineCompressed(temp.D) + if err != nil { + return err + } + + // Assign the points to the Ciphertext struct + c.C = pointC + c.D = pointD + + return nil +} From 3956e6cbd6a0a51082ab3e5200a140cc3d8af058 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 8 Oct 2024 12:27:34 -0700 Subject: [PATCH 05/14] increase time out --- .github/workflows/golangci.yml | 3 --- .github/workflows/test.yml | 6 +----- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/golangci.yml b/.github/workflows/golangci.yml index 5ff8e0f..0ec63e0 100644 --- a/.github/workflows/golangci.yml +++ b/.github/workflows/golangci.yml @@ -8,8 +8,6 @@ on: pull_request: permissions: contents: read - # Optional: allow read access to pull request. Use with `only-new-issues` option. - # pull-requests: read jobs: golangci: name: lint @@ -22,6 +20,5 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: - # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version version: v1.60.1 args: --timeout 10m0s diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 421f023..ccba329 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,10 +10,6 @@ permissions: contents: read pull-requests: write -concurrency: - group: ci-${{ github.ref }}-tests - cancel-in-progress: true - jobs: tests: runs-on: ubuntu-latest @@ -37,7 +33,7 @@ jobs: ~/.cache/go-build key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - name: Run unit tests - run: go test -mod=readonly -race -timeout 5m ./... + run: go test -mod=readonly -race -timeout 10m ./... unit-test-check: name: Unit Test Check From 4762d5fa72482f0e6e1c32a846ee27b3634f7bf6 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 8 Oct 2024 14:06:22 -0700 Subject: [PATCH 06/14] increase time out to 20m --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ccba329..0383a80 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,7 +33,7 @@ jobs: ~/.cache/go-build key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - name: Run unit tests - run: go test -mod=readonly -race -timeout 10m ./... + run: go test -mod=readonly -race -timeout 20m ./... unit-test-check: name: Unit Test Check From 4ed93ee61e1af651a9ea8e49a8578638751c4155 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 8 Oct 2024 14:50:12 -0700 Subject: [PATCH 07/14] update setup to run elgamal tests in parallel --- .github/workflows/test.yml | 100 +++++++++++++++++++--- pkg/encryption/elgamal/encryption.go | 41 +++++---- pkg/encryption/elgamal/encryption_test.go | 42 +++++---- 3 files changed, 128 insertions(+), 55 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0383a80..5a287f6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,34 +11,106 @@ permissions: pull-requests: write jobs: + setup: + runs-on: ubuntu-latest + outputs: + cache-key: ${{ steps.cache-go-build.outputs.cache-hit }} + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.21 + + - name: Cache Go modules and build cache + id: cache-go-build + uses: actions/cache@v3 + with: + path: | + ~/go/pkg/mod + ~/.cache/golangci-lint + ~/.cache/go-build + key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} + restore-keys: | + ${{ runner.os }}-go-build- + + # General Tests Job (Excluding ElGamal) tests: + needs: setup runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 with: go-version: 1.21 - - uses: technote-space/get-diff-action@v6.1.0 + + - name: Restore cache + uses: actions/cache@v3 + with: + path: | + ~/go/pkg/mod + ~/.cache/golangci-lint + ~/.cache/go-build + key: ${{ needs.setup.outputs.cache-key }} + + - name: Determine packages to test (excluding elgamal) + id: packages + run: | + PACKAGES=$(go list ./... | grep -v '/pkg/encryption/elgamal') + echo "PACKAGES=$PACKAGES" >> $GITHUB_ENV + + - name: Run general unit tests + run: | + go test -mod=readonly -race -timeout 5m $PACKAGES + + # Define a matrix for ElGamal package test subsets + elgamal-tests: + needs: setup + runs-on: ubuntu-latest + strategy: + matrix: + test_group: [ + 'EncryptionDecryption', + 'KeyGeneration', + '48BitEncryptionDecryption', + 'AddCiphertext' + ] + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 with: - PATTERNS: | - **/**.go - go.mod - go.sum - - name: Get data from Go build cache + go-version: 1.21 + + - name: Restore cache uses: actions/cache@v3 with: path: | ~/go/pkg/mod ~/.cache/golangci-lint ~/.cache/go-build - key: ${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - - name: Run unit tests - run: go test -mod=readonly -race -timeout 20m ./... + key: ${{ needs.setup.outputs.cache-key }} + + - name: Run ElGamal ${{ matrix.test_group }} tests + run: | + cd pkg/encryption/elgamal + mapfile -t TESTS < <(go list -f '{{.Dir}}/*.go' ./...) + # Modify the run pattern based on your test naming conventions + go test -mod=readonly -race -timeout 20m -run ^Test${{ matrix.test_group }} ./... unit-test-check: name: Unit Test Check runs-on: ubuntu-latest - needs: tests + needs: + - tests + - elgamal-tests if: always() steps: - name: Get workflow conclusion @@ -49,7 +121,7 @@ jobs: retry_on: error timeout_seconds: 30 command: | - jobs=$(curl https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs) + jobs=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/jobs) job_statuses=$(echo "$jobs" | jq -r '.jobs[] | .conclusion') for status in $job_statuses @@ -61,4 +133,4 @@ jobs: fi done - echo "All tests have passed!" + echo "All tests have passed!" \ No newline at end of file diff --git a/pkg/encryption/elgamal/encryption.go b/pkg/encryption/elgamal/encryption.go index 5ab77de..aa7424a 100644 --- a/pkg/encryption/elgamal/encryption.go +++ b/pkg/encryption/elgamal/encryption.go @@ -10,7 +10,7 @@ import ( type TwistedElGamal struct { curve *curves.Curve - mapping map[string]int + mapping map[string]uint64 // Notes the sizes decrypted so far. This helps us know if the values we need are already in the map. maxMapping map[MaxBits]bool @@ -19,7 +19,7 @@ type TwistedElGamal struct { func NewTwistedElgamal(curve *curves.Curve) *TwistedElGamal { var s ristretto.Point s.SetZero() - mapping := make(map[string]int) + mapping := make(map[string]uint64) maxMapping := make(map[MaxBits]bool) mapping[s.String()] = 0 @@ -30,6 +30,7 @@ func NewTwistedElgamal(curve *curves.Curve) *TwistedElGamal { } } +// Encrypt encrypts a message using the public key pk. func (teg TwistedElGamal) Encrypt(pk curves.Point, message uint64) (*Ciphertext, curves.Scalar, error) { // Fixed base points G and H H := teg.GetH() @@ -40,7 +41,7 @@ func (teg TwistedElGamal) Encrypt(pk curves.Point, message uint64) (*Ciphertext, x, _ := teg.curve.Scalar.SetBigInt(bigIntMessage) // Generate a random scalar r - randomFactor := curves.ED25519().Scalar.Random(crand.Reader) + randomFactor := teg.curve.Scalar.Random(crand.Reader) // Compute the Pedersen commitment: C = r * H + x * G rH := H.Mul(randomFactor) // r * H @@ -57,6 +58,7 @@ func (teg TwistedElGamal) Encrypt(pk curves.Point, message uint64) (*Ciphertext, return &ciphertext, randomFactor, nil } +// EncryptWithRand encrypts a message using the public key pk and a given random factor. func (teg TwistedElGamal) EncryptWithRand(pk curves.Point, message uint64, randomFactor curves.Scalar) (*Ciphertext, curves.Scalar, error) { // Fixed base points G and H H := teg.GetH() @@ -83,7 +85,7 @@ func (teg TwistedElGamal) EncryptWithRand(pk curves.Point, message uint64, rando // Decrypt decrypts the ciphertext ct using the private key sk = s. // MaxBits denotes the maximum size of the decrypted message. The lower this can be set, the faster we can decrypt the message. -func (teg TwistedElGamal) Decrypt(sk curves.Scalar, ct *Ciphertext, maxBits MaxBits) (*int, error) { +func (teg TwistedElGamal) Decrypt(sk curves.Scalar, ct *Ciphertext, maxBits MaxBits) (uint64, error) { G := teg.GetG() // Compute s * D @@ -105,13 +107,13 @@ func (teg TwistedElGamal) Decrypt(sk curves.Scalar, ct *Ciphertext, maxBits MaxB // Then iterate over all values of x_lo. // If for some value x_lo, (x * G) - (x_lo * G) exists in the map above, then we satisfy the equation above and x = x_lo + 2^(maxBits/2) * x_hi - iMax := 1<<(uint(maxBits)/2) - 1 + iMax := uint64(1<<(uint(maxBits)/2) - 1) - for i := 0; i < iMax; i++ { + for i := uint64(0); i < iMax; i++ { // Attempt to brute-force x. // We test all values of x_lo in (x * G) - x_lo * G - xValue := new(big.Int).SetUint64(uint64(i)) - x, _ := curves.ED25519().Scalar.SetBigInt(xValue) + xValue := new(big.Int).SetUint64(i) + x, _ := teg.curve.Scalar.SetBigInt(xValue) xLoG := G.Mul(x) test := result.Sub(xLoG) @@ -123,14 +125,14 @@ func (teg TwistedElGamal) Decrypt(sk curves.Scalar, ct *Ciphertext, maxBits MaxB continue } xComputed := xHiMultiplied + i - return &xComputed, nil + return xComputed, nil } } - return nil, fmt.Errorf("could not find x") + return 0, fmt.Errorf("could not find x") } -// Helper function to create large maps used by the decryption funciton. +// updateIterMap Helper function to create large maps used by the decryption funciton. // Constructs the mapping of x_hi * 2^(maxBits/2) * G : x_hi * (maxBits/2) for all values of x_hi. // This table can then be used to find x_lo. func (teg TwistedElGamal) updateIterMap(maxBits MaxBits) { @@ -141,24 +143,25 @@ func (teg TwistedElGamal) updateIterMap(maxBits MaxBits) { // Shift i left by shift bits to multiply by 2^shift xhiMultipliedValue := i << shift xhiMultiplied := new(big.Int).SetUint64(uint64(xhiMultipliedValue)) - x_hi, _ := curves.ED25519().Scalar.SetBigInt(xhiMultiplied) + x_hi, _ := teg.curve.Scalar.SetBigInt(xhiMultiplied) key := G.Mul(x_hi) compressedKey := getCompressedKeyString(key) - teg.mapping[compressedKey] = xhiMultipliedValue + teg.mapping[compressedKey] = uint64(xhiMultipliedValue) } } -// Optimistically decrypt up to a 48 bit number. -// Since creating the map for a 48 bit number takes a large amount of time, we work our way up in hopes that we find the answer before having to create the 48 bit map. -func (teg TwistedElGamal) DecryptLargeNumber(sk curves.Scalar, ct *Ciphertext, maxBits MaxBits) (*int, error) { +// DecryptLargeNumber Optimistically decrypt up to a 48 bit number. +// Since creating the map for a 48 bit number takes a large amount of time, we work our way up in hopes that we find +// the answer before having to create the 48 bit map. +func (teg TwistedElGamal) DecryptLargeNumber(sk curves.Scalar, ct *Ciphertext, maxBits MaxBits) (uint64, error) { if maxBits > MaxBits48 { - return nil, fmt.Errorf("maxBits must be at most 48, provided (%d)", maxBits) + return 0, fmt.Errorf("maxBits must be at most 48, provided (%d)", maxBits) } values := []MaxBits{MaxBits16, MaxBits32, MaxBits40, MaxBits48} for _, bits := range values { if bits > maxBits { - return nil, fmt.Errorf("Failed to find value") + return 0, fmt.Errorf("failed to find value") } res, err := teg.Decrypt(sk, ct, bits) @@ -167,7 +170,7 @@ func (teg TwistedElGamal) DecryptLargeNumber(sk curves.Scalar, ct *Ciphertext, m } } - return nil, fmt.Errorf("Failed to find value") + return 0, fmt.Errorf("failed to find value") } func getCompressedKeyString(key curves.Point) string { diff --git a/pkg/encryption/elgamal/encryption_test.go b/pkg/encryption/elgamal/encryption_test.go index 1ac4089..6917505 100644 --- a/pkg/encryption/elgamal/encryption_test.go +++ b/pkg/encryption/elgamal/encryption_test.go @@ -63,28 +63,28 @@ func TestEncryptionDecryption(t *testing.T) { altKeys, _ := eg.KeyGen(*altPrivateKey, denom) // Happy Path - value := 108 - ciphertext, _, err := eg.Encrypt(keys.PublicKey, uint64(value)) + value := uint64(108) + ciphertext, _, err := eg.Encrypt(keys.PublicKey, value) require.Nil(t, err, "Should have no error while encrypting") decrypted, err := eg.Decrypt(keys.PrivateKey, ciphertext, MaxBits16) require.Nil(t, err, "Should have no error while decrypting") - require.Equal(t, value, *decrypted, "Should have the same value") + require.Equal(t, value, decrypted, "Should have the same value") decrypted, err = eg.Decrypt(keys.PrivateKey, ciphertext, MaxBits32) require.Nil(t, err, "Should have no error while decrypting") - require.Equal(t, value, *decrypted, "Should have the same value") + require.Equal(t, value, decrypted, "Should have the same value") // Using a different private key to decrypt should yield an error. decryptedWrongly, err := eg.Decrypt(altKeys.PrivateKey, ciphertext, MaxBits32) - require.Nil(t, decryptedWrongly) + require.Zero(t, decryptedWrongly) require.Error(t, err, "Should be unable to decrypt using the wrong private key") // Test overflow behavior ciphertextOverflow, _, err := eg.Encrypt(keys.PublicKey, math.MaxUint64) require.Nil(t, err, "Should have no error while encrypting") decryptedOverflow, err := eg.Decrypt(keys.PrivateKey, ciphertextOverflow, MaxBits32) - require.Nil(t, decryptedOverflow) + require.Zero(t, decryptedOverflow) require.Error(t, err, "Should be unable to decrypt the invalid overflow value") } @@ -99,32 +99,32 @@ func Test48BitEncryptionDecryption(t *testing.T) { keys, _ := eg.KeyGen(*privateKey, denom) // First decrypt a 32 bit number (sets up the decryptor for a later test) - value := 108092 - ciphertext, _, err := eg.Encrypt(keys.PublicKey, uint64(value)) + value := uint64(108092) + ciphertext, _, err := eg.Encrypt(keys.PublicKey, value) require.Nil(t, err, "Should have no error while encrypting") decrypted, err := eg.DecryptLargeNumber(keys.PrivateKey, ciphertext, MaxBits48) require.Nil(t, err, "Should have no error while decrypting") - require.Equal(t, value, *decrypted, "Should have the same value") + require.Equal(t, value, decrypted, "Should have the same value") // Create a large <48 bit number to be encrypted. - largeValue := 1 << 39 - largeCiphertext, _, err := eg.Encrypt(keys.PublicKey, uint64(largeValue)) + largeValue := uint64(1 << 39) + largeCiphertext, _, err := eg.Encrypt(keys.PublicKey, largeValue) require.Nil(t, err, "Should have no error while encrypting") largeDecrypted, err := eg.DecryptLargeNumber(keys.PrivateKey, largeCiphertext, MaxBits48) require.Nil(t, err, "Should have no error while decrypting") - require.Equal(t, largeValue, *largeDecrypted, "Should have the same value") + require.Equal(t, largeValue, largeDecrypted, "Should have the same value") // Attempting to decrypt with the wrong max bits set should yield an error. decryptedWrongly, err := eg.DecryptLargeNumber(keys.PrivateKey, largeCiphertext, MaxBits32) - require.Nil(t, decryptedWrongly) + require.Zero(t, decryptedWrongly) require.Error(t, err, "Should be unable to decrypt using the wrong maxBits") // Decrypting the 48 bit value should not corrupt the map for 32 bit values. decrypted, err = eg.DecryptLargeNumber(keys.PrivateKey, ciphertext, MaxBits32) require.Nil(t, err, "Should still have no error while decrypting") - require.Equal(t, value, *decrypted, "Should still have the same value") + require.Equal(t, value, decrypted, "Should still have the same value") } func TestAddCiphertext(t *testing.T) { @@ -140,11 +140,11 @@ func TestAddCiphertext(t *testing.T) { altKeys, _ := eg.KeyGen(*altPrivateKey, denom) // Happy Path - value1 := 30842 - ciphertext1, _, err := eg.Encrypt(keys.PublicKey, uint64(value1)) + value1 := uint64(30842) + ciphertext1, _, err := eg.Encrypt(keys.PublicKey, value1) require.Nil(t, err, "Should have no error while encrypting") - value2 := 1901 + value2 := uint64(1901) ciphertext2, _, err := eg.Encrypt(keys.PublicKey, uint64(value2)) require.Nil(t, err, "Should have no error while encrypting") @@ -154,7 +154,7 @@ func TestAddCiphertext(t *testing.T) { decrypted, err := eg.Decrypt(keys.PrivateKey, ciphertextSum, MaxBits32) require.Nil(t, err, "Should have no error while decrypting") require.NotNil(t, decrypted) - require.Equal(t, value1+value2, *decrypted, "Decrypted sum should be correct") + require.Equal(t, value1+value2, decrypted, "Decrypted sum should be correct") // Test that the add operation is commutative by adding in the other order. ciphertextSumInv, err := AddCiphertext(ciphertext2, ciphertext1) @@ -170,11 +170,9 @@ func TestAddCiphertext(t *testing.T) { ciphertextSum, err = AddCiphertext(ciphertext1, ciphertext2alt) require.Nil(t, err, "Even though ciphertexts were encoded using different keys, addition doesn't throw an error.") - decrypted, err = eg.Decrypt(keys.PrivateKey, ciphertextSum, MaxBits32) - require.Nil(t, decrypted) + _, err = eg.Decrypt(keys.PrivateKey, ciphertextSum, MaxBits32) require.Error(t, err, "Ciphertext should be undecodable using either private key") - decrypted, err = eg.Decrypt(altKeys.PrivateKey, ciphertextSum, MaxBits32) - require.Nil(t, decrypted) + _, err = eg.Decrypt(altKeys.PrivateKey, ciphertextSum, MaxBits32) require.Error(t, err, "Ciphertext should be undecodable using either private key") } From c27e96c589c956936fcac9bcf726d6fddd46deef Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 8 Oct 2024 15:35:48 -0700 Subject: [PATCH 08/14] more tests + refactoring --- .github/workflows/test.yml | 7 +-- pkg/encryption/elgamal/encryption.go | 30 ++++--------- pkg/encryption/elgamal/encryption_test.go | 55 +++++++++++++++++++++++ 3 files changed, 68 insertions(+), 24 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5a287f6..51878a4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,7 +66,7 @@ jobs: - name: Run general unit tests run: | - go test -mod=readonly -race -timeout 5m $PACKAGES + go test -mod=readonly -race -v -timeout 5m $PACKAGES # Define a matrix for ElGamal package test subsets elgamal-tests: @@ -78,7 +78,8 @@ jobs: 'EncryptionDecryption', 'KeyGeneration', '48BitEncryptionDecryption', - 'AddCiphertext' + 'AddCiphertext', + 'TwistedElGamal' ] steps: - name: Checkout repository @@ -103,7 +104,7 @@ jobs: cd pkg/encryption/elgamal mapfile -t TESTS < <(go list -f '{{.Dir}}/*.go' ./...) # Modify the run pattern based on your test naming conventions - go test -mod=readonly -race -timeout 20m -run ^Test${{ matrix.test_group }} ./... + go test -mod=readonly -race -timeout 20m -run ^Test${{ matrix.test_group }} -v ./... unit-test-check: name: Unit Test Check diff --git a/pkg/encryption/elgamal/encryption.go b/pkg/encryption/elgamal/encryption.go index aa7424a..256bf05 100644 --- a/pkg/encryption/elgamal/encryption.go +++ b/pkg/encryption/elgamal/encryption.go @@ -32,30 +32,10 @@ func NewTwistedElgamal(curve *curves.Curve) *TwistedElGamal { // Encrypt encrypts a message using the public key pk. func (teg TwistedElGamal) Encrypt(pk curves.Point, message uint64) (*Ciphertext, curves.Scalar, error) { - // Fixed base points G and H - H := teg.GetH() - G := teg.GetG() - - // Convert message x (big.Int) to a scalar on the elliptic curve - bigIntMessage := new(big.Int).SetUint64(message) - x, _ := teg.curve.Scalar.SetBigInt(bigIntMessage) - // Generate a random scalar r randomFactor := teg.curve.Scalar.Random(crand.Reader) - // Compute the Pedersen commitment: C = r * H + x * G - rH := H.Mul(randomFactor) // r * H - xG := G.Mul(x) // x * G - C := rH.Add(xG) // C = r * H + x * G - - // Compute the decryption handle: D = r * P - D := pk.Mul(randomFactor) // D = r * P - ciphertext := Ciphertext{ - C: C, - D: D, - } - - return &ciphertext, randomFactor, nil + return teg.EncryptWithRand(pk, message, randomFactor) } // EncryptWithRand encrypts a message using the public key pk and a given random factor. @@ -86,6 +66,14 @@ func (teg TwistedElGamal) EncryptWithRand(pk curves.Point, message uint64, rando // Decrypt decrypts the ciphertext ct using the private key sk = s. // MaxBits denotes the maximum size of the decrypted message. The lower this can be set, the faster we can decrypt the message. func (teg TwistedElGamal) Decrypt(sk curves.Scalar, ct *Ciphertext, maxBits MaxBits) (uint64, error) { + if sk == nil { + return 0, fmt.Errorf("invalid private key") + } + + if ct == nil || ct.C == nil || ct.D == nil { + return 0, fmt.Errorf("invalid ciphertext") + } + G := teg.GetG() // Compute s * D diff --git a/pkg/encryption/elgamal/encryption_test.go b/pkg/encryption/elgamal/encryption_test.go index 6917505..18758d4 100644 --- a/pkg/encryption/elgamal/encryption_test.go +++ b/pkg/encryption/elgamal/encryption_test.go @@ -176,3 +176,58 @@ func TestAddCiphertext(t *testing.T) { _, err = eg.Decrypt(altKeys.PrivateKey, ciphertextSum, MaxBits32) require.Error(t, err, "Ciphertext should be undecodable using either private key") } + +func TestTwistedElGamal_InvalidCiphertext(t *testing.T) { + denom := "factory/sei1239081236472sd/testToken" + curve := curves.ED25519() + eg := NewTwistedElgamal(curve) + privateKey, _ := GenerateKey() + keys, _ := eg.KeyGen(*privateKey, denom) + + invalidCt := &Ciphertext{} + + decrypted, err := eg.Decrypt(keys.PrivateKey, invalidCt, MaxBits48) + require.Error(t, err, "Decryption should fail for invalid ciphertext") + require.Equal(t, "invalid ciphertext", err.Error()) + require.Zero(t, decrypted, "Decrypted value should be zero for invalid ciphertext") +} + +func TestTwistedElGamal_NilPrivateKey(t *testing.T) { + denom := "factory/sei1239081236472sd/testToken" + curve := curves.ED25519() + eg := NewTwistedElgamal(curve) + + // Generate a valid key pair for comparison + privateKey, _ := GenerateKey() + keys, _ := eg.KeyGen(*privateKey, denom) + + // Encrypt a value with a valid public key + value := uint64(12345) + ciphertext, _, err := eg.Encrypt(keys.PublicKey, value) + require.Nil(t, err, "Encryption should not fail with a valid public key") + + // Attempt to decrypt with a nil private key + decrypted, err := eg.Decrypt(nil, ciphertext, MaxBits32) + require.Error(t, err, "Decryption should fail with a nil private key") + require.Equal(t, "invalid private key", err.Error()) + require.Zero(t, decrypted, "Decrypted value should be zero with a nil private key") +} + +func TestTwistedElGamalWithRand_EncryptDecryptWithRand(t *testing.T) { + denom := "factory/sei1239081236472sd/testToken" + curve := curves.ED25519() + eg := NewTwistedElgamal(curve) + + // Generate a valid key pair for comparison + privateKey, _ := GenerateKey() + keys, _ := eg.KeyGen(*privateKey, denom) + + message := uint64(555555555) + randomFactor := curve.Scalar.Random(rand.Reader) + ct, _, err := eg.EncryptWithRand(keys.PublicKey, message, randomFactor) + require.NoError(t, err, "Encryption with randomFactor should not fail") + + decrypted, err := eg.DecryptLargeNumber(keys.PrivateKey, ct, MaxBits48) + require.NoError(t, err, "Decryption should not fail") + require.Equal(t, message, decrypted, "Decrypted message should match original") +} From 6f9c8fcf3463db3faffdf7f973ad7a7fcc22ce05 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 8 Oct 2024 16:08:39 -0700 Subject: [PATCH 09/14] even more tests --- pkg/encryption/elgamal/encryption.go | 8 +++ pkg/encryption/elgamal/encryption_test.go | 69 ++++++++++++++++++++++- 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/pkg/encryption/elgamal/encryption.go b/pkg/encryption/elgamal/encryption.go index 256bf05..49c5fe8 100644 --- a/pkg/encryption/elgamal/encryption.go +++ b/pkg/encryption/elgamal/encryption.go @@ -40,6 +40,14 @@ func (teg TwistedElGamal) Encrypt(pk curves.Point, message uint64) (*Ciphertext, // EncryptWithRand encrypts a message using the public key pk and a given random factor. func (teg TwistedElGamal) EncryptWithRand(pk curves.Point, message uint64, randomFactor curves.Scalar) (*Ciphertext, curves.Scalar, error) { + if pk == nil { + return nil, nil, fmt.Errorf("invalid public key") + } + + if randomFactor == nil { + return nil, nil, fmt.Errorf("invalid random factor") + } + // Fixed base points G and H H := teg.GetH() G := teg.GetG() diff --git a/pkg/encryption/elgamal/encryption_test.go b/pkg/encryption/elgamal/encryption_test.go index 18758d4..4b95766 100644 --- a/pkg/encryption/elgamal/encryption_test.go +++ b/pkg/encryption/elgamal/encryption_test.go @@ -125,6 +125,11 @@ func Test48BitEncryptionDecryption(t *testing.T) { decrypted, err = eg.DecryptLargeNumber(keys.PrivateKey, ciphertext, MaxBits32) require.Nil(t, err, "Should still have no error while decrypting") require.Equal(t, value, decrypted, "Should still have the same value") + + // Passing in an invalid maxBits should yield an error. + _, err = eg.DecryptLargeNumber(keys.PrivateKey, largeCiphertext, MaxBits(64)) + require.Error(t, err, "Should be unable to decrypt using an invalid maxBits") + require.Equal(t, "maxBits must be at most 48, provided (64)", err.Error()) } func TestAddCiphertext(t *testing.T) { @@ -213,7 +218,7 @@ func TestTwistedElGamal_NilPrivateKey(t *testing.T) { require.Zero(t, decrypted, "Decrypted value should be zero with a nil private key") } -func TestTwistedElGamalWithRand_EncryptDecryptWithRand(t *testing.T) { +func TestTwistedElGamal_EncryptDecryptWithRand(t *testing.T) { denom := "factory/sei1239081236472sd/testToken" curve := curves.ED25519() eg := NewTwistedElgamal(curve) @@ -231,3 +236,65 @@ func TestTwistedElGamalWithRand_EncryptDecryptWithRand(t *testing.T) { require.NoError(t, err, "Decryption should not fail") require.Equal(t, message, decrypted, "Decrypted message should match original") } + +func TestTwistedElGamal_DecryptWithZeroBits(t *testing.T) { + denom := "factory/sei1239081236472sd/testToken" + curve := curves.ED25519() + eg := NewTwistedElgamal(curve) + + // Generate a valid key pair for comparison + privateKey, _ := GenerateKey() + keys, _ := eg.KeyGen(*privateKey, denom) + + message := uint64(555555555) + randomFactor := curve.Scalar.Random(rand.Reader) + ct, _, err := eg.EncryptWithRand(keys.PublicKey, message, randomFactor) + require.NoError(t, err, "Encryption with randomFactor should not fail") + + _, err = eg.DecryptLargeNumber(keys.PrivateKey, ct, MaxBits(0)) + require.Error(t, err, "Decryption should fail") + require.Equal(t, "failed to find value", err.Error()) +} + +func TestTwistedElGamal_EncryptInvalidKey(t *testing.T) { + curve := curves.ED25519() + eg := NewTwistedElgamal(curve) + + // Test with nil public key + _, _, err := eg.Encrypt(nil, 12345) + require.Error(t, err, "Encryption should fail with a nil public key") + require.Equal(t, "invalid public key", err.Error()) +} + +func TestTwistedElGamal_EncryptInvalidRandomFactor(t *testing.T) { + denom := "factory/sei1239081236472sd/testToken" + curve := curves.ED25519() + eg := NewTwistedElgamal(curve) + + // Generate a valid key pair for comparison + privateKey, _ := GenerateKey() + keys, _ := eg.KeyGen(*privateKey, denom) + + // Test with nil public key + _, _, err := eg.EncryptWithRand(keys.PublicKey, uint64(12345), nil) + require.Error(t, err, "Encryption should fail with nil random factor") + require.Equal(t, "invalid random factor", err.Error()) +} + +func TestTwistedElGamal_EncryptBoundaryValues(t *testing.T) { + denom := "factory/sei1239081236472sd/testToken" + curve := curves.ED25519() + eg := NewTwistedElgamal(curve) + + // Generate a valid key pair for comparison + privateKey, _ := GenerateKey() + keys, _ := eg.KeyGen(*privateKey, denom) + + // Test with the smallest possible value (0) + _, _, err := eg.Encrypt(keys.PublicKey, 0) + require.NoError(t, err, "Encryption should not fail with the smallest possible value") + + // Test with the largest possible value (MaxUint64) + _, _, err = eg.Encrypt(keys.PublicKey, math.MaxUint64) + require.NoError(t, err, "Encryption should not fail with the largest possible value") +} From d66547c28de708923d42d14719747291af70e634 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Tue, 8 Oct 2024 16:43:41 -0700 Subject: [PATCH 10/14] golang docs --- .github/workflows/test.yml | 2 +- pkg/encryption/aes.go | 9 +++++---- pkg/encryption/elgamal/common.go | 13 +++++++++++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 51878a4..a3ed287 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -134,4 +134,4 @@ jobs: fi done - echo "All tests have passed!" \ No newline at end of file + echo "All tests have passed!" diff --git a/pkg/encryption/aes.go b/pkg/encryption/aes.go index 85ec57e..325f182 100644 --- a/pkg/encryption/aes.go +++ b/pkg/encryption/aes.go @@ -15,11 +15,12 @@ import ( "golang.org/x/crypto/hkdf" ) +// GenerateKey generates a new ECDSA private key using the secp256k1 curve. func GenerateKey() (*ecdsa.PrivateKey, error) { return ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) } -// GetAesKey derives a 32-byte AES key using the provided ECDSA private key and denomination string. +// GetAESKey derives a 32-byte AES key using the provided ECDSA private key and denomination string. // It employs HKDF with SHA-256, using the ECDSA private key bytes and a SHA-256 hash of the denom as salt. func GetAESKey(privKey ecdsa.PrivateKey, denom string) ([]byte, error) { if privKey.D == nil { @@ -48,7 +49,7 @@ func GetAESKey(privKey ecdsa.PrivateKey, denom string) ([]byte, error) { return aesKey, nil } -// Key must be a len 32 byte array for AES-256 +// EncryptAESGCM Key must be a len 32 byte array for AES-256 func EncryptAESGCM(value uint64, key []byte) (string, error) { // Create a GCM cipher mode instance aesgcm, err := getCipher(key) @@ -73,7 +74,7 @@ func EncryptAESGCM(value uint64, key []byte) (string, error) { return base64.StdEncoding.EncodeToString(ciphertext), nil } -// Key must be a len 32 byte array for AES-256 +// DecryptAESGCM Key must be a len 32 byte array for AES-256 func DecryptAESGCM(ciphertextBase64 string, key []byte) (uint64, error) { // Decode the Base64-encoded ciphertext ciphertext, err := base64.StdEncoding.DecodeString(ciphertextBase64) @@ -105,7 +106,7 @@ func DecryptAESGCM(ciphertextBase64 string, key []byte) (uint64, error) { return value, nil } -// Creates the cipher from the key. Key must be a len 32 byte array for AES-256 +// getCipher Creates the cipher from the key. Key must be a len 32 byte array for AES-256 func getCipher(key []byte) (cipher.AEAD, error) { // Create a new AES cipher block, err := aes.NewCipher(key) diff --git a/pkg/encryption/elgamal/common.go b/pkg/encryption/elgamal/common.go index fc2278d..df390be 100644 --- a/pkg/encryption/elgamal/common.go +++ b/pkg/encryption/elgamal/common.go @@ -3,14 +3,16 @@ package elgamal import ( "crypto/ecdsa" "crypto/sha256" + "io" + "github.com/coinbase/kryptology/pkg/core/curves" "golang.org/x/crypto/hkdf" - "io" ) -// H is a random point on the eliptic curve that is unrelated to G. +// H_STRING H is a random point on the elliptic curve that is unrelated to G. const H_STRING = "gPt25pi0eDphSiXWu0BIeIvyVATCtwhslTqfqvNhW2c" +// KeyGen generates a new key pair for the Twisted ElGamal encryption scheme. func (teg TwistedElGamal) KeyGen(privateKey ecdsa.PrivateKey, denom string) (*KeyPair, error) { // Fixed base point H H := teg.GetH() @@ -31,15 +33,22 @@ func (teg TwistedElGamal) KeyGen(privateKey ecdsa.PrivateKey, denom string) (*Ke }, nil } +// GetG returns the generator point G for the TwistedElGamal instance. +// This is derived from the underlying elliptic curve's generator point. func (teg TwistedElGamal) GetG() curves.Point { return curves.Point.Generator(teg.curve.Point) } +// GetH returns the hashed point H for the TwistedElGamal instance. +// The hash is computed using a predefined string constant H_STRING. +// This point is used as part of the ElGamal encryption scheme. func (teg TwistedElGamal) GetH() curves.Point { bytes := []byte(H_STRING) return teg.curve.Point.Hash(bytes) } +// getPrivateKey derives a private key for the ElGamal cryptosystem. +// It takes an ECDSA private key and a denomination string to generate the scalar. func getPrivateKey(privateKey ecdsa.PrivateKey, denom string) (curves.Scalar, error) { // Convert the ECDSA private key to bytes privKeyBytes := privateKey.D.Bytes() From d2d9278f5319078e72e3e1e4f7fd34d2244939eb Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Wed, 9 Oct 2024 10:35:22 -0700 Subject: [PATCH 11/14] address review comments --- pkg/encryption/elgamal/common.go | 6 ++-- pkg/encryption/elgamal/encryption.go | 14 ++++++++ pkg/encryption/elgamal/encryption_test.go | 44 +++++++++-------------- pkg/encryption/elgamal/operations.go | 17 +++++---- 4 files changed, 41 insertions(+), 40 deletions(-) diff --git a/pkg/encryption/elgamal/common.go b/pkg/encryption/elgamal/common.go index df390be..fe5e42a 100644 --- a/pkg/encryption/elgamal/common.go +++ b/pkg/encryption/elgamal/common.go @@ -17,7 +17,7 @@ func (teg TwistedElGamal) KeyGen(privateKey ecdsa.PrivateKey, denom string) (*Ke // Fixed base point H H := teg.GetH() - s, err := getPrivateKey(privateKey, denom) + s, err := teg.getPrivateKey(privateKey, denom) if err != nil { return nil, err } @@ -49,7 +49,7 @@ func (teg TwistedElGamal) GetH() curves.Point { // getPrivateKey derives a private key for the ElGamal cryptosystem. // It takes an ECDSA private key and a denomination string to generate the scalar. -func getPrivateKey(privateKey ecdsa.PrivateKey, denom string) (curves.Scalar, error) { +func (teg TwistedElGamal) getPrivateKey(privateKey ecdsa.PrivateKey, denom string) (curves.Scalar, error) { // Convert the ECDSA private key to bytes privKeyBytes := privateKey.D.Bytes() @@ -67,7 +67,7 @@ func getPrivateKey(privateKey ecdsa.PrivateKey, denom string) (curves.Scalar, er } // Initialize the scalar (private key) using the generated bytes - s, err := curves.ED25519().Scalar.SetBytesWide(scalarBytes[:]) + s, err := teg.curve.Scalar.SetBytesWide(scalarBytes[:]) if err != nil { return nil, err } diff --git a/pkg/encryption/elgamal/encryption.go b/pkg/encryption/elgamal/encryption.go index 49c5fe8..33ba03c 100644 --- a/pkg/encryption/elgamal/encryption.go +++ b/pkg/encryption/elgamal/encryption.go @@ -16,6 +16,20 @@ type TwistedElGamal struct { maxMapping map[MaxBits]bool } +func NewTwistedElgamalWithED25519Curve() *TwistedElGamal { + var s ristretto.Point + s.SetZero() + mapping := make(map[string]uint64) + maxMapping := make(map[MaxBits]bool) + mapping[s.String()] = 0 + + return &TwistedElGamal{ + curve: curves.ED25519(), + maxMapping: maxMapping, + mapping: mapping, + } +} + func NewTwistedElgamal(curve *curves.Curve) *TwistedElGamal { var s ristretto.Point s.SetZero() diff --git a/pkg/encryption/elgamal/encryption_test.go b/pkg/encryption/elgamal/encryption_test.go index 4b95766..0feb0a9 100644 --- a/pkg/encryption/elgamal/encryption_test.go +++ b/pkg/encryption/elgamal/encryption_test.go @@ -10,6 +10,8 @@ import ( "testing" ) +const DefaultTestDenom = "factory/sei1239081236472sd/testToken" + func GenerateKey() (*ecdsa.PrivateKey, error) { return ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) } @@ -18,15 +20,12 @@ func TestKeyGeneration(t *testing.T) { privateKey, err := GenerateKey() require.Nil(t, err) - ed25519 := curves.ED25519() - eg := NewTwistedElgamal(ed25519) - - denom := "factory/sei1239081236470/testToken" - keyPair, err := eg.KeyGen(*privateKey, denom) + eg := NewTwistedElgamalWithED25519Curve() + keyPair, err := eg.KeyGen(*privateKey, DefaultTestDenom) require.Nil(t, err) // Test that keyPair is deterministically generated - keyPairAgain, err := eg.KeyGen(*privateKey, denom) + keyPairAgain, err := eg.KeyGen(*privateKey, DefaultTestDenom) require.Nil(t, err) require.Equal(t, keyPair, keyPairAgain, "PK should be deterministically generated") @@ -57,10 +56,8 @@ func TestEncryptionDecryption(t *testing.T) { ed25519 := curves.ED25519() eg := NewTwistedElgamal(ed25519) - denom := "factory/sei1239081236470/testToken" - - keys, _ := eg.KeyGen(*privateKey, denom) - altKeys, _ := eg.KeyGen(*altPrivateKey, denom) + keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) + altKeys, _ := eg.KeyGen(*altPrivateKey, DefaultTestDenom) // Happy Path value := uint64(108) @@ -92,11 +89,10 @@ func TestEncryptionDecryption(t *testing.T) { func Test48BitEncryptionDecryption(t *testing.T) { privateKey, err := GenerateKey() require.Nil(t, err) - denom := "factory/sei1239081236470/testToken" ed25519 := curves.ED25519() eg := NewTwistedElgamal(ed25519) - keys, _ := eg.KeyGen(*privateKey, denom) + keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) // First decrypt a 32 bit number (sets up the decryptor for a later test) value := uint64(108092) @@ -136,13 +132,11 @@ func TestAddCiphertext(t *testing.T) { privateKey, _ := GenerateKey() altPrivateKey, _ := GenerateKey() - denom := "factory/sei1239081236470/testToken" - ed25519 := curves.ED25519() eg := NewTwistedElgamal(ed25519) - keys, _ := eg.KeyGen(*privateKey, denom) - altKeys, _ := eg.KeyGen(*altPrivateKey, denom) + keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) + altKeys, _ := eg.KeyGen(*altPrivateKey, DefaultTestDenom) // Happy Path value1 := uint64(30842) @@ -183,11 +177,10 @@ func TestAddCiphertext(t *testing.T) { } func TestTwistedElGamal_InvalidCiphertext(t *testing.T) { - denom := "factory/sei1239081236472sd/testToken" curve := curves.ED25519() eg := NewTwistedElgamal(curve) privateKey, _ := GenerateKey() - keys, _ := eg.KeyGen(*privateKey, denom) + keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) invalidCt := &Ciphertext{} @@ -198,13 +191,12 @@ func TestTwistedElGamal_InvalidCiphertext(t *testing.T) { } func TestTwistedElGamal_NilPrivateKey(t *testing.T) { - denom := "factory/sei1239081236472sd/testToken" curve := curves.ED25519() eg := NewTwistedElgamal(curve) // Generate a valid key pair for comparison privateKey, _ := GenerateKey() - keys, _ := eg.KeyGen(*privateKey, denom) + keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) // Encrypt a value with a valid public key value := uint64(12345) @@ -219,13 +211,12 @@ func TestTwistedElGamal_NilPrivateKey(t *testing.T) { } func TestTwistedElGamal_EncryptDecryptWithRand(t *testing.T) { - denom := "factory/sei1239081236472sd/testToken" curve := curves.ED25519() eg := NewTwistedElgamal(curve) // Generate a valid key pair for comparison privateKey, _ := GenerateKey() - keys, _ := eg.KeyGen(*privateKey, denom) + keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) message := uint64(555555555) randomFactor := curve.Scalar.Random(rand.Reader) @@ -238,13 +229,12 @@ func TestTwistedElGamal_EncryptDecryptWithRand(t *testing.T) { } func TestTwistedElGamal_DecryptWithZeroBits(t *testing.T) { - denom := "factory/sei1239081236472sd/testToken" curve := curves.ED25519() eg := NewTwistedElgamal(curve) // Generate a valid key pair for comparison privateKey, _ := GenerateKey() - keys, _ := eg.KeyGen(*privateKey, denom) + keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) message := uint64(555555555) randomFactor := curve.Scalar.Random(rand.Reader) @@ -267,13 +257,12 @@ func TestTwistedElGamal_EncryptInvalidKey(t *testing.T) { } func TestTwistedElGamal_EncryptInvalidRandomFactor(t *testing.T) { - denom := "factory/sei1239081236472sd/testToken" curve := curves.ED25519() eg := NewTwistedElgamal(curve) // Generate a valid key pair for comparison privateKey, _ := GenerateKey() - keys, _ := eg.KeyGen(*privateKey, denom) + keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) // Test with nil public key _, _, err := eg.EncryptWithRand(keys.PublicKey, uint64(12345), nil) @@ -282,13 +271,12 @@ func TestTwistedElGamal_EncryptInvalidRandomFactor(t *testing.T) { } func TestTwistedElGamal_EncryptBoundaryValues(t *testing.T) { - denom := "factory/sei1239081236472sd/testToken" curve := curves.ED25519() eg := NewTwistedElgamal(curve) // Generate a valid key pair for comparison privateKey, _ := GenerateKey() - keys, _ := eg.KeyGen(*privateKey, denom) + keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) // Test with the smallest possible value (0) _, _, err := eg.Encrypt(keys.PublicKey, 0) diff --git a/pkg/encryption/elgamal/operations.go b/pkg/encryption/elgamal/operations.go index 05f3f25..d13b0ea 100644 --- a/pkg/encryption/elgamal/operations.go +++ b/pkg/encryption/elgamal/operations.go @@ -2,7 +2,6 @@ package elgamal import ( "fmt" - "github.com/coinbase/kryptology/pkg/core/curves" "math/big" ) @@ -14,7 +13,7 @@ func (teg TwistedElGamal) AddScalar(ciphertext *Ciphertext, amount uint64) (*Cip G := teg.GetG() // Create a scalar from the amount. bigIntAmount := new(big.Int).SetUint64(amount) - scalarAmount, err := curves.ED25519().Scalar.SetBigInt(bigIntAmount) + scalarAmount, err := teg.curve.Scalar.SetBigInt(bigIntAmount) if err != nil { return nil, err } @@ -37,7 +36,7 @@ func (teg TwistedElGamal) SubScalar(ciphertext *Ciphertext, amount uint64) (*Cip G := teg.GetG() // Create a scalar from the amount. bigIntAmount := new(big.Int).SetUint64(amount) - scalarAmount, err := curves.ED25519().Scalar.SetBigInt(bigIntAmount) + scalarAmount, err := teg.curve.Scalar.SetBigInt(bigIntAmount) if err != nil { return nil, err } @@ -81,9 +80,9 @@ func SubtractCiphertext(ct1 *Ciphertext, ct2 *Ciphertext) (*Ciphertext, error) { } // ScalarMultCiphertext Multiply takes a ciphertext ct and returns the ciphertext of their ct * factor. -func ScalarMultCiphertext(ct *Ciphertext, factor uint64) (*Ciphertext, error) { +func (teg TwistedElGamal) ScalarMultCiphertext(ct *Ciphertext, factor uint64) (*Ciphertext, error) { scalarValue := new(big.Int).SetUint64(factor) - factorScalar, _ := curves.ED25519().Scalar.SetBigInt(scalarValue) + factorScalar, _ := teg.curve.Scalar.SetBigInt(scalarValue) // Cmul = C * Factor cMul := ct.C.Mul(factorScalar) @@ -100,12 +99,12 @@ func ScalarMultCiphertext(ct *Ciphertext, factor uint64) (*Ciphertext, error) { // Additional Functions // AddWithLoHi performs the operation: left_ciphertext + (right_ciphertext_lo + 2^16 * right_ciphertext_hi) -func AddWithLoHi(leftCiphertext, rightCiphertextLo, rightCiphertextHi *Ciphertext) (*Ciphertext, error) { +func (teg TwistedElGamal) AddWithLoHi(leftCiphertext, rightCiphertextLo, rightCiphertextHi *Ciphertext) (*Ciphertext, error) { // Step 1: Define shift_scalar as 2^16 (which is 65536) shiftScalar := 1 << 16 // Step 2: Shift rightCiphertextHi by multiplying by shift_scalar - shiftedRightCiphertextHi, err := ScalarMultCiphertext(rightCiphertextHi, uint64(shiftScalar)) + shiftedRightCiphertextHi, err := teg.ScalarMultCiphertext(rightCiphertextHi, uint64(shiftScalar)) if err != nil { return nil, fmt.Errorf("failed to shift rightCiphertextHi: %v", err) } @@ -127,12 +126,12 @@ func AddWithLoHi(leftCiphertext, rightCiphertextLo, rightCiphertextHi *Ciphertex } // SubWithLoHi performs the operation: left_ciphertext - (right_ciphertext_lo + 2^16 * right_ciphertext_hi) -func SubWithLoHi(leftCiphertext, rightCiphertextLo, rightCiphertextHi *Ciphertext) (*Ciphertext, error) { +func (teg TwistedElGamal) SubWithLoHi(leftCiphertext, rightCiphertextLo, rightCiphertextHi *Ciphertext) (*Ciphertext, error) { // Step 1: Define shift_scalar as 2^16 (which is 65536) shiftScalar := 1 << 16 // Step 2: Shift rightCiphertextHi by multiplying by shift_scalar - shiftedRightCiphertextHi, err := ScalarMultCiphertext(rightCiphertextHi, uint64(shiftScalar)) + shiftedRightCiphertextHi, err := teg.ScalarMultCiphertext(rightCiphertextHi, uint64(shiftScalar)) if err != nil { return nil, fmt.Errorf("failed to shift rightCiphertextHi: %v", err) } From c63c513f940aa69a26ecf910b5c456ebb0372cf9 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Wed, 9 Oct 2024 10:43:54 -0700 Subject: [PATCH 12/14] - More test for different curves. - Plug in new ED25519 curve constructors in tests --- pkg/encryption/elgamal/encryption_test.go | 99 +++++++++++++++++++---- 1 file changed, 83 insertions(+), 16 deletions(-) diff --git a/pkg/encryption/elgamal/encryption_test.go b/pkg/encryption/elgamal/encryption_test.go index 0feb0a9..ced2d3d 100644 --- a/pkg/encryption/elgamal/encryption_test.go +++ b/pkg/encryption/elgamal/encryption_test.go @@ -12,12 +12,12 @@ import ( const DefaultTestDenom = "factory/sei1239081236472sd/testToken" -func GenerateKey() (*ecdsa.PrivateKey, error) { +func generateKey() (*ecdsa.PrivateKey, error) { return ecdsa.GenerateKey(secp256k1.S256(), rand.Reader) } func TestKeyGeneration(t *testing.T) { - privateKey, err := GenerateKey() + privateKey, err := generateKey() require.Nil(t, err) eg := NewTwistedElgamalWithED25519Curve() @@ -42,7 +42,40 @@ func TestKeyGeneration(t *testing.T) { require.NotEqual(t, keyPair, keyPairDiffSalt, "PK should be different for different salt") // Test that different privateKey should generate different PK - altPrivateKey, err := GenerateKey() + altPrivateKey, err := generateKey() + require.Nil(t, err) + keyPairDiffPK, err := eg.KeyGen(*altPrivateKey, altDenom) + require.Nil(t, err) + require.NotEqual(t, keyPair, keyPairDiffPK, "PK should be different for different ESDCA Private Key") +} + +func TestKeyGenerationBLS12377G1Curve(t *testing.T) { + privateKey, err := generateKey() + require.Nil(t, err) + + eg := NewTwistedElgamal(curves.BLS12377G1()) + keyPair, err := eg.KeyGen(*privateKey, DefaultTestDenom) + require.Nil(t, err) + + // Test that keyPair is deterministically generated + keyPairAgain, err := eg.KeyGen(*privateKey, DefaultTestDenom) + require.Nil(t, err) + require.Equal(t, keyPair, keyPairAgain, "PK should be deterministically generated") + + // Test that changing the salt should generate a different key + altDenom := "factory/sei1239081236470/testToken1" + keyPairDiffSalt, err := eg.KeyGen(*privateKey, altDenom) + require.Nil(t, err) + require.NotEqual(t, keyPair, keyPairDiffSalt, "PK should be different for different salt") + + // Test same thing for salt of same length + altDenom = "factory/sei1239081236470/testTokeN" + keyPairDiffSalt, err = eg.KeyGen(*privateKey, altDenom) + require.Nil(t, err) + require.NotEqual(t, keyPair, keyPairDiffSalt, "PK should be different for different salt") + + // Test that different privateKey should generate different PK + altPrivateKey, err := generateKey() require.Nil(t, err) keyPairDiffPK, err := eg.KeyGen(*altPrivateKey, altDenom) require.Nil(t, err) @@ -50,11 +83,45 @@ func TestKeyGeneration(t *testing.T) { } func TestEncryptionDecryption(t *testing.T) { - privateKey, _ := GenerateKey() - altPrivateKey, _ := GenerateKey() + privateKey, _ := generateKey() + altPrivateKey, _ := generateKey() - ed25519 := curves.ED25519() - eg := NewTwistedElgamal(ed25519) + eg := NewTwistedElgamalWithED25519Curve() + + keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) + altKeys, _ := eg.KeyGen(*altPrivateKey, DefaultTestDenom) + + // Happy Path + value := uint64(108) + ciphertext, _, err := eg.Encrypt(keys.PublicKey, value) + require.Nil(t, err, "Should have no error while encrypting") + + decrypted, err := eg.Decrypt(keys.PrivateKey, ciphertext, MaxBits16) + require.Nil(t, err, "Should have no error while decrypting") + require.Equal(t, value, decrypted, "Should have the same value") + + decrypted, err = eg.Decrypt(keys.PrivateKey, ciphertext, MaxBits32) + require.Nil(t, err, "Should have no error while decrypting") + require.Equal(t, value, decrypted, "Should have the same value") + + // Using a different private key to decrypt should yield an error. + decryptedWrongly, err := eg.Decrypt(altKeys.PrivateKey, ciphertext, MaxBits32) + require.Zero(t, decryptedWrongly) + require.Error(t, err, "Should be unable to decrypt using the wrong private key") + + // Test overflow behavior + ciphertextOverflow, _, err := eg.Encrypt(keys.PublicKey, math.MaxUint64) + require.Nil(t, err, "Should have no error while encrypting") + decryptedOverflow, err := eg.Decrypt(keys.PrivateKey, ciphertextOverflow, MaxBits32) + require.Zero(t, decryptedOverflow) + require.Error(t, err, "Should be unable to decrypt the invalid overflow value") +} + +func TestEncryptionDecryptionBLS12377G1Curve(t *testing.T) { + privateKey, _ := generateKey() + altPrivateKey, _ := generateKey() + + eg := NewTwistedElgamal(curves.BLS12377G1()) keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) altKeys, _ := eg.KeyGen(*altPrivateKey, DefaultTestDenom) @@ -87,7 +154,7 @@ func TestEncryptionDecryption(t *testing.T) { // Due to the size of 48 bit numbers, this test takes a really long time (~1hr) to run. func Test48BitEncryptionDecryption(t *testing.T) { - privateKey, err := GenerateKey() + privateKey, err := generateKey() require.Nil(t, err) ed25519 := curves.ED25519() @@ -129,8 +196,8 @@ func Test48BitEncryptionDecryption(t *testing.T) { } func TestAddCiphertext(t *testing.T) { - privateKey, _ := GenerateKey() - altPrivateKey, _ := GenerateKey() + privateKey, _ := generateKey() + altPrivateKey, _ := generateKey() ed25519 := curves.ED25519() eg := NewTwistedElgamal(ed25519) @@ -179,7 +246,7 @@ func TestAddCiphertext(t *testing.T) { func TestTwistedElGamal_InvalidCiphertext(t *testing.T) { curve := curves.ED25519() eg := NewTwistedElgamal(curve) - privateKey, _ := GenerateKey() + privateKey, _ := generateKey() keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) invalidCt := &Ciphertext{} @@ -195,7 +262,7 @@ func TestTwistedElGamal_NilPrivateKey(t *testing.T) { eg := NewTwistedElgamal(curve) // Generate a valid key pair for comparison - privateKey, _ := GenerateKey() + privateKey, _ := generateKey() keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) // Encrypt a value with a valid public key @@ -215,7 +282,7 @@ func TestTwistedElGamal_EncryptDecryptWithRand(t *testing.T) { eg := NewTwistedElgamal(curve) // Generate a valid key pair for comparison - privateKey, _ := GenerateKey() + privateKey, _ := generateKey() keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) message := uint64(555555555) @@ -233,7 +300,7 @@ func TestTwistedElGamal_DecryptWithZeroBits(t *testing.T) { eg := NewTwistedElgamal(curve) // Generate a valid key pair for comparison - privateKey, _ := GenerateKey() + privateKey, _ := generateKey() keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) message := uint64(555555555) @@ -261,7 +328,7 @@ func TestTwistedElGamal_EncryptInvalidRandomFactor(t *testing.T) { eg := NewTwistedElgamal(curve) // Generate a valid key pair for comparison - privateKey, _ := GenerateKey() + privateKey, _ := generateKey() keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) // Test with nil public key @@ -275,7 +342,7 @@ func TestTwistedElGamal_EncryptBoundaryValues(t *testing.T) { eg := NewTwistedElgamal(curve) // Generate a valid key pair for comparison - privateKey, _ := GenerateKey() + privateKey, _ := generateKey() keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) // Test with the smallest possible value (0) From 906f272e352735558cfb8bdee8307f4362c21d71 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Wed, 9 Oct 2024 14:48:15 -0700 Subject: [PATCH 13/14] - Only allow ED25519 curve for now, but cache it to reduce amount of init calls - add marshaling test --- pkg/encryption/elgamal/encryption.go | 21 +--- pkg/encryption/elgamal/encryption_test.go | 122 ++++++---------------- pkg/encryption/elgamal/types.go | 7 +- pkg/encryption/elgamal/types_test.go | 31 ++++++ 4 files changed, 70 insertions(+), 111 deletions(-) create mode 100644 pkg/encryption/elgamal/types_test.go diff --git a/pkg/encryption/elgamal/encryption.go b/pkg/encryption/elgamal/encryption.go index 33ba03c..29caeb0 100644 --- a/pkg/encryption/elgamal/encryption.go +++ b/pkg/encryption/elgamal/encryption.go @@ -16,7 +16,8 @@ type TwistedElGamal struct { maxMapping map[MaxBits]bool } -func NewTwistedElgamalWithED25519Curve() *TwistedElGamal { +// NewTwistedElgamal creates a new TwistedElGamal instance with ED25519 curve. +func NewTwistedElgamal() *TwistedElGamal { var s ristretto.Point s.SetZero() mapping := make(map[string]uint64) @@ -30,30 +31,16 @@ func NewTwistedElgamalWithED25519Curve() *TwistedElGamal { } } -func NewTwistedElgamal(curve *curves.Curve) *TwistedElGamal { - var s ristretto.Point - s.SetZero() - mapping := make(map[string]uint64) - maxMapping := make(map[MaxBits]bool) - mapping[s.String()] = 0 - - return &TwistedElGamal{ - curve: curve, - maxMapping: maxMapping, - mapping: mapping, - } -} - // Encrypt encrypts a message using the public key pk. func (teg TwistedElGamal) Encrypt(pk curves.Point, message uint64) (*Ciphertext, curves.Scalar, error) { // Generate a random scalar r randomFactor := teg.curve.Scalar.Random(crand.Reader) - return teg.EncryptWithRand(pk, message, randomFactor) + return teg.encryptWithRand(pk, message, randomFactor) } // EncryptWithRand encrypts a message using the public key pk and a given random factor. -func (teg TwistedElGamal) EncryptWithRand(pk curves.Point, message uint64, randomFactor curves.Scalar) (*Ciphertext, curves.Scalar, error) { +func (teg TwistedElGamal) encryptWithRand(pk curves.Point, message uint64, randomFactor curves.Scalar) (*Ciphertext, curves.Scalar, error) { if pk == nil { return nil, nil, fmt.Errorf("invalid public key") } diff --git a/pkg/encryption/elgamal/encryption_test.go b/pkg/encryption/elgamal/encryption_test.go index ced2d3d..57e15fb 100644 --- a/pkg/encryption/elgamal/encryption_test.go +++ b/pkg/encryption/elgamal/encryption_test.go @@ -20,40 +20,7 @@ func TestKeyGeneration(t *testing.T) { privateKey, err := generateKey() require.Nil(t, err) - eg := NewTwistedElgamalWithED25519Curve() - keyPair, err := eg.KeyGen(*privateKey, DefaultTestDenom) - require.Nil(t, err) - - // Test that keyPair is deterministically generated - keyPairAgain, err := eg.KeyGen(*privateKey, DefaultTestDenom) - require.Nil(t, err) - require.Equal(t, keyPair, keyPairAgain, "PK should be deterministically generated") - - // Test that changing the salt should generate a different key - altDenom := "factory/sei1239081236470/testToken1" - keyPairDiffSalt, err := eg.KeyGen(*privateKey, altDenom) - require.Nil(t, err) - require.NotEqual(t, keyPair, keyPairDiffSalt, "PK should be different for different salt") - - // Test same thing for salt of same length - altDenom = "factory/sei1239081236470/testTokeN" - keyPairDiffSalt, err = eg.KeyGen(*privateKey, altDenom) - require.Nil(t, err) - require.NotEqual(t, keyPair, keyPairDiffSalt, "PK should be different for different salt") - - // Test that different privateKey should generate different PK - altPrivateKey, err := generateKey() - require.Nil(t, err) - keyPairDiffPK, err := eg.KeyGen(*altPrivateKey, altDenom) - require.Nil(t, err) - require.NotEqual(t, keyPair, keyPairDiffPK, "PK should be different for different ESDCA Private Key") -} - -func TestKeyGenerationBLS12377G1Curve(t *testing.T) { - privateKey, err := generateKey() - require.Nil(t, err) - - eg := NewTwistedElgamal(curves.BLS12377G1()) + eg := NewTwistedElgamal() keyPair, err := eg.KeyGen(*privateKey, DefaultTestDenom) require.Nil(t, err) @@ -86,42 +53,7 @@ func TestEncryptionDecryption(t *testing.T) { privateKey, _ := generateKey() altPrivateKey, _ := generateKey() - eg := NewTwistedElgamalWithED25519Curve() - - keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) - altKeys, _ := eg.KeyGen(*altPrivateKey, DefaultTestDenom) - - // Happy Path - value := uint64(108) - ciphertext, _, err := eg.Encrypt(keys.PublicKey, value) - require.Nil(t, err, "Should have no error while encrypting") - - decrypted, err := eg.Decrypt(keys.PrivateKey, ciphertext, MaxBits16) - require.Nil(t, err, "Should have no error while decrypting") - require.Equal(t, value, decrypted, "Should have the same value") - - decrypted, err = eg.Decrypt(keys.PrivateKey, ciphertext, MaxBits32) - require.Nil(t, err, "Should have no error while decrypting") - require.Equal(t, value, decrypted, "Should have the same value") - - // Using a different private key to decrypt should yield an error. - decryptedWrongly, err := eg.Decrypt(altKeys.PrivateKey, ciphertext, MaxBits32) - require.Zero(t, decryptedWrongly) - require.Error(t, err, "Should be unable to decrypt using the wrong private key") - - // Test overflow behavior - ciphertextOverflow, _, err := eg.Encrypt(keys.PublicKey, math.MaxUint64) - require.Nil(t, err, "Should have no error while encrypting") - decryptedOverflow, err := eg.Decrypt(keys.PrivateKey, ciphertextOverflow, MaxBits32) - require.Zero(t, decryptedOverflow) - require.Error(t, err, "Should be unable to decrypt the invalid overflow value") -} - -func TestEncryptionDecryptionBLS12377G1Curve(t *testing.T) { - privateKey, _ := generateKey() - altPrivateKey, _ := generateKey() - - eg := NewTwistedElgamal(curves.BLS12377G1()) + eg := NewTwistedElgamal() keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) altKeys, _ := eg.KeyGen(*altPrivateKey, DefaultTestDenom) @@ -157,8 +89,7 @@ func Test48BitEncryptionDecryption(t *testing.T) { privateKey, err := generateKey() require.Nil(t, err) - ed25519 := curves.ED25519() - eg := NewTwistedElgamal(ed25519) + eg := NewTwistedElgamal() keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) // First decrypt a 32 bit number (sets up the decryptor for a later test) @@ -199,8 +130,7 @@ func TestAddCiphertext(t *testing.T) { privateKey, _ := generateKey() altPrivateKey, _ := generateKey() - ed25519 := curves.ED25519() - eg := NewTwistedElgamal(ed25519) + eg := NewTwistedElgamal() keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) altKeys, _ := eg.KeyGen(*altPrivateKey, DefaultTestDenom) @@ -244,8 +174,7 @@ func TestAddCiphertext(t *testing.T) { } func TestTwistedElGamal_InvalidCiphertext(t *testing.T) { - curve := curves.ED25519() - eg := NewTwistedElgamal(curve) + eg := NewTwistedElgamal() privateKey, _ := generateKey() keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) @@ -258,8 +187,7 @@ func TestTwistedElGamal_InvalidCiphertext(t *testing.T) { } func TestTwistedElGamal_NilPrivateKey(t *testing.T) { - curve := curves.ED25519() - eg := NewTwistedElgamal(curve) + eg := NewTwistedElgamal() // Generate a valid key pair for comparison privateKey, _ := generateKey() @@ -278,16 +206,15 @@ func TestTwistedElGamal_NilPrivateKey(t *testing.T) { } func TestTwistedElGamal_EncryptDecryptWithRand(t *testing.T) { - curve := curves.ED25519() - eg := NewTwistedElgamal(curve) + eg := NewTwistedElgamal() // Generate a valid key pair for comparison privateKey, _ := generateKey() keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) message := uint64(555555555) - randomFactor := curve.Scalar.Random(rand.Reader) - ct, _, err := eg.EncryptWithRand(keys.PublicKey, message, randomFactor) + randomFactor := curves.ED25519().Scalar.Random(rand.Reader) + ct, _, err := eg.encryptWithRand(keys.PublicKey, message, randomFactor) require.NoError(t, err, "Encryption with randomFactor should not fail") decrypted, err := eg.DecryptLargeNumber(keys.PrivateKey, ct, MaxBits48) @@ -295,9 +222,25 @@ func TestTwistedElGamal_EncryptDecryptWithRand(t *testing.T) { require.Equal(t, message, decrypted, "Decrypted message should match original") } +func TestTwistedElGamal_EncryptMessageTwice(t *testing.T) { + curve := curves.ED25519() + eg := NewTwistedElgamal() + + // Generate a valid key pair for comparison + privateKey, _ := generateKey() + keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) + + message := uint64(555555555) + randomFactor := curve.Scalar.Random(rand.Reader) + ct1, _, _ := eg.encryptWithRand(keys.PublicKey, message, randomFactor) + ct2, _, _ := eg.encryptWithRand(keys.PublicKey, message, randomFactor) + + require.Equal(t, ct1, ct2, "Ciphertexts should be the same for the same message and random factor") +} + func TestTwistedElGamal_DecryptWithZeroBits(t *testing.T) { curve := curves.ED25519() - eg := NewTwistedElgamal(curve) + eg := NewTwistedElgamal() // Generate a valid key pair for comparison privateKey, _ := generateKey() @@ -305,7 +248,7 @@ func TestTwistedElGamal_DecryptWithZeroBits(t *testing.T) { message := uint64(555555555) randomFactor := curve.Scalar.Random(rand.Reader) - ct, _, err := eg.EncryptWithRand(keys.PublicKey, message, randomFactor) + ct, _, err := eg.encryptWithRand(keys.PublicKey, message, randomFactor) require.NoError(t, err, "Encryption with randomFactor should not fail") _, err = eg.DecryptLargeNumber(keys.PrivateKey, ct, MaxBits(0)) @@ -314,8 +257,7 @@ func TestTwistedElGamal_DecryptWithZeroBits(t *testing.T) { } func TestTwistedElGamal_EncryptInvalidKey(t *testing.T) { - curve := curves.ED25519() - eg := NewTwistedElgamal(curve) + eg := NewTwistedElgamal() // Test with nil public key _, _, err := eg.Encrypt(nil, 12345) @@ -324,22 +266,20 @@ func TestTwistedElGamal_EncryptInvalidKey(t *testing.T) { } func TestTwistedElGamal_EncryptInvalidRandomFactor(t *testing.T) { - curve := curves.ED25519() - eg := NewTwistedElgamal(curve) + eg := NewTwistedElgamal() // Generate a valid key pair for comparison privateKey, _ := generateKey() keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) // Test with nil public key - _, _, err := eg.EncryptWithRand(keys.PublicKey, uint64(12345), nil) + _, _, err := eg.encryptWithRand(keys.PublicKey, uint64(12345), nil) require.Error(t, err, "Encryption should fail with nil random factor") require.Equal(t, "invalid random factor", err.Error()) } func TestTwistedElGamal_EncryptBoundaryValues(t *testing.T) { - curve := curves.ED25519() - eg := NewTwistedElgamal(curve) + eg := NewTwistedElgamal() // Generate a valid key pair for comparison privateKey, _ := generateKey() diff --git a/pkg/encryption/elgamal/types.go b/pkg/encryption/elgamal/types.go index e6cb627..94a11bb 100644 --- a/pkg/encryption/elgamal/types.go +++ b/pkg/encryption/elgamal/types.go @@ -29,7 +29,7 @@ const ( ) // MarshalJSON for Ciphertext -func (c Ciphertext) MarshalJSON() ([]byte, error) { +func (c *Ciphertext) MarshalJSON() ([]byte, error) { // Serialize the points to a format you prefer return json.Marshal(map[string]interface{}{ "c": c.C.ToAffineCompressed(), // Assuming `ToAffineCompressed` returns a byte slice @@ -52,11 +52,12 @@ func (c *Ciphertext) UnmarshalJSON(data []byte) error { // Convert the byte arrays back into curve points // Assuming `FromCompressed` is a method to parse compressed points - pointC, err := curves.ED25519().Point.FromAffineCompressed(temp.C) + ed25519Curve := curves.ED25519() + pointC, err := ed25519Curve.Point.FromAffineCompressed(temp.C) if err != nil { return err } - pointD, err := curves.ED25519().Point.FromAffineCompressed(temp.D) + pointD, err := ed25519Curve.Point.FromAffineCompressed(temp.D) if err != nil { return err } diff --git a/pkg/encryption/elgamal/types_test.go b/pkg/encryption/elgamal/types_test.go new file mode 100644 index 0000000..a3f10da --- /dev/null +++ b/pkg/encryption/elgamal/types_test.go @@ -0,0 +1,31 @@ +package elgamal + +import ( + "encoding/json" + "github.com/stretchr/testify/require" + "testing" +) + +func TestCiphertext_MarshalJSON(t *testing.T) { + privateKey, _ := generateKey() + eg := NewTwistedElgamal() + + keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) + + value := uint64(108) + ciphertext, _, err := eg.Encrypt(keys.PublicKey, value) + + // Marshal the Ciphertext to JSON + data, err := json.Marshal(ciphertext) + require.NoError(t, err, "Marshaling should not produce an error") + + // Unmarshal the JSON back to a Ciphertext + var unmarshaled Ciphertext + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err, "Unmarshaling should not produce an error") + + // Compare the original and unmarshaled Ciphertext + require.True(t, ciphertext.C.Equal(unmarshaled.C), "C points should be equal") + require.True(t, ciphertext.D.Equal(unmarshaled.D), "D points should be equal") + +} From ee26f2d1fc4dbef87a748ad6e8d74494fad18252 Mon Sep 17 00:00:00 2001 From: _dssei_ Date: Wed, 9 Oct 2024 14:53:07 -0700 Subject: [PATCH 14/14] - fix linting issue --- pkg/encryption/elgamal/types_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/encryption/elgamal/types_test.go b/pkg/encryption/elgamal/types_test.go index a3f10da..a419803 100644 --- a/pkg/encryption/elgamal/types_test.go +++ b/pkg/encryption/elgamal/types_test.go @@ -13,7 +13,7 @@ func TestCiphertext_MarshalJSON(t *testing.T) { keys, _ := eg.KeyGen(*privateKey, DefaultTestDenom) value := uint64(108) - ciphertext, _, err := eg.Encrypt(keys.PublicKey, value) + ciphertext, _, _ := eg.Encrypt(keys.PublicKey, value) // Marshal the Ciphertext to JSON data, err := json.Marshal(ciphertext)