Skip to content

Commit

Permalink
Feat: implement keygen tool (#1)
Browse files Browse the repository at this point in the history
* add go module

* implement keygen

* add makefile

* add github stuff

* update readme
  • Loading branch information
bullet-tooth authored Mar 25, 2024
1 parent 62814ef commit 10978de
Show file tree
Hide file tree
Showing 12 changed files with 338 additions and 1 deletion.
34 changes: 34 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
projects: [ "stroomnetwork/Stroom" ]
assignees: ''

---

## 💬 Description

<!--- Add some description here -->

## 💭 Expected Behavior

<!--- Tell us what should happen -->

## 😑 Current Behavior

<!--- Tell us what happens instead of the expected behavior -->

## 🎯 Possible Implementation

<!--- Not obligatory, but suggest a fix/reason for the bug, -->

## 🪜 Steps to Reproduce

<!--- Provide a link to a live example, or an unambiguous set of steps to -->
<!--- reproduce this bug. Include code to reproduce, if relevant -->

1.
2.
3.
28 changes: 28 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
projects: [ "stroomnetwork/Stroom" ]
assignees: ''

---

## 💬 Description

<!--- Add some description here -->

## 💡 Suggested Changes

<!--- Way of implementation, or better actual changes explanation with code links -->

## ✔ Acceptance Criteria

<!--- System state/behaviour after changes being applied. Can be specified as todo list -->

- [ ] Do something
- [ ] Do something else

## 🧪 Testing Strategy

<!--- Brief listing of testing effort -->
21 changes: 21 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## Description

<!--- Describe the purpose of the PR in detail -->

### Scope

<!--- Describe your changes in detail -->

- 🚀 Implement ...
- ✨ Improve ...
- 🐛 Fix ...
- 🛠 Refactor ...
- 📚 Document ...
- 🧪 Test ...
- 🚧 In progress ...

## Related Issue

<!--- Please link to the issue here if applicable: -->

Resolves #ISSUE-NUMBER
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
- package-ecosystem: "github-actions"
directory: /
schedule:
interval: weekly
- package-ecosystem: "gomod"
directory: /
schedule:
interval: weekly
20 changes: 20 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: CI

on:
push:

jobs:
go:
name: Go Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.21.5'
cache: true
- name: Run build
run: make build
- name: Run tests
run: make test
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,8 @@

# Go workspace file
go.work

.idea/
.vscode/
.run/
build/
42 changes: 42 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
SHELL=/bin/bash

GREEN := $(shell tput -Txterm setaf 2)
YELLOW := $(shell tput -Txterm setaf 3)
WHITE := $(shell tput -Txterm setaf 7)
CYAN := $(shell tput -Txterm setaf 6)
RESET := $(shell tput -Txterm sgr0)

LDFLAGS := -X '$(PACKAGE_NAME)/version.Commit=$(shell git describe --always --tags --dirty)' \
-X '$(PACKAGE_NAME)/version.CommitHash=$(shell git rev-parse HEAD)' \
-X '$(PACKAGE_NAME)/version.GoVersion=$(shell go version)' \

.PHONY: all
all: help

## Build:

.PHONY: build
build: ## Build the project
go build -v -o ./build/keygen -installsuffix cgo -ldflags "$(LDFLAGS)" ./cmd/keygen

.PHONY: test
test: ## Run unit tests
go test -v ./...

.PHONY: clean
clean: ## Remove build files and caches.
rm -rf build
go clean -i -r -cache -testcache

## Help:
.PHONY: help
help: ## Show this help
@echo ''
@echo 'Usage:'
@echo ' ${YELLOW}make${RESET} ${GREEN}<target>${RESET}'
@echo ''
@echo 'Targets:'
@awk 'BEGIN {FS = ":.*?## "} { \
if (/^[a-zA-Z0-9_-]+:.*?##.*$$/) {printf " ${YELLOW}%-20s${GREEN}%s${RESET}\n", $$1, $$2} \
else if (/^## .*$$/) {printf " ${CYAN}%s${RESET}\n", substr($$1,4)} \
}' $(MAKEFILE_LIST)
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
# keygen
# keygen

## TODO:

- [ ] Add tests
- [ ] Add build matrix for different platforms
- [ ] Add release build to publish binaries
- [ ] Add docker packaging and release
- [ ] Add checksum and verification
- [ ] Write Readme
85 changes: 85 additions & 0 deletions cmd/keygen/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package main

import (
"encoding/json"
"errors"
"fmt"
"log"
"os"

"github.com/stroomnetwork/keygen"
"github.com/urfave/cli/v2"
)

func main() {
app := cli.NewApp()
app.Name = "keygen"
app.Usage = "Command line tool to securely generate random keys"
app.DefaultCommand = "generate"
app.Commands = []*cli.Command{
{
Name: "generate",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Required: false,
TakesFile: true,
Usage: "File name to write keys output, for example keys.json",
},
&cli.BoolFlag{
Name: "force",
Usage: "Overwrite output file if it exists",
Required: false,
Aliases: []string{"f"},
},
},
Action: generate,
},
}

