diff --git a/cmd/burrow/main.go b/cmd/burrow/main.go index 6fb5ee54db1b4eb647c56175ee9283cd0e2de959..d1fa13f458c656e2ee27d8a5b00f23116e397d4b 100644 --- a/cmd/burrow/main.go +++ b/cmd/burrow/main.go @@ -14,6 +14,7 @@ import ( "github.com/hyperledger/burrow/genesis" "github.com/hyperledger/burrow/genesis/spec" "github.com/hyperledger/burrow/keys" + "github.com/hyperledger/burrow/keys/mock" "github.com/hyperledger/burrow/logging" logging_config "github.com/hyperledger/burrow/logging/config" "github.com/hyperledger/burrow/logging/config/presets" @@ -144,6 +145,15 @@ func main() { genesisDocOpt := cmd.StringOpt("g genesis-doc", "", "GenesisDoc in JSON or TOML to embed in config") + generateKeysOpt := cmd.StringOpt("x generate-keys", "", + "File to output containing secret keys as JSON or according to a custom template (see --keys-template). "+ + "Note that using this options means the keys will not be generated in the default keys instance") + + keysTemplateOpt := cmd.StringOpt("z keys-template", mock.DefaultDumpKeysFormat, + fmt.Sprintf("Go text/template template (left delim: %s right delim: %s) to generate secret keys "+ + "file specified with --generate-keys. Default:\n%s", mock.LeftTemplateDelim, mock.RightTemplateDelim, + mock.DefaultDumpKeysFormat)) + separateGenesisDoc := cmd.StringOpt("s separate-genesis-doc", "", "Emit a separate genesis doc as JSON or TOML") validatorIndexOpt := cmd.IntOpt("v validator-index", -1, @@ -162,7 +172,8 @@ func main() { chainNameOpt := cmd.StringOpt("n chain-name", "", "Default chain name") - cmd.Spec = "[--keys-url=<keys URL>] [--genesis-spec=<GenesisSpec file> | --genesis-doc=<GenesisDoc file>] " + + cmd.Spec = "[--keys-url=<keys URL> | (--generate-keys=<secret keys files> [--keys-template=<text template for each key>])] " + + "[--genesis-spec=<GenesisSpec file> | --genesis-doc=<GenesisDoc file>] " + "[--separate-genesis-doc=<genesis JSON file>] [--validator-index=<index>] [--chain-name] [--json-out] " + "[--logging=<logging program>] [--describe-logging] [--debug]" @@ -192,14 +203,28 @@ func main() { conf.Keys.URL = *keysUrlOpt } + // Genesis Spec if *genesisSpecOpt != "" { genesisSpec := new(spec.GenesisSpec) err := source.FromFile(*genesisSpecOpt, genesisSpec) if err != nil { fatalf("could not read GenesisSpec: %v", err) } - keyClient := keys.NewKeyClient(conf.Keys.URL, logging.NewNoopLogger()) - conf.GenesisDoc, err = genesisSpec.GenesisDoc(keyClient) + if *generateKeysOpt != "" { + keyClient := mock.NewMockKeyClient() + conf.GenesisDoc, err = genesisSpec.GenesisDoc(keyClient) + + secretKeysString, err := keyClient.DumpKeys(*keysTemplateOpt) + if err != nil { + fatalf("Could not dump keys: %v", err) + } + err = ioutil.WriteFile(*generateKeysOpt, []byte(secretKeysString), 0700) + if err != nil { + fatalf("Could not write secret keys: %v", err) + } + } else { + conf.GenesisDoc, err = genesisSpec.GenesisDoc(keys.NewKeyClient(conf.Keys.URL, logging.NewNoopLogger())) + } if err != nil { fatalf("could not realise GenesisSpec: %v", err) } @@ -212,6 +237,7 @@ func main() { conf.GenesisDoc = genesisDoc } + // Which validator am I? if *validatorIndexOpt > -1 { if conf.GenesisDoc == nil { fatalf("Unable to set ValidatorAddress from provided validator-index since no " + @@ -227,6 +253,7 @@ func main() { conf.ValidatorAddress = &conf.GenesisDoc.Validators[0].Address } + // Logging if *loggingOpt != "" { ops := strings.Split(*loggingOpt, ",") sinkConfig, err := presets.BuildSinkConfig(ops...) diff --git a/keys/mock/key_client_mock.go b/keys/mock/key_client_mock.go index 96a614cac83ebe8367a84e76fad91563a1198375..1faa65e269e5ed2fb4112f41c5adfd60e6ae8076 100644 --- a/keys/mock/key_client_mock.go +++ b/keys/mock/key_client_mock.go @@ -18,11 +18,18 @@ import ( "crypto/rand" "fmt" + "bytes" + "text/template" + + "encoding/base64" + acm "github.com/hyperledger/burrow/account" . "github.com/hyperledger/burrow/keys" "github.com/tendermint/ed25519" crypto "github.com/tendermint/go-crypto" + "github.com/tmthrgd/go-hex" "golang.org/x/crypto/ripemd160" + "github.com/pkg/errors" ) //--------------------------------------------------------------------- @@ -30,14 +37,35 @@ import ( // Simple ed25519 key structure for mock purposes with ripemd160 address type MockKey struct { + Name string Address acm.Address - PrivateKey [ed25519.PrivateKeySize]byte PublicKey []byte + PrivateKey []byte } -func newMockKey() (*MockKey, error) { +const DefaultDumpKeysFormat = `{ + "Keys": [<< range $index, $key := . >><< if $index>>,<< end >> + { + "Name": "<< $key.Name >>", + "Address": "<< $key.Address >>", + "PublicKey": "<< $key.PublicKeyBase64 >>", + "PrivateKey": "<< $key.PrivateKeyBase64 >>" + }<< end >> + ] +}` + +const LeftTemplateDelim = "<<" +const RightTemplateDelim = ">>" + +var DefaultDumpKeysTemplate = template.Must(template.New("MockKeyClient_DumpKeys"). + Delims(LeftTemplateDelim, RightTemplateDelim). + Parse(DefaultDumpKeysFormat)) + +func newMockKey(name string) (*MockKey, error) { key := &MockKey{ - PublicKey: make([]byte, ed25519.PublicKeySize), + Name: name, + PublicKey: make([]byte, ed25519.PublicKeySize), + PrivateKey: make([]byte, ed25519.PrivateKeySize), } // this is a mock key, so the entropy of the source is purely // for testing @@ -65,15 +93,34 @@ func mockKeyFromPrivateAccount(privateAccount acm.PrivateAccount) *MockKey { panic(fmt.Errorf("mock key client only supports ed25519 private keys at present")) } key := &MockKey{ - Address: privateAccount.Address(), - PublicKey: privateAccount.PublicKey().RawBytes(), + Name: privateAccount.Address().String(), + Address: privateAccount.Address(), + PublicKey: privateAccount.PublicKey().RawBytes(), + PrivateKey: privateAccount.PrivateKey().RawBytes(), } - copy(key.PrivateKey[:], privateAccount.PrivateKey().RawBytes()) return key } func (mockKey *MockKey) Sign(message []byte) (acm.Signature, error) { - return acm.SignatureFromBytes(ed25519.Sign(&mockKey.PrivateKey, message)[:]) + var privateKey [ed25519.PrivateKeySize]byte + copy(privateKey[:], mockKey.PrivateKey) + return acm.SignatureFromBytes(ed25519.Sign(&privateKey, message)[:]) +} + +func (mockKey *MockKey) PrivateKeyBase64() string { + return base64.StdEncoding.EncodeToString(mockKey.PrivateKey[:]) +} + +func (mockKey *MockKey) PrivateKeyHex() string { + return hex.EncodeUpperToString(mockKey.PrivateKey[:]) +} + +func (mockKey *MockKey) PublicKeyBase64() string { + return base64.StdEncoding.EncodeToString(mockKey.PublicKey) +} + +func (mockKey *MockKey) PublicKeyHex() string { + return hex.EncodeUpperToString(mockKey.PublicKey) } //--------------------------------------------------------------------- @@ -96,26 +143,26 @@ func NewMockKeyClient(privateAccounts ...acm.PrivateAccount) *MockKeyClient { return client } -func (mock *MockKeyClient) NewKey() acm.Address { +func (mkc *MockKeyClient) NewKey(name string) acm.Address { // Only tests ED25519 curve and ripemd160. - key, err := newMockKey() + key, err := newMockKey(name) if err != nil { panic(fmt.Sprintf("Mocked key client failed on key generation: %s", err)) } - mock.knownKeys[key.Address] = key + mkc.knownKeys[key.Address] = key return key.Address } -func (mock *MockKeyClient) Sign(signAddress acm.Address, message []byte) (acm.Signature, error) { - key := mock.knownKeys[signAddress] +func (mkc *MockKeyClient) Sign(signAddress acm.Address, message []byte) (acm.Signature, error) { + key := mkc.knownKeys[signAddress] if key == nil { return acm.Signature{}, fmt.Errorf("Unknown address (%s)", signAddress) } return key.Sign(message) } -func (mock *MockKeyClient) PublicKey(address acm.Address) (acm.PublicKey, error) { - key := mock.knownKeys[address] +func (mkc *MockKeyClient) PublicKey(address acm.Address) (acm.PublicKey, error) { + key := mkc.knownKeys[address] if key == nil { return acm.PublicKey{}, fmt.Errorf("Unknown address (%s)", address) } @@ -124,10 +171,27 @@ func (mock *MockKeyClient) PublicKey(address acm.Address) (acm.PublicKey, error) return acm.PublicKeyFromGoCryptoPubKey(pubKeyEd25519.Wrap()) } -func (mock *MockKeyClient) Generate(keyName string, keyType KeyType) (acm.Address, error) { - return mock.NewKey(), nil +func (mkc *MockKeyClient) Generate(keyName string, keyType KeyType) (acm.Address, error) { + return mkc.NewKey(keyName), nil } -func (mock *MockKeyClient) HealthCheck() error { +func (mkc *MockKeyClient) HealthCheck() error { return nil } + +func (mkc *MockKeyClient) DumpKeys(templateString string) (string, error) { + tmpl, err := template.New("DumpKeys").Delims(LeftTemplateDelim, RightTemplateDelim).Parse(templateString) + if err != nil { + errors.Wrap(err, "could not dump keys to template") + } + buf := new(bytes.Buffer) + keys := make([]*MockKey, 0, len(mkc.knownKeys)) + for _, k := range mkc.knownKeys { + keys = append(keys, k) + } + err = tmpl.Execute(buf, keys) + if err != nil { + return "", err + } + return buf.String(), nil +} diff --git a/keys/mock/key_client_mock_test.go b/keys/mock/key_client_mock_test.go new file mode 100644 index 0000000000000000000000000000000000000000..ffcec0019c000eecfa116ec1be3e2b275cb6c992 --- /dev/null +++ b/keys/mock/key_client_mock_test.go @@ -0,0 +1,29 @@ +package mock + +import ( + "testing" + + "encoding/json" + + "github.com/hyperledger/burrow/keys" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMockKeyClient_DumpKeys(t *testing.T) { + keyClient := NewMockKeyClient() + _, err := keyClient.Generate("foo", keys.KeyTypeEd25519Ripemd160) + require.NoError(t, err) + _, err = keyClient.Generate("foobar", keys.KeyTypeEd25519Ripemd160) + require.NoError(t, err) + dump, err := keyClient.DumpKeys(DefaultDumpKeysFormat) + require.NoError(t, err) + + // Check JSON equal + var keys struct{ Keys []*MockKey } + err = json.Unmarshal([]byte(dump), &keys) + require.NoError(t, err) + bs, err := json.MarshalIndent(keys, "", " ") + require.NoError(t, err) + assert.Equal(t, string(bs), dump) +}