feat: generate key command and crypto pkg

This commit is contained in:
Panjamapong Sermsawatsri
2024-04-23 16:44:16 +07:00
parent 185d26c651
commit be316442ea
7 changed files with 1295 additions and 5 deletions

2
.gitignore vendored
View File

@@ -3,6 +3,8 @@
# Eg. ignore.foo_test.go, ignore.credentials.json, ignore.config.yml
ignore.*
/key
**/cmd.local/**
**/cmd.local.**/**

View File

@@ -14,13 +14,14 @@ var (
// root command
cmd = &cobra.Command{
Use: "gaze",
Long: `Description of gaze indexer`,
Long: `Gaze in a Bitcoin meta-protocol indexer`,
}
// sub-commands
// sub-commandsf
cmds = []*cobra.Command{
NewVersionCommand(),
NewRunCommand(),
NewGenerateKeypairCommand(),
}
)

View File

@@ -0,0 +1,82 @@
package cmd
import (
"crypto/rand"
"encoding/hex"
"fmt"
"os"
"path"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/cockroachdb/errors"
"github.com/gaze-network/indexer-network/common/errs"
"github.com/spf13/cobra"
)
type generateKeypairCmdOptions struct {
Path string
}
func NewGenerateKeypairCommand() *cobra.Command {
opts := &generateKeypairCmdOptions{}
cmd := &cobra.Command{
Use: "generate-keypair",
Short: "Generate new public/private keypair for encryption and signature generation",
RunE: func(cmd *cobra.Command, args []string) error {
return generateKeypairHandler(opts, cmd, args)
},
}
flags := cmd.Flags()
flags.StringVar(&opts.Path, "path", "/data/keys", `Path to save to key pair file`)
return cmd
}
func generateKeypairHandler(opts *generateKeypairCmdOptions, _ *cobra.Command, _ []string) error {
fmt.Printf("Generating key pair\n")
privKeyBytes := make([]byte, 32)
_, err := rand.Read(privKeyBytes)
if err != nil {
return errors.Wrap(errs.SomethingWentWrong, "random bytes")
}
_, pubKey := btcec.PrivKeyFromBytes(privKeyBytes)
serializedPubKey := pubKey.SerializeCompressed()
// fmt.Println(hex.EncodeToString(privKeyBytes))
fmt.Printf("Public key: %s\n", hex.EncodeToString(serializedPubKey))
err = os.MkdirAll(opts.Path, 0o755)
if err != nil {
return errors.Wrap(errs.SomethingWentWrong, "create directory")
}
privateKeyPath := path.Join(opts.Path, "priv.key")
_, err = os.Stat(privateKeyPath)
if err == nil {
fmt.Printf("Existing private key found at %s\n[WARNING] THE EXISTING PRIVATE KEY WILL BE LOST\nType [replace] to replace existing private key: ", privateKeyPath)
var ans string
fmt.Scanln(&ans)
if ans != "replace" {
fmt.Printf("Keypair generation aborted\n")
return nil
}
}
err = os.WriteFile(privateKeyPath, []byte(hex.EncodeToString(privKeyBytes)), 0o644)
if err != nil {
return errors.Wrap(err, "write private key file")
}
fmt.Printf("Private key saved at %s\n", privateKeyPath)
publicKeyPath := path.Join(opts.Path, "pub.key")
err = os.WriteFile(publicKeyPath, []byte(hex.EncodeToString(serializedPubKey)), 0o644)
if err != nil {
return errors.Wrap(errs.SomethingWentWrong, "write public key file")
}
fmt.Printf("Public key saved at %s\n", publicKeyPath)
return nil
}

8
go.mod
View File

@@ -26,15 +26,17 @@ require (
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.1.3 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/ecies/go/v2 v2.0.9 // indirect
github.com/ethereum/go-ethereum v1.13.5 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/getsentry/sentry-go v0.18.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect

1058
go.sum

File diff suppressed because it is too large Load Diff

92
pkg/crypto/crypto.go Normal file
View File

@@ -0,0 +1,92 @@
package crypto
import (
"encoding/base64"
"encoding/hex"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/ecdsa"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/cockroachdb/errors"
ecies "github.com/ecies/go/v2"
)
type Client struct {
privateKey *btcec.PrivateKey
eciesPrivateKey *ecies.PrivateKey
}
func New(privateKeyStr string) (*Client, error) {
if privateKeyStr != "" {
privateKeyBytes, err := hex.DecodeString(privateKeyStr)
println(len(privateKeyBytes))
if err != nil {
return nil, errors.Wrap(err, "decode private key")
}
privateKey, _ := btcec.PrivKeyFromBytes(privateKeyBytes)
eciesPrivateKey := ecies.NewPrivateKeyFromBytes(privateKeyBytes)
return &Client{
privateKey: privateKey,
eciesPrivateKey: eciesPrivateKey,
}, nil
}
return &Client{}, nil
}
func (c *Client) Sign(message string) string {
messageHash := chainhash.DoubleHashB([]byte(message))
signature := ecdsa.Sign(c.privateKey, messageHash)
return hex.EncodeToString(signature.Serialize())
}
func (c *Client) Verify(message, sigStr, pubKeyStr string) (bool, error) {
sigBytes, err := hex.DecodeString(sigStr)
if err != nil {
return false, errors.Wrap(err, "signature decode")
}
pubBytes, err := hex.DecodeString(pubKeyStr)
if err != nil {
return false, errors.Wrap(err, "pubkey decode")
}
pubKey, err := btcec.ParsePubKey(pubBytes)
if err != nil {
return false, errors.Wrap(err, "pubkey parse")
}
messageHash := chainhash.DoubleHashB([]byte(message))
signature, err := ecdsa.ParseSignature(sigBytes)
if err != nil {
return false, errors.Wrap(err, "signature parse")
}
return signature.Verify(messageHash, pubKey), nil
}
func (c *Client) Encrypt(message, pubKeyStr string) (string, error) {
pubKey, err := ecies.NewPublicKeyFromHex(pubKeyStr)
if err != nil {
return "", errors.Wrap(err, "parse pubkey")
}
ciphertext, err := ecies.Encrypt(pubKey, []byte(message))
if err != nil {
return "", errors.Wrap(err, "encrypt message")
}
ciphertextStr := base64.StdEncoding.EncodeToString(ciphertext)
return ciphertextStr, nil
}
func (c *Client) Decrypt(ciphertextStr string) (string, error) {
ciphertext, err := base64.StdEncoding.DecodeString(ciphertextStr)
if err != nil {
return "", errors.Wrap(err, "decode ciphertext")
}
plaintext, err := ecies.Decrypt(c.eciesPrivateKey, ciphertext)
if err != nil {
return "", errors.Wrap(err, "decrypt")
}
return string(plaintext), nil
}

53
pkg/crypto/crypto_test.go Normal file
View File

@@ -0,0 +1,53 @@
package crypto
import (
"testing"
"github.com/stretchr/testify/assert"
)
const (
privateKeyStr = "ce9c2fd75623e82a83ed743518ec7749f6f355f7301dd432400b087717fed2f2"
pubKeyStr = "0251e2dfcdeea17cc9726e4be0855cd0bae19e64f3e247b10760cd76851e7df47e"
)
func TestEncryptDecrypt(t *testing.T) {
plaintext := "hello world"
privClient, err := New(privateKeyStr)
assert.NoError(t, err)
pubClient, err := New("")
assert.NoError(t, err)
ciphertext, err := pubClient.Encrypt(plaintext, pubKeyStr)
assert.NoError(t, err)
decrypted, err := privClient.Decrypt(ciphertext)
assert.NoError(t, err)
assert.Equal(t, plaintext, decrypted)
}
func TestSignVerify(t *testing.T) {
plaintext := "hello world"
invalidSignature := "3044022066504a82e2bc23167214e05497a1ca957add9cacc078aa69f5417079a4d56f0002206b215920b046c779d4a58d4029c26dbadcaf6d3c884b3463f44e70ef9146c1cd"
privClient, err := New(privateKeyStr)
assert.NoError(t, err)
pubClient, err := New("")
assert.NoError(t, err)
signature := privClient.Sign(plaintext)
println(signature)
verified, err := pubClient.Verify(plaintext, signature, pubKeyStr)
assert.NoError(t, err)
assert.True(t, verified)
verified, err = pubClient.Verify(plaintext, invalidSignature, pubKeyStr)
assert.NoError(t, err)
assert.False(t, verified)
}