diff --git a/client/cmd/link.go b/client/cmd/link.go
new file mode 100644
index 0000000..03ada27
--- /dev/null
+++ b/client/cmd/link.go
@@ -0,0 +1,142 @@
+package cmd
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+var symlinkPath string
+
+var linkCmd = &cobra.Command{
+ Use: "link",
+ Short: "Create a symlink to qclient in PATH",
+ Long: fmt.Sprintf(`Create a symlink to the qclient binary in a suitable directory in your PATH.
+This allows you to run qclient from anywhere without specifying the full path.
+
+By default it will create the symlink in the current directory /usr/local/bin.
+You can also specify a custom directory using the --path flag.
+
+Example: qclient link --path /usr/local/bin`),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ // Get the path to the current executable
+ execPath, err := os.Executable()
+ if err != nil {
+ return fmt.Errorf("failed to get executable path: %w", err)
+ }
+
+ // Determine the target directory and path for the symlink
+ targetDir, targetPath, err := determineSymlinkLocation()
+ if err != nil {
+ return err
+ }
+
+ // If operation was cancelled by the user
+ if targetDir == "" && targetPath == "" {
+ return nil
+ }
+
+ // Create the symlink (handles existing symlinks)
+ if err := utils.CreateSymlink(execPath, targetPath); err != nil {
+ return err
+ }
+
+ // Save the symlink path to ClientConfig
+ config := utils.ClientConfig{
+ SymlinkPath: targetPath,
+ }
+
+ // Use the UpdateClientConfig function to save the config
+ if err := utils.UpdateClientConfig(&config); err != nil {
+ fmt.Printf("Warning: Failed to save symlink path to config: %v\n", err)
+ } else {
+ fmt.Printf("Saved symlink path %s to client configuration\n", targetPath)
+ }
+
+ fmt.Printf("Symlink created at %s\n", targetPath)
+ return nil
+ },
+}
+
+// determineSymlinkLocation finds the appropriate location for the symlink
+// Returns the target directory, the full path for the symlink, and any error
+func determineSymlinkLocation() (string, string, error) {
+ // If user provided a custom path
+ if symlinkPath != "" {
+ return validateUserProvidedPath(symlinkPath)
+ }
+
+ // Otherwise, find a suitable directory in PATH
+ return utils.NodeDefaultSymlinkDir, utils.DefaultQClientSymlinkPath, nil
+}
+
+// isDirectoryInPath checks if a directory is in the PATH environment variable
+func isDirectoryInPath(dir string) bool {
+ pathEnv := os.Getenv("PATH")
+ pathDirs := strings.Split(pathEnv, string(os.PathListSeparator))
+
+ // Normalize paths for comparison
+ absDir, err := filepath.Abs(dir)
+ if err != nil {
+ return false
+ }
+
+ for _, pathDir := range pathDirs {
+ absPathDir, err := filepath.Abs(pathDir)
+ if err != nil {
+ continue
+ }
+
+ if absDir == absPathDir {
+ return true
+ }
+ }
+
+ return false
+}
+
+// validateUserProvidedPath checks if the provided path is a valid directory
+func validateUserProvidedPath(path string) (string, string, error) {
+ // Check if the provided path is a directory
+ info, err := os.Stat(path)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return "", "", fmt.Errorf("directory does not exist: %s", path)
+ }
+ return "", "", fmt.Errorf("error checking directory: %w", err)
+ }
+
+ if !info.IsDir() {
+ return "", "", fmt.Errorf("the specified path is not a directory: %s", path)
+ }
+
+ // Check if the directory is in PATH
+ if !isDirectoryInPath(path) {
+ // Ask user for confirmation to proceed with a directory not in PATH
+ fmt.Printf("Warning: The directory '%s' is not in your PATH environment variable.\n", path)
+ fmt.Println("The symlink will be created, but you may not be able to run 'qclient' from anywhere.")
+ fmt.Print("Do you want to continue? [y/N]: ")
+
+ var response string
+ fmt.Scanln(&response)
+ if strings.ToLower(response) != "y" {
+ fmt.Println("Operation cancelled.")
+ return "", "", nil
+ }
+ }
+
+ // Use the provided directory
+ targetDir := path
+ targetPath := filepath.Join(targetDir, "qclient")
+
+ return targetDir, targetPath, nil
+}
+
+func init() {
+ rootCmd.AddCommand(linkCmd)
+ linkCmd.Flags().StringVar(&symlinkPath, "path", "", "Specify a custom directory for the symlink")
+}
diff --git a/client/cmd/node/info.go b/client/cmd/node/info.go
new file mode 100644
index 0000000..c905c6e
--- /dev/null
+++ b/client/cmd/node/info.go
@@ -0,0 +1,46 @@
+package node
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+// infoCmd represents the info command
+var infoCmd = &cobra.Command{
+ Use: "info",
+ Short: "Get information about the Quilibrium node",
+ Long: `Get information about the Quilibrium node.
+Available subcommands:
+ latest-version Get the latest available version of Quilibrium node
+
+Examples:
+ # Get the latest version
+ qclient node info latest-version`,
+}
+
+// latestVersionCmd represents the latest-version command
+var latestVersionCmd = &cobra.Command{
+ Use: "latest-version",
+ Short: "Get the latest available version of Quilibrium node",
+ Long: `Get the latest available version of Quilibrium node by querying the releases API.
+This command fetches the version information from https://releases.quilibrium.com/release.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ version, err := utils.GetLatestVersion(utils.ReleaseTypeNode)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+ fmt.Fprintf(os.Stdout, "Latest available version: %s\n", version)
+ },
+}
+
+func init() {
+ // Add the latest-version subcommand to the info command
+ infoCmd.AddCommand(latestVersionCmd)
+
+ // Add the info command to the node command
+ NodeCmd.AddCommand(infoCmd)
+}
diff --git a/client/cmd/node/install.go b/client/cmd/node/install.go
new file mode 100644
index 0000000..a206a08
--- /dev/null
+++ b/client/cmd/node/install.go
@@ -0,0 +1,115 @@
+package node
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+// installCmd represents the command to install the Quilibrium node
+var installCmd = &cobra.Command{
+ Use: "install [version]",
+ Short: "Install Quilibrium node",
+ Long: `Install Quilibrium node binary.
+If no version is specified, the latest version will be installed.
+
+
+Examples:
+ # Install the latest version
+ qclient node install
+
+ # Install a specific version
+ qclient node install 2.1.0
+
+ # Install without creating a dedicated user
+ qclient node install --no-create-user`,
+ Args: cobra.RangeArgs(0, 1),
+ Run: func(cmd *cobra.Command, args []string) {
+ // Get system information and validate
+ osType, arch, err := utils.GetSystemInfo()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+
+ // Determine version to install
+ version := determineVersion(args)
+
+ fmt.Fprintf(os.Stdout, "Installing Quilibrium node for %s-%s, version: %s\n", osType, arch, version)
+
+ // Check if we need to create a dedicated user (opt-out)
+
+ if err := createNodeUser(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error creating user: %v\n", err)
+ fmt.Fprintf(os.Stdout, "Continuing with installation as current user...\n")
+ }
+
+ // Install the node
+ installNode(version)
+ },
+}
+
+func init() {
+ // Add the install command to the node command
+ NodeCmd.AddCommand(installCmd)
+}
+
+// installNode installs the Quilibrium node
+func installNode(version string) {
+ // Create installation directory
+ if err := utils.ValidateAndCreateDir(installPath); err != nil {
+ fmt.Fprintf(os.Stderr, "Error creating installation directory: %v\n", err)
+ return
+ }
+
+ // Create data directory
+ if err := utils.ValidateAndCreateDir(dataPath); err != nil {
+ fmt.Fprintf(os.Stderr, "Error creating data directory: %v\n", err)
+ return
+ }
+
+ // Download and install the node
+ if version == "latest" {
+ latestVersion, err := utils.GetLatestVersion(utils.ReleaseTypeNode)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error getting latest version: %v\n", err)
+ return
+ }
+
+ version = latestVersion
+ fmt.Fprintf(os.Stdout, "Installing latest version: %s\n", version)
+ }
+
+ if err := installByVersion(version); err != nil {
+ fmt.Fprintf(os.Stderr, "Error installing specific version: %v\n", err)
+ return
+ }
+
+ // Finish installation
+ nodeBinaryPath := filepath.Join(installPath, version, "node")
+ finishInstallation(nodeBinaryPath, version)
+}
+
+// installByVersion installs a specific version of the Quilibrium node
+func installByVersion(version string) error {
+ // Create version-specific directory
+ versionDir := filepath.Join(installPath, version)
+ if err := utils.ValidateAndCreateDir(versionDir); err != nil {
+ return fmt.Errorf("failed to create version directory: %w", err)
+ }
+
+ // Download the release
+ if err := utils.DownloadRelease(utils.ReleaseTypeNode, version); err != nil {
+ return fmt.Errorf("failed to download release: %w", err)
+ }
+
+ // Download signature files
+ if err := utils.DownloadReleaseSignatures(utils.ReleaseTypeNode, version); err != nil {
+ return fmt.Errorf("failed to download signature files: %w", err)
+ }
+
+ return nil
+}
diff --git a/client/cmd/node/node.go b/client/cmd/node/node.go
new file mode 100644
index 0000000..7b931d6
--- /dev/null
+++ b/client/cmd/node/node.go
@@ -0,0 +1,59 @@
+package node
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+var (
+ // Base URL for Quilibrium releases
+ baseReleaseURL = "https://releases.quilibrium.com"
+
+ // Default symlink path for the node binary
+ defaultSymlinkPath = "/usr/local/bin/quilibrium-node"
+
+ // Default installation directory base paths in order of preference
+ installPath = "/opt/quilibrium"
+
+ // Default data directory paths in order of preference
+ dataPath = "/var/lib/quilibrium"
+
+ logPath = "/var/log/quilibrium"
+
+ // Default user to run the node
+ nodeUser = "quilibrium"
+
+ serviceName = "quilibrium-node"
+
+ // Default config file name
+ defaultConfigFileName = "node.yaml"
+
+ osType string
+ arch string
+)
+
+// NodeCmd represents the node command
+var NodeCmd = &cobra.Command{
+ Use: "node",
+ Short: "Quilibrium node commands",
+ Long: `Run Quilibrium node commands.`,
+}
+
+func init() {
+ // Add subcommands
+ NodeCmd.AddCommand(installCmd)
+ NodeCmd.AddCommand(updateNodeCmd)
+ NodeCmd.AddCommand(nodeServiceCmd)
+
+ localOsType, localArch, err := utils.GetSystemInfo()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+
+ osType = localOsType
+ arch = localArch
+}
diff --git a/client/cmd/node/service.go b/client/cmd/node/service.go
new file mode 100644
index 0000000..d689d15
--- /dev/null
+++ b/client/cmd/node/service.go
@@ -0,0 +1,284 @@
+package node
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+// nodeServiceCmd represents the command to manage the Quilibrium node service
+var nodeServiceCmd = &cobra.Command{
+ Use: "service [command]",
+ Short: "Manage the Quilibrium node service",
+ Long: `Manage the Quilibrium node service.
+Available commands:
+ start Start the node service
+ stop Stop the node service
+ restart Restart the node service
+ status Check the status of the node service
+ enable Enable the node service to start on boot
+ disable Disable the node service from starting on boot
+ install Install the service for the current OS
+
+Examples:
+ # Start the node service
+ qclient node service start
+
+ # Check service status
+ qclient node service status
+
+ # Enable service to start on boot
+ qclient node service enable`,
+ Args: cobra.ExactArgs(1),
+ Run: func(cmd *cobra.Command, args []string) {
+ command := args[0]
+ switch command {
+ case "start":
+ startService()
+ case "stop":
+ stopService()
+ case "restart":
+ restartService()
+ case "status":
+ checkServiceStatus()
+ case "enable":
+ enableService()
+ case "disable":
+ disableService()
+ case "reload":
+ reloadService()
+ case "install":
+ installService()
+ default:
+ fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
+ fmt.Fprintf(os.Stderr, "Available commands: start, stop, restart, status, enable, disable, reload, install\n")
+ os.Exit(1)
+ }
+ },
+}
+
+// installService installs the appropriate service configuration for the current OS
+func installService() {
+ if err := utils.CheckAndRequestSudo("Installing service requires root privileges"); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+
+ fmt.Fprintf(os.Stdout, "Installing Quilibrium node service for %s...\n", osType)
+
+ if osType == "darwin" {
+ installMacOSService()
+ } else if osType == "linux" {
+ if err := createSystemdServiceFile(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error creating systemd service file: %v\n", err)
+ return
+ }
+ } else {
+ fmt.Fprintf(os.Stderr, "Error: Unsupported operating system: %s\n", osType)
+ return
+ }
+
+ fmt.Fprintf(os.Stdout, "Quilibrium node service installed successfully\n")
+}
+
+// startService starts the Quilibrium node service
+func startService() {
+ if err := utils.CheckAndRequestSudo("Starting service requires root privileges"); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+
+ if osType == "darwin" {
+ // MacOS launchd command
+ cmd := exec.Command("sudo", "launchctl", "start", fmt.Sprintf("com.quilibrium.%s", serviceName))
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error starting service: %v\n", err)
+ return
+ }
+ } else {
+ // Linux systemd command
+ cmd := exec.Command("sudo", "systemctl", "start", serviceName)
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error starting service: %v\n", err)
+ return
+ }
+ }
+
+ fmt.Fprintf(os.Stdout, "Started Quilibrium node service\n")
+}
+
+// stopService stops the Quilibrium node service
+func stopService() {
+ if err := utils.CheckAndRequestSudo("Stopping service requires root privileges"); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+
+ if osType == "darwin" {
+ // MacOS launchd command
+ cmd := exec.Command("sudo", "launchctl", "stop", fmt.Sprintf("com.quilibrium.%s", serviceName))
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error stopping service: %v\n", err)
+ return
+ }
+ } else {
+ // Linux systemd command
+ cmd := exec.Command("sudo", "systemctl", "stop", serviceName)
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error stopping service: %v\n", err)
+ return
+ }
+ }
+
+ fmt.Fprintf(os.Stdout, "Stopped Quilibrium node service\n")
+}
+
+// restartService restarts the Quilibrium node service
+func restartService() {
+ if err := utils.CheckAndRequestSudo("Restarting service requires root privileges"); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+
+ if osType == "darwin" {
+ // MacOS launchd command - stop then start
+ stopCmd := exec.Command("sudo", "launchctl", "stop", fmt.Sprintf("com.quilibrium.%s", serviceName))
+ if err := stopCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error stopping service: %v\n", err)
+ return
+ }
+
+ startCmd := exec.Command("sudo", "launchctl", "start", fmt.Sprintf("com.quilibrium.%s", serviceName))
+ if err := startCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error starting service: %v\n", err)
+ return
+ }
+ } else {
+ // Linux systemd command
+ cmd := exec.Command("sudo", "systemctl", "restart", serviceName)
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error restarting service: %v\n", err)
+ return
+ }
+ }
+
+ fmt.Fprintf(os.Stdout, "Restarted Quilibrium node service\n")
+}
+
+// reloadService reloads the Quilibrium node service
+func reloadService() {
+ if err := utils.CheckAndRequestSudo("Reloading service requires root privileges"); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+
+ if osType == "darwin" {
+ // MacOS launchd command - unload then load
+ plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", serviceName)
+ unloadCmd := exec.Command("sudo", "launchctl", "unload", plistPath)
+ if err := unloadCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error unloading service: %v\n", err)
+ return
+ }
+
+ loadCmd := exec.Command("sudo", "launchctl", "load", plistPath)
+ if err := loadCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error loading service: %v\n", err)
+ return
+ }
+
+ fmt.Fprintf(os.Stdout, "Reloaded launchd service\n")
+ } else {
+ // Linux systemd command
+ cmd := exec.Command("sudo", "systemctl", "daemon-reload")
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error reloading service: %v\n", err)
+ return
+ }
+
+ fmt.Fprintf(os.Stdout, "Reloaded systemd service\n")
+ }
+}
+
+// checkServiceStatus checks the status of the Quilibrium node service
+func checkServiceStatus() {
+ if err := utils.CheckAndRequestSudo("Checking service status requires root privileges"); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+
+ if osType == "darwin" {
+ // MacOS launchd command
+ cmd := exec.Command("sudo", "launchctl", "list", fmt.Sprintf("com.quilibrium.%s", serviceName))
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error checking service status: %v\n", err)
+ }
+ } else {
+ // Linux systemd command
+ cmd := exec.Command("sudo", "systemctl", "status", serviceName)
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error checking service status: %v\n", err)
+ }
+ }
+}
+
+// enableService enables the Quilibrium node service to start on boot
+func enableService() {
+ if err := utils.CheckAndRequestSudo("Enabling service requires root privileges"); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+
+ if osType == "darwin" {
+ // MacOS launchd command - load with -w flag to enable at boot
+ plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", serviceName)
+ cmd := exec.Command("sudo", "launchctl", "load", "-w", plistPath)
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error enabling service: %v\n", err)
+ return
+ }
+ } else {
+ // Linux systemd command
+ cmd := exec.Command("sudo", "systemctl", "enable", serviceName)
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error enabling service: %v\n", err)
+ return
+ }
+ }
+
+ fmt.Fprintf(os.Stdout, "Enabled Quilibrium node service to start on boot\n")
+}
+
+// disableService disables the Quilibrium node service from starting on boot
+func disableService() {
+ if err := utils.CheckAndRequestSudo("Disabling service requires root privileges"); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+
+ if osType == "darwin" {
+ // MacOS launchd command - unload with -w flag to disable at boot
+ plistPath := fmt.Sprintf("/Library/LaunchDaemons/com.quilibrium.%s.plist", serviceName)
+ cmd := exec.Command("sudo", "launchctl", "unload", "-w", plistPath)
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error disabling service: %v\n", err)
+ return
+ }
+ } else {
+ // Linux systemd command
+ cmd := exec.Command("sudo", "systemctl", "disable", serviceName)
+ if err := cmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error disabling service: %v\n", err)
+ return
+ }
+ }
+
+ fmt.Fprintf(os.Stdout, "Disabled Quilibrium node service from starting on boot\n")
+}
diff --git a/client/cmd/node/shared.go b/client/cmd/node/shared.go
new file mode 100644
index 0000000..568e032
--- /dev/null
+++ b/client/cmd/node/shared.go
@@ -0,0 +1,411 @@
+package node
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "text/template"
+
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+// determineVersion gets the version to install from args or defaults to "latest"
+func determineVersion(args []string) string {
+ if len(args) > 0 {
+ return args[0]
+ }
+ return "latest"
+}
+
+// confirmPaths asks the user to confirm the installation and data paths
+func confirmPaths(installPath, dataPath string) bool {
+ fmt.Print("Do you want to continue with these paths? [Y/n]: ")
+ reader := bufio.NewReader(os.Stdin)
+ response, _ := reader.ReadString('\n')
+ response = strings.TrimSpace(strings.ToLower(response))
+
+ return response == "" || response == "y" || response == "yes"
+}
+
+// createNodeUser creates a dedicated user for running the node
+func createNodeUser() error {
+ fmt.Fprintf(os.Stdout, "Creating dedicated user '%s' for running the node...\n", nodeUser)
+
+ // Check for sudo privileges
+ if err := utils.CheckAndRequestSudo("Creating system user requires root privileges"); err != nil {
+ return fmt.Errorf("failed to get sudo privileges: %w", err)
+ }
+
+ var cmd *exec.Cmd
+
+ if osType == "linux" {
+ // Check if user already exists
+ checkCmd := exec.Command("id", nodeUser)
+ if checkCmd.Run() == nil {
+ fmt.Fprintf(os.Stdout, "User '%s' already exists\n", nodeUser)
+ return nil
+ }
+
+ // Create user on Linux
+ cmd = exec.Command("useradd", "-r", "-s", "/bin/false", "-m", "-c", "Quilibrium Node User", nodeUser)
+ } else if osType == "darwin" {
+ // Check if user already exists on macOS
+ checkCmd := exec.Command("dscl", ".", "-read", "/Users/"+nodeUser)
+ if checkCmd.Run() == nil {
+ fmt.Fprintf(os.Stdout, "User '%s' already exists\n", nodeUser)
+ return nil
+ }
+
+ // Create user on macOS
+ // Get next available user ID
+ uidCmd := exec.Command("dscl", ".", "-list", "/Users", "UniqueID")
+ uidOutput, err := uidCmd.Output()
+ if err != nil {
+ return fmt.Errorf("failed to get user IDs: %v", err)
+ }
+
+ // Find the highest UID and add 1
+ var maxUID int = 500 // Start with a reasonable system UID
+ for _, line := range strings.Split(string(uidOutput), "\n") {
+ fields := strings.Fields(line)
+ if len(fields) >= 2 {
+ var uid int
+ fmt.Sscanf(fields[len(fields)-1], "%d", &uid)
+ if uid > maxUID && uid < 65000 { // Avoid system UIDs
+ maxUID = uid
+ }
+ }
+ }
+ nextUID := maxUID + 1
+
+ // Create the user
+ cmd = exec.Command("dscl", ".", "-create", "/Users/"+nodeUser)
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("Failed to create user: %v", err)
+ }
+
+ // Set the user's properties
+ commands := [][]string{
+ {"-create", "/Users/" + nodeUser, "UniqueID", fmt.Sprintf("%d", nextUID)},
+ {"-create", "/Users/" + nodeUser, "PrimaryGroupID", "20"}, // staff group
+ {"-create", "/Users/" + nodeUser, "UserShell", "/bin/false"},
+ {"-create", "/Users/" + nodeUser, "NFSHomeDirectory", "/var/empty"},
+ {"-create", "/Users/" + nodeUser, "RealName", "Quilibrium Node User"},
+ }
+
+ for _, args := range commands {
+ cmd = exec.Command("dscl", append([]string{"."}, args...)...)
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("failed to set user property %s: %v", args[1], err)
+ }
+ }
+
+ // Disable the user account
+ cmd = exec.Command("dscl", ".", "-create", "/Users/"+nodeUser, "Password", "*")
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("failed to disable user account: %v", err)
+ }
+
+ return nil
+ } else {
+ return fmt.Errorf("user creation not supported on %s", osType)
+ }
+
+ // Run the command
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("failed to create user: %w", err)
+ }
+
+ fmt.Fprintf(os.Stdout, "User '%s' created successfully\n", nodeUser)
+ return nil
+}
+
+// setOwnership sets the ownership of directories to the node user
+func setOwnership() {
+ fmt.Fprintf(os.Stdout, "Setting ownership of %s and %s to %s...\n", installPath, dataPath, nodeUser)
+
+ // Change ownership of installation directory
+ chownCmd := exec.Command("chown", "-R", nodeUser+":"+nodeUser, installPath)
+ if err := chownCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: Failed to change ownership of %s: %v\n", installPath, err)
+ }
+
+ // Change ownership of data directory
+ chownCmd = exec.Command("chown", "-R", nodeUser+":"+nodeUser, dataPath)
+ if err := chownCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: Failed to change ownership of %s: %v\n", dataPath, err)
+ }
+}
+
+// setupLogRotation creates a logrotate configuration file for the Quilibrium node
+func setupLogRotation() error {
+ // Check if we need sudo privileges for creating logrotate config
+ if err := utils.CheckAndRequestSudo("Creating logrotate configuration requires root privileges"); err != nil {
+ return fmt.Errorf("failed to get sudo privileges: %w", err)
+ }
+
+ // Create logrotate configuration
+ configContent := fmt.Sprintf(`%s/*.log {
+ daily
+ rotate 7
+ compress
+ delaycompress
+ missingok
+ notifempty
+ create 0640 %s %s
+ postrotate
+ systemctl reload quilibrium-node >/dev/null 2>&1 || true
+ endscript
+}`, logPath, nodeUser, nodeUser)
+
+ // Write the configuration file
+ configPath := "/etc/logrotate.d/quilibrium-node"
+ if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
+ return fmt.Errorf("failed to create logrotate configuration: %w", err)
+ }
+
+ // Create log directory with proper permissions
+ if err := os.MkdirAll(logPath, 0750); err != nil {
+ return fmt.Errorf("failed to create log directory: %w", err)
+ }
+
+ // Set ownership of log directory
+ chownCmd := exec.Command("chown", nodeUser+":"+nodeUser, logPath)
+ if err := chownCmd.Run(); err != nil {
+ return fmt.Errorf("failed to set log directory ownership: %w", err)
+ }
+
+ fmt.Fprintf(os.Stdout, "Created log rotation configuration at %s\n", configPath)
+ return nil
+}
+
+// finishInstallation completes the installation process by making the binary executable and creating a symlink
+func finishInstallation(nodeBinaryPath string, version string) {
+
+ setOwnership()
+
+ // Make the binary executable
+ if err := os.Chmod(nodeBinaryPath, 0755); err != nil {
+ fmt.Fprintf(os.Stderr, "Error making binary executable: %v\n", err)
+ return
+ }
+
+ // Check if we need sudo privileges for creating symlink in system directory
+ if strings.HasPrefix(defaultSymlinkPath, "/usr/") || strings.HasPrefix(defaultSymlinkPath, "/bin/") || strings.HasPrefix(defaultSymlinkPath, "/sbin/") {
+ if err := utils.CheckAndRequestSudo(fmt.Sprintf("Creating symlink at %s requires root privileges", defaultSymlinkPath)); err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: Failed to get sudo privileges: %v\n", err)
+ return
+ }
+ }
+
+ // Create symlink using the utils package
+ if err := utils.CreateSymlink(nodeBinaryPath, defaultSymlinkPath); err != nil {
+ fmt.Fprintf(os.Stderr, "Error creating symlink: %v\n", err)
+ }
+
+ // Set up log rotation
+ if err := setupLogRotation(); err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: Failed to set up log rotation: %v\n", err)
+ }
+
+ // Create systemd service file
+ if osType == "linux" {
+ if err := createSystemdServiceFile(); err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: Failed to create systemd service file: %v\n", err)
+ }
+ } else if osType == "darwin" {
+ installMacOSService()
+ } else {
+ fmt.Fprintf(os.Stderr, "Warning: Background service file creation not supported on %s\n", osType)
+ return
+ }
+
+ // Print success message
+ printSuccessMessage(version)
+}
+
+// printSuccessMessage prints a success message after installation
+func printSuccessMessage(version string) {
+ fmt.Fprintf(os.Stdout, "\nSuccessfully installed Quilibrium node %s\n", version)
+ fmt.Fprintf(os.Stdout, "Installation directory: %s\n", installPath)
+ fmt.Fprintf(os.Stdout, "Data directory: %s\n", dataPath)
+ fmt.Fprintf(os.Stdout, "Binary symlinked to %s\n", defaultSymlinkPath)
+ fmt.Fprintf(os.Stdout, "Log directory: %s\n", logPath)
+ fmt.Fprintf(os.Stdout, "Environment file: /etc/default/quilibrium-node\n")
+ fmt.Fprintf(os.Stdout, "Service file: /etc/systemd/system/quilibrium-node.service\n")
+
+ fmt.Fprintf(os.Stdout, "\nTo start the node, you can run:\n")
+ fmt.Fprintf(os.Stdout, " %s --config %s/config/config.yaml\n",
+ defaultSymlinkPath, dataPath)
+ fmt.Fprintf(os.Stdout, " # Or use systemd service:\n")
+ fmt.Fprintf(os.Stdout, " sudo systemctl start quilibrium-node\n")
+
+ fmt.Fprintf(os.Stdout, "\nFor more options, run:\n")
+ fmt.Fprintf(os.Stdout, " quilibrium-node --help\n")
+}
+
+// createSystemdServiceFile creates the systemd service file with environment file support
+func createSystemdServiceFile() error {
+ // Check if we need sudo privileges
+ if err := utils.CheckAndRequestSudo("Creating systemd service file requires root privileges"); err != nil {
+ return fmt.Errorf("failed to get sudo privileges: %w", err)
+ }
+
+ // Create environment file content
+ envContent := fmt.Sprintf(`# Quilibrium Node Environment`, dataPath)
+
+ // Write environment file
+ envPath := filepath.Join(dataPath, "config", "quilibrium.env")
+ if err := os.WriteFile(envPath, []byte(envContent), 0640); err != nil {
+ return fmt.Errorf("failed to create environment file: %w", err)
+ }
+
+ // Set ownership of environment file
+ chownCmd := exec.Command("chown", nodeUser+":"+nodeUser, envPath)
+ if err := chownCmd.Run(); err != nil {
+ return fmt.Errorf("failed to set environment file ownership: %w", err)
+ }
+
+ // Create systemd service file content
+ serviceContent := fmt.Sprintf(`[Unit]
+Description=Quilibrium Node Service
+After=network.target
+
+[Service]
+Type=simple
+User=quilibrium
+EnvironmentFile=/opt/quilibrium/config/quilibrium.env
+ExecStart=/usr/local/bin/quilibrium-node --config /opt/quilibrium/config
+Restart=on-failure
+RestartSec=10
+LimitNOFILE=65535
+
+[Install]
+WantedBy=multi-user.target
+`, nodeUser, defaultSymlinkPath, dataPath)
+
+ // Write service file
+ servicePath := "/etc/systemd/system/quilibrium-node.service"
+ if err := os.WriteFile(servicePath, []byte(serviceContent), 0644); err != nil {
+ return fmt.Errorf("failed to create service file: %w", err)
+ }
+
+ // Reload systemd daemon
+ reloadCmd := exec.Command("systemctl", "daemon-reload")
+ if err := reloadCmd.Run(); err != nil {
+ return fmt.Errorf("failed to reload systemd daemon: %w", err)
+ }
+
+ fmt.Fprintf(os.Stdout, "Created systemd service file at %s\n", servicePath)
+ fmt.Fprintf(os.Stdout, "Created environment file at %s\n", envPath)
+ return nil
+}
+
+// installMacOSService installs a launchd service on macOS
+func installMacOSService() {
+ fmt.Println("Installing launchd service for Quilibrium node...")
+
+ // Create plist file content
+ plistTemplate := `
+
+
+
+ Label
+ {{.Label}}
+ ProgramArguments
+
+ /usr/local/bin/quilibrium-node
+ --config
+ /opt/quilibrium/config/
+
+ EnvironmentVariables
+
+ QUILIBRIUM_DATA_DIR
+ {{.DataPath}}
+ QUILIBRIUM_LOG_LEVEL
+ info
+ QUILIBRIUM_LISTEN_GRPC_MULTIADDR
+ /ip4/127.0.0.1/tcp/8337
+ QUILIBRIUM_LISTEN_REST_MULTIADDR
+ /ip4/127.0.0.1/tcp/8338
+ QUILIBRIUM_STATS_MULTIADDR
+ /dns/stats.quilibrium.com/tcp/443
+ QUILIBRIUM_NETWORK_ID
+ 0
+ QUILIBRIUM_DEBUG
+ false
+ QUILIBRIUM_SIGNATURE_CHECK
+ true
+
+ RunAtLoad
+
+ KeepAlive
+
+ StandardErrorPath
+ {{.LogPath}}/node.err
+ StandardOutPath
+ {{.LogPath}}/node.log
+
+`
+
+ // Prepare template data
+ data := struct {
+ Label string
+ DataPath string
+ ServiceName string
+ LogPath string
+ }{
+ Label: fmt.Sprintf("com.quilibrium.node"),
+ DataPath: dataPath,
+ ServiceName: "node",
+ LogPath: logPath,
+ }
+
+ // Parse and execute template
+ tmpl, err := template.New("plist").Parse(plistTemplate)
+ if err != nil {
+ fmt.Printf("Error creating plist template: %v\n", err)
+ return
+ }
+
+ // Determine plist file path
+ var plistPath = fmt.Sprintf("/Library/LaunchDaemons/%s.plist", data.Label)
+
+ // Write plist file
+ file, err := os.Create(plistPath)
+ if err != nil {
+ fmt.Printf("Error creating plist file: %v\n", err)
+ return
+ }
+ defer file.Close()
+
+ if err := tmpl.Execute(file, data); err != nil {
+ fmt.Printf("Error writing plist file: %v\n", err)
+ return
+ }
+
+ // Set correct permissions
+ chownCmd := exec.Command("chown", "root:wheel", plistPath)
+ if err := chownCmd.Run(); err != nil {
+ fmt.Printf("Warning: Failed to change ownership of plist file: %v\n", err)
+ }
+
+ // Load the service
+ var loadCmd = exec.Command("launchctl", "load", "-w", plistPath)
+
+ if err := loadCmd.Run(); err != nil {
+ fmt.Printf("Error loading service: %v\n", err)
+ fmt.Println("You may need to load the service manually.")
+ }
+
+ fmt.Printf("Launchd service installed successfully as %s\n", plistPath)
+ fmt.Println("\nTo start the service:")
+ fmt.Printf(" sudo launchctl start %s\n", data.Label)
+ fmt.Println("\nTo stop the service:")
+ fmt.Printf(" sudo launchctl stop %s\n", data.Label)
+ fmt.Println("\nTo view service logs:")
+ fmt.Printf(" cat %s/%s.log\n", data.LogPath, data.ServiceName)
+}
diff --git a/client/cmd/node/update.go b/client/cmd/node/update.go
new file mode 100644
index 0000000..01e602c
--- /dev/null
+++ b/client/cmd/node/update.go
@@ -0,0 +1,96 @@
+package node
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+// updateNodeCmd represents the command to update the Quilibrium node
+var updateNodeCmd = &cobra.Command{
+ Use: "update [version]",
+ Short: "Update Quilibrium node",
+ Long: `Update Quilibrium node to a specified version or the latest version.
+If no version is specified, the latest version will be installed.
+
+Examples:
+ # Update to the latest version
+ qclient node update
+
+ # Update to a specific version
+ qclient node update 2.1.0`,
+ Args: cobra.RangeArgs(0, 1),
+ Run: func(cmd *cobra.Command, args []string) {
+ // Get system information and validate
+
+ // Determine version to install
+ version := determineVersion(args)
+
+ fmt.Fprintf(os.Stdout, "Updating Quilibrium node for %s-%s, version: %s\n", osType, arch, version)
+
+ // Update the node
+ updateNode(version)
+ },
+}
+
+func init() {
+
+}
+
+// updateNode handles the node update process
+func updateNode(version string) {
+ // Check if we need sudo privileges
+ if err := utils.CheckAndRequestSudo(fmt.Sprintf("Updating node at %s requires root privileges", installPath)); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+
+ // Create version-specific installation directory
+ versionDir := filepath.Join(installPath, "node", version)
+ if err := os.MkdirAll(versionDir, 0755); err != nil {
+ fmt.Fprintf(os.Stderr, "Error creating installation directory: %v\n", err)
+ return
+ }
+
+ // Create data directory
+ versionDataDir := filepath.Join(dataPath, "node", version)
+ if err := os.MkdirAll(versionDataDir, 0755); err != nil {
+ fmt.Fprintf(os.Stderr, "Error creating data directory: %v\n", err)
+ return
+ }
+
+ // Construct the expected filename for the specified version
+ // Remove 'v' prefix if present for filename construction
+ versionWithoutV := strings.TrimPrefix(version, "v")
+ fileName := fmt.Sprintf("node-%s-%s-%s", versionWithoutV, osType, arch)
+
+ // Download the release directly
+ nodeBinaryPath := filepath.Join(dataPath, version, fileName)
+ err := utils.DownloadRelease(utils.ReleaseTypeNode, versionWithoutV)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error downloading version %s: %v\n", version, err)
+ fmt.Fprintf(os.Stderr, "The specified version %s does not exist for %s-%s\n", version, osType, arch)
+ // Clean up the created directories since installation failed
+ os.RemoveAll(versionDir)
+ os.RemoveAll(versionDataDir)
+ return
+ }
+
+ // Download signature files
+ if err := utils.DownloadReleaseSignatures(utils.ReleaseTypeNode, versionWithoutV); err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: Failed to download signature files: %v\n", err)
+ fmt.Fprintf(os.Stdout, "Continuing with installation...\n")
+ }
+
+ // Ensure log rotation is set up
+ if err := setupLogRotation(); err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: Failed to set up log rotation: %v\n", err)
+ }
+
+ // Successfully downloaded the specific version
+ finishInstallation(nodeBinaryPath, version)
+}
diff --git a/client/cmd/node/utils.go b/client/cmd/node/utils.go
new file mode 100644
index 0000000..2b4023a
--- /dev/null
+++ b/client/cmd/node/utils.go
@@ -0,0 +1 @@
+package node
diff --git a/client/cmd/root.go b/client/cmd/root.go
index 292a816..e2a3cdc 100644
--- a/client/cmd/root.go
+++ b/client/cmd/root.go
@@ -1,6 +1,7 @@
package cmd
import (
+ "bufio"
"bytes"
"crypto/tls"
"encoding/hex"
@@ -17,6 +18,8 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
+ "source.quilibrium.com/quilibrium/monorepo/client/cmd/node"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
"source.quilibrium.com/quilibrium/monorepo/node/config"
)
@@ -27,10 +30,11 @@ var simulateFail bool
var LightNode bool = false
var DryRun bool = false
var publicRPC bool = false
-
var rootCmd = &cobra.Command{
Use: "qclient",
- Short: "Quilibrium RPC Client",
+ Short: "Quilibrium client",
+ Long: `Quilibrium client is a command-line tool for managing Quilibrium nodes.
+It provides commands for installing, updating, and managing Quilibrium nodes.`,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if signatureCheck {
ex, err := os.Executable()
@@ -50,80 +54,97 @@ var rootCmd = &cobra.Command{
checksum := sha3.Sum256(b)
digest, err := os.ReadFile(ex + ".dgst")
if err != nil {
- fmt.Println("Digest file not found")
- os.Exit(1)
- }
+ fmt.Println("The digest file was not found. Do you want to continue without signature verification? (y/n)")
+ fmt.Println("You can also use --signature-check=false in your command to skip this prompt")
- parts := strings.Split(string(digest), " ")
- if len(parts) != 2 {
- fmt.Println("Invalid digest file format")
- os.Exit(1)
- }
+ reader := bufio.NewReader(os.Stdin)
+ response, _ := reader.ReadString('\n')
+ response = strings.TrimSpace(strings.ToLower(response))
- digestBytes, err := hex.DecodeString(parts[1][:64])
- if err != nil {
- fmt.Println("Invalid digest file format")
- os.Exit(1)
- }
-
- if !bytes.Equal(checksum[:], digestBytes) {
- fmt.Println("Invalid digest for node")
- os.Exit(1)
- }
-
- count := 0
-
- for i := 1; i <= len(config.Signatories); i++ {
- signatureFile := fmt.Sprintf(ex+".dgst.sig.%d", i)
- sig, err := os.ReadFile(signatureFile)
- if err != nil {
- continue
- }
-
- pubkey, _ := hex.DecodeString(config.Signatories[i-1])
- if !ed448.Verify(pubkey, digest, sig, "") {
- fmt.Printf("Failed signature check for signatory #%d\n", i)
+ if response != "y" && response != "yes" {
+ fmt.Println("Exiting due to missing digest file")
os.Exit(1)
}
- count++
- }
- if count < ((len(config.Signatories)-4)/2)+((len(config.Signatories)-4)%2) {
- fmt.Printf("Quorum on signatures not met")
- os.Exit(1)
- }
+ fmt.Println("Continuing without signature verification")
+ signatureCheck = false
+ } else {
+ parts := strings.Split(string(digest), " ")
+ if len(parts) != 2 {
+ fmt.Println("Invalid digest file format")
+ os.Exit(1)
+ }
- fmt.Println("Signature check passed")
+ digestBytes, err := hex.DecodeString(parts[1][:64])
+ if err != nil {
+ fmt.Println("Invalid digest file format")
+ os.Exit(1)
+ }
+
+ if !bytes.Equal(checksum[:], digestBytes) {
+ fmt.Println("Invalid digest for node")
+ os.Exit(1)
+ }
+
+ count := 0
+
+ for i := 1; i <= len(config.Signatories); i++ {
+ signatureFile := fmt.Sprintf(ex+".dgst.sig.%d", i)
+ sig, err := os.ReadFile(signatureFile)
+ if err != nil {
+ continue
+ }
+
+ pubkey, _ := hex.DecodeString(config.Signatories[i-1])
+ if !ed448.Verify(pubkey, digest, sig, "") {
+ fmt.Printf("Failed signature check for signatory #%d\n", i)
+ os.Exit(1)
+ }
+ count++
+ }
+
+ if count < ((len(config.Signatories)-4)/2)+((len(config.Signatories)-4)%2) {
+ fmt.Printf("Quorum on signatures not met")
+ os.Exit(1)
+ }
+
+ fmt.Println("Signature check passed")
+ }
} else {
fmt.Println("Signature check bypassed, be sure you know what you're doing")
}
- _, err := os.Stat(configDirectory)
- if os.IsNotExist(err) {
- fmt.Printf("config directory doesn't exist: %s\n", configDirectory)
- os.Exit(1)
- }
+ // Skip config checks for node and link commands
+ if len(os.Args) > 1 && (os.Args[1] != "node" && os.Args[1] != "link") {
+ // These commands handle their own configuration
+ _, err := os.Stat(configDirectory)
+ if os.IsNotExist(err) {
+ fmt.Printf("config directory doesn't exist: %s\n", configDirectory)
+ os.Exit(1)
+ }
- NodeConfig, err = config.LoadConfig(configDirectory, "", false)
- if err != nil {
- fmt.Printf("invalid config directory: %s\n", configDirectory)
- os.Exit(1)
- }
+ NodeConfig, err = config.LoadConfig(configDirectory, "", false)
+ if err != nil {
+ fmt.Printf("invalid config directory: %s\n", configDirectory)
+ os.Exit(1)
+ }
- if publicRPC {
- fmt.Println("Public RPC enabled, using light node")
- LightNode = true
- }
- if !LightNode && NodeConfig.ListenGRPCMultiaddr == "" {
- fmt.Println("No ListenGRPCMultiaddr found in config, using light node")
- LightNode = true
+ if publicRPC {
+ fmt.Println("Public RPC enabled, using light node")
+ LightNode = true
+ }
+
+ if !LightNode && NodeConfig.ListenGRPCMultiaddr == "" {
+ fmt.Println("No ListenGRPCMultiaddr found in config, using light node")
+ LightNode = true
+ }
}
},
}
func Execute() {
- err := rootCmd.Execute()
- if err != nil {
+ if err := rootCmd.Execute(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
@@ -193,6 +214,46 @@ func init() {
&publicRPC,
"public-rpc",
false,
- "uses the public RPC",
+ "uses the public RPCd",
)
+
+ // Create config directory if it doesn't exist
+ rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
+ // Skip for help command
+ if cmd.Name() == "help" {
+ return nil
+ }
+
+ // Create config directory if it doesn't exist
+ if err := os.MkdirAll(utils.ClientConfigDir, 0755); err != nil {
+ return fmt.Errorf("failed to create config directory: %v", err)
+ }
+
+ // Check for signature files
+ ex, err := os.Executable()
+ if err != nil {
+ return fmt.Errorf("failed to get executable path: %v", err)
+ }
+
+ digestPath := ex + ".dgst"
+ if signatureCheck && !utils.FileExists(digestPath) {
+ fmt.Println("Signature file not found. Would you like to download it? (y/n)")
+ reader := bufio.NewReader(os.Stdin)
+ response, _ := reader.ReadString('\n')
+ response = strings.TrimSpace(strings.ToLower(response))
+
+ if response == "y" || response == "yes" {
+ fmt.Println("Downloading signature file...")
+ // TODO: Implement signature download logic
+ fmt.Println("Signature download not implemented yet. Please download manually.")
+ } else {
+ fmt.Println("Continuing without signature verification")
+ signatureCheck = false
+ }
+ }
+ return nil
+ }
+
+ // Add the node command
+ rootCmd.AddCommand(node.NodeCmd)
}
diff --git a/client/cmd/update.go b/client/cmd/update.go
new file mode 100644
index 0000000..2db2b66
--- /dev/null
+++ b/client/cmd/update.go
@@ -0,0 +1,172 @@
+package cmd
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+var (
+ osType = runtime.GOOS
+ arch = runtime.GOARCH
+)
+
+// updateCmd represents the command to update the Quilibrium client
+var updateCmd = &cobra.Command{
+ Use: "update [version]",
+ Short: "Update Quilibrium client",
+ Long: `Update Quilibrium client to a specified version or the latest version.
+If no version is specified, the latest version will be installed.
+
+Examples:
+ # Update to the latest version
+ qclient update
+
+ # Update to a specific version
+ qclient update 2.1.0`,
+ Args: cobra.RangeArgs(0, 1),
+ Run: func(cmd *cobra.Command, args []string) {
+ // Get system information and validate
+ localOsType, localArch, err := utils.GetSystemInfo()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+
+ osType = localOsType
+ arch = localArch
+
+ // Determine version to install
+ version := determineVersion(args)
+
+ fmt.Fprintf(os.Stdout, "Updating Quilibrium client for %s-%s, version: %s\n", osType, arch, version)
+
+ // Update the client
+ updateClient(version)
+ },
+}
+
+func init() {
+ rootCmd.AddCommand(updateCmd)
+}
+
+// determineVersion gets the version to install from args or defaults to "latest"
+func determineVersion(args []string) string {
+ if len(args) > 0 {
+ return args[0]
+ }
+ return "latest"
+}
+
+// updateClient handles the client update process
+func updateClient(version string) {
+ // Check if we need sudo privileges
+ if err := utils.CheckAndRequestSudo(fmt.Sprintf("Updating client at %s requires root privileges", utils.ClientInstallPath)); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+
+ // Create version-specific installation directory
+ versionDir := filepath.Join(utils.ClientInstallPath, version)
+ if err := os.MkdirAll(versionDir, 0755); err != nil {
+ fmt.Fprintf(os.Stderr, "Error creating installation directory: %v\n", err)
+ return
+ }
+
+ // Create data directory
+ versionDataDir := filepath.Join(utils.ClientDataPath, version)
+ if err := os.MkdirAll(versionDataDir, 0755); err != nil {
+ fmt.Fprintf(os.Stderr, "Error creating data directory: %v\n", err)
+ return
+ }
+
+ // If version is "latest", get the latest version
+ if version == "latest" {
+ latestVersion, err := utils.GetLatestVersion(utils.ReleaseTypeQClient)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error getting latest version: %v\n", err)
+ return
+ }
+ version = latestVersion
+ fmt.Fprintf(os.Stdout, "Latest version: %s\n", version)
+ }
+
+ // Construct the expected filename for the specified version
+ // Remove 'v' prefix if present for filename construction
+ versionWithoutV := strings.TrimPrefix(version, "v")
+
+ // Download the release directly
+ err := utils.DownloadRelease(utils.ReleaseTypeQClient, versionWithoutV)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error downloading version %s: %v\n", version, err)
+ fmt.Fprintf(os.Stderr, "The specified version %s does not exist for %s-%s\n", version, osType, arch)
+ // Clean up the created directories since installation failed
+ os.RemoveAll(versionDir)
+ os.RemoveAll(versionDataDir)
+ return
+ }
+
+ // Download signature files
+ if err := utils.DownloadReleaseSignatures(utils.ReleaseTypeQClient, versionWithoutV); err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: Failed to download signature files: %v\n", err)
+ fmt.Fprintf(os.Stdout, "Continuing with installation...\n")
+ }
+
+ // Successfully downloaded the specific version
+ finishInstallation(version)
+}
+
+// finishInstallation completes the update process
+func finishInstallation(version string) {
+ // Read current config
+ config, err := utils.ReadClientConfig()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error reading config: %v\n", err)
+ return
+ }
+
+ // Update config with new version
+ config.Version = version
+ if err := utils.UpdateClientConfig(config); err != nil {
+ fmt.Fprintf(os.Stderr, "Error updating config: %v\n", err)
+ return
+ }
+
+ // Construct executable path
+ execPath := filepath.Join(utils.ClientInstallPath, version, "qclient")
+
+ // Make the binary executable
+ if err := os.Chmod(execPath, 0755); err != nil {
+ fmt.Fprintf(os.Stderr, "Error making binary executable: %v\n", err)
+ return
+ }
+
+ // Create symlink to the new version
+ symlinkPath := utils.DefaultQClientSymlinkPath
+ if config.SymlinkPath != "" {
+ symlinkPath = config.SymlinkPath
+ }
+
+ // Check if we need sudo privileges for creating symlink in system directory
+ if strings.HasPrefix(symlinkPath, "/usr/") || strings.HasPrefix(symlinkPath, "/bin/") || strings.HasPrefix(symlinkPath, "/sbin/") {
+ if err := utils.CheckAndRequestSudo(fmt.Sprintf("Creating symlink at %s requires root privileges", symlinkPath)); err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: Failed to get sudo privileges: %v\n", err)
+ return
+ }
+ }
+
+ // Create symlink
+ if err := utils.CreateSymlink(execPath, symlinkPath); err != nil {
+ fmt.Fprintf(os.Stderr, "Error creating symlink: %v\n", err)
+ return
+ }
+
+ fmt.Fprintf(os.Stdout, "Successfully updated qclient to version %s\n", version)
+ fmt.Fprintf(os.Stdout, "Executable: %s\n", execPath)
+ fmt.Fprintf(os.Stdout, "Symlink: %s\n", symlinkPath)
+}
diff --git a/client/cmd/version.go b/client/cmd/version.go
new file mode 100644
index 0000000..e55a680
--- /dev/null
+++ b/client/cmd/version.go
@@ -0,0 +1,86 @@
+package cmd
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+// Version information - fallback if executable name doesn't contain version
+const (
+ DefaultVersion = "1.0.0"
+)
+
+// VersionInfo holds version and hash information
+type VersionInfo struct {
+ Version string
+ SHA256 string
+ MD5 string
+}
+
+// GetVersionInfo extracts version from executable and optionally calculates hashes
+func GetVersionInfo(calcChecksum bool) (VersionInfo, error) {
+ executable, err := os.Executable()
+ if err != nil {
+ return VersionInfo{Version: DefaultVersion}, fmt.Errorf("error getting executable path: %v", err)
+ }
+
+ // Extract version from executable name (e.g. qclient-2.0.3-linux-amd)
+ baseName := filepath.Base(executable)
+ versionPattern := regexp.MustCompile(`qclient-([0-9]+\.[0-9]+\.[0-9]+)`)
+ matches := versionPattern.FindStringSubmatch(baseName)
+
+ version := DefaultVersion
+ if len(matches) > 1 {
+ version = matches[1]
+ }
+
+ // If version not found or checksum requested, calculate hash
+ if len(matches) <= 1 || calcChecksum {
+ sha256Hash, md5Hash, err := utils.CalculateFileHashes(executable)
+ if err != nil {
+ return VersionInfo{Version: version}, fmt.Errorf("error calculating file hashes: %v", err)
+ }
+
+ return VersionInfo{
+ Version: version,
+ SHA256: sha256Hash,
+ MD5: md5Hash,
+ }, nil
+ }
+
+ return VersionInfo{
+ Version: version,
+ }, nil
+}
+
+var versionCmd = &cobra.Command{
+ Use: "version",
+ Short: "Display the qclient version",
+ Long: `Display the qclient version and optionally calculate SHA256 and MD5 hashes of the executable.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ showChecksum, _ := cmd.Flags().GetBool("checksum")
+
+ info, err := GetVersionInfo(showChecksum)
+ if err != nil {
+ fmt.Printf("Error: %v\n", err)
+ return
+ }
+
+ fmt.Printf("qclient %s\n", info.Version)
+
+ if info.SHA256 != "" && info.MD5 != "" {
+ fmt.Printf("SHA256: %s\n", info.SHA256)
+ fmt.Printf("MD5: %s\n", info.MD5)
+ }
+ },
+}
+
+func init() {
+ versionCmd.Flags().Bool("checksum", false, "Show SHA256 and MD5 checksums of the executable")
+ rootCmd.AddCommand(versionCmd)
+}
diff --git a/client/test/Dockerfile b/client/test/Dockerfile
new file mode 100644
index 0000000..3463ef3
--- /dev/null
+++ b/client/test/Dockerfile
@@ -0,0 +1,39 @@
+# Use build argument to specify the base image
+ARG DISTRO=ubuntu
+ARG VERSION=24.04
+
+# Use the specified distribution as the base image
+FROM --platform=$BUILDPLATFORM ${DISTRO}:${VERSION}
+
+ARG TARGETARCH
+ARG TARGETOS
+
+RUN echo "TARGETARCH: $TARGETARCH"
+RUN echo "TARGETOS: $TARGETOS"
+
+# Install required packages
+RUN apt-get update && apt-get install -y \
+ curl \
+ sudo \
+ lsb-release \
+ && rm -rf /var/lib/apt/lists/*
+
+# Create a non-root user for testing
+RUN useradd -m -s /bin/bash testuser && \
+ echo "testuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers
+
+# Set working directory
+WORKDIR /app
+
+# Copy the client binary and test script
+COPY build/qclient/${TARGETARCH}_${TARGETOS}/qclient /opt/quilibrium/bin/qclient
+COPY test_install.sh /app/
+
+# Set permissions
+RUN chmod +x /opt/quilibrium/bin/qclient /app/test_install.sh
+
+# Switch to test user
+USER testuser
+
+# Run the test script
+CMD ["/app/test_install.sh"]
\ No newline at end of file
diff --git a/client/test/Dockerfile.qclient b/client/test/Dockerfile.qclient
new file mode 100644
index 0000000..3749984
--- /dev/null
+++ b/client/test/Dockerfile.qclient
@@ -0,0 +1,87 @@
+FROM ubuntu:24.04 AS build-base
+
+ENV PATH="${PATH}:/root/.cargo/bin/"
+
+# Install GMP 6.2 (6.3 which MacOS is using only available on Debian unstable)
+RUN apt-get update && apt-get install -y \
+ build-essential \
+ curl \
+ git \
+ cmake \
+ libgmp-dev \
+ libmpfr-dev \
+ libmpfr6 \
+ wget \
+ m4 \
+ pkg-config \
+ gcc \
+ g++ \
+ make \
+ autoconf \
+ automake \
+ libtool \
+ && rm -rf /var/lib/apt/lists/*
+
+# Install Go 1.22.5 for amd64
+RUN wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz && \
+ rm -rf /usr/local/go && \
+ tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz && \
+ rm go1.22.5.linux-amd64.tar.gz
+
+ENV PATH=$PATH:/usr/local/go/bin
+
+# Install FLINT library
+RUN git clone https://github.com/flintlib/flint.git && \
+ cd flint && \
+ git checkout flint-3.0 && \
+ ./bootstrap.sh && \
+ ./configure \
+ --prefix=/usr/local \
+ --with-gmp=/usr/local \
+ --with-mpfr=/usr/local \
+ --enable-static \
+ --disable-shared \
+ CFLAGS="-O3" && \
+ make && \
+ make install && \
+ cd .. && \
+ rm -rf flint
+
+# Install Rust toolchain
+COPY docker/rustup-init.sh /opt/rustup-init.sh
+RUN /opt/rustup-init.sh -y --profile minimal
+
+# Install uniffi-bindgen-go
+RUN cargo install uniffi-bindgen-go --git https://github.com/NordSecurity/uniffi-bindgen-go --tag v0.2.1+v0.25.0
+
+FROM build-base AS build
+
+ENV GOEXPERIMENT=arenas
+ENV QUILIBRIUM_SIGNATURE_CHECK=false
+
+WORKDIR /opt/ceremonyclient
+
+COPY . .
+
+## Generate Rust bindings for VDF
+WORKDIR /opt/ceremonyclient/vdf
+RUN ./generate.sh
+
+## Generate Rust bindings for BLS48581
+WORKDIR /opt/ceremonyclient/bls48581
+RUN ./generate.sh
+
+## Generate Rust bindings for VerEnc
+WORKDIR /opt/ceremonyclient/verenc
+RUN ./generate.sh
+
+# Build and install qclient
+WORKDIR /opt/ceremonyclient/client
+RUN ./build.sh -o qclient
+
+# Create final stage to copy the binary
+FROM scratch AS export-stage
+COPY --from=build /opt/ceremonyclient/client/qclient ./client/test/
+
+
+
diff --git a/client/test/README.md b/client/test/README.md
new file mode 100644
index 0000000..2ddd818
--- /dev/null
+++ b/client/test/README.md
@@ -0,0 +1,155 @@
+# Quil Test Runner Documentation
+
+This document describes the usage and functionality of the Quil test runner script (`run_tests.sh`), which is designed to run tests across different Linux distributions using Docker containers.
+
+## Overview
+
+The test runner allows you to:
+- Run tests on multiple Linux distributions simultaneously
+- Test on a specific distribution and version
+- Customize container tags for test runs
+- Build the client binary using a standardized Docker build process
+
+## Prerequisites
+
+- Docker installed and running on your system
+- Bash shell
+- Access to the Quil client source code
+- Sufficient disk space for building the client (approximately 2GB recommended)
+
+## Build Environment
+
+The test runner uses a multi-stage build process based on `Dockerfile.qclient` which includes:
+
+### Base Build Environment
+- Ubuntu 24.04 as the base image
+- Essential build tools (gcc, g++, make, etc.)
+- GMP 6.2 and MPFR libraries
+- Go 1.22.0 (amd64)
+- Rust toolchain (via rustup)
+- FLINT library (version 3.0)
+- uniffi-bindgen-go (v0.2.1+v0.25.0)
+
+### Build Process
+1. Generates Rust bindings for:
+ - VDF (Verifiable Delay Function)
+ - BLS48581 (Boneh-Lynn-Shacham signature scheme)
+ - VerEnc (Verifiable Encryption)
+2. Builds and installs:
+ - qclient binary
+
+## Usage
+
+### Basic Usage
+
+To run tests on all supported distributions (Ubuntu 22.04, Ubuntu 24.04, and Debian 12):
+
+```bash
+./run_tests.sh
+```
+
+### Custom Test Configuration
+
+To run tests on a specific distribution and version:
+
+```bash
+./run_tests.sh -d DISTRO -v VERSION [-t TAG]
+```
+
+#### Parameters:
+- `-d, --distro`: The Linux distribution to test (e.g., ubuntu, debian)
+- `-v, --version`: The version of the distribution (e.g., 22.04, 12)
+- `-t, --tag`: (Optional) Custom tag for the test container. If not provided, a tag will be automatically generated
+
+#### Examples:
+
+```bash
+# Test Ubuntu 22.04 with auto-generated tag
+./run_tests.sh -d ubuntu -v 22.04
+
+# Test Debian 12 with custom tag
+./run_tests.sh -d debian -v 12 -t my-custom-test
+
+# Show help message
+./run_tests.sh --help
+```
+
+## Supported Distributions
+
+By default, the script tests the following distributions:
+- Ubuntu 22.04
+- Ubuntu 24.04
+- Debian 12
+
+## How It Works
+
+1. The script first builds the client binary using `Dockerfile.qclient`:
+ - Creates a build container with all required dependencies
+ - Generates necessary Rust bindings
+ - Builds the qclient binary
+ - Extracts the binary to the test directory
+ - Cleans up the build container
+
+2. For each test run:
+ - Creates a Docker container using the specified distribution and version
+ - Copies the built client binary into the test container
+ - Builds the test environment
+ - Runs the tests
+ - Cleans up the container after completion
+
+## Error Handling
+
+- The script uses `set -e` to exit on any error
+- If any test fails, the script will exit with status code 1
+- Docker containers are automatically removed after test completion using the `--rm` flag
+- Build errors in `Dockerfile.qclient` will be clearly displayed in the output
+
+## Notes
+
+- When running all tests simultaneously, the script uses background processes to parallelize the test runs
+- The script automatically generates container tags if not specified, using the format `distroversion` (e.g., `ubuntu2204`)
+- Make sure you have sufficient system resources when running multiple tests simultaneously
+- The build process requires significant disk space due to the multi-stage build and dependencies
+- The client binary is built specifically for amd64 architecture
+
+## Troubleshooting
+
+If you encounter issues:
+
+1. Ensure Docker is running:
+ ```bash
+ systemctl status docker
+ ```
+
+2. Check Docker logs for container-specific issues:
+ ```bash
+ docker logs quil-test-[tag]
+ ```
+
+3. Verify you have sufficient disk space and memory for running multiple containers
+
+4. For build-related issues:
+ - Check if all required dependencies are available in the target distribution
+ - Verify the build environment has sufficient resources
+ - Check the build logs for specific error messages
+ - Ensure you're running on an amd64 system or using appropriate Docker platform settings
+
+## Direct Docker Build
+
+If you just want to build the qclient in a Docker container without running the tests (useful if you can't build for the target testing environment):
+
+```bash
+# in the project root directory
+## Will take awhile to build flint on initial build
+docker build -f client/test/Dockerfile.qclient -t qclient .
+```
+
+This command builds the Docker image with the qclient binary according to the specifications in `Dockerfile.qclient`. The resulting image will be tagged as `qclient`.
+
+## Contributing
+
+When adding new distributions or versions:
+1. Update the default test configurations in the script
+2. Ensure the corresponding Dockerfile supports the new distribution/version
+3. Test the changes thoroughly before committing
+4. Verify that all dependencies in `Dockerfile.qclient` are available in the target distribution
\ No newline at end of file
diff --git a/client/test/run_tests.sh b/client/test/run_tests.sh
new file mode 100755
index 0000000..a123f72
--- /dev/null
+++ b/client/test/run_tests.sh
@@ -0,0 +1,97 @@
+#!/bin/bash
+set -e
+
+# Help function
+show_help() {
+ echo "Usage: $0 [OPTIONS]"
+ echo "Run tests on specified Linux distributions"
+ echo ""
+ echo "Options:"
+ echo " -d, --distro DISTRO Specify the distribution (e.g., ubuntu, debian)"
+ echo " -v, --version VERSION Specify the version (e.g., 22.04, 12)"
+ echo " -t, --tag TAG Specify a custom tag for the test container"
+ echo " -h, --help Show this help message"
+ echo ""
+ echo "If no arguments are provided, runs tests on all supported distributions"
+ exit 0
+}
+
+# Parse command line arguments
+DISTRO=""
+VERSION=""
+TAG=""
+while [[ $# -gt 0 ]]; do
+ case $1 in
+ -d|--distro)
+ DISTRO="$2"
+ shift 2
+ ;;
+ -v|--version)
+ VERSION="$2"
+ shift 2
+ ;;
+ -t|--tag)
+ TAG="$2"
+ shift 2
+ ;;
+ -h|--help)
+ show_help
+ ;;
+ *)
+ echo "Unknown option: $1"
+ show_help
+ ;;
+ esac
+done
+
+# Build the client binary using Dockerfile.qclient
+echo "Building client binary using Dockerfile.qclient..."
+docker build -t quil-qclient-builder -f Dockerfile.qclient ..
+docker create --name quil-qclient-temp quil-qclient-builder
+docker cp quil-qclient-temp:/usr/local/bin/qclient ./qclient
+docker rm quil-qclient-temp
+
+# Function to run tests for a specific distribution
+run_distro_test() {
+ local distro=$1
+ local version=$2
+ local tag=$3
+ echo "Testing on $distro $version..."
+ docker build \
+ --build-arg DISTRO=$distro \
+ --build-arg VERSION=$version \
+ -t quil-test-$tag \
+ -f Dockerfile .
+ docker run --rm quil-test-$tag
+}
+
+# If custom distro/version/tag is provided, run single test
+if [ ! -z "$DISTRO" ] && [ ! -z "$VERSION" ]; then
+ if [ -z "$TAG" ]; then
+ TAG="${DISTRO}${VERSION//./}"
+ fi
+ echo "Running custom test configuration..."
+ run_distro_test "$DISTRO" "$VERSION" "$TAG"
+else
+ # Run tests on all distributions simultaneously
+ echo "Running tests on all distributions simultaneously..."
+ run_distro_test "ubuntu" "22.04" "ubuntu22" &
+ UBUNTU22_PID=$!
+
+ run_distro_test "ubuntu" "24.04" "ubuntu24" &
+ UBUNTU24_PID=$!
+
+ run_distro_test "debian" "12" "debian12" &
+ DEBIAN12_PID=$!
+
+ # Wait for all tests to complete
+ wait $UBUNTU22_PID $UBUNTU24_PID $DEBIAN12_PID
+
+ # Check exit status of each test
+ if [ $? -ne 0 ]; then
+ echo "One or more tests failed!"
+ exit 1
+ fi
+fi
+
+echo "All distribution tests completed!"
diff --git a/client/test/test_install.sh b/client/test/test_install.sh
new file mode 100644
index 0000000..e451677
--- /dev/null
+++ b/client/test/test_install.sh
@@ -0,0 +1,115 @@
+#!/bin/bash
+set -e
+
+# Get distribution information
+DISTRO=$(lsb_release -si 2>/dev/null || echo "Unknown")
+VERSION=$(lsb_release -sr 2>/dev/null || echo "Unknown")
+
+echo "Starting Quilibrium node installation test on $DISTRO $VERSION..."
+
+# Test 1: Install latest version
+echo "Test 1: Installing latest version..."
+qclient node install
+
+get_latest_version() {
+ # Fetch the latest version from the releases API
+ local latest_version=$(curl -s https://releases.quilibrium.com/release | head -n 1 | cut -d'-' -f2)
+
+ echo "$latest_version"
+}
+
+LATEST_VERSION=$(get_latest_version)
+
+# Verify installation
+echo "Verifying installation..."
+if [ ! -f "/opt/quilibrium/$LATEST_VERSION/node-$LATEST_VERSION-linux-amd64" ]; then
+ echo "Error: Latest version binary not found"
+ exit 1
+fi
+
+# Verify latest version matches
+echo "Verifying latest version matches..."
+get_latest_version
+
+# Test 2: Install specific version
+echo "Test 2: Installing specific version..."
+qclient node install "2.0.6.2"
+
+# Verify specific version installation
+echo "Verifying specific version installation..."
+if [ ! -f "/opt/quilibrium/2.0.6.2/node-2.0.6.2-linux-amd64" ]; then
+ echo "Error: Specific version binary not found"
+ exit 1
+fi
+
+# Test 3: Verify service file creation
+echo "Test 3: Verifying service file creation..."
+if [ ! -f "/etc/systemd/system/quilibrium-node.service" ]; then
+ echo "Error: Service file not found"
+ exit 1
+fi
+
+# Verify service file content
+echo "Verifying service file content..."
+if ! grep -q "EnvironmentFile=/etc/default/quilibrium-node" /etc/systemd/system/quilibrium-node.service; then
+ echo "Error: Service file missing EnvironmentFile directive"
+ exit 1
+fi
+
+# Test 4: Verify environment file
+echo "Test 4: Verifying environment file..."
+if [ ! -f "/etc/default/quilibrium-node" ]; then
+ echo "Error: Environment file not found"
+ exit 1
+fi
+
+# Verify environment file permissions
+echo "Verifying environment file permissions..."
+if [ "$(stat -c %a /etc/default/quilibrium-node)" != "640" ]; then
+ echo "Error: Environment file has incorrect permissions"
+ exit 1
+fi
+
+# Test 5: Verify data directory
+echo "Test 5: Verifying data directory..."
+if [ ! -d "/var/lib/quilibrium" ]; then
+ echo "Error: Data directory not found"
+ exit 1
+fi
+
+# Verify data directory permissions
+echo "Verifying data directory permissions..."
+if [ "$(stat -c %a /var/lib/quilibrium)" != "755" ]; then
+ echo "Error: Data directory has incorrect permissions"
+ exit 1
+fi
+
+# Test 6: Verify config file
+echo "Test 6: Verifying config file..."
+if [ ! -f "/var/lib/quilibrium/config/node.yaml" ]; then
+ echo "Error: Config file not found"
+ exit 1
+fi
+
+# Verify config file permissions
+echo "Verifying config file permissions..."
+if [ "$(stat -c %a /var/lib/quilibrium/config/node.yaml)" != "644" ]; then
+ echo "Error: Config file has incorrect permissions"
+ exit 1
+fi
+
+# Test 7: Verify binary symlink
+echo "Test 7: Verifying binary symlink..."
+if [ ! -L "/usr/local/bin/quilibrium-node" ]; then
+ echo "Error: Binary symlink not found"
+ exit 1
+fi
+
+# Test 8: Verify binary execution
+echo "Test 8: Verifying binary execution..."
+if ! quilibrium-node --version > /dev/null 2>&1; then
+ echo "Error: Binary execution failed"
+ exit 1
+fi
+
+echo "All tests passed successfully on $DISTRO $VERSION!"
\ No newline at end of file
diff --git a/client/utils/download.go b/client/utils/download.go
new file mode 100644
index 0000000..5c40a11
--- /dev/null
+++ b/client/utils/download.go
@@ -0,0 +1,168 @@
+package utils
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+var BaseReleaseURL = "https://releases.quilibrium.com"
+
+// DownloadRelease downloads a specific release file
+func DownloadRelease(releaseType ReleaseType, version string) error {
+ fileName := fmt.Sprintf("%s-%s-%s-%s", releaseType, version, osType, arch)
+ return DownloadReleaseFile(releaseType, fileName, version, true)
+}
+
+// GetLatestVersion fetches the latest version from the releases API
+func GetLatestVersion(releaseType ReleaseType) (string, error) {
+ // Determine the appropriate URL based on the release type
+ releaseURL := fmt.Sprintf("%s/release", BaseReleaseURL)
+ if releaseType == ReleaseTypeQClient {
+ releaseURL = fmt.Sprintf("%s/qclient-release", BaseReleaseURL)
+ }
+ resp, err := http.Get(releaseURL)
+ if err != nil {
+ return "", fmt.Errorf("failed to fetch latest version: %w", err)
+ }
+ defer resp.Body.Close()
+
+ scanner := bufio.NewScanner(resp.Body)
+ if !scanner.Scan() {
+ return "", fmt.Errorf("no response data found")
+ }
+
+ // Get the first line which contains the filename
+ filename := scanner.Text()
+
+ // Split the filename by "-" and get the version part
+ parts := strings.Split(filename, "-")
+ if len(parts) < 2 {
+ return "", fmt.Errorf("invalid filename format: %s", filename)
+ }
+
+ // The version is the second part (index 1)
+ version := parts[1]
+ return version, nil
+}
+
+// DownloadReleaseFile downloads a release file from the Quilibrium releases server
+func DownloadReleaseFile(releaseType ReleaseType, fileName string, version string, showError bool) error {
+ url := fmt.Sprintf("%s/%s", BaseReleaseURL, fileName)
+ destPath := filepath.Join(DataPath, string(releaseType), version, fileName)
+
+ resp, err := http.Get(url)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ if showError {
+ return fmt.Errorf("failed to download file: %s", resp.Status)
+ } else {
+ return nil
+ }
+ }
+
+ out, err := os.Create(destPath)
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+
+ _, err = io.Copy(out, resp.Body)
+ if err != nil {
+ return err
+ }
+ fmt.Fprintf(os.Stdout, "Downloaded %s to %s\n", fileName, destPath)
+ return nil
+}
+
+// DownloadReleaseSignatures downloads signature files for a release
+func DownloadReleaseSignatures(releaseType ReleaseType, version string) error {
+ var files []string
+ baseName := fmt.Sprintf("%s-%s-%s-%s", releaseType, version, osType, arch)
+
+ // Add digest file URL
+ files = append(files, baseName+".dgst")
+
+ // Add signature file URLs
+ signerNums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17}
+ for _, num := range signerNums {
+ files = append(files, fmt.Sprintf("%s.dgst.sig.%d", baseName, num))
+ }
+
+ for _, file := range files {
+ err := DownloadReleaseFile(releaseType, file, version, false)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// GetLatestReleaseFiles fetches the list of available release files
+func GetLatestReleaseFiles(releaseType ReleaseType) ([]string, error) {
+ releaseURL := fmt.Sprintf("%s/release", BaseReleaseURL)
+ if releaseType == ReleaseTypeQClient {
+ releaseURL = fmt.Sprintf("%s/qclient-release", BaseReleaseURL)
+ }
+ resp, err := http.Get(releaseURL)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("failed to fetch releases: %s", resp.Status)
+ }
+
+ // Read the response body and parse it
+ var releases []string
+
+ scanner := bufio.NewScanner(resp.Body)
+ for scanner.Scan() {
+ releases = append(releases, scanner.Text())
+ }
+
+ if err := scanner.Err(); err != nil {
+ return nil, fmt.Errorf("error reading response: %w", err)
+ }
+
+ return releases, nil
+}
+
+// FilterReleasesByOSArch filters releases by OS and architecture
+func FilterReleasesByOSArch(releases []string, osType, arch string) []string {
+ var filtered []string
+ for _, release := range releases {
+ if strings.Contains(release, osType) && strings.Contains(release, arch) {
+ filtered = append(filtered, release)
+ }
+ }
+ return filtered
+}
+
+// ExtractVersionFromFileName extracts the version from a release filename
+func ExtractVersionFromFileName(releaseType ReleaseType, fileName, osType, arch string) string {
+ version := strings.TrimPrefix(fileName, string(releaseType)+"-")
+ version = strings.TrimSuffix(version, "-"+osType+"-"+arch)
+ return version
+}
+
+// DownloadAllReleaseFiles downloads all release files
+func DownloadAllReleaseFiles(releaseType ReleaseType, fileNames []string, installDir string) bool {
+ for _, fileName := range fileNames {
+ filePath := filepath.Join(installDir, fileName)
+ if err := DownloadReleaseFile(releaseType, fileName, filePath, true); err != nil {
+ fmt.Fprintf(os.Stderr, "Error downloading release file %s: %v\n", fileName, err)
+ return false
+ }
+ }
+ return true
+}
diff --git a/client/utils/fileUtils.go b/client/utils/fileUtils.go
new file mode 100644
index 0000000..ef6d91d
--- /dev/null
+++ b/client/utils/fileUtils.go
@@ -0,0 +1,238 @@
+package utils
+
+import (
+ "crypto/md5"
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+
+ "gopkg.in/yaml.v3"
+)
+
+// DefaultNodeUser is the default user name for node operations
+var DefaultNodeUser = "quilibrium"
+var ClientConfigDir = filepath.Join("/etc/quilibrium/", "config")
+var ClientConfigPath = filepath.Join(ClientConfigDir, "client.yaml")
+var ClientInstallPath = filepath.Join("/opt/quilibrium/", "client")
+var DataPath = filepath.Join("/var/quilibrium/", "data")
+var ClientDataPath = filepath.Join(DataPath, "client")
+var NodeDataPath = filepath.Join(DataPath, "node")
+var NodeDefaultSymlinkDir = "/usr/local/bin"
+var DefaultNodeSymlinkPath = filepath.Join(NodeDefaultSymlinkDir, "quilibrium-node")
+var DefaultQClientSymlinkPath = filepath.Join(NodeDefaultSymlinkDir, "qclient")
+var osType = runtime.GOOS
+var arch = runtime.GOARCH
+
+// CalculateFileHashes calculates SHA256 and MD5 hashes for a file
+func CalculateFileHashes(filePath string) (string, string, error) {
+ file, err := os.Open(filePath)
+ if err != nil {
+ return "", "", fmt.Errorf("error opening file: %w", err)
+ }
+ defer file.Close()
+
+ // Calculate SHA256
+ sha256Hash := sha256.New()
+ if _, err := io.Copy(sha256Hash, file); err != nil {
+ return "", "", fmt.Errorf("error calculating SHA256: %w", err)
+ }
+
+ // Reset file position to beginning for MD5 calculation
+ if _, err := file.Seek(0, 0); err != nil {
+ return "", "", fmt.Errorf("error seeking file: %w", err)
+ }
+
+ // Calculate MD5
+ md5Hash := md5.New()
+ if _, err := io.Copy(md5Hash, file); err != nil {
+ return "", "", fmt.Errorf("error calculating MD5: %w", err)
+ }
+
+ return hex.EncodeToString(sha256Hash.Sum(nil)), hex.EncodeToString(md5Hash.Sum(nil)), nil
+}
+
+// CreateSymlink creates a symlink, handling the case where it already exists
+func CreateSymlink(execPath, targetPath string) error {
+ // Check if the symlink already exists
+ if _, err := os.Lstat(targetPath); err == nil {
+ // Symlink exists, ask if user wants to overwrite
+ if !ConfirmSymlinkOverwrite(targetPath) {
+ fmt.Println("Operation cancelled.")
+ return nil
+ }
+
+ // Remove existing symlink
+ if err := os.Remove(targetPath); err != nil {
+ return fmt.Errorf("failed to remove existing symlink: %w", err)
+ }
+ }
+
+ // Create the symlink
+ if err := os.Symlink(execPath, targetPath); err != nil {
+ return fmt.Errorf("failed to create symlink: %w", err)
+ }
+
+ return nil
+}
+
+// ReadClientConfig reads the client configuration from the specified config directory
+// If the config file doesn't exist, it returns an empty config
+func ReadClientConfig() (*ClientConfig, error) {
+ // Check if config file exists
+ if !FileExists(ClientConfigPath) {
+ // Return empty config if file doesn't exist
+ return &ClientConfig{}, nil
+ }
+
+ // Read the config file
+ data, err := os.ReadFile(ClientConfigPath)
+ if err != nil {
+ return nil, fmt.Errorf("error reading config file: %w", err)
+ }
+
+ // Parse YAML
+ var config ClientConfig
+ err = yaml.Unmarshal(data, &config)
+ if err != nil {
+ return nil, fmt.Errorf("error parsing config file: %w", err)
+ }
+
+ return &config, nil
+}
+
+// UpdateClientConfig updates the client configuration in the specified config directory
+// If the config file doesn't exist, it creates a new one
+func UpdateClientConfig(config *ClientConfig) error {
+ configDir := ClientConfigDir
+ // Check if we need sudo privileges (if config directory is in a system directory)
+ if err := CheckAndRequestSudo(fmt.Sprintf("Updating config directory at %s requires root privileges", configDir)); err != nil {
+ return fmt.Errorf("failed to get sudo privileges: %w", err)
+ }
+
+ // Create config directory if it doesn't exist
+ if err := os.MkdirAll(configDir, 0755); err != nil {
+ return fmt.Errorf("failed to create config directory: %w", err)
+ }
+
+ configPath := GetConfigPath(configDir)
+
+ // Marshal config to YAML
+ data, err := yaml.Marshal(config)
+ if err != nil {
+ return fmt.Errorf("error serializing config: %w", err)
+ }
+
+ // Write config to file
+ if err := os.WriteFile(configPath, data, 0644); err != nil {
+ return fmt.Errorf("error writing config file: %w", err)
+ }
+
+ // Set ownership if a dedicated user was created
+ if DefaultNodeUser != "" {
+ // Check for sudo privileges for changing ownership
+ if err := CheckAndRequestSudo(fmt.Sprintf("Changing ownership of %s requires root privileges", configPath)); err != nil {
+ return fmt.Errorf("failed to get sudo privileges: %w", err)
+ }
+
+ chownCmd := exec.Command("chown", DefaultNodeUser+":"+DefaultNodeUser, configPath)
+ if err := chownCmd.Run(); err != nil {
+ return fmt.Errorf("failed to change ownership of config file: %w", err)
+ }
+ }
+
+ return nil
+}
+
+// CreateConfigFile creates a basic configuration file for the node
+func CreateConfigFile(configDir, dataDir, version string) {
+ // Create a ClientConfig struct
+ config := ClientConfig{
+ Version: version,
+ DataDir: ClientDataPath,
+ SymlinkPath: DefaultQClientSymlinkPath,
+ }
+
+ // Use UpdateClientConfig to save the configuration
+ if err := UpdateClientConfig(&config); err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: Failed to create config file: %v\n", err)
+ return
+ }
+
+ fmt.Fprintf(os.Stdout, "Created configuration file at %s/config.yaml\n", configDir)
+}
+
+// ValidateAndCreateDir validates a directory path and creates it if it doesn't exist
+func ValidateAndCreateDir(path string) error {
+ // Check if the directory exists
+ info, err := os.Stat(path)
+ if err == nil {
+ // Path exists, check if it's a directory
+ if !info.IsDir() {
+ return fmt.Errorf("%s exists but is not a directory", path)
+ }
+ return nil
+ }
+
+ // Directory doesn't exist, try to create it
+ if os.IsNotExist(err) {
+ if err := os.MkdirAll(path, 0755); err != nil {
+ return fmt.Errorf("failed to create directory %s: %v", path, err)
+ }
+ return nil
+ }
+
+ // Some other error occurred
+ return fmt.Errorf("error checking directory %s: %v", path, err)
+}
+
+// IsWritable checks if a directory is writable
+func IsWritable(dir string) bool {
+ // Check if directory exists
+ info, err := os.Stat(dir)
+ if err != nil || !info.IsDir() {
+ return false
+ }
+
+ // Check if directory is writable by creating a temporary file
+ tempFile := filepath.Join(dir, ".quilibrium_write_test")
+ file, err := os.Create(tempFile)
+ if err != nil {
+ return false
+ }
+ file.Close()
+ os.Remove(tempFile)
+ return true
+}
+
+// CanCreateAndWrite checks if we can create and write to a directory
+func CanCreateAndWrite(dir string) bool {
+ // Try to create the directory
+ if err := os.MkdirAll(dir, 0755); err != nil {
+ return false
+ }
+
+ // Check if we can write to it
+ return IsWritable(dir)
+}
+
+// FileExists checks if a file exists
+func FileExists(path string) bool {
+ _, err := os.Stat(path)
+ return !os.IsNotExist(err)
+}
+
+// GetConfigPath returns the path to the client configuration file
+func GetConfigPath(configDir string) string {
+ return filepath.Join(configDir, "config.yaml")
+}
+
+// IsClientConfigured checks if the client is configured
+func IsClientConfigured(configDir string) bool {
+ configPath := GetConfigPath(configDir)
+ return FileExists(configPath)
+}
diff --git a/client/utils/system.go b/client/utils/system.go
new file mode 100644
index 0000000..b76289d
--- /dev/null
+++ b/client/utils/system.go
@@ -0,0 +1,28 @@
+package utils
+
+import (
+ "fmt"
+ "runtime"
+)
+
+// GetSystemInfo determines and validates the OS and architecture
+func GetSystemInfo() (string, string, error) {
+ osType := runtime.GOOS
+ arch := runtime.GOARCH
+
+ // Check if OS type is supported
+ if osType != "darwin" && osType != "linux" {
+ return "", "", fmt.Errorf("unsupported operating system: %s", osType)
+ }
+
+ // Map Go architecture names to Quilibrium architecture names
+ if arch == "amd64" {
+ arch = "amd64"
+ } else if arch == "arm64" {
+ arch = "arm64"
+ } else {
+ return "", "", fmt.Errorf("unsupported architecture: %s", arch)
+ }
+
+ return osType, arch, nil
+}
diff --git a/client/utils/types.go b/client/utils/types.go
new file mode 100644
index 0000000..871af7d
--- /dev/null
+++ b/client/utils/types.go
@@ -0,0 +1,20 @@
+package utils
+
+type ClientConfig struct {
+ Version string
+ DataDir string
+ SymlinkPath string
+}
+
+type NodeConfig struct {
+ ClientConfig
+ DataDir string
+ User string
+}
+
+type ReleaseType string
+
+const (
+ ReleaseTypeQClient ReleaseType = "qclient"
+ ReleaseTypeNode ReleaseType = "node"
+)
diff --git a/client/utils/userInputUtils.go b/client/utils/userInputUtils.go
new file mode 100644
index 0000000..8bb7ac3
--- /dev/null
+++ b/client/utils/userInputUtils.go
@@ -0,0 +1,37 @@
+package utils
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "strings"
+)
+
+// ConfirmSymlinkOverwrite asks the user to confirm overwriting an existing symlink
+func ConfirmSymlinkOverwrite(path string) bool {
+ fmt.Printf("Symlink already exists at %s. Overwrite? [y/N]: ", path)
+ var response string
+ fmt.Scanln(&response)
+ return strings.ToLower(response) == "y"
+}
+
+// CheckAndRequestSudo checks if we have sudo privileges and requests them if needed
+func CheckAndRequestSudo(reason string) error {
+ // Check if we're already root
+ if os.Geteuid() == 0 {
+ return nil
+ }
+
+ // Check if sudo is available
+ if _, err := exec.LookPath("sudo"); err != nil {
+ return fmt.Errorf("sudo is not available: %w", err)
+ }
+
+ // Request sudo privileges
+ cmd := exec.Command("sudo", "-v")
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("failed to get sudo privileges: %w", err)
+ }
+
+ return nil
+}
diff --git a/verenc/generated/verenc/verenc.c b/verenc/generated/verenc/verenc.c
new file mode 100644
index 0000000..b46e410
--- /dev/null
+++ b/verenc/generated/verenc/verenc.c
@@ -0,0 +1,8 @@
+#include
+
+// This file exists beacause of
+// https://github.com/golang/go/issues/11263
+
+void cgo_rust_task_callback_bridge_verenc(RustTaskCallback cb, const void * taskData, int8_t status) {
+ cb(taskData, status);
+}
\ No newline at end of file
diff --git a/verenc/generated/verenc/verenc.go b/verenc/generated/verenc/verenc.go
new file mode 100644
index 0000000..c36da21
--- /dev/null
+++ b/verenc/generated/verenc/verenc.go
@@ -0,0 +1,1055 @@
+package verenc
+
+// #include
+import "C"
+
+import (
+ "bytes"
+ "encoding/binary"
+ "fmt"
+ "io"
+ "math"
+ "unsafe"
+)
+
+type RustBuffer = C.RustBuffer
+
+type RustBufferI interface {
+ AsReader() *bytes.Reader
+ Free()
+ ToGoBytes() []byte
+ Data() unsafe.Pointer
+ Len() int
+ Capacity() int
+}
+
+func RustBufferFromExternal(b RustBufferI) RustBuffer {
+ return RustBuffer{
+ capacity: C.int(b.Capacity()),
+ len: C.int(b.Len()),
+ data: (*C.uchar)(b.Data()),
+ }
+}
+
+func (cb RustBuffer) Capacity() int {
+ return int(cb.capacity)
+}
+
+func (cb RustBuffer) Len() int {
+ return int(cb.len)
+}
+
+func (cb RustBuffer) Data() unsafe.Pointer {
+ return unsafe.Pointer(cb.data)
+}
+
+func (cb RustBuffer) AsReader() *bytes.Reader {
+ b := unsafe.Slice((*byte)(cb.data), C.int(cb.len))
+ return bytes.NewReader(b)
+}
+
+func (cb RustBuffer) Free() {
+ rustCall(func(status *C.RustCallStatus) bool {
+ C.ffi_verenc_rustbuffer_free(cb, status)
+ return false
+ })
+}
+
+func (cb RustBuffer) ToGoBytes() []byte {
+ return C.GoBytes(unsafe.Pointer(cb.data), C.int(cb.len))
+}
+
+func stringToRustBuffer(str string) RustBuffer {
+ return bytesToRustBuffer([]byte(str))
+}
+
+func bytesToRustBuffer(b []byte) RustBuffer {
+ if len(b) == 0 {
+ return RustBuffer{}
+ }
+ // We can pass the pointer along here, as it is pinned
+ // for the duration of this call
+ foreign := C.ForeignBytes{
+ len: C.int(len(b)),
+ data: (*C.uchar)(unsafe.Pointer(&b[0])),
+ }
+
+ return rustCall(func(status *C.RustCallStatus) RustBuffer {
+ return C.ffi_verenc_rustbuffer_from_bytes(foreign, status)
+ })
+}
+
+type BufLifter[GoType any] interface {
+ Lift(value RustBufferI) GoType
+}
+
+type BufLowerer[GoType any] interface {
+ Lower(value GoType) RustBuffer
+}
+
+type FfiConverter[GoType any, FfiType any] interface {
+ Lift(value FfiType) GoType
+ Lower(value GoType) FfiType
+}
+
+type BufReader[GoType any] interface {
+ Read(reader io.Reader) GoType
+}
+
+type BufWriter[GoType any] interface {
+ Write(writer io.Writer, value GoType)
+}
+
+type FfiRustBufConverter[GoType any, FfiType any] interface {
+ FfiConverter[GoType, FfiType]
+ BufReader[GoType]
+}
+
+func LowerIntoRustBuffer[GoType any](bufWriter BufWriter[GoType], value GoType) RustBuffer {
+ // This might be not the most efficient way but it does not require knowing allocation size
+ // beforehand
+ var buffer bytes.Buffer
+ bufWriter.Write(&buffer, value)
+
+ bytes, err := io.ReadAll(&buffer)
+ if err != nil {
+ panic(fmt.Errorf("reading written data: %w", err))
+ }
+ return bytesToRustBuffer(bytes)
+}
+
+func LiftFromRustBuffer[GoType any](bufReader BufReader[GoType], rbuf RustBufferI) GoType {
+ defer rbuf.Free()
+ reader := rbuf.AsReader()
+ item := bufReader.Read(reader)
+ if reader.Len() > 0 {
+ // TODO: Remove this
+ leftover, _ := io.ReadAll(reader)
+ panic(fmt.Errorf("Junk remaining in buffer after lifting: %s", string(leftover)))
+ }
+ return item
+}
+
+func rustCallWithError[U any](converter BufLifter[error], callback func(*C.RustCallStatus) U) (U, error) {
+ var status C.RustCallStatus
+ returnValue := callback(&status)
+ err := checkCallStatus(converter, status)
+
+ return returnValue, err
+}
+
+func checkCallStatus(converter BufLifter[error], status C.RustCallStatus) error {
+ switch status.code {
+ case 0:
+ return nil
+ case 1:
+ return converter.Lift(status.errorBuf)
+ case 2:
+ // when the rust code sees a panic, it tries to construct a rustbuffer
+ // with the message. but if that code panics, then it just sends back
+ // an empty buffer.
+ if status.errorBuf.len > 0 {
+ panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(status.errorBuf)))
+ } else {
+ panic(fmt.Errorf("Rust panicked while handling Rust panic"))
+ }
+ default:
+ return fmt.Errorf("unknown status code: %d", status.code)
+ }
+}
+
+func checkCallStatusUnknown(status C.RustCallStatus) error {
+ switch status.code {
+ case 0:
+ return nil
+ case 1:
+ panic(fmt.Errorf("function not returning an error returned an error"))
+ case 2:
+ // when the rust code sees a panic, it tries to construct a rustbuffer
+ // with the message. but if that code panics, then it just sends back
+ // an empty buffer.
+ if status.errorBuf.len > 0 {
+ panic(fmt.Errorf("%s", FfiConverterStringINSTANCE.Lift(status.errorBuf)))
+ } else {
+ panic(fmt.Errorf("Rust panicked while handling Rust panic"))
+ }
+ default:
+ return fmt.Errorf("unknown status code: %d", status.code)
+ }
+}
+
+func rustCall[U any](callback func(*C.RustCallStatus) U) U {
+ returnValue, err := rustCallWithError(nil, callback)
+ if err != nil {
+ panic(err)
+ }
+ return returnValue
+}
+
+func writeInt8(writer io.Writer, value int8) {
+ if err := binary.Write(writer, binary.BigEndian, value); err != nil {
+ panic(err)
+ }
+}
+
+func writeUint8(writer io.Writer, value uint8) {
+ if err := binary.Write(writer, binary.BigEndian, value); err != nil {
+ panic(err)
+ }
+}
+
+func writeInt16(writer io.Writer, value int16) {
+ if err := binary.Write(writer, binary.BigEndian, value); err != nil {
+ panic(err)
+ }
+}
+
+func writeUint16(writer io.Writer, value uint16) {
+ if err := binary.Write(writer, binary.BigEndian, value); err != nil {
+ panic(err)
+ }
+}
+
+func writeInt32(writer io.Writer, value int32) {
+ if err := binary.Write(writer, binary.BigEndian, value); err != nil {
+ panic(err)
+ }
+}
+
+func writeUint32(writer io.Writer, value uint32) {
+ if err := binary.Write(writer, binary.BigEndian, value); err != nil {
+ panic(err)
+ }
+}
+
+func writeInt64(writer io.Writer, value int64) {
+ if err := binary.Write(writer, binary.BigEndian, value); err != nil {
+ panic(err)
+ }
+}
+
+func writeUint64(writer io.Writer, value uint64) {
+ if err := binary.Write(writer, binary.BigEndian, value); err != nil {
+ panic(err)
+ }
+}
+
+func writeFloat32(writer io.Writer, value float32) {
+ if err := binary.Write(writer, binary.BigEndian, value); err != nil {
+ panic(err)
+ }
+}
+
+func writeFloat64(writer io.Writer, value float64) {
+ if err := binary.Write(writer, binary.BigEndian, value); err != nil {
+ panic(err)
+ }
+}
+
+func readInt8(reader io.Reader) int8 {
+ var result int8
+ if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
+ panic(err)
+ }
+ return result
+}
+
+func readUint8(reader io.Reader) uint8 {
+ var result uint8
+ if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
+ panic(err)
+ }
+ return result
+}
+
+func readInt16(reader io.Reader) int16 {
+ var result int16
+ if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
+ panic(err)
+ }
+ return result
+}
+
+func readUint16(reader io.Reader) uint16 {
+ var result uint16
+ if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
+ panic(err)
+ }
+ return result
+}
+
+func readInt32(reader io.Reader) int32 {
+ var result int32
+ if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
+ panic(err)
+ }
+ return result
+}
+
+func readUint32(reader io.Reader) uint32 {
+ var result uint32
+ if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
+ panic(err)
+ }
+ return result
+}
+
+func readInt64(reader io.Reader) int64 {
+ var result int64
+ if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
+ panic(err)
+ }
+ return result
+}
+
+func readUint64(reader io.Reader) uint64 {
+ var result uint64
+ if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
+ panic(err)
+ }
+ return result
+}
+
+func readFloat32(reader io.Reader) float32 {
+ var result float32
+ if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
+ panic(err)
+ }
+ return result
+}
+
+func readFloat64(reader io.Reader) float64 {
+ var result float64
+ if err := binary.Read(reader, binary.BigEndian, &result); err != nil {
+ panic(err)
+ }
+ return result
+}
+
+func init() {
+
+ uniffiCheckChecksums()
+}
+
+func uniffiCheckChecksums() {
+ // Get the bindings contract version from our ComponentInterface
+ bindingsContractVersion := 24
+ // Get the scaffolding contract version by calling the into the dylib
+ scaffoldingContractVersion := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint32_t {
+ return C.ffi_verenc_uniffi_contract_version(uniffiStatus)
+ })
+ if bindingsContractVersion != int(scaffoldingContractVersion) {
+ // If this happens try cleaning and rebuilding your project
+ panic("verenc: UniFFI contract version mismatch")
+ }
+ {
+ checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
+ return C.uniffi_verenc_checksum_func_chunk_data_for_verenc(uniffiStatus)
+ })
+ if checksum != 16794 {
+ // If this happens try cleaning and rebuilding your project
+ panic("verenc: uniffi_verenc_checksum_func_chunk_data_for_verenc: UniFFI API checksum mismatch")
+ }
+ }
+ {
+ checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
+ return C.uniffi_verenc_checksum_func_combine_chunked_data(uniffiStatus)
+ })
+ if checksum != 28541 {
+ // If this happens try cleaning and rebuilding your project
+ panic("verenc: uniffi_verenc_checksum_func_combine_chunked_data: UniFFI API checksum mismatch")
+ }
+ }
+ {
+ checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
+ return C.uniffi_verenc_checksum_func_new_verenc_proof(uniffiStatus)
+ })
+ if checksum != 7394 {
+ // If this happens try cleaning and rebuilding your project
+ panic("verenc: uniffi_verenc_checksum_func_new_verenc_proof: UniFFI API checksum mismatch")
+ }
+ }
+ {
+ checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
+ return C.uniffi_verenc_checksum_func_new_verenc_proof_encrypt_only(uniffiStatus)
+ })
+ if checksum != 17751 {
+ // If this happens try cleaning and rebuilding your project
+ panic("verenc: uniffi_verenc_checksum_func_new_verenc_proof_encrypt_only: UniFFI API checksum mismatch")
+ }
+ }
+ {
+ checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
+ return C.uniffi_verenc_checksum_func_verenc_compress(uniffiStatus)
+ })
+ if checksum != 11234 {
+ // If this happens try cleaning and rebuilding your project
+ panic("verenc: uniffi_verenc_checksum_func_verenc_compress: UniFFI API checksum mismatch")
+ }
+ }
+ {
+ checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
+ return C.uniffi_verenc_checksum_func_verenc_recover(uniffiStatus)
+ })
+ if checksum != 38626 {
+ // If this happens try cleaning and rebuilding your project
+ panic("verenc: uniffi_verenc_checksum_func_verenc_recover: UniFFI API checksum mismatch")
+ }
+ }
+ {
+ checksum := rustCall(func(uniffiStatus *C.RustCallStatus) C.uint16_t {
+ return C.uniffi_verenc_checksum_func_verenc_verify(uniffiStatus)
+ })
+ if checksum != 51440 {
+ // If this happens try cleaning and rebuilding your project
+ panic("verenc: uniffi_verenc_checksum_func_verenc_verify: UniFFI API checksum mismatch")
+ }
+ }
+}
+
+type FfiConverterUint8 struct{}
+
+var FfiConverterUint8INSTANCE = FfiConverterUint8{}
+
+func (FfiConverterUint8) Lower(value uint8) C.uint8_t {
+ return C.uint8_t(value)
+}
+
+func (FfiConverterUint8) Write(writer io.Writer, value uint8) {
+ writeUint8(writer, value)
+}
+
+func (FfiConverterUint8) Lift(value C.uint8_t) uint8 {
+ return uint8(value)
+}
+
+func (FfiConverterUint8) Read(reader io.Reader) uint8 {
+ return readUint8(reader)
+}
+
+type FfiDestroyerUint8 struct{}
+
+func (FfiDestroyerUint8) Destroy(_ uint8) {}
+
+type FfiConverterUint64 struct{}
+
+var FfiConverterUint64INSTANCE = FfiConverterUint64{}
+
+func (FfiConverterUint64) Lower(value uint64) C.uint64_t {
+ return C.uint64_t(value)
+}
+
+func (FfiConverterUint64) Write(writer io.Writer, value uint64) {
+ writeUint64(writer, value)
+}
+
+func (FfiConverterUint64) Lift(value C.uint64_t) uint64 {
+ return uint64(value)
+}
+
+func (FfiConverterUint64) Read(reader io.Reader) uint64 {
+ return readUint64(reader)
+}
+
+type FfiDestroyerUint64 struct{}
+
+func (FfiDestroyerUint64) Destroy(_ uint64) {}
+
+type FfiConverterBool struct{}
+
+var FfiConverterBoolINSTANCE = FfiConverterBool{}
+
+func (FfiConverterBool) Lower(value bool) C.int8_t {
+ if value {
+ return C.int8_t(1)
+ }
+ return C.int8_t(0)
+}
+
+func (FfiConverterBool) Write(writer io.Writer, value bool) {
+ if value {
+ writeInt8(writer, 1)
+ } else {
+ writeInt8(writer, 0)
+ }
+}
+
+func (FfiConverterBool) Lift(value C.int8_t) bool {
+ return value != 0
+}
+
+func (FfiConverterBool) Read(reader io.Reader) bool {
+ return readInt8(reader) != 0
+}
+
+type FfiDestroyerBool struct{}
+
+func (FfiDestroyerBool) Destroy(_ bool) {}
+
+type FfiConverterString struct{}
+
+var FfiConverterStringINSTANCE = FfiConverterString{}
+
+func (FfiConverterString) Lift(rb RustBufferI) string {
+ defer rb.Free()
+ reader := rb.AsReader()
+ b, err := io.ReadAll(reader)
+ if err != nil {
+ panic(fmt.Errorf("reading reader: %w", err))
+ }
+ return string(b)
+}
+
+func (FfiConverterString) Read(reader io.Reader) string {
+ length := readInt32(reader)
+ buffer := make([]byte, length)
+ read_length, err := reader.Read(buffer)
+ if err != nil {
+ panic(err)
+ }
+ if read_length != int(length) {
+ panic(fmt.Errorf("bad read length when reading string, expected %d, read %d", length, read_length))
+ }
+ return string(buffer)
+}
+
+func (FfiConverterString) Lower(value string) RustBuffer {
+ return stringToRustBuffer(value)
+}
+
+func (FfiConverterString) Write(writer io.Writer, value string) {
+ if len(value) > math.MaxInt32 {
+ panic("String is too large to fit into Int32")
+ }
+
+ writeInt32(writer, int32(len(value)))
+ write_length, err := io.WriteString(writer, value)
+ if err != nil {
+ panic(err)
+ }
+ if write_length != len(value) {
+ panic(fmt.Errorf("bad write length when writing string, expected %d, written %d", len(value), write_length))
+ }
+}
+
+type FfiDestroyerString struct{}
+
+func (FfiDestroyerString) Destroy(_ string) {}
+
+type CompressedCiphertext struct {
+ Ctexts []VerencCiphertext
+ Aux [][]uint8
+}
+
+func (r *CompressedCiphertext) Destroy() {
+ FfiDestroyerSequenceTypeVerencCiphertext{}.Destroy(r.Ctexts)
+ FfiDestroyerSequenceSequenceUint8{}.Destroy(r.Aux)
+}
+
+type FfiConverterTypeCompressedCiphertext struct{}
+
+var FfiConverterTypeCompressedCiphertextINSTANCE = FfiConverterTypeCompressedCiphertext{}
+
+func (c FfiConverterTypeCompressedCiphertext) Lift(rb RustBufferI) CompressedCiphertext {
+ return LiftFromRustBuffer[CompressedCiphertext](c, rb)
+}
+
+func (c FfiConverterTypeCompressedCiphertext) Read(reader io.Reader) CompressedCiphertext {
+ return CompressedCiphertext{
+ FfiConverterSequenceTypeVerencCiphertextINSTANCE.Read(reader),
+ FfiConverterSequenceSequenceUint8INSTANCE.Read(reader),
+ }
+}
+
+func (c FfiConverterTypeCompressedCiphertext) Lower(value CompressedCiphertext) RustBuffer {
+ return LowerIntoRustBuffer[CompressedCiphertext](c, value)
+}
+
+func (c FfiConverterTypeCompressedCiphertext) Write(writer io.Writer, value CompressedCiphertext) {
+ FfiConverterSequenceTypeVerencCiphertextINSTANCE.Write(writer, value.Ctexts)
+ FfiConverterSequenceSequenceUint8INSTANCE.Write(writer, value.Aux)
+}
+
+type FfiDestroyerTypeCompressedCiphertext struct{}
+
+func (_ FfiDestroyerTypeCompressedCiphertext) Destroy(value CompressedCiphertext) {
+ value.Destroy()
+}
+
+type VerencCiphertext struct {
+ C1 []uint8
+ C2 []uint8
+ I uint64
+}
+
+func (r *VerencCiphertext) Destroy() {
+ FfiDestroyerSequenceUint8{}.Destroy(r.C1)
+ FfiDestroyerSequenceUint8{}.Destroy(r.C2)
+ FfiDestroyerUint64{}.Destroy(r.I)
+}
+
+type FfiConverterTypeVerencCiphertext struct{}
+
+var FfiConverterTypeVerencCiphertextINSTANCE = FfiConverterTypeVerencCiphertext{}
+
+func (c FfiConverterTypeVerencCiphertext) Lift(rb RustBufferI) VerencCiphertext {
+ return LiftFromRustBuffer[VerencCiphertext](c, rb)
+}
+
+func (c FfiConverterTypeVerencCiphertext) Read(reader io.Reader) VerencCiphertext {
+ return VerencCiphertext{
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterUint64INSTANCE.Read(reader),
+ }
+}
+
+func (c FfiConverterTypeVerencCiphertext) Lower(value VerencCiphertext) RustBuffer {
+ return LowerIntoRustBuffer[VerencCiphertext](c, value)
+}
+
+func (c FfiConverterTypeVerencCiphertext) Write(writer io.Writer, value VerencCiphertext) {
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.C1)
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.C2)
+ FfiConverterUint64INSTANCE.Write(writer, value.I)
+}
+
+type FfiDestroyerTypeVerencCiphertext struct{}
+
+func (_ FfiDestroyerTypeVerencCiphertext) Destroy(value VerencCiphertext) {
+ value.Destroy()
+}
+
+type VerencDecrypt struct {
+ BlindingPubkey []uint8
+ DecryptionKey []uint8
+ Statement []uint8
+ Ciphertexts CompressedCiphertext
+}
+
+func (r *VerencDecrypt) Destroy() {
+ FfiDestroyerSequenceUint8{}.Destroy(r.BlindingPubkey)
+ FfiDestroyerSequenceUint8{}.Destroy(r.DecryptionKey)
+ FfiDestroyerSequenceUint8{}.Destroy(r.Statement)
+ FfiDestroyerTypeCompressedCiphertext{}.Destroy(r.Ciphertexts)
+}
+
+type FfiConverterTypeVerencDecrypt struct{}
+
+var FfiConverterTypeVerencDecryptINSTANCE = FfiConverterTypeVerencDecrypt{}
+
+func (c FfiConverterTypeVerencDecrypt) Lift(rb RustBufferI) VerencDecrypt {
+ return LiftFromRustBuffer[VerencDecrypt](c, rb)
+}
+
+func (c FfiConverterTypeVerencDecrypt) Read(reader io.Reader) VerencDecrypt {
+ return VerencDecrypt{
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterTypeCompressedCiphertextINSTANCE.Read(reader),
+ }
+}
+
+func (c FfiConverterTypeVerencDecrypt) Lower(value VerencDecrypt) RustBuffer {
+ return LowerIntoRustBuffer[VerencDecrypt](c, value)
+}
+
+func (c FfiConverterTypeVerencDecrypt) Write(writer io.Writer, value VerencDecrypt) {
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.BlindingPubkey)
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.DecryptionKey)
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.Statement)
+ FfiConverterTypeCompressedCiphertextINSTANCE.Write(writer, value.Ciphertexts)
+}
+
+type FfiDestroyerTypeVerencDecrypt struct{}
+
+func (_ FfiDestroyerTypeVerencDecrypt) Destroy(value VerencDecrypt) {
+ value.Destroy()
+}
+
+type VerencProof struct {
+ BlindingPubkey []uint8
+ EncryptionKey []uint8
+ Statement []uint8
+ Challenge []uint8
+ Polycom [][]uint8
+ Ctexts []VerencCiphertext
+ SharesRands []VerencShare
+}
+
+func (r *VerencProof) Destroy() {
+ FfiDestroyerSequenceUint8{}.Destroy(r.BlindingPubkey)
+ FfiDestroyerSequenceUint8{}.Destroy(r.EncryptionKey)
+ FfiDestroyerSequenceUint8{}.Destroy(r.Statement)
+ FfiDestroyerSequenceUint8{}.Destroy(r.Challenge)
+ FfiDestroyerSequenceSequenceUint8{}.Destroy(r.Polycom)
+ FfiDestroyerSequenceTypeVerencCiphertext{}.Destroy(r.Ctexts)
+ FfiDestroyerSequenceTypeVerencShare{}.Destroy(r.SharesRands)
+}
+
+type FfiConverterTypeVerencProof struct{}
+
+var FfiConverterTypeVerencProofINSTANCE = FfiConverterTypeVerencProof{}
+
+func (c FfiConverterTypeVerencProof) Lift(rb RustBufferI) VerencProof {
+ return LiftFromRustBuffer[VerencProof](c, rb)
+}
+
+func (c FfiConverterTypeVerencProof) Read(reader io.Reader) VerencProof {
+ return VerencProof{
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceTypeVerencCiphertextINSTANCE.Read(reader),
+ FfiConverterSequenceTypeVerencShareINSTANCE.Read(reader),
+ }
+}
+
+func (c FfiConverterTypeVerencProof) Lower(value VerencProof) RustBuffer {
+ return LowerIntoRustBuffer[VerencProof](c, value)
+}
+
+func (c FfiConverterTypeVerencProof) Write(writer io.Writer, value VerencProof) {
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.BlindingPubkey)
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.EncryptionKey)
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.Statement)
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.Challenge)
+ FfiConverterSequenceSequenceUint8INSTANCE.Write(writer, value.Polycom)
+ FfiConverterSequenceTypeVerencCiphertextINSTANCE.Write(writer, value.Ctexts)
+ FfiConverterSequenceTypeVerencShareINSTANCE.Write(writer, value.SharesRands)
+}
+
+type FfiDestroyerTypeVerencProof struct{}
+
+func (_ FfiDestroyerTypeVerencProof) Destroy(value VerencProof) {
+ value.Destroy()
+}
+
+type VerencProofAndBlindingKey struct {
+ BlindingKey []uint8
+ BlindingPubkey []uint8
+ DecryptionKey []uint8
+ EncryptionKey []uint8
+ Statement []uint8
+ Challenge []uint8
+ Polycom [][]uint8
+ Ctexts []VerencCiphertext
+ SharesRands []VerencShare
+}
+
+func (r *VerencProofAndBlindingKey) Destroy() {
+ FfiDestroyerSequenceUint8{}.Destroy(r.BlindingKey)
+ FfiDestroyerSequenceUint8{}.Destroy(r.BlindingPubkey)
+ FfiDestroyerSequenceUint8{}.Destroy(r.DecryptionKey)
+ FfiDestroyerSequenceUint8{}.Destroy(r.EncryptionKey)
+ FfiDestroyerSequenceUint8{}.Destroy(r.Statement)
+ FfiDestroyerSequenceUint8{}.Destroy(r.Challenge)
+ FfiDestroyerSequenceSequenceUint8{}.Destroy(r.Polycom)
+ FfiDestroyerSequenceTypeVerencCiphertext{}.Destroy(r.Ctexts)
+ FfiDestroyerSequenceTypeVerencShare{}.Destroy(r.SharesRands)
+}
+
+type FfiConverterTypeVerencProofAndBlindingKey struct{}
+
+var FfiConverterTypeVerencProofAndBlindingKeyINSTANCE = FfiConverterTypeVerencProofAndBlindingKey{}
+
+func (c FfiConverterTypeVerencProofAndBlindingKey) Lift(rb RustBufferI) VerencProofAndBlindingKey {
+ return LiftFromRustBuffer[VerencProofAndBlindingKey](c, rb)
+}
+
+func (c FfiConverterTypeVerencProofAndBlindingKey) Read(reader io.Reader) VerencProofAndBlindingKey {
+ return VerencProofAndBlindingKey{
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceTypeVerencCiphertextINSTANCE.Read(reader),
+ FfiConverterSequenceTypeVerencShareINSTANCE.Read(reader),
+ }
+}
+
+func (c FfiConverterTypeVerencProofAndBlindingKey) Lower(value VerencProofAndBlindingKey) RustBuffer {
+ return LowerIntoRustBuffer[VerencProofAndBlindingKey](c, value)
+}
+
+func (c FfiConverterTypeVerencProofAndBlindingKey) Write(writer io.Writer, value VerencProofAndBlindingKey) {
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.BlindingKey)
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.BlindingPubkey)
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.DecryptionKey)
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.EncryptionKey)
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.Statement)
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.Challenge)
+ FfiConverterSequenceSequenceUint8INSTANCE.Write(writer, value.Polycom)
+ FfiConverterSequenceTypeVerencCiphertextINSTANCE.Write(writer, value.Ctexts)
+ FfiConverterSequenceTypeVerencShareINSTANCE.Write(writer, value.SharesRands)
+}
+
+type FfiDestroyerTypeVerencProofAndBlindingKey struct{}
+
+func (_ FfiDestroyerTypeVerencProofAndBlindingKey) Destroy(value VerencProofAndBlindingKey) {
+ value.Destroy()
+}
+
+type VerencShare struct {
+ S1 []uint8
+ S2 []uint8
+ I uint64
+}
+
+func (r *VerencShare) Destroy() {
+ FfiDestroyerSequenceUint8{}.Destroy(r.S1)
+ FfiDestroyerSequenceUint8{}.Destroy(r.S2)
+ FfiDestroyerUint64{}.Destroy(r.I)
+}
+
+type FfiConverterTypeVerencShare struct{}
+
+var FfiConverterTypeVerencShareINSTANCE = FfiConverterTypeVerencShare{}
+
+func (c FfiConverterTypeVerencShare) Lift(rb RustBufferI) VerencShare {
+ return LiftFromRustBuffer[VerencShare](c, rb)
+}
+
+func (c FfiConverterTypeVerencShare) Read(reader io.Reader) VerencShare {
+ return VerencShare{
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterSequenceUint8INSTANCE.Read(reader),
+ FfiConverterUint64INSTANCE.Read(reader),
+ }
+}
+
+func (c FfiConverterTypeVerencShare) Lower(value VerencShare) RustBuffer {
+ return LowerIntoRustBuffer[VerencShare](c, value)
+}
+
+func (c FfiConverterTypeVerencShare) Write(writer io.Writer, value VerencShare) {
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.S1)
+ FfiConverterSequenceUint8INSTANCE.Write(writer, value.S2)
+ FfiConverterUint64INSTANCE.Write(writer, value.I)
+}
+
+type FfiDestroyerTypeVerencShare struct{}
+
+func (_ FfiDestroyerTypeVerencShare) Destroy(value VerencShare) {
+ value.Destroy()
+}
+
+type FfiConverterSequenceUint8 struct{}
+
+var FfiConverterSequenceUint8INSTANCE = FfiConverterSequenceUint8{}
+
+func (c FfiConverterSequenceUint8) Lift(rb RustBufferI) []uint8 {
+ return LiftFromRustBuffer[[]uint8](c, rb)
+}
+
+func (c FfiConverterSequenceUint8) Read(reader io.Reader) []uint8 {
+ length := readInt32(reader)
+ if length == 0 {
+ return nil
+ }
+ result := make([]uint8, 0, length)
+ for i := int32(0); i < length; i++ {
+ result = append(result, FfiConverterUint8INSTANCE.Read(reader))
+ }
+ return result
+}
+
+func (c FfiConverterSequenceUint8) Lower(value []uint8) RustBuffer {
+ return LowerIntoRustBuffer[[]uint8](c, value)
+}
+
+func (c FfiConverterSequenceUint8) Write(writer io.Writer, value []uint8) {
+ if len(value) > math.MaxInt32 {
+ panic("[]uint8 is too large to fit into Int32")
+ }
+
+ writeInt32(writer, int32(len(value)))
+ for _, item := range value {
+ FfiConverterUint8INSTANCE.Write(writer, item)
+ }
+}
+
+type FfiDestroyerSequenceUint8 struct{}
+
+func (FfiDestroyerSequenceUint8) Destroy(sequence []uint8) {
+ for _, value := range sequence {
+ FfiDestroyerUint8{}.Destroy(value)
+ }
+}
+
+type FfiConverterSequenceTypeVerencCiphertext struct{}
+
+var FfiConverterSequenceTypeVerencCiphertextINSTANCE = FfiConverterSequenceTypeVerencCiphertext{}
+
+func (c FfiConverterSequenceTypeVerencCiphertext) Lift(rb RustBufferI) []VerencCiphertext {
+ return LiftFromRustBuffer[[]VerencCiphertext](c, rb)
+}
+
+func (c FfiConverterSequenceTypeVerencCiphertext) Read(reader io.Reader) []VerencCiphertext {
+ length := readInt32(reader)
+ if length == 0 {
+ return nil
+ }
+ result := make([]VerencCiphertext, 0, length)
+ for i := int32(0); i < length; i++ {
+ result = append(result, FfiConverterTypeVerencCiphertextINSTANCE.Read(reader))
+ }
+ return result
+}
+
+func (c FfiConverterSequenceTypeVerencCiphertext) Lower(value []VerencCiphertext) RustBuffer {
+ return LowerIntoRustBuffer[[]VerencCiphertext](c, value)
+}
+
+func (c FfiConverterSequenceTypeVerencCiphertext) Write(writer io.Writer, value []VerencCiphertext) {
+ if len(value) > math.MaxInt32 {
+ panic("[]VerencCiphertext is too large to fit into Int32")
+ }
+
+ writeInt32(writer, int32(len(value)))
+ for _, item := range value {
+ FfiConverterTypeVerencCiphertextINSTANCE.Write(writer, item)
+ }
+}
+
+type FfiDestroyerSequenceTypeVerencCiphertext struct{}
+
+func (FfiDestroyerSequenceTypeVerencCiphertext) Destroy(sequence []VerencCiphertext) {
+ for _, value := range sequence {
+ FfiDestroyerTypeVerencCiphertext{}.Destroy(value)
+ }
+}
+
+type FfiConverterSequenceTypeVerencShare struct{}
+
+var FfiConverterSequenceTypeVerencShareINSTANCE = FfiConverterSequenceTypeVerencShare{}
+
+func (c FfiConverterSequenceTypeVerencShare) Lift(rb RustBufferI) []VerencShare {
+ return LiftFromRustBuffer[[]VerencShare](c, rb)
+}
+
+func (c FfiConverterSequenceTypeVerencShare) Read(reader io.Reader) []VerencShare {
+ length := readInt32(reader)
+ if length == 0 {
+ return nil
+ }
+ result := make([]VerencShare, 0, length)
+ for i := int32(0); i < length; i++ {
+ result = append(result, FfiConverterTypeVerencShareINSTANCE.Read(reader))
+ }
+ return result
+}
+
+func (c FfiConverterSequenceTypeVerencShare) Lower(value []VerencShare) RustBuffer {
+ return LowerIntoRustBuffer[[]VerencShare](c, value)
+}
+
+func (c FfiConverterSequenceTypeVerencShare) Write(writer io.Writer, value []VerencShare) {
+ if len(value) > math.MaxInt32 {
+ panic("[]VerencShare is too large to fit into Int32")
+ }
+
+ writeInt32(writer, int32(len(value)))
+ for _, item := range value {
+ FfiConverterTypeVerencShareINSTANCE.Write(writer, item)
+ }
+}
+
+type FfiDestroyerSequenceTypeVerencShare struct{}
+
+func (FfiDestroyerSequenceTypeVerencShare) Destroy(sequence []VerencShare) {
+ for _, value := range sequence {
+ FfiDestroyerTypeVerencShare{}.Destroy(value)
+ }
+}
+
+type FfiConverterSequenceSequenceUint8 struct{}
+
+var FfiConverterSequenceSequenceUint8INSTANCE = FfiConverterSequenceSequenceUint8{}
+
+func (c FfiConverterSequenceSequenceUint8) Lift(rb RustBufferI) [][]uint8 {
+ return LiftFromRustBuffer[[][]uint8](c, rb)
+}
+
+func (c FfiConverterSequenceSequenceUint8) Read(reader io.Reader) [][]uint8 {
+ length := readInt32(reader)
+ if length == 0 {
+ return nil
+ }
+ result := make([][]uint8, 0, length)
+ for i := int32(0); i < length; i++ {
+ result = append(result, FfiConverterSequenceUint8INSTANCE.Read(reader))
+ }
+ return result
+}
+
+func (c FfiConverterSequenceSequenceUint8) Lower(value [][]uint8) RustBuffer {
+ return LowerIntoRustBuffer[[][]uint8](c, value)
+}
+
+func (c FfiConverterSequenceSequenceUint8) Write(writer io.Writer, value [][]uint8) {
+ if len(value) > math.MaxInt32 {
+ panic("[][]uint8 is too large to fit into Int32")
+ }
+
+ writeInt32(writer, int32(len(value)))
+ for _, item := range value {
+ FfiConverterSequenceUint8INSTANCE.Write(writer, item)
+ }
+}
+
+type FfiDestroyerSequenceSequenceUint8 struct{}
+
+func (FfiDestroyerSequenceSequenceUint8) Destroy(sequence [][]uint8) {
+ for _, value := range sequence {
+ FfiDestroyerSequenceUint8{}.Destroy(value)
+ }
+}
+
+func ChunkDataForVerenc(data []uint8) [][]uint8 {
+ return FfiConverterSequenceSequenceUint8INSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
+ return C.uniffi_verenc_fn_func_chunk_data_for_verenc(FfiConverterSequenceUint8INSTANCE.Lower(data), _uniffiStatus)
+ }))
+}
+
+func CombineChunkedData(chunks [][]uint8) []uint8 {
+ return FfiConverterSequenceUint8INSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
+ return C.uniffi_verenc_fn_func_combine_chunked_data(FfiConverterSequenceSequenceUint8INSTANCE.Lower(chunks), _uniffiStatus)
+ }))
+}
+
+func NewVerencProof(data []uint8) VerencProofAndBlindingKey {
+ return FfiConverterTypeVerencProofAndBlindingKeyINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
+ return C.uniffi_verenc_fn_func_new_verenc_proof(FfiConverterSequenceUint8INSTANCE.Lower(data), _uniffiStatus)
+ }))
+}
+
+func NewVerencProofEncryptOnly(data []uint8, encryptionKeyBytes []uint8) VerencProofAndBlindingKey {
+ return FfiConverterTypeVerencProofAndBlindingKeyINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
+ return C.uniffi_verenc_fn_func_new_verenc_proof_encrypt_only(FfiConverterSequenceUint8INSTANCE.Lower(data), FfiConverterSequenceUint8INSTANCE.Lower(encryptionKeyBytes), _uniffiStatus)
+ }))
+}
+
+func VerencCompress(proof VerencProof) CompressedCiphertext {
+ return FfiConverterTypeCompressedCiphertextINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
+ return C.uniffi_verenc_fn_func_verenc_compress(FfiConverterTypeVerencProofINSTANCE.Lower(proof), _uniffiStatus)
+ }))
+}
+
+func VerencRecover(recovery VerencDecrypt) []uint8 {
+ return FfiConverterSequenceUint8INSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) RustBufferI {
+ return C.uniffi_verenc_fn_func_verenc_recover(FfiConverterTypeVerencDecryptINSTANCE.Lower(recovery), _uniffiStatus)
+ }))
+}
+
+func VerencVerify(proof VerencProof) bool {
+ return FfiConverterBoolINSTANCE.Lift(rustCall(func(_uniffiStatus *C.RustCallStatus) C.int8_t {
+ return C.uniffi_verenc_fn_func_verenc_verify(FfiConverterTypeVerencProofINSTANCE.Lower(proof), _uniffiStatus)
+ }))
+}
diff --git a/verenc/generated/verenc/verenc.h b/verenc/generated/verenc/verenc.h
new file mode 100644
index 0000000..f240c59
--- /dev/null
+++ b/verenc/generated/verenc/verenc.h
@@ -0,0 +1,439 @@
+
+
+// This file was autogenerated by some hot garbage in the `uniffi` crate.
+// Trust me, you don't want to mess with it!
+
+
+
+#include
+#include
+
+// The following structs are used to implement the lowest level
+// of the FFI, and thus useful to multiple uniffied crates.
+// We ensure they are declared exactly once, with a header guard, UNIFFI_SHARED_H.
+#ifdef UNIFFI_SHARED_H
+ // We also try to prevent mixing versions of shared uniffi header structs.
+ // If you add anything to the #else block, you must increment the version suffix in UNIFFI_SHARED_HEADER_V6
+ #ifndef UNIFFI_SHARED_HEADER_V6
+ #error Combining helper code from multiple versions of uniffi is not supported
+ #endif // ndef UNIFFI_SHARED_HEADER_V6
+#else
+#define UNIFFI_SHARED_H
+#define UNIFFI_SHARED_HEADER_V6
+// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
+// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️
+
+typedef struct RustBuffer {
+ int32_t capacity;
+ int32_t len;
+ uint8_t *data;
+} RustBuffer;
+
+typedef int32_t (*ForeignCallback)(uint64_t, int32_t, uint8_t *, int32_t, RustBuffer *);
+
+// Task defined in Rust that Go executes
+typedef void (*RustTaskCallback)(const void *, int8_t);
+
+// Callback to execute Rust tasks using a Go routine
+//
+// Args:
+// executor: ForeignExecutor lowered into a uint64_t value
+// delay: Delay in MS
+// task: RustTaskCallback to call
+// task_data: data to pass the task callback
+typedef int8_t (*ForeignExecutorCallback)(uint64_t, uint32_t, RustTaskCallback, void *);
+
+typedef struct ForeignBytes {
+ int32_t len;
+ const uint8_t *data;
+} ForeignBytes;
+
+// Error definitions
+typedef struct RustCallStatus {
+ int8_t code;
+ RustBuffer errorBuf;
+} RustCallStatus;
+
+// Continuation callback for UniFFI Futures
+typedef void (*RustFutureContinuation)(void * , int8_t);
+
+// ⚠️ Attention: If you change this #else block (ending in `#endif // def UNIFFI_SHARED_H`) you *must* ⚠️
+// ⚠️ increment the version suffix in all instances of UNIFFI_SHARED_HEADER_V6 in this file. ⚠️
+#endif // def UNIFFI_SHARED_H
+
+// Needed because we can't execute the callback directly from go.
+void cgo_rust_task_callback_bridge_verenc(RustTaskCallback, const void *, int8_t);
+
+int8_t uniffiForeignExecutorCallbackverenc(uint64_t, uint32_t, RustTaskCallback, void*);
+
+void uniffiFutureContinuationCallbackverenc(void*, int8_t);
+
+RustBuffer uniffi_verenc_fn_func_chunk_data_for_verenc(
+ RustBuffer data,
+ RustCallStatus* out_status
+);
+
+RustBuffer uniffi_verenc_fn_func_combine_chunked_data(
+ RustBuffer chunks,
+ RustCallStatus* out_status
+);
+
+RustBuffer uniffi_verenc_fn_func_new_verenc_proof(
+ RustBuffer data,
+ RustCallStatus* out_status
+);
+
+RustBuffer uniffi_verenc_fn_func_new_verenc_proof_encrypt_only(
+ RustBuffer data,
+ RustBuffer encryption_key_bytes,
+ RustCallStatus* out_status
+);
+
+RustBuffer uniffi_verenc_fn_func_verenc_compress(
+ RustBuffer proof,
+ RustCallStatus* out_status
+);
+
+RustBuffer uniffi_verenc_fn_func_verenc_recover(
+ RustBuffer recovery,
+ RustCallStatus* out_status
+);
+
+int8_t uniffi_verenc_fn_func_verenc_verify(
+ RustBuffer proof,
+ RustCallStatus* out_status
+);
+
+RustBuffer ffi_verenc_rustbuffer_alloc(
+ int32_t size,
+ RustCallStatus* out_status
+);
+
+RustBuffer ffi_verenc_rustbuffer_from_bytes(
+ ForeignBytes bytes,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rustbuffer_free(
+ RustBuffer buf,
+ RustCallStatus* out_status
+);
+
+RustBuffer ffi_verenc_rustbuffer_reserve(
+ RustBuffer buf,
+ int32_t additional,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_continuation_callback_set(
+ RustFutureContinuation callback,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_poll_u8(
+ void* handle,
+ void* uniffi_callback,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_cancel_u8(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_free_u8(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+uint8_t ffi_verenc_rust_future_complete_u8(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_poll_i8(
+ void* handle,
+ void* uniffi_callback,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_cancel_i8(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_free_i8(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+int8_t ffi_verenc_rust_future_complete_i8(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_poll_u16(
+ void* handle,
+ void* uniffi_callback,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_cancel_u16(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_free_u16(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+uint16_t ffi_verenc_rust_future_complete_u16(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_poll_i16(
+ void* handle,
+ void* uniffi_callback,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_cancel_i16(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_free_i16(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+int16_t ffi_verenc_rust_future_complete_i16(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_poll_u32(
+ void* handle,
+ void* uniffi_callback,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_cancel_u32(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_free_u32(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+uint32_t ffi_verenc_rust_future_complete_u32(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_poll_i32(
+ void* handle,
+ void* uniffi_callback,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_cancel_i32(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_free_i32(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+int32_t ffi_verenc_rust_future_complete_i32(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_poll_u64(
+ void* handle,
+ void* uniffi_callback,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_cancel_u64(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_free_u64(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+uint64_t ffi_verenc_rust_future_complete_u64(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_poll_i64(
+ void* handle,
+ void* uniffi_callback,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_cancel_i64(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_free_i64(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+int64_t ffi_verenc_rust_future_complete_i64(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_poll_f32(
+ void* handle,
+ void* uniffi_callback,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_cancel_f32(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_free_f32(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+float ffi_verenc_rust_future_complete_f32(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_poll_f64(
+ void* handle,
+ void* uniffi_callback,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_cancel_f64(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_free_f64(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+double ffi_verenc_rust_future_complete_f64(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_poll_pointer(
+ void* handle,
+ void* uniffi_callback,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_cancel_pointer(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_free_pointer(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void* ffi_verenc_rust_future_complete_pointer(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_poll_rust_buffer(
+ void* handle,
+ void* uniffi_callback,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_cancel_rust_buffer(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_free_rust_buffer(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+RustBuffer ffi_verenc_rust_future_complete_rust_buffer(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_poll_void(
+ void* handle,
+ void* uniffi_callback,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_cancel_void(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_free_void(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+void ffi_verenc_rust_future_complete_void(
+ void* handle,
+ RustCallStatus* out_status
+);
+
+uint16_t uniffi_verenc_checksum_func_chunk_data_for_verenc(
+ RustCallStatus* out_status
+);
+
+uint16_t uniffi_verenc_checksum_func_combine_chunked_data(
+ RustCallStatus* out_status
+);
+
+uint16_t uniffi_verenc_checksum_func_new_verenc_proof(
+ RustCallStatus* out_status
+);
+
+uint16_t uniffi_verenc_checksum_func_new_verenc_proof_encrypt_only(
+ RustCallStatus* out_status
+);
+
+uint16_t uniffi_verenc_checksum_func_verenc_compress(
+ RustCallStatus* out_status
+);
+
+uint16_t uniffi_verenc_checksum_func_verenc_recover(
+ RustCallStatus* out_status
+);
+
+uint16_t uniffi_verenc_checksum_func_verenc_verify(
+ RustCallStatus* out_status
+);
+
+uint32_t ffi_verenc_uniffi_contract_version(
+ RustCallStatus* out_status
+);
+
+
+