├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── doc.go ├── encryption_test.go ├── examples_test.go ├── go.mod ├── go.sum ├── hash.go ├── private_key.go ├── private_key_test.go ├── public_key.go ├── public_key_test.go ├── signature.go └── signature_test.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go # The name of the workflow that will appear on Github 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | # Allows you to run this workflow manually from the Actions tab 9 | workflow_dispatch: 10 | 11 | jobs: 12 | 13 | build: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest] 18 | go: [1.20.7] 19 | steps: 20 | - uses: actions/checkout@v3 21 | 22 | - name: Set up Go 23 | uses: actions/setup-go@v4 24 | with: 25 | go-version: ${{ matrix.go }} 26 | 27 | - name: Build 28 | run: go install 29 | 30 | - name: Install goveralls 31 | run: go install github.com/mattn/goveralls@latest 32 | 33 | - name: Run Unit tests 34 | run: | 35 | go test -race -covermode atomic -coverprofile=covprofile ./... 36 | 37 | - name: Send coverage 38 | env: 39 | COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | run: goveralls -coverprofile=covprofile -service=github 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Leonid Gorkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Easy Elliptic Curve Cryptography in Go 2 | 3 | ![GitHub Workflow Status](https://github.com/regnull/easyecc/actions/workflows/go.yml/badge.svg) 4 | [![GoDoc reference example](https://img.shields.io/badge/godoc-reference-blue.svg)](https://godoc.org/github.com/regnull/easyecc) 5 | [![GoReportCard example](https://goreportcard.com/badge/github.com/regnull/easyecc)](https://goreportcard.com/report/github.com/regnull/easyecc) 6 | [![Coverage Status](https://coveralls.io/repos/github/regnull/easyecc/badge.svg?branch=master)](https://coveralls.io/github/regnull/easyecc?branch=master) 7 | 8 | This package ties several other commonly used cryptography packages together. The goal is to make common cryptographic operations simple. 9 | The following elliptic curves are supported: 10 | 11 | * [secp256k1](https://en.bitcoin.it/wiki/Secp256k1) 12 | 13 | * [P-256](https://neuromancer.sk/std/nist/P-256) 14 | 15 | * [P-384](https://neuromancer.sk/std/nist/P-384) 16 | 17 | * [P-521](https://neuromancer.sk/std/nist/P-521) 18 | 19 | 20 | This package was originally the part of https://github.com/regnull/ubikom, but then became its own little package, because why not. 21 | 22 | ## Examples 23 | 24 | (see examples_test.go and encryption_test.go files). 25 | 26 | Elliptic curves are defined as constants: 27 | 28 | ```Go 29 | const ( 30 | SECP256K1 EllipticCurve = 1 31 | P256 EllipticCurve = 2 32 | P384 EllipticCurve = 3 33 | P521 EllipticCurve = 4 34 | ) 35 | ``` 36 | 37 | Use them when creating keys. 38 | 39 | ## Sign hash and verify signature (Using [ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm)) 40 | 41 | ```Go 42 | privateKey := CreatePrivateKey(P256, big.NewInt(12345)) 43 | data := "super secret message" 44 | hash := Hash256([]byte(data)) 45 | signature, err := privateKey.Sign(hash) 46 | if err != nil { 47 | log.Fatal(err) 48 | } 49 | publicKey := privateKey.PublicKey() 50 | success := signature.Verify(publicKey, hash) 51 | fmt.Printf("Signature verified: %v\n", success) 52 | // Output: Signature verified: true 53 | ``` 54 | 55 | ## Encrypt with shared secret (Using [ECDH](https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman)): 56 | 57 | ```Go 58 | aliceKey, err := GeneratePrivateKey(P256) 59 | if err != nil { 60 | log.Fatal(err) 61 | } 62 | bobKey, err := GeneratePrivateKey(P256) 63 | if err != nil { 64 | log.Fatal(err) 65 | } 66 | data := "super secret message" 67 | encrypted, err := aliceKey.EncryptECDH([]byte(data), bobKey.PublicKey()) 68 | if err != nil { 69 | log.Fatal(err) 70 | } 71 | decrypted, err := bobKey.DecryptECDH(encrypted, aliceKey.PublicKey()) 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | fmt.Printf("%s\n", string(decrypted)) 76 | // Output: super secret message 77 | ``` 78 | 79 | ## Encrypt private key with passphrase 80 | 81 | ```Go 82 | privateKey := CreatePrivateKey(P256, big.NewInt(12345)) 83 | encryptedKey, err := privateKey.EncryptKeyWithPassphrase("my passphrase") 84 | if err != nil { 85 | log.Fatal(err) 86 | } 87 | decryptedKey, err := CreatePrivateKeyFromEncrypted(P256, encryptedKey, "my passphrase") 88 | fmt.Printf("%d\n", decryptedKey.Secret()) 89 | // Output: 12345 90 | ``` 91 | 92 | ## Serialize Public Key 93 | 94 | ```Go 95 | privateKey := CreatePrivateKey(P256, big.NewInt(12345)) 96 | publicKey := privateKey.PublicKey() 97 | serializedCompressed := publicKey.SerializeCompressed() 98 | fmt.Printf("%x\n", serializedCompressed) 99 | publicKeyCopy, err := DeserializeCompressed(P256, serializedCompressed) 100 | if err != nil { 101 | log.Fatal(err) 102 | } 103 | sameKey := publicKey.Equal(publicKeyCopy) 104 | fmt.Printf("the correct key was created: %v\n", sameKey) 105 | // Output: 0226efcebd0ee9e34a669187e18b3a9122b2f733945b649cc9f9f921e9f9dad812 106 | // the correct key was created: true 107 | ``` 108 | 109 | ## Getting Bitcoin and Ethereum addresses: 110 | ```Go 111 | // BitcoinAddress and EthereumAddress only work for secp256k1 curve. 112 | privateKey := CreatePrivateKey(SECP256K1, big.NewInt(12345)) 113 | publicKey := privateKey.PublicKey() 114 | bitcoinAddress, err := publicKey.BitcoinAddress() 115 | if err != nil { 116 | log.Fatal(err) 117 | } 118 | fmt.Printf("Bitcoin address: %s\n", bitcoinAddress) 119 | ethereumAddress, err := publicKey.EthereumAddress() 120 | if err != nil { 121 | log.Fatal(err) 122 | } 123 | fmt.Printf("Ethereum address: %s\n", ethereumAddress) 124 | // Output: Bitcoin address: 12vieiAHxBe4qCUrwvfb2kRkDuc8kQ2VZ2 125 | // Ethereum address: 0xEB4665750b1382DF4AeBF49E04B429AAAc4d9929 126 | ``` 127 | 128 | ## JWK Support 129 | 130 | EasyECC offers some limited JWK support (see https://www.rfc-editor.org/rfc/rfc7517). 131 | Private keys can be exported and imported as JWK JSON: 132 | ```Go 133 | privateKey := CreatePrivateKey(P256, big.NewInt(12345)) 134 | jwkBytes, err := privateKey.MarshalToJWK() 135 | if err != nil { 136 | log.Fatal(err) 137 | } 138 | fmt.Printf("%s\n", jwkBytes) 139 | 140 | privateKeyCopy, err := CreatePrivateKeyFromJWK(jwkBytes) 141 | if err != nil { 142 | log.Fatal(err) 143 | } 144 | if privateKey.Equal(privateKeyCopy) { 145 | fmt.Printf("keys match!") 146 | } 147 | // Output: { 148 | // "kty": "EC", 149 | // "crv": "P-256", 150 | // "x": "Ju/OvQ7p40pmkYfhizqRIrL3M5RbZJzJ+fkh6fna2BI", 151 | // "y": "kCOL3pzHuzMNFQxncE3SWucFUgV0S28xv0BwdFhy0OY", 152 | // "d": "MDk" 153 | // } 154 | // keys match! 155 | ``` 156 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package easyecc ties together several other common packages and makes it easy to 3 | perform common elliptic key cryptography operations on multiple curves 4 | (including secp256k1, used by Bitcoin, see https://en.bitcoin.it/wiki/Secp256k1). 5 | 6 | In addition to secp256k1, P-256, P-384 and P-521 are also supported. 7 | 8 | These operations include: 9 | 10 | -- Creating private keys, in various ways 11 | 12 | -- Saving private key to file, possibly passphrase-protected 13 | 14 | -- Reading and decrypting private key from file 15 | 16 | -- Signing data using the private key and verifying with the public key (ECDSA) 17 | 18 | -- Encrypting data using a symmetric encryption key derived from private key/public key pair (ECDH) 19 | 20 | See the examples for more information. 21 | */ 22 | package easyecc 23 | -------------------------------------------------------------------------------- /encryption_test.go: -------------------------------------------------------------------------------- 1 | package easyecc 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/ethereum/go-ethereum/crypto" 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | ) 11 | 12 | func TestEncryption(t *testing.T) { 13 | require := require.New(t) 14 | 15 | alicePrivateKey, err := GeneratePrivateKey(SECP256K1) 16 | require.NoError(err) 17 | 18 | bobPrivateKey, err := GeneratePrivateKey(SECP256K1) 19 | require.NoError(err) 20 | 21 | key1x, key1y := crypto.S256().ScalarMult(alicePrivateKey.PublicKey().X(), alicePrivateKey.PublicKey().Y(), 22 | bobPrivateKey.Secret().Bytes()) 23 | 24 | key2x, key2y := crypto.S256().ScalarMult(bobPrivateKey.PublicKey().X(), bobPrivateKey.PublicKey().Y(), 25 | alicePrivateKey.Secret().Bytes()) 26 | 27 | require.Equal(key1x, key2x) 28 | require.Equal(key1y, key2y) 29 | } 30 | 31 | func TestEncryptDecrypt(t *testing.T) { 32 | assert := assert.New(t) 33 | 34 | alicePrivateKey, err := GeneratePrivateKey(P256) 35 | assert.NoError(err) 36 | 37 | bobPrivateKey, err := GeneratePrivateKey(P256) 38 | assert.NoError(err) 39 | 40 | message := "All that we are is the result of what we have thought" 41 | ciphertext, err := alicePrivateKey.EncryptECDH([]byte(message), bobPrivateKey.PublicKey()) 42 | assert.NoError(err) 43 | 44 | plaintext, err := bobPrivateKey.DecryptECDH(ciphertext, alicePrivateKey.PublicKey()) 45 | assert.NoError(err) 46 | 47 | assert.True(bytes.Equal([]byte(message), plaintext)) 48 | 49 | // Test error on different curves. 50 | spongeBobPrivateKey, err := GeneratePrivateKey(P521) 51 | assert.NoError(err) 52 | _, err = alicePrivateKey.EncryptECDH([]byte(message), spongeBobPrivateKey.PublicKey()) 53 | assert.Equal(ErrDifferentCurves, err) 54 | } 55 | 56 | func TestEncryptDecryptSymmetric(t *testing.T) { 57 | assert := assert.New(t) 58 | 59 | privateKey, err := GeneratePrivateKey(P521) 60 | assert.NoError(err) 61 | 62 | message := "super secret message" 63 | encrypted, err := privateKey.EncryptSymmetric([]byte(message)) 64 | assert.NoError(err) 65 | 66 | assert.True(len(encrypted) > len([]byte(message))) 67 | 68 | decrypted, err := privateKey.DecryptSymmetric(encrypted) 69 | assert.NoError(err) 70 | assert.EqualValues(message, string(decrypted)) 71 | } 72 | -------------------------------------------------------------------------------- /examples_test.go: -------------------------------------------------------------------------------- 1 | package easyecc 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "math/big" 7 | ) 8 | 9 | func ExamplePrivateKey_Sign() { 10 | privateKey := CreatePrivateKey(P256, big.NewInt(12345)) 11 | data := "super secret message" 12 | hash := Hash256([]byte(data)) 13 | signature, err := privateKey.Sign(hash) 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | publicKey := privateKey.PublicKey() 18 | success := signature.Verify(publicKey, hash) 19 | fmt.Printf("Signature verified: %v\n", success) 20 | // Output: Signature verified: true 21 | } 22 | 23 | func ExamplePrivateKey_Encrypt() { 24 | aliceKey, err := GeneratePrivateKey(P256) 25 | if err != nil { 26 | log.Fatal(err) 27 | } 28 | bobKey, err := GeneratePrivateKey(P256) 29 | if err != nil { 30 | log.Fatal(err) 31 | } 32 | data := "super secret message" 33 | encrypted, err := aliceKey.EncryptECDH([]byte(data), bobKey.PublicKey()) 34 | if err != nil { 35 | log.Fatal(err) 36 | } 37 | decrypted, err := bobKey.DecryptECDH(encrypted, aliceKey.PublicKey()) 38 | if err != nil { 39 | log.Fatal(err) 40 | } 41 | fmt.Printf("%s\n", string(decrypted)) 42 | // Output: super secret message 43 | } 44 | 45 | func ExamplePrivateKey_EncryptKeyWithPassphrase() { 46 | privateKey := CreatePrivateKey(P256, big.NewInt(12345)) 47 | encryptedKey, err := privateKey.EncryptKeyWithPassphrase("my passphrase") 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | decryptedKey, err := CreatePrivateKeyFromEncrypted(P256, encryptedKey, "my passphrase") 52 | if err != nil { 53 | log.Fatal(err) 54 | } 55 | fmt.Printf("%d\n", decryptedKey.Secret()) 56 | // Output: 12345 57 | } 58 | 59 | func ExamplePrivateKey_MarshalToJWK() { 60 | privateKey := CreatePrivateKey(P256, big.NewInt(12345)) 61 | jwkBytes, err := privateKey.MarshalToJWK() 62 | if err != nil { 63 | log.Fatal(err) 64 | } 65 | fmt.Printf("%s\n", jwkBytes) 66 | 67 | privateKeyCopy, err := CreatePrivateKeyFromJWK(jwkBytes) 68 | if err != nil { 69 | log.Fatal(err) 70 | } 71 | if privateKey.Equal(privateKeyCopy) { 72 | fmt.Printf("keys match!") 73 | } 74 | // Output: { 75 | // "kty": "EC", 76 | // "crv": "P-256", 77 | // "x": "Ju/OvQ7p40pmkYfhizqRIrL3M5RbZJzJ+fkh6fna2BI", 78 | // "y": "kCOL3pzHuzMNFQxncE3SWucFUgV0S28xv0BwdFhy0OY", 79 | // "d": "MDk" 80 | // } 81 | // keys match! 82 | } 83 | 84 | func ExamplePublicKey_SerializeCompressed() { 85 | privateKey := CreatePrivateKey(P256, big.NewInt(12345)) 86 | publicKey := privateKey.PublicKey() 87 | serializedCompressed := publicKey.SerializeCompressed() 88 | fmt.Printf("%x\n", serializedCompressed) 89 | publicKeyCopy, err := DeserializeCompressed(P256, serializedCompressed) 90 | if err != nil { 91 | log.Fatal(err) 92 | } 93 | sameKey := publicKey.Equal(publicKeyCopy) 94 | fmt.Printf("the correct key was created: %v\n", sameKey) 95 | // Output: 0226efcebd0ee9e34a669187e18b3a9122b2f733945b649cc9f9f921e9f9dad812 96 | // the correct key was created: true 97 | } 98 | 99 | func ExamplePublicKey_BitcoinAndEthereumAddress() { 100 | // BitcoinAddress and EthereumAddress only work for secp256k1 curve. 101 | privateKey := CreatePrivateKey(SECP256K1, big.NewInt(12345)) 102 | publicKey := privateKey.PublicKey() 103 | bitcoinAddress, err := publicKey.BitcoinAddress() 104 | if err != nil { 105 | log.Fatal(err) 106 | } 107 | fmt.Printf("Bitcoin address: %s\n", bitcoinAddress) 108 | ethereumAddress, err := publicKey.EthereumAddress() 109 | if err != nil { 110 | log.Fatal(err) 111 | } 112 | fmt.Printf("Ethereum address: %s\n", ethereumAddress) 113 | // Output: Bitcoin address: 12vieiAHxBe4qCUrwvfb2kRkDuc8kQ2VZ2 114 | // Ethereum address: 0xEB4665750b1382DF4AeBF49E04B429AAAc4d9929 115 | } 116 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/regnull/easyecc 2 | 3 | go 1.21 4 | 5 | require ( 6 | github.com/ethereum/go-ethereum v1.13.4 7 | github.com/mr-tron/base58 v1.2.0 8 | github.com/stretchr/testify v1.8.4 9 | github.com/tyler-smith/go-bip39 v1.1.0 10 | golang.org/x/crypto v0.14.0 11 | ) 12 | 13 | require ( 14 | github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect 17 | github.com/holiman/uint256 v1.2.3 // indirect 18 | github.com/pmezard/go-difflib v1.0.0 // indirect 19 | golang.org/x/sys v0.13.0 // indirect 20 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 21 | gopkg.in/yaml.v3 v3.0.1 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= 2 | github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= 3 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= 4 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= 8 | github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= 9 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs= 10 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= 11 | github.com/ethereum/go-ethereum v1.13.4 h1:25HJnaWVg3q1O7Z62LaaI6S9wVq8QCw3K88g8wEzrcM= 12 | github.com/ethereum/go-ethereum v1.13.4/go.mod h1:I0U5VewuuTzvBtVzKo7b3hJzDhXOUtn9mJW7SsIPB0Q= 13 | github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o= 14 | github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw= 15 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 16 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 17 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 18 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 19 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 20 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 21 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 22 | github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= 23 | github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= 24 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 25 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 26 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= 27 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 28 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 29 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 30 | github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= 31 | github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= 32 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 33 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 34 | golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= 35 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 36 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 37 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 38 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 39 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 40 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 42 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 44 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 45 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 46 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 47 | -------------------------------------------------------------------------------- /hash.go: -------------------------------------------------------------------------------- 1 | package easyecc 2 | 3 | import ( 4 | "crypto/sha256" 5 | "hash" 6 | 7 | "golang.org/x/crypto/ripemd160" 8 | ) 9 | 10 | // Hash256 does two rounds of SHA256 hashing. 11 | func Hash256(data []byte) []byte { 12 | h := sha256.Sum256(data) 13 | h1 := sha256.Sum256(h[:]) 14 | return h1[:] 15 | } 16 | 17 | // Calculate the hash of hasher over buf. 18 | func calcHash(buf []byte, hasher hash.Hash) []byte { 19 | hasher.Write(buf) 20 | return hasher.Sum(nil) 21 | } 22 | 23 | // Hash160 calculates the hash ripemd160(sha256(b)). 24 | func Hash160(buf []byte) []byte { 25 | return calcHash(calcHash(buf, sha256.New()), ripemd160.New()) 26 | } 27 | -------------------------------------------------------------------------------- /private_key.go: -------------------------------------------------------------------------------- 1 | package easyecc 2 | 3 | import ( 4 | "bytes" 5 | "crypto/aes" 6 | "crypto/cipher" 7 | "crypto/ecdh" 8 | "crypto/ecdsa" 9 | "crypto/elliptic" 10 | "crypto/rand" 11 | "crypto/sha256" 12 | "encoding/base64" 13 | "encoding/json" 14 | "fmt" 15 | "io" 16 | "math/big" 17 | "os" 18 | "strings" 19 | 20 | "github.com/ethereum/go-ethereum/crypto" 21 | "github.com/tyler-smith/go-bip39" 22 | "golang.org/x/crypto/pbkdf2" 23 | "golang.org/x/crypto/scrypt" 24 | ) 25 | 26 | const ( 27 | PBKDF2_ITER = 16384 28 | PBKDF2_SIZE = 32 29 | ) 30 | 31 | var ErrUnsupportedCurve = fmt.Errorf("the operation is not supported on this curve") 32 | var ErrDifferentCurves = fmt.Errorf("the keys must use the same curve") 33 | var ErrUnsupportedKeyType = fmt.Errorf("unsupported key type") 34 | 35 | type EllipticCurve int 36 | 37 | const ( 38 | INVALID_CURVE EllipticCurve = -1 39 | SECP256K1 EllipticCurve = 1 40 | P256 EllipticCurve = 2 41 | P384 EllipticCurve = 3 42 | P521 EllipticCurve = 4 43 | ) 44 | 45 | func (ec EllipticCurve) String() string { 46 | switch ec { 47 | case SECP256K1: 48 | return "secp256k1" 49 | case P256: 50 | return "P-256" 51 | case P384: 52 | return "P-384" 53 | case P521: 54 | return "P-521" 55 | } 56 | return "Invalid" 57 | } 58 | 59 | func StringToEllipticCurve(s string) EllipticCurve { 60 | switch strings.ToUpper(s) { 61 | case "SECP256K1": 62 | return SECP256K1 63 | case "P-256": 64 | return P256 65 | case "P-384": 66 | return P384 67 | case "P-521": 68 | return P521 69 | } 70 | 71 | return INVALID_CURVE 72 | } 73 | 74 | func getCurve(curve EllipticCurve) elliptic.Curve { 75 | switch curve { 76 | case SECP256K1: 77 | return crypto.S256() 78 | case P256: 79 | return elliptic.P256() 80 | case P384: 81 | return elliptic.P384() 82 | case P521: 83 | return elliptic.P521() 84 | } 85 | return nil 86 | } 87 | 88 | func getKeyLength(curve EllipticCurve) int { 89 | switch curve { 90 | case SECP256K1: 91 | return 32 92 | case P256: 93 | return 32 94 | case P384: 95 | return 48 96 | case P521: 97 | return 66 98 | } 99 | return -1 100 | } 101 | 102 | // PrivateKey represents elliptic cryptography private key. 103 | type PrivateKey struct { 104 | privateKey *ecdsa.PrivateKey 105 | } 106 | 107 | type privateKeyJSON struct { 108 | Kty string `json:"kty"` 109 | Crv string `json:"crv"` 110 | X string `json:"x"` 111 | Y string `json:"y"` 112 | D string `json:"d"` 113 | } 114 | 115 | // GeneratePrivateKey creates a new random private key, 116 | // given a curve. 117 | func GeneratePrivateKey(curve EllipticCurve) (*PrivateKey, error) { 118 | privateKey, err := ecdsa.GenerateKey(getCurve(curve), rand.Reader) 119 | if err != nil { 120 | return nil, fmt.Errorf("failed to generate private key, %v", err) 121 | } 122 | return &PrivateKey{privateKey: privateKey}, nil 123 | } 124 | 125 | // CreatePrivateKeyFromETHKey creates private key from eth generated key 126 | func CreatePrivateKeyFromETHKey(privateKey *ecdsa.PrivateKey) *PrivateKey { 127 | return &PrivateKey{privateKey: privateKey} 128 | } 129 | 130 | // CreatePrivateKey creates a private key on the given curve from secret. 131 | func CreatePrivateKey(curve EllipticCurve, secret *big.Int) *PrivateKey { 132 | privateKey := &ecdsa.PrivateKey{ 133 | D: secret} 134 | privateKey.PublicKey.Curve = getCurve(curve) 135 | privateKey.PublicKey.X, privateKey.PublicKey.Y = privateKey.PublicKey.Curve.ScalarBaseMult(secret.Bytes()) 136 | return &PrivateKey{privateKey: privateKey} 137 | } 138 | 139 | // CreatePrivateKeyFromPassword creates a private key on the given curve from password using PBKDF2 algorithm. 140 | func CreatePrivateKeyFromPassword(curve EllipticCurve, password, salt []byte) *PrivateKey { 141 | secret := pbkdf2.Key(password, salt, PBKDF2_ITER, PBKDF2_SIZE, sha256.New) 142 | return CreatePrivateKey(curve, new(big.Int).SetBytes(secret)) 143 | } 144 | 145 | // CreatePrivateKeyFromEncrypted creates a private key from from encrypted private 146 | // key using the passphrase. 147 | // Encryption is done using AES-256 with CGM cipher, with a key derived from the passphrase. 148 | func CreatePrivateKeyFromEncrypted(curve EllipticCurve, data []byte, passphrase string) (*PrivateKey, 149 | error) { 150 | // Data length must be the key length plus at least one more byte. 151 | // TODO: This doesn't look like a useful check. 152 | if len(data) < getKeyLength(curve)+1 { 153 | return nil, fmt.Errorf("invalid data") 154 | } 155 | salt, data := data[len(data)-32:], data[:len(data)-32] 156 | 157 | key, _, err := deriveKey([]byte(passphrase), salt) 158 | if err != nil { 159 | return nil, err 160 | } 161 | 162 | keyBytes, err := decrypt(key, data) 163 | if err != nil { 164 | return nil, err 165 | } 166 | 167 | secret := new(big.Int).SetBytes(keyBytes) 168 | return CreatePrivateKey(curve, secret), nil 169 | } 170 | 171 | // NewPrivateKeyFromFile loads private key using given curve 172 | // from file and decrypts it using the given passphrase. 173 | // If the passphrase is an empty string, no decryption is done (the file content is assumed 174 | // to be not encrypted). 175 | func CreatePrivateKeyFromFile(curve EllipticCurve, fileName string, passphrase string) (*PrivateKey, error) { 176 | // TODO: Perhaps rename this function to something like LoadPrivateKey in the next major version? 177 | b, err := os.ReadFile(fileName) 178 | if err != nil { 179 | return nil, fmt.Errorf("failed to load private key: %v", err) 180 | } 181 | 182 | if passphrase != "" { 183 | return CreatePrivateKeyFromEncrypted(curve, b, passphrase) 184 | } 185 | 186 | secret := new(big.Int) 187 | secret.SetBytes(b) 188 | return CreatePrivateKey(curve, secret), nil 189 | } 190 | 191 | // CreatePrivateKeyFromJWKFile loads private key from file in JWK format, optionally 192 | // decrypting it. 193 | func CreatePrivateKeyFromJWKFile(fileName string, passphrase string) (*PrivateKey, error) { 194 | // TODO: Rename this to LoadPrivateKeyAsJWK in the next major version. 195 | data, err := os.ReadFile(fileName) 196 | if err != nil { 197 | return nil, fmt.Errorf("failed to load private key: %v", err) 198 | } 199 | 200 | var jsonBytes []byte 201 | if passphrase != "" { 202 | salt, data := data[len(data)-32:], data[:len(data)-32] 203 | key, _, err := deriveKey([]byte(passphrase), salt) 204 | if err != nil { 205 | return nil, err 206 | } 207 | 208 | jsonBytes, err = decrypt(key, data) 209 | if err != nil { 210 | return nil, err 211 | } 212 | } else { 213 | jsonBytes = data 214 | } 215 | 216 | return CreatePrivateKeyFromJWK(jsonBytes) 217 | } 218 | 219 | // NewPrivateKeyFromMnemonic creates private key on given curve from a mnemonic phrase. 220 | func CreatePrivateKeyFromMnemonic(curve EllipticCurve, mnemonic string) (*PrivateKey, error) { 221 | if curve != SECP256K1 && curve != P256 { 222 | return nil, ErrUnsupportedCurve 223 | } 224 | b, err := bip39.EntropyFromMnemonic(mnemonic) 225 | if err != nil { 226 | return nil, err 227 | } 228 | secret := new(big.Int).SetBytes(b) 229 | return CreatePrivateKey(curve, secret), nil 230 | } 231 | 232 | // Secret returns the private key's secret. 233 | func (pk *PrivateKey) Secret() *big.Int { 234 | return pk.privateKey.D 235 | } 236 | 237 | // CreatePrivateKeyFromJWK creates private key from JWK-encoded 238 | // representation. 239 | // See https://www.rfc-editor.org/rfc/rfc7517. 240 | func CreatePrivateKeyFromJWK(data []byte) (*PrivateKey, error) { 241 | var pkJSON privateKeyJSON 242 | err := json.Unmarshal(data, &pkJSON) 243 | if err != nil { 244 | return nil, err 245 | } 246 | if pkJSON.Kty != "EC" { 247 | return nil, ErrUnsupportedKeyType 248 | } 249 | curve := StringToEllipticCurve(pkJSON.Crv) 250 | if curve == INVALID_CURVE { 251 | return nil, ErrUnsupportedCurve 252 | } 253 | // JWK uses Base64url encoding, which is Base64 encoding without padding. 254 | dBytes, err := base64. 255 | StdEncoding.WithPadding(base64.NoPadding). 256 | DecodeString(pkJSON.D) 257 | if err != nil { 258 | return nil, err 259 | } 260 | d := new(big.Int) 261 | d.SetBytes(dBytes) 262 | return CreatePrivateKey(curve, d), nil 263 | } 264 | 265 | // Save saves the private key to the specified file. If the passphrase is given, the key will 266 | // be encrypted with this passphrase. If the passphrase is an empty string, the key is not 267 | // encrypted. 268 | func (pk *PrivateKey) Save(fileName string, passphrase string) error { 269 | if passphrase != "" { 270 | data, err := pk.EncryptKeyWithPassphrase(passphrase) 271 | if err != nil { 272 | return err 273 | } 274 | return os.WriteFile(fileName, data, 0600) 275 | } 276 | 277 | // Pad with zero bytes if necessary. 278 | b := padWithZeros(pk.privateKey.D.Bytes(), getKeyLength(pk.Curve())) 279 | return os.WriteFile(fileName, b, 0600) 280 | } 281 | 282 | // SaveAsJWK writes the key to a file in JWK format, optionally encrypting it 283 | // with a passphrase. 284 | func (pk *PrivateKey) SaveAsJWK(fileName string, passphrase string) error { 285 | jsonBytes, err := pk.MarshalToJWK() 286 | if err != nil { 287 | return err 288 | } 289 | if passphrase != "" { 290 | data, err := encryptWithPassphrase(passphrase, jsonBytes) 291 | if err != nil { 292 | return err 293 | } 294 | return os.WriteFile(fileName, data, 0600) 295 | } 296 | 297 | return os.WriteFile(fileName, jsonBytes, 0600) 298 | } 299 | 300 | // PublicKey returns the public key derived from this private key. 301 | func (pk *PrivateKey) PublicKey() *PublicKey { 302 | return &PublicKey{publicKey: &pk.privateKey.PublicKey} 303 | } 304 | 305 | // Curve returns the elliptic curve for this public key. 306 | func (pk *PrivateKey) Curve() EllipticCurve { 307 | if pk.privateKey.Curve == crypto.S256() { 308 | return SECP256K1 309 | } 310 | if pk.privateKey.Curve == elliptic.P256() { 311 | return P256 312 | } 313 | if pk.privateKey.Curve == elliptic.P384() { 314 | return P384 315 | } 316 | if pk.privateKey.Curve == elliptic.P521() { 317 | return P521 318 | } 319 | return INVALID_CURVE 320 | } 321 | 322 | // Sign signs (ECDSA) the hash using the private key and returns signature. 323 | // See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm. 324 | func (pk *PrivateKey) Sign(hash []byte) (*Signature, error) { 325 | r, s, err := ecdsa.Sign(rand.Reader, pk.privateKey, hash) 326 | if err != nil { 327 | return nil, err 328 | } 329 | return &Signature{R: r, S: s}, nil 330 | } 331 | 332 | // getSharedEncryptionKeySecp256k1_Legacy computes a shared encryption key for SECP256K1 curve 333 | // in a way that is consistent with how it's done in crypto/ecdh. 334 | // This is the old way of doing this, which is somewhat different from what crypto/ecdh does. 335 | // The latter returns X coordinate bytes while we join X and Y and hash the result. 336 | func (pk *PrivateKey) getSharedEncryptionKeySecp256k1_Legacy(counterParty *PublicKey) []byte { 337 | x, y := crypto.S256().ScalarMult(counterParty.X(), counterParty.Y(), 338 | pk.privateKey.D.Bytes()) 339 | b := bytes.Join([][]byte{x.Bytes(), y.Bytes()}, nil) 340 | hash := sha256.Sum256(b) 341 | return hash[:] 342 | } 343 | 344 | // getSharedEncryptionKeySecp256k1 computes a shared encryption key for SECP256K1 curve 345 | // in a way that is consistent with how it's done in crypto/ecdh. 346 | func (pk *PrivateKey) getSharedEncryptionKeySecp256k1(counterParty *PublicKey) []byte { 347 | x, _ := crypto.S256().ScalarMult(counterParty.X(), counterParty.Y(), 348 | pk.privateKey.D.Bytes()) 349 | return x.Bytes() 350 | } 351 | 352 | func encrypt(key []byte, content []byte) ([]byte, error) { 353 | c, err := aes.NewCipher(key[:32]) // The key must be 32 bytes long. 354 | if err != nil { 355 | return nil, fmt.Errorf("failed to create cipher: %v", err) 356 | } 357 | 358 | gcm, err := cipher.NewGCM(c) 359 | if err != nil { 360 | return nil, fmt.Errorf("failed to create GCM: %v", err) 361 | } 362 | 363 | nonce := make([]byte, gcm.NonceSize()) 364 | if _, err = io.ReadFull(rand.Reader, nonce); err != nil { 365 | return nil, fmt.Errorf("failed to populate nonce: %v", err) 366 | } 367 | 368 | return gcm.Seal(nonce, nonce, content, nil), nil 369 | } 370 | 371 | // EncryptSymmetric encrypts content using this private key. The same private key 372 | // must be used for decryption. 373 | // Encryption is done using AES-256 with CGM cipher. 374 | func (pk *PrivateKey) EncryptSymmetric(content []byte) ([]byte, error) { 375 | key := sha256.Sum256(pk.privateKey.D.Bytes()) 376 | return encrypt(key[:], content) 377 | } 378 | 379 | func decrypt(key []byte, content []byte) ([]byte, error) { 380 | c, err := aes.NewCipher(key[:32]) // The key must be 32 bytes long. 381 | if err != nil { 382 | return nil, fmt.Errorf("failed to create cipher: %v", err) 383 | } 384 | 385 | gcm, err := cipher.NewGCM(c) 386 | if err != nil { 387 | return nil, fmt.Errorf("failed to create GCM: %v", err) 388 | } 389 | 390 | nonceSize := gcm.NonceSize() 391 | if len(content) < nonceSize { 392 | return nil, fmt.Errorf("invalid content") 393 | } 394 | 395 | nonce, ciphertext := content[:nonceSize], content[nonceSize:] 396 | plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) 397 | if err != nil { 398 | return nil, fmt.Errorf("failed to decrypt: %v", err) 399 | } 400 | return plaintext, nil 401 | } 402 | 403 | // DecryptSymmetric decrypts the content that was previously encrypted using this private key. 404 | // Decryption is done using AES-256 with CGM cipher. 405 | func (pk *PrivateKey) DecryptSymmetric(content []byte) ([]byte, error) { 406 | key := sha256.Sum256(pk.privateKey.D.Bytes()) 407 | return decrypt(key[:], content) 408 | } 409 | 410 | func encryptWithPassphrase(passphrase string, content []byte) ([]byte, error) { 411 | key, salt, err := deriveKey([]byte(passphrase), nil) 412 | if err != nil { 413 | return nil, err 414 | } 415 | 416 | ciphertext, err := encrypt(key, content) 417 | if err != nil { 418 | return nil, err 419 | } 420 | ciphertext = append(ciphertext, salt...) 421 | return ciphertext, nil 422 | } 423 | 424 | // EncryptKeyWithPassphrase encrypts this private key using a passphrase. 425 | // Encryption is done using AES-256 with CGM cipher, with a key derived from the passphrase. 426 | func (pk *PrivateKey) EncryptKeyWithPassphrase(passphrase string) ([]byte, error) { 427 | return encryptWithPassphrase(passphrase, pk.privateKey.D.Bytes()) 428 | } 429 | 430 | // Mnemonic returns a mnemonic phrase which can be used to recover this private key. 431 | func (pk *PrivateKey) Mnemonic() (string, error) { 432 | if pk.Curve() != SECP256K1 && pk.Curve() != P256 { 433 | return "", ErrUnsupportedCurve 434 | } 435 | return bip39.NewMnemonic(padWithZeros(pk.privateKey.D.Bytes(), 32)) 436 | } 437 | 438 | // Equal returns true if this key is equal to the other key. 439 | func (pk *PrivateKey) Equal(other *PrivateKey) bool { 440 | return pk.privateKey.D.Cmp(other.privateKey.D) == 0 441 | } 442 | 443 | // ToECDSA returns this key as crypto/ecdsa private key. 444 | func (pk *PrivateKey) ToECDSA() *ecdsa.PrivateKey { 445 | return pk.privateKey 446 | } 447 | 448 | // GetECDHEncryptionKey returns a shared key that can be used to encrypt data 449 | // exchanged by two parties, using Elliptic Curve Diffie-Hellman algorithm (ECDH). 450 | // For Alice and Bob, the key is guaranteed to be the 451 | // same when it's derived from Alice's private key and Bob's public key or 452 | // Alice's public key and Bob's private key. 453 | // 454 | // See https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman. 455 | func (pk *PrivateKey) GetECDHEncryptionKey(publicKey *PublicKey) ([]byte, error) { 456 | if pk.Curve() != publicKey.Curve() { 457 | return nil, ErrDifferentCurves 458 | } 459 | var privateKey *ecdh.PrivateKey 460 | var pubKey *ecdh.PublicKey 461 | var err error 462 | switch pk.Curve() { 463 | case SECP256K1: 464 | // This curve is not supported by crypto/ecdh, so we have to handle 465 | // it as a special case. 466 | encryptionKey := pk.getSharedEncryptionKeySecp256k1(publicKey) 467 | return padWithZeros(encryptionKey, 32), nil 468 | case P256: 469 | key := padWithZeros(pk.Secret().Bytes(), getKeyLength(pk.Curve())) 470 | privateKey, err = ecdh.P256().NewPrivateKey(key) 471 | if err != nil { 472 | return nil, err 473 | } 474 | pubKey, err = ecdh.P256().NewPublicKey(publicKey.Serialize()) 475 | if err != nil { 476 | return nil, err 477 | } 478 | case P384: 479 | key := padWithZeros(pk.Secret().Bytes(), getKeyLength(pk.Curve())) 480 | privateKey, err = ecdh.P384().NewPrivateKey(key) 481 | if err != nil { 482 | return nil, err 483 | } 484 | pubKey, err = ecdh.P384().NewPublicKey(publicKey.Serialize()) 485 | if err != nil { 486 | return nil, err 487 | } 488 | case P521: 489 | key := padWithZeros(pk.Secret().Bytes(), getKeyLength(pk.Curve())) 490 | privateKey, err = ecdh.P521().NewPrivateKey(key) 491 | if err != nil { 492 | return nil, err 493 | } 494 | pubKey, err = ecdh.P521().NewPublicKey(publicKey.Serialize()) 495 | if err != nil { 496 | return nil, err 497 | } 498 | } 499 | encryptionKey, err := privateKey.ECDH(pubKey) 500 | if err != nil { 501 | return nil, err 502 | } 503 | return encryptionKey, nil 504 | } 505 | 506 | func (pk *PrivateKey) EncryptECDH(content []byte, publicKey *PublicKey) ([]byte, error) { 507 | encryptionKey, err := pk.GetECDHEncryptionKey(publicKey) 508 | if err != nil { 509 | return nil, err 510 | } 511 | return encrypt(encryptionKey, content) 512 | } 513 | 514 | func (pk *PrivateKey) DecryptECDH(content []byte, publicKey *PublicKey) ([]byte, error) { 515 | encryptionKey, err := pk.GetECDHEncryptionKey(publicKey) 516 | if err != nil { 517 | return nil, err 518 | } 519 | return decrypt(encryptionKey, content) 520 | } 521 | 522 | // MarshalToJWK returns the key JWK representation, 523 | // see https://www.rfc-editor.org/rfc/rfc7517. 524 | func (pk *PrivateKey) MarshalToJWK() ([]byte, error) { 525 | xEncoded := base64.StdEncoding. 526 | WithPadding(base64.NoPadding). 527 | EncodeToString(pk.PublicKey().X().Bytes()) 528 | yEncoded := base64.StdEncoding. 529 | WithPadding(base64.NoPadding). 530 | EncodeToString(pk.PublicKey().Y().Bytes()) 531 | dEncoded := base64.StdEncoding. 532 | WithPadding(base64.NoPadding). 533 | EncodeToString(pk.Secret().Bytes()) 534 | 535 | return json.MarshalIndent(privateKeyJSON{ 536 | Kty: "EC", 537 | Crv: pk.Curve().String(), 538 | X: xEncoded, 539 | Y: yEncoded, 540 | D: dEncoded, 541 | }, "", " ") 542 | } 543 | 544 | // deriveKey creates symmetric encryption key and salt (both are 32 bytes long) 545 | // from password. If salt is not given (nil), new random one is created. 546 | func deriveKey(password, salt []byte) ([]byte, []byte, error) { 547 | if salt == nil { 548 | salt = make([]byte, 32) 549 | if _, err := rand.Read(salt); err != nil { 550 | return nil, nil, err 551 | } 552 | } 553 | key, err := scrypt.Key(password, salt, 16384, 8, 1, 32) 554 | if err != nil { 555 | return nil, nil, err 556 | } 557 | return key, salt, nil 558 | } 559 | 560 | func padWithZeros(b []byte, l int) []byte { 561 | for len(b) < l { 562 | b = append([]byte{0}, b...) 563 | } 564 | return b 565 | } 566 | 567 | // Everything below is deprecated. 568 | 569 | // NewRandomPrivateKey creates a new random private key using SECP256K1 curve. 570 | // 571 | // Deprecated: Use GeneratePrivateKey instead. 572 | func NewRandomPrivateKey() (*PrivateKey, error) { 573 | return GeneratePrivateKey(SECP256K1) 574 | } 575 | 576 | // NewPrivateKey returns new private key created from the secret using SECP256K1 curve. 577 | // 578 | // Deprecated: Use CreatePrivateKey instead. 579 | func NewPrivateKey(secret *big.Int) *PrivateKey { 580 | return CreatePrivateKey(SECP256K1, secret) 581 | } 582 | 583 | // NewPrivateKeyFromPassword creates a new private key from password and salt using SECP256K1 curve. 584 | // 585 | // Deprecated: Use CreatePrivateKeyFromPassword. 586 | func NewPrivateKeyFromPassword(password, salt []byte) *PrivateKey { 587 | return CreatePrivateKeyFromPassword(SECP256K1, password, salt) 588 | } 589 | 590 | // NewPrivateKeyFromEncryptedWithPassphrase creates a new private key using SECP256K1 curve 591 | // from encrypted private key using the passphrase. 592 | // 593 | // Deprecated: Use CreatePrivateKeyFromEncrypted instead. 594 | func NewPrivateKeyFromEncryptedWithPassphrase(data []byte, passphrase string) (*PrivateKey, error) { 595 | return CreatePrivateKeyFromEncrypted(SECP256K1, data, passphrase) 596 | } 597 | 598 | // NewPrivateKeyFromFile loads private key using SECP256K1 curve 599 | // from file and decrypts it using the given passphrase. 600 | // If the passphrase is an empty string, no decryption is done (the file content is assumed 601 | // to be not encrypted). 602 | // 603 | // Deprecated: Use CreatePrivateKeyFromFile instead. 604 | func NewPrivateKeyFromFile(fileName string, passphrase string) (*PrivateKey, error) { 605 | b, err := os.ReadFile(fileName) 606 | if err != nil { 607 | return nil, fmt.Errorf("failed to load private key: %w", err) 608 | } 609 | 610 | if len(b) < 32 { 611 | return nil, fmt.Errorf("invalid private key length") 612 | } 613 | 614 | if passphrase != "" { 615 | return NewPrivateKeyFromEncryptedWithPassphrase(b, passphrase) 616 | } 617 | 618 | if len(b) != 32 { 619 | return nil, fmt.Errorf("invalid private key length") 620 | } 621 | 622 | secret := new(big.Int) 623 | secret.SetBytes(b) 624 | return NewPrivateKey(secret), nil 625 | } 626 | 627 | // NewPrivateKeyFromMnemonic creates private key on SECP256K1 curve from a mnemonic phrase. 628 | // 629 | // Deprecated: Use CreatePrivateKeyFromMnemonic instead. 630 | func NewPrivateKeyFromMnemonic(mnemonic string) (*PrivateKey, error) { 631 | return CreatePrivateKeyFromMnemonic(SECP256K1, mnemonic) 632 | } 633 | 634 | // Encrypt encrypts content with a shared key derived from this private key and the 635 | // counter party public key. Works only on secp256k1 curve. 636 | // 637 | // Deprecated: Use EncryptECDH instead, which works on all supported curves. 638 | // Notice that Encrypt/Decrypt and EncryptECDH/DecryptECDH are not compatible on 639 | // secp256k1 curve, since they are using different ways of generating shared encryption key. 640 | func (pk *PrivateKey) Encrypt(content []byte, publicKey *PublicKey) ([]byte, error) { 641 | if pk.Curve() != SECP256K1 { 642 | return nil, ErrUnsupportedCurve 643 | } 644 | encryptionKey := pk.getSharedEncryptionKeySecp256k1_Legacy(publicKey) 645 | return encrypt(encryptionKey, content) 646 | } 647 | 648 | // Decrypt decrypts content with a shared key derived from this private key and the 649 | // counter party public key. 650 | // 651 | // Deprecated: Use DecryptECDH instead, which works on all supported curves. 652 | // Notice that Encrypt/Decrypt and EncryptECDH/DecryptECDH are not compatible on 653 | // secp256k1 curve, since they are using different ways of generating shared encryption key. 654 | func (pk *PrivateKey) Decrypt(content []byte, publicKey *PublicKey) ([]byte, error) { 655 | if pk.Curve() != SECP256K1 { 656 | return nil, ErrUnsupportedCurve 657 | } 658 | encryptionKey := pk.getSharedEncryptionKeySecp256k1_Legacy(publicKey) 659 | return decrypt(encryptionKey, content) 660 | } 661 | -------------------------------------------------------------------------------- /private_key_test.go: -------------------------------------------------------------------------------- 1 | package easyecc 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ecdsa" 6 | "crypto/elliptic" 7 | "fmt" 8 | "math/big" 9 | "math/rand" 10 | "os" 11 | "path" 12 | "testing" 13 | 14 | "github.com/ethereum/go-ethereum/crypto" 15 | "github.com/stretchr/testify/assert" 16 | "github.com/stretchr/testify/require" 17 | ) 18 | 19 | var curves = []EllipticCurve{SECP256K1, P256, P384, P521} 20 | 21 | func Test_PrivateKey_EllipticCurveToString(t *testing.T) { 22 | assert := assert.New(t) 23 | 24 | assert.EqualValues("secp256k1", SECP256K1.String()) 25 | assert.EqualValues("Invalid", INVALID_CURVE.String()) 26 | } 27 | 28 | func Test_PrivateKey_getCurve(t *testing.T) { 29 | assert := assert.New(t) 30 | 31 | assert.Equal(elliptic.P256(), getCurve(P256)) 32 | assert.Nil(getCurve(EllipticCurve(999))) 33 | } 34 | 35 | func Test_PrivateKey_getKeyLength(t *testing.T) { 36 | assert := assert.New(t) 37 | 38 | assert.Equal(32, getKeyLength(P256)) 39 | assert.Equal(-1, getKeyLength(EllipticCurve(999))) 40 | } 41 | 42 | func Test_PrivateKey_NewRandom(t *testing.T) { 43 | assert := assert.New(t) 44 | 45 | for _, curve := range curves { 46 | pk, err := GeneratePrivateKey(curve) 47 | assert.NoError(err) 48 | assert.NotNil(pk) 49 | } 50 | } 51 | 52 | func Test_PrivateKey_Save(t *testing.T) { 53 | assert := assert.New(t) 54 | 55 | dir, err := os.MkdirTemp("", "pktest") 56 | assert.NoError(err) 57 | for _, curve := range curves { 58 | pk, err := GeneratePrivateKey(curve) 59 | assert.NoError(err) 60 | 61 | fileName := path.Join(dir, fmt.Sprintf("private_key_%v", curve)) 62 | err = pk.Save(fileName, "") 63 | assert.NoError(err) 64 | 65 | _, err = os.Stat(fileName) 66 | assert.NoError(err) 67 | } 68 | assert.NoError(os.RemoveAll(dir)) 69 | } 70 | 71 | func Test_PrivateKey_SaveWithPassphrase(t *testing.T) { 72 | assert := assert.New(t) 73 | 74 | passphrase := "super secret password" 75 | dir, err := os.MkdirTemp("", "pktest") 76 | assert.NoError(err) 77 | for _, curve := range curves { 78 | pk, err := GeneratePrivateKey(curve) 79 | assert.NoError(err) 80 | 81 | fileName := path.Join(dir, fmt.Sprintf("private_key_%v", curve)) 82 | err = pk.Save(fileName, passphrase) 83 | assert.NoError(err) 84 | 85 | _, err = os.Stat(fileName) 86 | assert.NoError(err) 87 | 88 | if curve == SECP256K1 { 89 | // Test deprecated function. 90 | loadedPk, err := NewPrivateKeyFromFile(fileName, passphrase) 91 | assert.NoError(err) 92 | assert.NotNil(loadedPk) 93 | } 94 | 95 | loadedPk, err := CreatePrivateKeyFromFile(curve, fileName, passphrase) 96 | assert.NoError(err) 97 | assert.NotNil(loadedPk) 98 | assert.True(pk.Equal(loadedPk)) 99 | } 100 | assert.NoError(os.RemoveAll(dir)) 101 | 102 | // Test deprecated function. 103 | _, err = NewPrivateKeyFromFile("some-non-existent-file", "foo") 104 | assert.Error(err) 105 | } 106 | 107 | func Test_PrivateKey_LoadFromBadFile(t *testing.T) { 108 | assert := assert.New(t) 109 | 110 | _, err := CreatePrivateKeyFromFile(P256, "some-none-existing-file", "foo") 111 | assert.Error(err) 112 | } 113 | 114 | func Test_PrivateKey_Load(t *testing.T) { 115 | assert := assert.New(t) 116 | 117 | dir, err := os.MkdirTemp("", "pktest") 118 | assert.NoError(err) 119 | for _, curve := range curves { 120 | pk, err := GeneratePrivateKey(curve) 121 | assert.NoError(err) 122 | 123 | fileName := path.Join(dir, fmt.Sprintf("private_key_%v", curve)) 124 | err = pk.Save(fileName, "") 125 | assert.NoError(err) 126 | 127 | if curve == SECP256K1 { 128 | // Test the deprecated function. 129 | pkCopy, err := NewPrivateKeyFromFile(fileName, "") 130 | assert.NoError(err) 131 | assert.NotNil(pkCopy) 132 | } 133 | 134 | pkCopy, err := CreatePrivateKeyFromFile(curve, fileName, "") 135 | assert.NoError(err) 136 | assert.NotNil(pkCopy) 137 | assert.EqualValues(pk.privateKey.D, pkCopy.privateKey.D) 138 | assert.EqualValues(pk.privateKey.PublicKey.X, pkCopy.privateKey.PublicKey.X) 139 | assert.EqualValues(pk.privateKey.PublicKey.Y, pkCopy.privateKey.PublicKey.Y) 140 | } 141 | assert.NoError(os.RemoveAll(dir)) 142 | 143 | _, err = CreatePrivateKeyFromFile(P256, "some_non_existent_file", "foo") 144 | assert.Error(err) 145 | } 146 | 147 | func Test_PrivateKey_SerializeDeserialize(t *testing.T) { 148 | assert := assert.New(t) 149 | 150 | // Confirm that serialization/deserialization work as expected. 151 | // Serialize/deserialize a bunch of keys. 152 | 153 | r := rand.New(rand.NewSource(123)) 154 | for _, curve := range curves { 155 | for i := 0; i < 1000; i++ { 156 | secret := r.Int63() 157 | privateKey := CreatePrivateKey(curve, big.NewInt(secret)) 158 | serialized := privateKey.PublicKey().SerializeCompressed() 159 | publicKey, err := DeserializeCompressed(curve, serialized) 160 | assert.NoError(err) 161 | assert.Equal(privateKey.PublicKey().publicKey.X, publicKey.publicKey.X) 162 | assert.Equal(privateKey.PublicKey().publicKey.Y, publicKey.publicKey.Y) 163 | } 164 | } 165 | } 166 | 167 | func Test_PrivateKey_EncryptDecrypt(t *testing.T) { 168 | assert := assert.New(t) 169 | 170 | for _, curve := range curves { 171 | key, err := GeneratePrivateKey(curve) 172 | assert.NoError(err) 173 | 174 | encrypted, err := key.EncryptKeyWithPassphrase("super secret spies") 175 | assert.NoError(err) 176 | assert.NotNil(encrypted) 177 | 178 | key1, err := CreatePrivateKeyFromEncrypted(curve, encrypted, "super secret spies") 179 | assert.NoError(err) 180 | assert.True(key1.privateKey.Equal(key.privateKey)) 181 | } 182 | } 183 | 184 | func Test_PrivateKey_FromPassword(t *testing.T) { 185 | assert := assert.New(t) 186 | 187 | for _, curve := range curves { 188 | key := CreatePrivateKeyFromPassword(curve, []byte("super secret spies"), []byte{0x11, 0x22, 0x33, 0x44}) 189 | assert.NotNil(key) 190 | } 191 | 192 | // Deprecated function. 193 | key := NewPrivateKeyFromPassword([]byte("super secret spies"), []byte{0x11, 0x22, 0x33, 0x44}) 194 | assert.NotNil(key) 195 | } 196 | 197 | func Test_PrivateKey_NewPrivateKeyFromEncryptedWithPassphrase_InvalidData(t *testing.T) { 198 | assert := assert.New(t) 199 | 200 | for _, curve := range curves { 201 | key, err := CreatePrivateKeyFromEncrypted(curve, []byte("bad data"), "foo") 202 | assert.Nil(key) 203 | assert.Error((err)) 204 | } 205 | } 206 | 207 | func Test_PrivateKey_Mnemonic(t *testing.T) { 208 | assert := assert.New(t) 209 | 210 | for _, curve := range []EllipticCurve{SECP256K1, P256} { 211 | key := CreatePrivateKey(curve, big.NewInt(123456)) 212 | mnemonic, err := key.Mnemonic() 213 | assert.NoError(err) 214 | 215 | key1, err := CreatePrivateKeyFromMnemonic(curve, mnemonic) 216 | assert.NoError(err) 217 | 218 | assert.True(key.Equal(key1)) 219 | } 220 | 221 | // Try unsupported curve. 222 | key, err := GeneratePrivateKey(P521) 223 | assert.NoError(err) 224 | _, err = key.Mnemonic() 225 | assert.Equal(ErrUnsupportedCurve, err) 226 | _, err = CreatePrivateKeyFromMnemonic(P521, "foo bar baz") 227 | assert.Equal(ErrUnsupportedCurve, err) 228 | 229 | // Try bad mnemonic. 230 | _, err = CreatePrivateKeyFromMnemonic(SECP256K1, "foo bar baz") 231 | assert.Error(err) 232 | 233 | // Deprecated function. 234 | key = CreatePrivateKey(SECP256K1, big.NewInt(123456)) 235 | mnemonic, err := key.Mnemonic() 236 | assert.NoError(err) 237 | 238 | key1, err := NewPrivateKeyFromMnemonic(mnemonic) 239 | assert.NoError(err) 240 | 241 | assert.True(key.Equal(key1)) 242 | } 243 | 244 | func Test_PrivateKey_PadOnSave(t *testing.T) { 245 | assert := assert.New(t) 246 | 247 | key := CreatePrivateKey(SECP256K1, big.NewInt(123)) 248 | 249 | dir, err := os.MkdirTemp("", "pktest") 250 | assert.NoError(err) 251 | 252 | fileName := path.Join(dir, "private_key") 253 | err = key.Save(fileName, "") 254 | assert.NoError(err) 255 | 256 | fi, err := os.Stat(fileName) 257 | assert.NoError(err) 258 | assert.EqualValues(32, fi.Size()) 259 | 260 | key1, err := CreatePrivateKeyFromFile(SECP256K1, fileName, "") 261 | assert.NoError(err) 262 | 263 | assert.True(key.Equal(key1)) 264 | 265 | assert.NoError(os.RemoveAll(dir)) 266 | } 267 | 268 | func Test_PrivateKey_Curve(t *testing.T) { 269 | assert := assert.New(t) 270 | 271 | for _, curve := range curves { 272 | key, err := GeneratePrivateKey(curve) 273 | assert.NoError(err) 274 | assert.Equal(curve, key.Curve()) 275 | } 276 | } 277 | 278 | func Test_PrivateKey_EncryptECDH(t *testing.T) { 279 | assert := assert.New(t) 280 | 281 | for _, curve := range curves { 282 | aliceKey, err := GeneratePrivateKey(curve) 283 | assert.NoError(err) 284 | bobKey, err := GeneratePrivateKey(curve) 285 | assert.NoError(err) 286 | 287 | message := "Putin Huylo" 288 | encrypted, err := aliceKey.EncryptECDH([]byte(message), bobKey.PublicKey()) 289 | assert.NoError(err) 290 | decrypted, err := bobKey.DecryptECDH(encrypted, aliceKey.PublicKey()) 291 | assert.NoError(err) 292 | 293 | assert.True(bytes.Equal([]byte(message), decrypted)) 294 | } 295 | } 296 | 297 | func Test_PrivateKey_EncryptLegacy(t *testing.T) { 298 | assert := assert.New(t) 299 | 300 | curve := SECP256K1 // Legacy encryption works only on this curve. 301 | aliceKey, err := GeneratePrivateKey(curve) 302 | assert.NoError(err) 303 | bobKey, err := GeneratePrivateKey(curve) 304 | assert.NoError(err) 305 | 306 | message := "Putin Huylo" 307 | encrypted, err := aliceKey.Encrypt([]byte(message), bobKey.PublicKey()) 308 | assert.NoError(err) 309 | decrypted, err := bobKey.Decrypt(encrypted, aliceKey.PublicKey()) 310 | assert.NoError(err) 311 | 312 | assert.True(bytes.Equal([]byte(message), decrypted)) 313 | 314 | // Try unsupported curve. 315 | spongeBobKey, err := GeneratePrivateKey(P521) 316 | assert.NoError(err) 317 | encrypted, err = spongeBobKey.Encrypt([]byte(message), bobKey.PublicKey()) 318 | assert.Equal(ErrUnsupportedCurve, err) 319 | 320 | _, err = spongeBobKey.Decrypt(encrypted, aliceKey.PublicKey()) 321 | assert.Equal(ErrUnsupportedCurve, err) 322 | } 323 | 324 | func Test_PrivateKey_ToECDSA(t *testing.T) { 325 | assert := assert.New(t) 326 | 327 | for _, curve := range curves { 328 | privateKey, err := GeneratePrivateKey(curve) 329 | assert.NoError(err) 330 | assert.NotNil(privateKey.ToECDSA()) 331 | } 332 | } 333 | 334 | func Test_PrivateKey_ToJWK(t *testing.T) { 335 | assert := assert.New(t) 336 | 337 | for _, curve := range curves { 338 | privateKey, err := GeneratePrivateKey(curve) 339 | assert.NoError(err) 340 | jsonStr, err := privateKey.MarshalToJWK() 341 | assert.NoError(err) 342 | assert.True(len(jsonStr) > 10) 343 | privateKeyCopy, err := CreatePrivateKeyFromJWK(jsonStr) 344 | assert.NoError(err) 345 | assert.True(privateKey.Equal(privateKeyCopy)) 346 | } 347 | 348 | _, err := CreatePrivateKeyFromJWK([]byte("{{{{not valid JSON %$##$")) 349 | assert.Error(err) 350 | 351 | _, err = CreatePrivateKeyFromJWK([]byte("{\"kty\": \"XYZ\"}")) 352 | assert.Equal(ErrUnsupportedKeyType, err) 353 | 354 | _, err = CreatePrivateKeyFromJWK([]byte("{\"kty\": \"EC\", \"crv\": \"MyCurve\"}")) 355 | assert.Equal(ErrUnsupportedCurve, err) 356 | } 357 | 358 | func Test_PrivateKey_SaveAsJWK(t *testing.T) { 359 | assert := assert.New(t) 360 | 361 | dir, err := os.MkdirTemp("", "pktest") 362 | assert.NoError(err) 363 | // Without encryption. 364 | for _, curve := range curves { 365 | privateKey, err := GeneratePrivateKey(curve) 366 | assert.NoError(err) 367 | fileName := path.Join(dir, fmt.Sprintf("private_key_%v", curve)) 368 | err = privateKey.SaveAsJWK(fileName, "") 369 | assert.NoError(err) 370 | privateKeyCopy, err := CreatePrivateKeyFromJWKFile(fileName, "") 371 | assert.NoError(err) 372 | assert.True(privateKey.Equal(privateKeyCopy)) 373 | } 374 | // With encryption. 375 | passphrase := "potato123" 376 | for _, curve := range curves { 377 | privateKey, err := GeneratePrivateKey(curve) 378 | assert.NoError(err) 379 | fileName := path.Join(dir, fmt.Sprintf("private_key_enc_%v", curve)) 380 | err = privateKey.SaveAsJWK(fileName, passphrase) 381 | assert.NoError(err) 382 | privateKeyCopy, err := CreatePrivateKeyFromJWKFile(fileName, passphrase) 383 | assert.NoError(err) 384 | assert.True(privateKey.Equal(privateKeyCopy)) 385 | } 386 | assert.NoError(os.RemoveAll(dir)) 387 | 388 | _, err = CreatePrivateKeyFromJWKFile("some_non_existent_file", "foo") 389 | assert.Error(err) 390 | } 391 | 392 | func TestCreatePrivateKeyFromETHKey(t *testing.T) { 393 | t.Run("data should me encrypted and decrypted", func(t *testing.T) { 394 | // given 395 | alicePrivateKey := getPrivateKeyFromETH(t) 396 | 397 | // when 398 | encrypted, err := alicePrivateKey.EncryptSymmetric([]byte("super secret spies")) 399 | require.NoError(t, err) 400 | require.NotNil(t, encrypted) 401 | 402 | // then 403 | data, err := alicePrivateKey.DecryptSymmetric(encrypted) 404 | require.NoError(t, err) 405 | require.NotNil(t, data) 406 | 407 | require.Equal(t, "super secret spies", string(data)) 408 | }) 409 | t.Run("key should return same address", func(t *testing.T) { 410 | // given 411 | alicePrivateKey, err := crypto.GenerateKey() 412 | require.NoError(t, err) 413 | publicKeyECDSA, ok := alicePrivateKey.Public().(*ecdsa.PublicKey) 414 | require.True(t, ok) 415 | ethAddress := crypto.PubkeyToAddress(*publicKeyECDSA) 416 | key := CreatePrivateKeyFromETHKey(alicePrivateKey) 417 | 418 | // when 419 | publicKey := key.PublicKey() 420 | keyAddress, err := publicKey.EthereumAddress() 421 | require.NoError(t, err) 422 | 423 | // then 424 | require.Equal(t, ethAddress.String(), keyAddress) 425 | }) 426 | t.Run("keys should generate shared secret", func(t *testing.T) { 427 | // given 428 | alicePK := getPrivateKeyFromETH(t) 429 | bobPK := getPrivateKeyFromETH(t) 430 | data := "super secret message" 431 | 432 | // when 433 | encrypted, err := alicePK.EncryptECDH([]byte(data), bobPK.PublicKey()) 434 | require.NoError(t, err) 435 | 436 | decrypted, err := bobPK.DecryptECDH(encrypted, alicePK.PublicKey()) 437 | require.NoError(t, err) 438 | 439 | // then 440 | require.Equal(t, data, string(decrypted)) 441 | }) 442 | } 443 | 444 | func getPrivateKeyFromETH(t *testing.T) *PrivateKey { 445 | privateKey, err := crypto.GenerateKey() 446 | require.NoError(t, err) 447 | 448 | return CreatePrivateKeyFromETHKey(privateKey) 449 | } 450 | -------------------------------------------------------------------------------- /public_key.go: -------------------------------------------------------------------------------- 1 | package easyecc 2 | 3 | import ( 4 | "bytes" 5 | "crypto/ecdsa" 6 | "crypto/elliptic" 7 | "fmt" 8 | "math/big" 9 | 10 | "github.com/ethereum/go-ethereum/crypto" 11 | "github.com/mr-tron/base58" 12 | ) 13 | 14 | // PublicKey represents elliptic curve cryptography private key. 15 | type PublicKey struct { 16 | publicKey *ecdsa.PublicKey 17 | } 18 | 19 | // We must deal with secp256k1 here, because elliptic UnmarshalCompressed cannot handle it. 20 | func unmarshalCompressedSECP256K1(serialized []byte) (*PublicKey, error) { 21 | if len(serialized) != 33 { 22 | return nil, fmt.Errorf("invalid serialized compressed public key") 23 | } 24 | 25 | even := false 26 | if serialized[0] == 0x02 { 27 | even = true 28 | } else if serialized[0] == 0x03 { 29 | even = false 30 | } else { 31 | return nil, fmt.Errorf("invalid serialized compressed public key") 32 | } 33 | x := new(big.Int).SetBytes(serialized[1:]) 34 | P := crypto.S256().Params().P 35 | sqrtExp := new(big.Int).Add(P, big.NewInt(1)) 36 | sqrtExp = sqrtExp.Div(sqrtExp, big.NewInt(4)) 37 | alpha := new(big.Int).Exp(x, big.NewInt(3), P) 38 | alpha.Add(alpha, crypto.S256().Params().B) 39 | beta := new(big.Int).Exp(alpha, sqrtExp, P) 40 | var evenBeta *big.Int 41 | var oddBeta *big.Int 42 | if new(big.Int).Mod(beta, big.NewInt(2)).Cmp(big.NewInt(0)) == 0 { 43 | evenBeta = beta 44 | oddBeta = new(big.Int).Sub(P, beta) 45 | } else { 46 | evenBeta = new(big.Int).Sub(P, beta) 47 | oddBeta = beta 48 | } 49 | var y *big.Int 50 | if even { 51 | y = evenBeta 52 | } else { 53 | y = oddBeta 54 | } 55 | return &PublicKey{publicKey: &ecdsa.PublicKey{ 56 | Curve: crypto.S256(), 57 | X: x, 58 | Y: y}}, nil 59 | } 60 | 61 | // NewPublicFromSerializedCompressed creates new public key from serialized 62 | // compressed format. 63 | func NewPublicFromSerializedCompressed(serialized []byte) (*PublicKey, error) { 64 | return DeserializeCompressed(SECP256K1, serialized) 65 | } 66 | 67 | func Deserialize(curve EllipticCurve, serialized []byte) (*PublicKey, error) { 68 | // if curve == SECP256K1 { 69 | // return nil, fmt.Errorf("cannot deserialize on SECP256K1 curve") 70 | // } 71 | x, y := elliptic.Unmarshal(getCurve(curve), serialized) 72 | return &PublicKey{publicKey: &ecdsa.PublicKey{ 73 | Curve: getCurve(curve), 74 | X: x, 75 | Y: y}}, nil 76 | } 77 | 78 | func DeserializeCompressed(curve EllipticCurve, serialized []byte) (*PublicKey, error) { 79 | if curve == SECP256K1 { 80 | // Special case - elliptic.UnmarshalCompressed cannot handle it. 81 | return unmarshalCompressedSECP256K1(serialized) 82 | } 83 | x, y := elliptic.UnmarshalCompressed(getCurve(curve), serialized) 84 | return &PublicKey{publicKey: &ecdsa.PublicKey{ 85 | Curve: getCurve(curve), 86 | X: x, 87 | Y: y}}, nil 88 | } 89 | 90 | // CreatePublicKeyFromPoint creates a new public key given a point on the curve. 91 | func CreatePublicKeyFromPoint(curve elliptic.Curve, x *big.Int, y *big.Int) *PublicKey { 92 | if x == nil || y == nil { 93 | return nil 94 | } 95 | return &PublicKey{ 96 | publicKey: &ecdsa.PublicKey{ 97 | Curve: curve, 98 | X: x, 99 | Y: y, 100 | }, 101 | } 102 | 103 | } 104 | 105 | func (pbk *PublicKey) Serialize() []byte { 106 | return elliptic.Marshal(pbk.publicKey.Curve, pbk.publicKey.X, pbk.publicKey.Y) 107 | } 108 | 109 | // SerializeCompressed returns the private key serialized in SEC compressed format. The result 110 | // is 33 bytes long. 111 | func (pbk *PublicKey) SerializeCompressed() []byte { 112 | return elliptic.MarshalCompressed(pbk.publicKey.Curve, pbk.publicKey.X, pbk.publicKey.Y) 113 | } 114 | 115 | // Curve returns the elliptic curve for this public key. 116 | func (pbk *PublicKey) Curve() EllipticCurve { 117 | if pbk.publicKey.Curve == crypto.S256() { 118 | return SECP256K1 119 | } 120 | if pbk.publicKey.Curve == elliptic.P256() { 121 | return P256 122 | } 123 | if pbk.publicKey.Curve == elliptic.P384() { 124 | return P384 125 | } 126 | if pbk.publicKey.Curve == elliptic.P521() { 127 | return P521 128 | } 129 | return -1 130 | } 131 | 132 | // X returns X component of the public key. 133 | func (pbk *PublicKey) X() *big.Int { 134 | return pbk.publicKey.X 135 | } 136 | 137 | // Y returns Y component of the public key. 138 | func (pbk *PublicKey) Y() *big.Int { 139 | return pbk.publicKey.Y 140 | } 141 | 142 | // BitcoinAddress returns the Bitcoin address for this public key. 143 | // Unless the public key is on SECP256K1 curve, ErrUnsupportedCurve is returned. 144 | func (pbk *PublicKey) BitcoinAddress() (string, error) { 145 | if pbk.Curve() != SECP256K1 { 146 | return "", ErrUnsupportedCurve 147 | } 148 | prefix := []byte{0x00} 149 | s := pbk.SerializeCompressed() 150 | hash := Hash160(s) 151 | s1 := bytes.Join([][]byte{prefix, hash}, nil) 152 | checkSum := Hash256(s1)[0:4] 153 | addr := bytes.Join([][]byte{s1, checkSum}, nil) 154 | return base58.Encode(addr), nil 155 | } 156 | 157 | // EthereumAddress returns an Ethereum address for this public key. 158 | // Unless the public key is on SECP256K1 curve, ErrUnsupportedCurve is returned. 159 | func (pbk *PublicKey) EthereumAddress() (string, error) { 160 | if pbk.Curve() != SECP256K1 { 161 | return "", ErrUnsupportedCurve 162 | } 163 | return crypto.PubkeyToAddress(*pbk.publicKey).Hex(), nil 164 | } 165 | 166 | // Equal returns true if this key is equal to the other key. 167 | func (pbk *PublicKey) Equal(other *PublicKey) bool { 168 | if other == nil { 169 | return false 170 | } 171 | return pbk.publicKey.X.Cmp(other.publicKey.X) == 0 && 172 | pbk.publicKey.Y.Cmp(other.publicKey.Y) == 0 173 | } 174 | 175 | // EqualSerializedCompressed returns true if this key is equal to the other, 176 | // given as serialized compressed representation. 177 | func (pbk *PublicKey) EqualSerializedCompressed(other []byte) bool { 178 | return bytes.Equal(pbk.SerializeCompressed(), other) 179 | } 180 | 181 | // ToECDSA returns this key as crypto/ecdsa public key. 182 | func (pbk *PublicKey) ToECDSA() *ecdsa.PublicKey { 183 | return pbk.publicKey 184 | } 185 | -------------------------------------------------------------------------------- /public_key_test.go: -------------------------------------------------------------------------------- 1 | package easyecc 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | "fmt" 8 | "math/big" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | var serializedKeys = map[EllipticCurve]string{ 15 | SECP256K1: "0357a4f368868a8a6d572991e484e664810ff14c05c0fa023275251151fe0e53d1", 16 | P256: "035959f21263a385367a2737020e9c912f7ec94a1c7f535bb104d8be472728bb84", 17 | P384: "0283a4f85dbb29ffd415547c8f88924d2dc7d5c2e7ac371fded2360e645e142534d924d5fc7182298ae78e43dc042e3185", 18 | P521: "02014f3231b54b107be2ed7ec03dd85e2169111a087bb29454200a06f2e470ef5060f97c255ce621a771c1689a4defafa057ae100c1552428de87c757510a71271b5cb", 19 | } 20 | 21 | type keyComponents struct { 22 | X string 23 | Y string 24 | } 25 | 26 | var serializedKeyComponents = map[EllipticCurve]keyComponents{ 27 | SECP256K1: { 28 | X: "57a4f368868a8a6d572991e484e664810ff14c05c0fa023275251151fe0e53d1", 29 | Y: "0d6cc87c5bc29b83368e17869e964f2f53d52ea3aa3e5a9efa1fa578123a0c6d", 30 | }, 31 | P256: { 32 | X: "5959f21263a385367a2737020e9c912f7ec94a1c7f535bb104d8be472728bb84", 33 | Y: "669f338928b52ac42850f2444d1c1bf4db10e21a21151b39c126ed88ca1b93f5", 34 | }, 35 | P384: { 36 | X: "83a4f85dbb29ffd415547c8f88924d2dc7d5c2e7ac371fded2360e645e142534d924d5fc7182298ae78e43dc042e3185", 37 | Y: "28cac85e8ab852371b3481ef26c3a08c7ab6834fca48b3238c80d924a79d8c2f3b1c2b54c8e2d949a77f6ce7f579bb08", 38 | }, 39 | P521: { 40 | X: "14f3231b54b107be2ed7ec03dd85e2169111a087bb29454200a06f2e470ef5060f97c255ce621a771c1689a4defafa057ae100c1552428de87c757510a71271b5cb", 41 | Y: "172092d6462143a95cdbbb08dc1b2c464ce7ff915036cb9c844323e8c88cee4f4bc16d8bfbfafe3f3c1871373351bc69b46f541745d8acb5c66d3c24e70fde428a4", 42 | }, 43 | } 44 | 45 | func Test_PublicKey_SerializeCompressed(t *testing.T) { 46 | assert := assert.New(t) 47 | 48 | for _, curve := range curves { 49 | privateKey := CreatePrivateKey(curve, big.NewInt(5001)) 50 | publicKey := privateKey.PublicKey() 51 | serialized := publicKey.SerializeCompressed() 52 | assert.EqualValues(serializedKeys[curve], fmt.Sprintf("%x", serialized)) 53 | assert.True(true) 54 | } 55 | } 56 | 57 | func Test_PublicKey_Curve(t *testing.T) { 58 | assert := assert.New(t) 59 | 60 | for _, curve := range curves { 61 | privateKey := CreatePrivateKey(curve, big.NewInt(5001)) 62 | publicKey := privateKey.PublicKey() 63 | assert.Equal(curve, publicKey.Curve()) 64 | } 65 | } 66 | 67 | func Test_PublicKey_FromSerializedCompressed(t *testing.T) { 68 | assert := assert.New(t) 69 | 70 | for _, curve := range curves { 71 | serialized, _ := new(big.Int).SetString(serializedKeys[curve], 16) 72 | publicKey, err := DeserializeCompressed(curve, serialized.Bytes()) 73 | assert.NoError(err) 74 | assert.NotNil(publicKey) 75 | assert.EqualValues(serializedKeyComponents[curve].X, fmt.Sprintf("%064x", publicKey.publicKey.X)) 76 | assert.EqualValues(serializedKeyComponents[curve].Y, fmt.Sprintf("%064x", publicKey.publicKey.Y)) 77 | } 78 | } 79 | 80 | func Test_PublicKey_Address(t *testing.T) { 81 | assert := assert.New(t) 82 | 83 | secret, _ := new(big.Int).SetString("12345deadbeef", 16) 84 | privateKey := NewPrivateKey(secret) 85 | address, err := privateKey.PublicKey().BitcoinAddress() 86 | assert.NoError(err) 87 | assert.Equal("1F1Pn2y6pDb68E5nYJJeba4TLg2U7B6KF1", address) 88 | } 89 | 90 | func Test_PublicKey_Equal(t *testing.T) { 91 | assert := assert.New(t) 92 | 93 | privateKey1, err := NewRandomPrivateKey() 94 | assert.NoError(err) 95 | publicKey1 := privateKey1.PublicKey() 96 | publicKey2, err := NewPublicFromSerializedCompressed(publicKey1.SerializeCompressed()) 97 | assert.NoError(err) 98 | 99 | assert.True(publicKey1.Equal(publicKey2)) 100 | assert.True(publicKey1.EqualSerializedCompressed(publicKey2.SerializeCompressed())) 101 | 102 | privateKey3, err := NewRandomPrivateKey() 103 | assert.NoError(err) 104 | publicKey3 := privateKey3.PublicKey() 105 | 106 | assert.False(publicKey1.Equal(publicKey3)) 107 | assert.False(publicKey1.EqualSerializedCompressed(publicKey3.SerializeCompressed())) 108 | } 109 | 110 | func Test_PublicKey_SerializeSECP256K1(t *testing.T) { 111 | assert := assert.New(t) 112 | 113 | key, err := GeneratePrivateKey(SECP256K1) 114 | assert.NoError(err) 115 | 116 | b := key.PublicKey().Serialize() 117 | assert.True(len(b) > 0) 118 | 119 | publicKey, err := Deserialize(SECP256K1, b) 120 | assert.NoError(err) 121 | 122 | assert.True(publicKey.Equal(key.PublicKey())) 123 | } 124 | 125 | func Test_PublicKey_ToECDSA(t *testing.T) { 126 | assert := assert.New(t) 127 | 128 | for _, curve := range curves { 129 | privateKey, err := GeneratePrivateKey(curve) 130 | assert.NoError(err) 131 | assert.NotNil(privateKey.PublicKey().ToECDSA()) 132 | } 133 | } 134 | 135 | func Test_PublicKey_BitcoinEthereumAddress(t *testing.T) { 136 | assert := assert.New(t) 137 | 138 | privateKey := CreatePrivateKey(SECP256K1, big.NewInt(12345)) 139 | publicKey := privateKey.PublicKey() 140 | bitcoinAddress, err := publicKey.BitcoinAddress() 141 | assert.NoError(err) 142 | assert.Equal("12vieiAHxBe4qCUrwvfb2kRkDuc8kQ2VZ2", bitcoinAddress) 143 | ethereumAddress, err := publicKey.EthereumAddress() 144 | assert.NoError(err) 145 | assert.Equal("0xEB4665750b1382DF4AeBF49E04B429AAAc4d9929", ethereumAddress) 146 | 147 | wrongCurveKey := CreatePrivateKey(P521, big.NewInt(12345)) 148 | _, err = wrongCurveKey.PublicKey().BitcoinAddress() 149 | assert.Equal(ErrUnsupportedCurve, err) 150 | _, err = wrongCurveKey.PublicKey().EthereumAddress() 151 | assert.Equal(ErrUnsupportedCurve, err) 152 | } 153 | 154 | func Test_PublicKey_CreateFromPoint(t *testing.T) { 155 | randomBigInt := func(bitSize int) *big.Int { 156 | max := new(big.Int) 157 | max.Exp(big.NewInt(2), big.NewInt(int64(bitSize)), nil).Sub(max, big.NewInt(1)) 158 | n, err := rand.Int(rand.Reader, max) 159 | if err != nil { 160 | return nil 161 | } 162 | return n 163 | } 164 | x := randomBigInt(256) 165 | y := randomBigInt(256) 166 | type args struct { 167 | curve elliptic.Curve 168 | x *big.Int 169 | y *big.Int 170 | } 171 | tests := []struct { 172 | name string 173 | args args 174 | want *PublicKey 175 | }{ 176 | { 177 | name: "valid input", 178 | args: args{curve: elliptic.P256(), x: x, y: y}, 179 | want: &PublicKey{publicKey: &ecdsa.PublicKey{elliptic.P256(), x, y}}, 180 | }, 181 | { 182 | name: "invalid input x", 183 | args: args{curve: elliptic.P256(), x: nil, y: y}, 184 | want: nil, 185 | }, 186 | { 187 | name: "invalid input y", 188 | args: args{curve: elliptic.P256(), x: x, y: nil}, 189 | want: nil, 190 | }, 191 | } 192 | for _, tt := range tests { 193 | t.Run(tt.name, func(t *testing.T) { 194 | assert.Equalf(t, tt.want, CreatePublicKeyFromPoint(tt.args.curve, 195 | tt.args.x, tt.args.y), "NewKey(%v, %v, %v)", tt.args.curve, tt.args.x, tt.args.y) 196 | }) 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /signature.go: -------------------------------------------------------------------------------- 1 | package easyecc 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "math/big" 6 | ) 7 | 8 | // Signature represents a cryptographic signature (ECDSA). 9 | // See https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm 10 | type Signature struct { 11 | R *big.Int 12 | S *big.Int 13 | } 14 | 15 | // Verify verifies the signer using the public key and the hash of the data. 16 | func (sig *Signature) Verify(key *PublicKey, hash []byte) bool { 17 | return ecdsa.Verify(key.publicKey, hash, sig.R, sig.S) 18 | } 19 | -------------------------------------------------------------------------------- /signature_test.go: -------------------------------------------------------------------------------- 1 | package easyecc 2 | 3 | import ( 4 | "crypto/sha256" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func Test_SignAndVerify(t *testing.T) { 11 | assert := assert.New(t) 12 | 13 | data := []byte("hello there") 14 | for _, curve := range curves { 15 | for i := 0; i < 100; i++ { 16 | hash := sha256.Sum256(data) 17 | pkey, err := GeneratePrivateKey(curve) 18 | assert.NoError(err) 19 | sig, err := pkey.Sign(hash[:]) 20 | assert.NoError(err) 21 | assert.True(sig.Verify(pkey.PublicKey(), hash[:])) 22 | } 23 | } 24 | } 25 | --------------------------------------------------------------------------------