if err := app.Run(os.Args); err != nil {
log.Fatal("Error: ", err)
}
}

func generate(cCtx *cli.Context) error {
keys, err := keygen.GenerateRandomKeys()
if err != nil {
return fmt.Errorf("can't generate keys: %w", err)
}
keysJson, err := json.MarshalIndent(keys, "", " ")
if err != nil {
return fmt.Errorf("can't marshal JSON: %w", err)
}

outputFile := cCtx.String("output")
if outputFile == "" {
return printToConsole(keysJson)
}
// print to file
if fileExists(outputFile) && !cCtx.Bool("force") {
return fmt.Errorf("output file '%s' already exists", outputFile)
}
err = os.WriteFile(outputFile, keysJson, 0644)
if err != nil {
return fmt.Errorf("can't write to file '%s': %w", outputFile, err)
}
return nil
}

func fileExists(filePath string) bool {
_, err := os.Stat(filePath)
return !errors.Is(err, os.ErrNotExist)
}

func printToConsole(keysJson []byte) error {
fmt.Println("")
fmt.Println("!!!DO NOT FORGET TO BACKUP THIS KEY!!!")
fmt.Println("")
fmt.Println(string(keysJson))
fmt.Println("")
fmt.Println("!!!DO NOT FORGET TO BACKUP THIS KEY!!!")
fmt.Println("")
return nil
}
15 changes: 15 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module github.com/stroomnetwork/keygen

go 1.21.5

require (
github.com/btcsuite/btcd/btcec/v2 v2.1.3
github.com/urfave/cli/v2 v2.27.1
)

require (
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
)
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
github.com/btcsuite/btcd/btcec/v2 v2.1.3 h1:xM/n3yIhHAhHy04z4i43C8p4ehixJZMsnrVJkgl+MTE=
github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
50 changes: 50 additions & 0 deletions keygen.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package keygen

import (
"encoding/hex"

"github.com/btcsuite/btcd/btcec/v2"
)

type KeysOutput struct {
PublicKey string `json:"public_key"`
PrivateKey string `json:"private_key"`
}

// GenerateRandomKeys generates `bip-0340` compatible keys and encodes in hex.
func GenerateRandomKeys() (*KeysOutput, error) {
privateKey, err := btcec.NewPrivateKey()
if err != nil {
return nil, err
}
if HasOddY(privateKey.PubKey()) {
privateKey = NegatePrivateKey(privateKey)
}
output := &KeysOutput{
PublicKey: hex.EncodeToString(privateKey.PubKey().SerializeCompressed()),
PrivateKey: hex.EncodeToString(privateKey.Serialize()),
}

return output, nil
}

// HasOddY returns true if y coordinate of pubKey is odd.
// BIP-340 requires that y coordinate is even.
// Does not change its arguments.
func HasOddY(pubKey *btcec.PublicKey) bool {
return pubKey.Y().Bit(0) == 1
}

// NegatePrivateKey returns negated private key.
// For private key `a`, it returns `-a`. Such as `a + (-a) = 0`.
// Does not change its arguments.
func NegatePrivateKey(privateKey *btcec.PrivateKey) *btcec.PrivateKey {
var negated btcec.ModNScalar
negated.Set(&privateKey.Key)
negated.Negate()

var negatedPrivateKey btcec.PrivateKey
negatedPrivateKey.Key = negated

return &negatedPrivateKey
}

0 comments on commit 10978de

Please sign in to comment.