diff --git a/Dockerfile b/Dockerfile
index 44a17fda1..ec10c0f75 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -35,6 +35,8 @@ RUN cp ./peering.json /app/peering.json
# using distroless cc "nonroot" image, which includes everything in the base image (glibc, libssl and openssl)
FROM gcr.io/distroless/cc-debian12:nonroot
+HEALTHCHECK --interval=10s --timeout=5s --retries=30 CMD ["/app/iota-core", "tools", "node-info"]
+
# Copy the app dir into distroless image
COPY --chown=nonroot:nonroot --from=build /app /app
diff --git a/Dockerfile.dev b/Dockerfile.dev
index e9073b384..f91006d26 100644
--- a/Dockerfile.dev
+++ b/Dockerfile.dev
@@ -61,6 +61,8 @@ RUN mkdir -p /app/data/peerdb
# using distroless cc "nonroot" image, which includes everything in the base image (glibc, libssl and openssl)
FROM gcr.io/distroless/cc-debian12:nonroot
+HEALTHCHECK --interval=10s --timeout=5s --retries=30 CMD ["/app/iota-core", "tools", "node-info"]
+
# Copy the app dir into distroless image
COPY --chown=nonroot:nonroot --from=build /app /app
diff --git a/components/app/app.go b/components/app/app.go
index 4cb029500..5c4b42957 100644
--- a/components/app/app.go
+++ b/components/app/app.go
@@ -1,6 +1,9 @@
package app
import (
+ "fmt"
+ "os"
+
"github.com/iotaledger/hive.go/app"
"github.com/iotaledger/hive.go/app/components/profiling"
"github.com/iotaledger/hive.go/app/components/shutdown"
@@ -15,6 +18,7 @@ import (
"github.com/iotaledger/iota-core/components/restapi"
coreapi "github.com/iotaledger/iota-core/components/restapi/core"
"github.com/iotaledger/iota-core/components/validator"
+ "github.com/iotaledger/iota-core/pkg/toolset"
)
var (
@@ -28,6 +32,12 @@ var (
func App() *app.App {
return app.New(Name, Version,
// app.WithVersionCheck("iotaledger", "iota-core"),
+ app.WithUsageText(fmt.Sprintf(`Usage of %s (%s %s):
+
+Run '%s tools' to list all available tools.
+
+Command line flags:
+`, os.Args[0], Name, Version, os.Args[0])),
app.WithInitComponent(InitComponent),
app.WithComponents(
shutdown.Component,
@@ -63,5 +73,15 @@ func init() {
AdditionalConfigs: []*app.ConfigurationSet{
app.NewConfigurationSet("peering", "peering", "peeringConfigFilePath", "peeringConfig", false, true, false, "peering.json", "n"),
},
+ Init: initialize,
+ }
+}
+
+func initialize(_ *app.App) error {
+ if toolset.ShouldHandleTools() {
+ toolset.HandleTools()
+ // HandleTools will call os.Exit
}
+
+ return nil
}
diff --git a/components/p2p/component.go b/components/p2p/component.go
index 9635df585..dff432bb7 100644
--- a/components/p2p/component.go
+++ b/components/p2p/component.go
@@ -230,7 +230,7 @@ func provide(c *dig.Container) error {
if err := c.Provide(func(deps p2pDeps) p2pResult {
res := p2pResult{}
- privKeyFilePath := filepath.Join(deps.P2PDatabasePath, "identity.key")
+ privKeyFilePath := filepath.Join(deps.P2PDatabasePath, IdentityPrivateKeyFileName)
// make sure nobody copies around the peer store since it contains the private key of the node
Component.LogInfof(`WARNING: never share your "%s" folder as it contains your node's private key!`, deps.P2PDatabasePath)
diff --git a/components/p2p/params.go b/components/p2p/params.go
index 37f099d85..7b2cba4e7 100644
--- a/components/p2p/params.go
+++ b/components/p2p/params.go
@@ -6,7 +6,8 @@ import (
const (
// CfgPeers defines the static peers this node should retain a connection to (CLI).
- CfgPeers = "peers"
+ CfgPeers = "peers"
+ IdentityPrivateKeyFileName = "identity.key"
)
// ParametersP2P contains the definition of configuration parameters used by the p2p plugin.
diff --git a/components/restapi/params.go b/components/restapi/params.go
index 440fdc102..121d07ae8 100644
--- a/components/restapi/params.go
+++ b/components/restapi/params.go
@@ -9,7 +9,7 @@ type ParametersRestAPI struct {
// Enabled defines whether the REST API plugin is enabled.
Enabled bool `default:"true" usage:"whether the REST API plugin is enabled"`
// the bind address on which the REST API listens on
- BindAddress string `default:"0.0.0.0:8080" usage:"the bind address on which the REST API listens on"`
+ BindAddress string `default:"0.0.0.0:14265" usage:"the bind address on which the REST API listens on"`
// the HTTP REST routes which can be called without authorization. Wildcards using * are allowed
PublicRoutes []string `usage:"the HTTP REST routes which can be called without authorization. Wildcards using * are allowed"`
// the HTTP REST routes which need to be called with authorization. Wildcards using * are allowed
diff --git a/config_defaults.json b/config_defaults.json
index 53114f287..f24471e14 100644
--- a/config_defaults.json
+++ b/config_defaults.json
@@ -44,7 +44,7 @@
},
"restAPI": {
"enabled": true,
- "bindAddress": "0.0.0.0:8080",
+ "bindAddress": "0.0.0.0:14265",
"publicRoutes": [
"/health",
"/api/routes",
diff --git a/deploy/ansible/roles/iota-core-node/templates/docker-compose-iota-core.yml.j2 b/deploy/ansible/roles/iota-core-node/templates/docker-compose-iota-core.yml.j2
index a5284d45b..d3bc9d82b 100644
--- a/deploy/ansible/roles/iota-core-node/templates/docker-compose-iota-core.yml.j2
+++ b/deploy/ansible/roles/iota-core-node/templates/docker-compose-iota-core.yml.j2
@@ -26,7 +26,7 @@ services:
ports:
- "14666:14666/tcp" # P2P
- "6061:6061/tcp" # pprof
- - "8080:8080/tcp" # REST-API
+ - "8080:14265/tcp" # REST-API
- "8081:8081/tcp" # Dashboard
- "9311:9311/tcp" # Prometheus
- "9029:9029/tcp" # INX
@@ -46,7 +46,7 @@ services:
--p2p.db.path=/app/data/peerdb
--profiling.enabled=true
--profiling.bindAddress=0.0.0.0:6061
- --restAPI.bindAddress=0.0.0.0:8080
+ --restAPI.bindAddress=0.0.0.0:14265
--database.path=/app/data/database
--protocol.snapshot.path=/app/data/snapshot.bin
{% if 'node-01' in inventory_hostname or 'node-02' in inventory_hostname or 'node-03' in inventory_hostname %}
@@ -73,7 +73,7 @@ services:
restart: unless-stopped
depends_on:
iota-core:
- condition: service_started
+ condition: service_healthy
ulimits:
nofile:
soft: 16384
@@ -93,7 +93,7 @@ services:
restart: unless-stopped
depends_on:
iota-core:
- condition: service_started
+ condition: service_healthy
inx-indexer:
condition: service_started
environment:
@@ -111,7 +111,7 @@ services:
restart: unless-stopped
depends_on:
iota-core:
- condition: service_started
+ condition: service_healthy
inx-indexer:
condition: service_started
inx-blockissuer:
diff --git a/documentation/docs/references/configuration.md b/documentation/docs/references/configuration.md
index bbcd516e6..ba034fde1 100644
--- a/documentation/docs/references/configuration.md
+++ b/documentation/docs/references/configuration.md
@@ -175,7 +175,7 @@ Example:
| Name | Description | Type | Default value |
| ------------------------------ | ---------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| enabled | Whether the REST API plugin is enabled | boolean | true |
-| bindAddress | The bind address on which the REST API listens on | string | "0.0.0.0:8080" |
+| bindAddress | The bind address on which the REST API listens on | string | "0.0.0.0:14265" |
| publicRoutes | The HTTP REST routes which can be called without authorization. Wildcards using \* are allowed | array | /health
/api/routes
/api/core/v3/info
/api/core/v3/blocks\*
/api/core/v3/transactions\*
/api/core/v3/commitments\*
/api/core/v3/outputs\*
/api/core/v3/accounts\*
/api/core/v3/validators\*
/api/core/v3/rewards\*
/api/core/v3/committee
/api/debug/v2/\*
/api/indexer/v2/\*
/api/mqtt/v2 |
| protectedRoutes | The HTTP REST routes which need to be called with authorization. Wildcards using \* are allowed | array | /api/\* |
| debugRequestLoggerEnabled | Whether the debug logging for requests should be enabled | boolean | false |
@@ -204,7 +204,7 @@ Example:
{
"restAPI": {
"enabled": true,
- "bindAddress": "0.0.0.0:8080",
+ "bindAddress": "0.0.0.0:14265",
"publicRoutes": [
"/health",
"/api/routes",
diff --git a/pkg/toolset/ed25519.go b/pkg/toolset/ed25519.go
new file mode 100644
index 000000000..bae379fbf
--- /dev/null
+++ b/pkg/toolset/ed25519.go
@@ -0,0 +1,172 @@
+package toolset
+
+import (
+ "crypto/ed25519"
+ "encoding/hex"
+ "fmt"
+ "os"
+
+ flag "github.com/spf13/pflag"
+ "github.com/wollac/iota-crypto-demo/pkg/bip32path"
+ "github.com/wollac/iota-crypto-demo/pkg/bip39"
+ "github.com/wollac/iota-crypto-demo/pkg/slip10"
+ "github.com/wollac/iota-crypto-demo/pkg/slip10/eddsa"
+
+ "github.com/iotaledger/hive.go/app/configuration"
+ "github.com/iotaledger/hive.go/crypto"
+ iotago "github.com/iotaledger/iota.go/v4"
+)
+
+func printEd25519Info(mnemonic bip39.Mnemonic, path bip32path.Path, prvKey ed25519.PrivateKey, pubKey ed25519.PublicKey, hrp iotago.NetworkPrefix, outputJSON bool) error {
+ addr := iotago.Ed25519AddressFromPubKey(pubKey)
+
+ type keys struct {
+ BIP39 string `json:"mnemonic,omitempty"`
+ BIP32 string `json:"path,omitempty"`
+ PrivateKey string `json:"privateKey,omitempty"`
+ PublicKey string `json:"publicKey"`
+ Ed25519Address string `json:"ed25519"`
+ Bech32Address string `json:"bech32"`
+ }
+
+ k := keys{
+ PublicKey: hex.EncodeToString(pubKey),
+ Ed25519Address: hex.EncodeToString(addr[:]),
+ Bech32Address: addr.Bech32(hrp),
+ }
+
+ if prvKey != nil {
+ k.PrivateKey = hex.EncodeToString(prvKey)
+ }
+
+ if mnemonic != nil {
+ k.BIP39 = mnemonic.String()
+ k.BIP32 = path.String()
+ }
+
+ if outputJSON {
+ return printJSON(k)
+ }
+
+ if len(k.BIP39) > 0 {
+ fmt.Println("Your seed BIP39 mnemonic: ", k.BIP39)
+ fmt.Println()
+ fmt.Println("Your BIP32 path: ", k.BIP32)
+ }
+
+ if k.PrivateKey != "" {
+ fmt.Println("Your ed25519 private key: ", k.PrivateKey)
+ }
+
+ fmt.Println("Your ed25519 public key: ", k.PublicKey)
+ fmt.Println("Your ed25519 address: ", k.Ed25519Address)
+ fmt.Println("Your bech32 address: ", k.Bech32Address)
+
+ return nil
+}
+
+func generateEd25519Key(args []string) error {
+ fs := configuration.NewUnsortedFlagSet("", flag.ContinueOnError)
+ hrpFlag := fs.String(FlagToolHRP, string(iotago.PrefixTestnet), "the HRP which should be used for the Bech32 address")
+ bip32Path := fs.String(FlagToolBIP32Path, "m/44'/4218'/0'/0'/0'", "the BIP32 path that should be used to derive keys from seed")
+ mnemonicFlag := fs.String(FlagToolMnemonic, "", "the BIP-39 mnemonic sentence that should be used to derive the seed from (optional)")
+ outputJSONFlag := fs.Bool(FlagToolOutputJSON, false, FlagToolDescriptionOutputJSON)
+
+ fs.Usage = func() {
+ _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", ToolEd25519Key)
+ fs.PrintDefaults()
+ println(fmt.Sprintf("\nexample: %s --%s %s",
+ ToolEd25519Key,
+ FlagToolHRP,
+ string(iotago.PrefixTestnet)))
+ }
+
+ if err := parseFlagSet(fs, args); err != nil {
+ return err
+ }
+
+ if len(*hrpFlag) == 0 {
+ return fmt.Errorf("'%s' not specified", FlagToolHRP)
+ }
+
+ if len(*bip32Path) == 0 {
+ return fmt.Errorf("'%s' not specified", FlagToolBIP32Path)
+ }
+
+ var mnemonicSentence bip39.Mnemonic
+ if len(*mnemonicFlag) == 0 {
+ // Generate random entropy by using ed25519 key generation and using the private key seed (32 bytes)
+ _, random, err := ed25519.GenerateKey(nil)
+ if err != nil {
+ return err
+ }
+ entropy := random.Seed()
+
+ mnemonicSentence, err = bip39.EntropyToMnemonic(entropy)
+ if err != nil {
+ return err
+ }
+ } else {
+ mnemonicSentence = bip39.ParseMnemonic(*mnemonicFlag)
+ if len(mnemonicSentence) != 24 {
+ return fmt.Errorf("'%s' contains an invalid sentence length. Mnemonic should be 24 words", FlagToolMnemonic)
+ }
+ }
+
+ path, err := bip32path.ParsePath(*bip32Path)
+ if err != nil {
+ return err
+ }
+
+ seed, err := bip39.MnemonicToSeed(mnemonicSentence, "")
+ if err != nil {
+ return err
+ }
+
+ key, err := slip10.DeriveKeyFromPath(seed, eddsa.Ed25519(), path)
+ if err != nil {
+ return err
+ }
+ pubKey, prvKey := key.Key.(eddsa.Seed).Ed25519Key()
+
+ return printEd25519Info(mnemonicSentence, path, ed25519.PrivateKey(prvKey), ed25519.PublicKey(pubKey), iotago.NetworkPrefix(*hrpFlag), *outputJSONFlag)
+}
+
+func generateEd25519Address(args []string) error {
+ fs := configuration.NewUnsortedFlagSet("", flag.ContinueOnError)
+ hrpFlag := fs.String(FlagToolHRP, string(iotago.PrefixTestnet), "the HRP which should be used for the Bech32 address")
+ publicKeyFlag := fs.String(FlagToolPublicKey, "", "an ed25519 public key")
+ outputJSONFlag := fs.Bool(FlagToolOutputJSON, false, FlagToolDescriptionOutputJSON)
+
+ fs.Usage = func() {
+ _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", ToolEd25519Addr)
+ fs.PrintDefaults()
+ println(fmt.Sprintf("\nexample: %s --%s %s --%s %s",
+ ToolEd25519Addr,
+ FlagToolHRP,
+ string(iotago.PrefixTestnet),
+ FlagToolPublicKey,
+ "[PUB_KEY]",
+ ))
+ }
+
+ if err := parseFlagSet(fs, args); err != nil {
+ return err
+ }
+
+ if len(*hrpFlag) == 0 {
+ return fmt.Errorf("'%s' not specified", FlagToolHRP)
+ }
+
+ if len(*publicKeyFlag) == 0 {
+ return fmt.Errorf("'%s' not specified", FlagToolPublicKey)
+ }
+
+ // parse pubkey
+ pubKey, err := crypto.ParseEd25519PublicKeyFromString(*publicKeyFlag)
+ if err != nil {
+ return fmt.Errorf("can't decode '%s': %w", FlagToolPublicKey, err)
+ }
+
+ return printEd25519Info(nil, nil, nil, pubKey, iotago.NetworkPrefix(*hrpFlag), *outputJSONFlag)
+}
diff --git a/pkg/toolset/jwt.go b/pkg/toolset/jwt.go
new file mode 100644
index 000000000..9585eee8c
--- /dev/null
+++ b/pkg/toolset/jwt.go
@@ -0,0 +1,109 @@
+package toolset
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/libp2p/go-libp2p/core/peer"
+ flag "github.com/spf13/pflag"
+
+ "github.com/iotaledger/hive.go/app/configuration"
+ hivep2p "github.com/iotaledger/hive.go/crypto/p2p"
+ "github.com/iotaledger/hive.go/crypto/pem"
+ "github.com/iotaledger/iota-core/components/p2p"
+ "github.com/iotaledger/iota-core/pkg/jwt"
+)
+
+func generateJWTApiToken(args []string) error {
+
+ fs := configuration.NewUnsortedFlagSet("", flag.ContinueOnError)
+ databasePathFlag := fs.String(FlagToolDatabasePath, DefaultValueP2PDatabasePath, "the path to the p2p database folder")
+ apiJWTSaltFlag := fs.String(FlagToolSalt, DefaultValueAPIJWTTokenSalt, "salt used inside the JWT tokens for the REST API")
+ outputJSONFlag := fs.Bool(FlagToolOutputJSON, false, FlagToolDescriptionOutputJSON)
+
+ fs.Usage = func() {
+ _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", ToolJWTApi)
+ fs.PrintDefaults()
+ println(fmt.Sprintf("\nexample: %s --%s %s --%s %s",
+ ToolJWTApi,
+ FlagToolDatabasePath,
+ DefaultValueP2PDatabasePath,
+ FlagToolSalt,
+ DefaultValueAPIJWTTokenSalt))
+ }
+
+ if err := parseFlagSet(fs, args); err != nil {
+ return err
+ }
+
+ if len(*databasePathFlag) == 0 {
+ return fmt.Errorf("'%s' not specified", FlagToolDatabasePath)
+ }
+ if len(*apiJWTSaltFlag) == 0 {
+ return fmt.Errorf("'%s' not specified", FlagToolSalt)
+ }
+
+ databasePath := *databasePathFlag
+ privKeyFilePath := filepath.Join(databasePath, p2p.IdentityPrivateKeyFileName)
+
+ salt := *apiJWTSaltFlag
+
+ _, err := os.Stat(privKeyFilePath)
+ switch {
+ case os.IsNotExist(err):
+ // private key does not exist
+ return fmt.Errorf("private key file (%s) does not exist", privKeyFilePath)
+
+ case err == nil || os.IsExist(err):
+ // private key file exists
+
+ default:
+ return fmt.Errorf("unable to check private key file (%s): %w", privKeyFilePath, err)
+ }
+
+ privKey, err := pem.ReadEd25519PrivateKeyFromPEMFile(privKeyFilePath)
+ if err != nil {
+ return fmt.Errorf("reading private key file for peer identity failed: %w", err)
+ }
+
+ libp2pPrivKey, err := hivep2p.Ed25519PrivateKeyToLibp2pPrivateKey(privKey)
+ if err != nil {
+ return fmt.Errorf("reading private key file for peer identity failed: %w", err)
+ }
+
+ peerID, err := peer.IDFromPublicKey(libp2pPrivKey.GetPublic())
+ if err != nil {
+ return fmt.Errorf("unable to get peer identity from public key: %w", err)
+ }
+
+ // API tokens do not expire.
+ jwtAuth, err := jwt.NewAuth(salt,
+ 0,
+ peerID.String(),
+ libp2pPrivKey,
+ )
+ if err != nil {
+ return fmt.Errorf("JWT auth initialization failed: %w", err)
+ }
+
+ jwtToken, err := jwtAuth.IssueJWT()
+ if err != nil {
+ return fmt.Errorf("issuing JWT token failed: %w", err)
+ }
+
+ if *outputJSONFlag {
+
+ result := struct {
+ JWT string `json:"jwt"`
+ }{
+ JWT: jwtToken,
+ }
+
+ return printJSON(result)
+ }
+
+ fmt.Println("Your API JWT token: ", jwtToken)
+
+ return nil
+}
diff --git a/pkg/toolset/node_info.go b/pkg/toolset/node_info.go
new file mode 100644
index 000000000..058a3e6d6
--- /dev/null
+++ b/pkg/toolset/node_info.go
@@ -0,0 +1,50 @@
+package toolset
+
+import (
+ "context"
+ "fmt"
+ "os"
+
+ flag "github.com/spf13/pflag"
+
+ "github.com/iotaledger/hive.go/app/configuration"
+ "github.com/iotaledger/iota.go/v4/nodeclient"
+)
+
+func nodeInfo(args []string) error {
+ fs := configuration.NewUnsortedFlagSet("", flag.ContinueOnError)
+ nodeURLFlag := fs.String(FlagToolNodeURL, "http://localhost:14265", "URL of the node (optional)")
+ outputJSONFlag := fs.Bool(FlagToolOutputJSON, false, FlagToolDescriptionOutputJSON)
+
+ fs.Usage = func() {
+ _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", ToolNodeInfo)
+ fs.PrintDefaults()
+ println(fmt.Sprintf("\nexample: %s --%s %s",
+ ToolNodeInfo,
+ FlagToolNodeURL,
+ "http://192.168.1.221:14265",
+ ))
+ }
+
+ if err := parseFlagSet(fs, args); err != nil {
+ return err
+ }
+
+ client, err := nodeclient.New(*nodeURLFlag)
+ if err != nil {
+ return err
+ }
+
+ info, err := client.Info(context.Background())
+ if err != nil {
+ return err
+ }
+
+ if *outputJSONFlag {
+ return printJSON(info)
+ }
+
+ fmt.Printf("Name: %s\nVersion: %s\nLatestAcceptedBlockSlot: %d\nLatestConfirmedBlockSlot: %d\nIsHealthy: %s\n", info.Name, info.Version, info.Status.LatestAcceptedBlockSlot, info.Status.LatestConfirmedBlockSlot, yesOrNo(info.Status.IsHealthy))
+
+ return nil
+}
diff --git a/pkg/toolset/p2p_identity_extract.go b/pkg/toolset/p2p_identity_extract.go
new file mode 100644
index 000000000..45a364d10
--- /dev/null
+++ b/pkg/toolset/p2p_identity_extract.go
@@ -0,0 +1,66 @@
+package toolset
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ flag "github.com/spf13/pflag"
+
+ "github.com/iotaledger/hive.go/app/configuration"
+ hivep2p "github.com/iotaledger/hive.go/crypto/p2p"
+ "github.com/iotaledger/hive.go/crypto/pem"
+ "github.com/iotaledger/iota-core/components/p2p"
+)
+
+func extractP2PIdentity(args []string) error {
+
+ fs := configuration.NewUnsortedFlagSet("", flag.ContinueOnError)
+ databasePathFlag := fs.String(FlagToolDatabasePath, DefaultValueP2PDatabasePath, "the path to the p2p database folder")
+ outputJSONFlag := fs.Bool(FlagToolOutputJSON, false, FlagToolDescriptionOutputJSON)
+
+ fs.Usage = func() {
+ _, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", ToolP2PExtractIdentity)
+ fs.PrintDefaults()
+ println(fmt.Sprintf("\nexample: %s --%s %s",
+ ToolP2PExtractIdentity,
+ FlagToolDatabasePath,
+ DefaultValueP2PDatabasePath))
+ }
+
+ if err := parseFlagSet(fs, args); err != nil {
+ return err
+ }
+
+ if len(*databasePathFlag) == 0 {
+ return fmt.Errorf("'%s' not specified", FlagToolDatabasePath)
+ }
+
+ databasePath := *databasePathFlag
+ privKeyFilePath := filepath.Join(databasePath, p2p.IdentityPrivateKeyFileName)
+
+ _, err := os.Stat(privKeyFilePath)
+ switch {
+ case os.IsNotExist(err):
+ // private key does not exist
+ return fmt.Errorf("private key file (%s) does not exist", privKeyFilePath)
+
+ case err == nil || os.IsExist(err):
+ // private key file exists
+
+ default:
+ return fmt.Errorf("unable to check private key file (%s): %w", privKeyFilePath, err)
+ }
+
+ privKey, err := pem.ReadEd25519PrivateKeyFromPEMFile(privKeyFilePath)
+ if err != nil {
+ return fmt.Errorf("reading private key file for peer identity failed: %w", err)
+ }
+
+ libp2pPrivKey, err := hivep2p.Ed25519PrivateKeyToLibp2pPrivateKey(privKey)
+ if err != nil {
+ return err
+ }
+
+ return printP2PIdentity(libp2pPrivKey, libp2pPrivKey.GetPublic(), *outputJSONFlag)
+}
diff --git a/pkg/toolset/p2p_identity_gen.go b/pkg/toolset/p2p_identity_gen.go
new file mode 100644
index 000000000..b376fd5e8
--- /dev/null
+++ b/pkg/toolset/p2p_identity_gen.go
@@ -0,0 +1,136 @@
+package toolset
+
+import (
+ "crypto/ed25519"
+ "encoding/hex"
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/libp2p/go-libp2p/core/crypto"
+ "github.com/libp2p/go-libp2p/core/peer"
+ "github.com/mr-tron/base58"
+ flag "github.com/spf13/pflag"
+
+ "github.com/iotaledger/hive.go/app/configuration"
+ hivecrypto "github.com/iotaledger/hive.go/crypto"
+ "github.com/iotaledger/hive.go/crypto/pem"
+ "github.com/iotaledger/iota-core/components/p2p"
+ "github.com/iotaledger/iota.go/v4/hexutil"
+)
+
+func generateP2PIdentity(args []string) error {
+
+ fs := configuration.NewUnsortedFlagSet("", flag.ContinueOnError)
+ databasePathFlag := fs.String(FlagToolOutputPath, DefaultValueP2PDatabasePath, "the path to the output folder")
+ privateKeyFlag := fs.String(FlagToolPrivateKey, "", "the p2p private key")
+ outputJSONFlag := fs.Bool(FlagToolOutputJSON, false, FlagToolDescriptionOutputJSON)
+
+ fs.Usage = func() {
+ fmt.Fprintf(os.Stderr, "Usage of %s:\n", ToolP2PIdentityGen)
+ fs.PrintDefaults()
+ println(fmt.Sprintf("\nexample: %s --%s %s --%s %s",
+ ToolP2PIdentityGen,
+ FlagToolDatabasePath,
+ DefaultValueP2PDatabasePath,
+ FlagToolPrivateKey,
+ "[PRIVATE_KEY]",
+ ))
+ }
+
+ if err := parseFlagSet(fs, args); err != nil {
+ return err
+ }
+
+ if len(*databasePathFlag) == 0 {
+ return fmt.Errorf("'%s' not specified", FlagToolDatabasePath)
+ }
+
+ databasePath := *databasePathFlag
+ privKeyFilePath := filepath.Join(databasePath, p2p.IdentityPrivateKeyFileName)
+
+ if err := os.MkdirAll(databasePath, 0700); err != nil {
+ return fmt.Errorf("could not create peer store database dir '%s': %w", databasePath, err)
+ }
+
+ _, err := os.Stat(privKeyFilePath)
+ switch {
+ case err == nil || os.IsExist(err):
+ // private key file already exists
+ return fmt.Errorf("private key file (%s) already exists", privKeyFilePath)
+
+ case os.IsNotExist(err):
+ // private key file does not exist, create a new one
+
+ default:
+ return fmt.Errorf("unable to check private key file (%s): %w", privKeyFilePath, err)
+ }
+
+ var privKey ed25519.PrivateKey
+ if privateKeyFlag != nil && len(*privateKeyFlag) > 0 {
+ privKey, err = hivecrypto.ParseEd25519PrivateKeyFromString(*privateKeyFlag)
+ if err != nil {
+ return fmt.Errorf("invalid private key given '%s': %w", *privateKeyFlag, err)
+ }
+ } else {
+ // create identity
+ _, privKey, err = ed25519.GenerateKey(nil)
+ if err != nil {
+ return fmt.Errorf("unable to generate Ed25519 private key for peer identity: %w", err)
+ }
+ }
+
+ libp2pPrivKey, libp2pPubKey, err := crypto.KeyPairFromStdKey(&privKey)
+ if err != nil {
+ return fmt.Errorf("unable to convert given private key '%s': %w", hexutil.EncodeHex(privKey), err)
+ }
+
+ if err := pem.WriteEd25519PrivateKeyToPEMFile(privKeyFilePath, privKey); err != nil {
+ return fmt.Errorf("writing private key file for peer identity failed: %w", err)
+ }
+
+ return printP2PIdentity(libp2pPrivKey, libp2pPubKey, *outputJSONFlag)
+}
+
+func printP2PIdentity(libp2pPrivKey crypto.PrivKey, libp2pPubKey crypto.PubKey, outputJSON bool) error {
+
+ type P2PIdentity struct {
+ PrivateKey string `json:"privateKey"`
+ PublicKey string `json:"publicKey"`
+ PublicKeyBase58 string `json:"publicKeyBase58"`
+ PeerID string `json:"peerId"`
+ }
+
+ privKeyBytes, err := libp2pPrivKey.Raw()
+ if err != nil {
+ return fmt.Errorf("unable to get raw private key bytes: %w", err)
+ }
+
+ pubKeyBytes, err := libp2pPubKey.Raw()
+ if err != nil {
+ return fmt.Errorf("unable to get raw public key bytes: %w", err)
+ }
+
+ peerID, err := peer.IDFromPublicKey(libp2pPubKey)
+ if err != nil {
+ return fmt.Errorf("unable to get peer identity from public key: %w", err)
+ }
+
+ identity := P2PIdentity{
+ PrivateKey: hex.EncodeToString(privKeyBytes),
+ PublicKey: hex.EncodeToString(pubKeyBytes),
+ PublicKeyBase58: base58.Encode(pubKeyBytes),
+ PeerID: peerID.String(),
+ }
+
+ if outputJSON {
+ return printJSON(identity)
+ }
+
+ fmt.Println("Your p2p private key (hex): ", identity.PrivateKey)
+ fmt.Println("Your p2p public key (hex): ", identity.PublicKey)
+ fmt.Println("Your p2p public key (base58): ", identity.PublicKeyBase58)
+ fmt.Println("Your p2p PeerID: ", identity.PeerID)
+
+ return nil
+}
diff --git a/pkg/toolset/toolset.go b/pkg/toolset/toolset.go
new file mode 100644
index 000000000..cad18b14e
--- /dev/null
+++ b/pkg/toolset/toolset.go
@@ -0,0 +1,157 @@
+package toolset
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "strings"
+
+ flag "github.com/spf13/pflag"
+
+ "github.com/iotaledger/hive.go/app/configuration"
+ "github.com/iotaledger/hive.go/ierrors"
+)
+
+const (
+ FlagToolDatabasePath = "databasePath"
+
+ FlagToolOutputPath = "outputPath"
+
+ FlagToolPrivateKey = "privateKey"
+ FlagToolPublicKey = "publicKey"
+
+ FlagToolHRP = "hrp"
+ FlagToolBIP32Path = "bip32Path"
+ FlagToolMnemonic = "mnemonic"
+ FlagToolSalt = "salt"
+
+ FlagToolNodeURL = "nodeURL"
+
+ FlagToolOutputJSON = "json"
+ FlagToolDescriptionOutputJSON = "format output as JSON"
+)
+
+const (
+ ToolP2PIdentityGen = "p2pidentity-gen"
+ ToolP2PExtractIdentity = "p2pidentity-extract"
+ ToolEd25519Key = "ed25519-key"
+ ToolEd25519Addr = "ed25519-addr"
+ ToolJWTApi = "jwt-api"
+ ToolNodeInfo = "node-info"
+)
+
+const (
+ DefaultValueAPIJWTTokenSalt = "IOTA"
+ DefaultValueP2PDatabasePath = "testnet/p2pstore"
+)
+
+// ShouldHandleTools checks if tools were requested.
+func ShouldHandleTools() bool {
+ args := os.Args[1:]
+
+ for _, arg := range args {
+ if strings.ToLower(arg) == "tool" || strings.ToLower(arg) == "tools" {
+ return true
+ }
+ }
+
+ return false
+}
+
+// HandleTools handles available tools.
+func HandleTools() {
+
+ args := os.Args[1:]
+ if len(args) == 1 {
+ listTools()
+ os.Exit(1)
+ }
+
+ tools := map[string]func([]string) error{
+ ToolP2PIdentityGen: generateP2PIdentity,
+ ToolP2PExtractIdentity: extractP2PIdentity,
+ ToolEd25519Key: generateEd25519Key,
+ ToolEd25519Addr: generateEd25519Address,
+ ToolJWTApi: generateJWTApiToken,
+ ToolNodeInfo: nodeInfo,
+ }
+
+ tool, exists := tools[strings.ToLower(args[1])]
+ if !exists {
+ fmt.Print("tool not found.\n\n")
+ listTools()
+ os.Exit(1)
+ }
+
+ if err := tool(args[2:]); err != nil {
+ if ierrors.Is(err, flag.ErrHelp) {
+ // help text was requested
+ os.Exit(0)
+ }
+
+ fmt.Printf("\nerror: %s\n", err)
+ os.Exit(1)
+ }
+
+ os.Exit(0)
+}
+
+func listTools() {
+ fmt.Printf("%-20s generates a p2p identity private key file\n", fmt.Sprintf("%s:", ToolP2PIdentityGen))
+ fmt.Printf("%-20s extracts the p2p identity from the private key file\n", fmt.Sprintf("%s:", ToolP2PExtractIdentity))
+ fmt.Printf("%-20s generates an ed25519 key pair\n", fmt.Sprintf("%s:", ToolEd25519Key))
+ fmt.Printf("%-20s generates an ed25519 address from a public key\n", fmt.Sprintf("%s:", ToolEd25519Addr))
+ fmt.Printf("%-20s generates a JWT token for REST-API access\n", fmt.Sprintf("%s:", ToolJWTApi))
+ fmt.Printf("%-20s queries the info endpoint of a node\n", fmt.Sprintf("%s:", ToolNodeInfo))
+}
+
+func yesOrNo(value bool) string {
+ if value {
+ return "YES"
+ }
+
+ return "NO"
+}
+
+func parseFlagSet(fs *flag.FlagSet, args []string) error {
+
+ if err := fs.Parse(args); err != nil {
+ return err
+ }
+
+ // Check if all parameters were parsed
+ if fs.NArg() != 0 {
+ return ierrors.New("too much arguments")
+ }
+
+ return nil
+}
+
+func printJSON(obj interface{}) error {
+ output, err := json.MarshalIndent(obj, "", " ")
+ if err != nil {
+ return err
+ }
+
+ fmt.Println(string(output))
+
+ return nil
+}
+
+//nolint:unused // we will need it at a later point in time
+func loadConfigFile(filePath string, parameters map[string]any) error {
+ config := configuration.New()
+ flagset := configuration.NewUnsortedFlagSet("", flag.ContinueOnError)
+
+ for namespace, pointerToStruct := range parameters {
+ config.BindParameters(flagset, namespace, pointerToStruct)
+ }
+
+ if err := config.LoadFile(filePath); err != nil {
+ return fmt.Errorf("loading config file failed: %w", err)
+ }
+
+ config.UpdateBoundParameters()
+
+ return nil
+}
diff --git a/tools/docker-network/docker-compose.yml b/tools/docker-network/docker-compose.yml
index bd63e2eab..22d21b2dc 100644
--- a/tools/docker-network/docker-compose.yml
+++ b/tools/docker-network/docker-compose.yml
@@ -20,7 +20,7 @@ services:
networks:
- iota-core
ports:
- - "8080:8080/tcp" # REST-API
+ - "8080:14265/tcp" # REST-API
- "8081:8081/tcp" # Dashboard
- "6081:6061/tcp" # pprof
- "9089:9029/tcp" # INX
@@ -49,7 +49,7 @@ services:
networks:
- iota-core
ports:
- - "8070:8080/tcp" # REST-API
+ - "8070:14265/tcp" # REST-API
- "8071:8081/tcp" # Dashboard
- "6071:6061/tcp" # pprof
- "9029:9029/tcp" # INX
@@ -77,7 +77,7 @@ services:
networks:
- iota-core
ports:
- - "8090:8080/tcp" # REST-API
+ - "8090:14265/tcp" # REST-API
- "8091:8081/tcp" # Dashboard
- "6091:6061/tcp" # pprof
- "9099:9029/tcp" # INX
@@ -105,7 +105,7 @@ services:
networks:
- iota-core
ports:
- - "8040:8080/tcp" # REST-API
+ - "8040:14265/tcp" # REST-API
- "8041:8081/tcp" # Dashboard
- "6041:6061/tcp" # pprof
- "9049:9029/tcp" # INX
@@ -130,7 +130,7 @@ services:
networks:
- iota-core
ports:
- - "8030:8080/tcp" # REST-API
+ - "8030:14265/tcp" # REST-API
- "8031:8081/tcp" # Dashboard
- "6031:6061/tcp" # pprof
- "9039:9029/tcp" # INX
@@ -193,7 +193,7 @@ services:
restart: unless-stopped
depends_on:
node-1-validator:
- condition: service_started
+ condition: service_healthy
ulimits:
nofile:
soft: 16384
@@ -210,7 +210,7 @@ services:
restart: unless-stopped
depends_on:
node-1-validator:
- condition: service_started
+ condition: service_healthy
inx-indexer:
condition: service_started
networks:
@@ -229,7 +229,7 @@ services:
restart: unless-stopped
depends_on:
node-1-validator:
- condition: service_started
+ condition: service_healthy
inx-indexer:
condition: service_started
inx-blockissuer:
diff --git a/tools/gendoc/go.mod b/tools/gendoc/go.mod
index f024b6b9d..5fdd31230 100644
--- a/tools/gendoc/go.mod
+++ b/tools/gendoc/go.mod
@@ -156,6 +156,7 @@ require (
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect
+ github.com/wollac/iota-crypto-demo v0.0.0-20221117162917-b10619eccb98 // indirect
github.com/zyedidia/generic v1.2.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/otel v1.19.0 // indirect
diff --git a/tools/gendoc/go.sum b/tools/gendoc/go.sum
index 2a2b0dd6e..79c766b0b 100644
--- a/tools/gendoc/go.sum
+++ b/tools/gendoc/go.sum
@@ -668,6 +668,8 @@ github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSD
github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw=
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k=
github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h1:8UvriyWtv5Q5EOgjHaSseUEdkQfvwFv1I/In/O2M9gc=
+github.com/wollac/iota-crypto-demo v0.0.0-20221117162917-b10619eccb98 h1:i7k63xHOX2ntuHrhHewfKro67c834jug2DIk599fqAA=
+github.com/wollac/iota-crypto-demo v0.0.0-20221117162917-b10619eccb98/go.mod h1:Knu2XMRWe8SkwTlHc/+ghP+O9DEaZRQQEyTjvLJ5Cck=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=