diff --git a/Dockerfile.release b/Dockerfile.release
index f6f3b2e..fec9d52 100644
--- a/Dockerfile.release
+++ b/Dockerfile.release
@@ -1,4 +1,4 @@
-FROM golang:1.20.14-alpine3.19 as build
+FROM golang:1.20.14-alpine3.19 AS build
ARG NODE_VERSION
ARG MAX_KEY_ID
diff --git a/Dockerfile.source b/Dockerfile.source
index 3ebfe58..89f9475 100644
--- a/Dockerfile.source
+++ b/Dockerfile.source
@@ -1,9 +1,16 @@
-FROM ubuntu:24.04 as build-base
+# syntax=docker.io/docker/dockerfile:1.7-labs
+FROM --platform=${BUILDPLATFORM} ubuntu:24.04 AS base
ENV PATH="${PATH}:/root/.cargo/bin/"
+ARG TARGETOS
+ARG TARGETARCH
+
# Install GMP 6.2 (6.3 which MacOS is using only available on Debian unstable)
-RUN apt-get update && apt-get install -y \
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/var/cache/apt,sharing=locked \
+ --mount=type=cache,target=/var/lib/apt,sharing=locked \
+ apt-get update && apt-get install -y \
build-essential \
curl \
git \
@@ -29,24 +36,30 @@ RUN wget https://raw.githubusercontent.com/emp-toolkit/emp-readme/master/scripts
RUN python install.py --install --tool --ot
-RUN cd emp-tool && sed -i 's/add_library(${NAME} SHARED ${sources})/add_library(${NAME} STATIC ${sources})/g' CMakeLists.txt && mkdir build && cd build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local && cd .. && make && make install && cd ..
-RUN cd emp-ot && mkdir build && cd build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local && cd .. && make && make install && cd ..
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ cd emp-tool && sed -i 's/add_library(${NAME} SHARED ${sources})/add_library(${NAME} STATIC ${sources})/g' CMakeLists.txt && mkdir build && cd build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local && cd .. && make && make install && cd ..
-RUN apt update && apt install -y wget && \
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ cd emp-ot && mkdir build && cd build && cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local && cd .. && make && make install && cd ..
+
+ARG GO_VERSION=1.23.5
+RUN --mount=type=cache,target=/usr/local,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ apt update && apt install -y wget && \
ARCH=$(dpkg --print-architecture) && \
case ${ARCH} in \
amd64) GOARCH=amd64 ;; \
arm64) GOARCH=arm64 ;; \
*) echo "Unsupported architecture: ${ARCH}" && exit 1 ;; \
esac && \
- wget https://go.dev/dl/go1.23.5.linux-${GOARCH}.tar.gz && \
+ wget https://go.dev/dl/go${GO_VERSION}.linux-${GOARCH}.tar.gz && \
rm -rf /usr/local/go && \
- tar -C /usr/local -xzf go1.23.5.linux-${GOARCH}.tar.gz && \
- rm go1.23.5.linux-${GOARCH}.tar.gz
+ tar -C /usr/local -xzf go${GO_VERSION}.linux-${GOARCH}.tar.gz && \
+ rm go${GO_VERSION}.linux-${GOARCH}.tar.gz
ENV PATH=$PATH:/usr/local/go/bin
-RUN git clone https://github.com/flintlib/flint.git && \
+RUN --mount=type=cache,target=/usr/local,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ git clone https://github.com/flintlib/flint.git && \
cd flint && \
git checkout flint-3.0 && \
./bootstrap.sh && \
@@ -64,65 +77,197 @@ RUN git clone https://github.com/flintlib/flint.git && \
COPY docker/rustup-init.sh /opt/rustup-init.sh
-RUN /opt/rustup-init.sh -y --profile minimal
+RUN --mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ /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
+RUN --mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ 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
+FROM base AS build
ENV GOEXPERIMENT=arenas
ENV QUILIBRIUM_SIGNATURE_CHECK=false
+# Install grpcurl before building the node and client
+# as to avoid needing to redo it on rebuilds
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
+
WORKDIR /opt/ceremonyclient
-COPY . .
+# Copy everything except node and client so as to avoid
+# invalidating the cache at this point on client or node rebuilds
-WORKDIR /opt/ceremonyclient/ferret
-RUN ./generate.sh
+COPY --exclude=node \
+ --exclude=client \
+ --exclude=sidecar . .
+
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ go mod download
## Generate Rust bindings for VDF
WORKDIR /opt/ceremonyclient/vdf
-RUN ./generate.sh
+
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ go mod download
+
+RUN --mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ ./generate.sh
+
+
+## Generate Rust bindings for Ferret
+WORKDIR /opt/ceremonyclient/ferret
+
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/bin,id=go-bin-${TARGETOS}-${TARGETARCH} \
+ go mod download
+
+RUN --mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/bin,id=go-bin-${TARGETOS}-${TARGETARCH} \
+ ./generate.sh
## Generate Rust bindings for BLS48581
WORKDIR /opt/ceremonyclient/bls48581
-RUN ./generate.sh
+
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
+ go mod download
+
+RUN --mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ ./generate.sh
## Generate Rust bindings for VerEnc
WORKDIR /opt/ceremonyclient/verenc
-RUN ./generate.sh
+
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ go mod download
+
+RUN --mount=type=cache,target=/root/.cargo,id=cargo-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/opt/ceremonyclient/target/,id=target-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ ./generate.sh
+
+FROM build AS build-node
# Build and install the node
+COPY ./node /opt/ceremonyclient/node
WORKDIR /opt/ceremonyclient/node
-RUN ./build.sh && cp node /usr/bin
-RUN go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ ./build.sh && cp node /usr/bin
+FROM build AS build-qclient
+ARG TARGETOS
+ARG TARGETARCH
# Build and install qclient
+COPY ./node /opt/ceremonyclient/node
+
+WORKDIR /opt/ceremonyclient/node
+
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ go mod download
+
+COPY ./client /opt/ceremonyclient/client
WORKDIR /opt/ceremonyclient/client
-RUN ./build.sh -o qclient && cp qclient /usr/bin
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ go mod download
+ARG BINARIES_DIR=/opt/ceremonyclient/target/release
+ENV CGO_ENABLED=1
+ENV CGO_LDFLAGS="-L/usr/local/lib -lflint -lgmp -lmpfr -ldl -lm -L$BINARIES_DIR -lvdf -lverenc -lbls48581 -static"
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/opt/ceremonyclient/target,id=target-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags "-linkmode 'external'" -o qclient
+RUN cp qclient /usr/bin
+
+FROM build AS build-sidecar
# Build and install sidecar
+COPY ./node /opt/ceremonyclient/node
+
+WORKDIR /opt/ceremonyclient/node
+
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ go mod download
+
+COPY ./sidecar /opt/ceremonyclient/sidecar
WORKDIR /opt/ceremonyclient/sidecar
-RUN ./build.sh -o sidecar && cp sidecar /usr/bin
+
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/root/.cache/go-build,id=go-build-${TARGETOS}-${TARGETARCH} \
+ go mod download
+
+RUN --mount=type=cache,target=/usr/local/,id=usr-local-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/go/pkg/mod,id=go-mod-${TARGETOS}-${TARGETARCH} \
+ --mount=type=cache,target=/opt/ceremonyclient/target,id=target-${TARGETOS}-${TARGETARCH} \
+ ./build.sh -o sidecar && cp sidecar /usr/bin
# Allows exporting single binary
-FROM scratch as qclient
-COPY --from=build /usr/bin/qclient /qclient
-ENTRYPOINT [ "/qclient" ]
-
-# Allows exporting single binary
-FROM scratch as sidecar
-COPY --from=build /usr/bin/sidecar /sidecar
+FROM scratch AS sidecar
+COPY --from=build-sidecar /usr/bin/sidecar /sidecar
ENTRYPOINT [ "/sidecar" ]
# Allows exporting single binary
FROM scratch AS node
-COPY --from=build /usr/bin/node /node
+COPY --from=build-node /usr/bin/node /node
ENTRYPOINT [ "/node" ]
+# Allows exporting single binary
+FROM scratch AS qclient-unix
+COPY --from=build-qclient /usr/bin/qclient /qclient
+ENTRYPOINT [ "/qclient" ]
+
+FROM qclient-unix AS qclient-linux
+FROM qclient-unix AS qclient-darwin
+FROM qclient-${TARGETOS} AS qclient
+
FROM ubuntu:24.04
RUN apt-get update && apt-get install libflint-dev -y
@@ -146,8 +291,8 @@ LABEL org.opencontainers.image.revision=$GIT_COMMIT
RUN apt-get update && apt-get install -y ca-certificates
-COPY --from=build /usr/bin/node /usr/local/bin
-COPY --from=build /opt/ceremonyclient/client/qclient /usr/local/bin
+COPY --from=build-node /usr/bin/node /usr/local/bin
+COPY --from=build-qclient /opt/ceremonyclient/client/qclient /usr/local/bin
WORKDIR /root
diff --git a/Dockerfile.source.dockerignore b/Dockerfile.source.dockerignore
new file mode 100644
index 0000000..3f1c209
--- /dev/null
+++ b/Dockerfile.source.dockerignore
@@ -0,0 +1,12 @@
+client/build/
+target/
+node/build/
+node/.vscode/
+.git
+.gitignore
+vdf/generated/
+bls48581/generated/
+ferret/generated/
+sidecar/generated/
+verenc/generated/
+Dockerfile*
\ No newline at end of file
diff --git a/Dockerfile.sourceavx512 b/Dockerfile.sourceavx512
index c5b1dc8..f94cb04 100644
--- a/Dockerfile.sourceavx512
+++ b/Dockerfile.sourceavx512
@@ -1,4 +1,4 @@
-FROM ubuntu:24.04 as build-base
+FROM ubuntu:24.04 AS build-base
ENV PATH="${PATH}:/root/.cargo/bin/"
@@ -61,7 +61,7 @@ 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
+FROM build-base AS build
ENV GOEXPERIMENT=arenas
ENV QUILIBRIUM_SIGNATURE_CHECK=false
@@ -90,7 +90,7 @@ WORKDIR /opt/ceremonyclient/client
RUN ./build.sh -o qclient && cp qclient /usr/bin
# Allows exporting single binary
-FROM scratch as qclient
+FROM scratch AS qclient
COPY --from=build /usr/bin/qclient /qclient
ENTRYPOINT [ "/qclient" ]
diff --git a/Taskfile.yaml b/Taskfile.yaml
index 575384e..03cc072 100644
--- a/Taskfile.yaml
+++ b/Taskfile.yaml
@@ -85,7 +85,7 @@ tasks:
- docker build --platform linux/amd64 -f Dockerfile.sourceavx512 --output node/build/amd64_avx512_linux --target=node .
build_qclient_amd64_linux:
- desc: Build the QClient node binary for AMD64 Linux. Outputs to node/build.
+ desc: Build the QClient node binary for AMD64 Linux. Outputs to client/build.
cmds:
- docker build --platform linux/amd64 -f Dockerfile.source --output client/build/amd64_linux --target=qclient .
@@ -142,3 +142,8 @@ tasks:
cmds:
- docker push ${QUILIBRIUM_IMAGE_NAME:-quilibrium}:{{.VERSION}}
- docker push ${QUILIBRIUM_IMAGE_NAME:-quilibrium}:latest
+
+ test:qclient:
+ desc: Test the Quilibrium docker image.
+ cmds:
+ - client/test/run_tests.sh -d 'ubuntu' -v '24.04'
diff --git a/client/build.sh b/client/build.sh
index 1d2757f..5d889b7 100755
--- a/client/build.sh
+++ b/client/build.sh
@@ -26,7 +26,8 @@ case "$os_type" in
fi
;;
"Linux")
- go build -ldflags "-linkmode 'external' -extldflags '-L$BINARIES_DIR -Wl,-Bstatic -lvdf -lbls48581 -lverenc -Wl,-Bdynamic -lstdc++ -ldl -lm -lflint -lgmp -lmpfr'" "$@"
+ export CGO_LDFLAGS="-L/usr/local/lib -lflint -lgmp -lmpfr -ldl -lm -L$BINARIES_DIR -lvdf -lverenc -lbls48581 -static"
+ go build -ldflags "-linkmode 'external'" "$@"
;;
*)
echo "Unsupported platform"
diff --git a/client/cmd/config.go b/client/cmd/config.go
deleted file mode 100644
index a0c4b7e..0000000
--- a/client/cmd/config.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package cmd
-
-import "github.com/spf13/cobra"
-
-var configCmd = &cobra.Command{
- Use: "config",
- Short: "Performs a configuration operation",
-}
-
-func init() {
- rootCmd.AddCommand(configCmd)
-}
diff --git a/client/cmd/config/config.go b/client/cmd/config/config.go
new file mode 100644
index 0000000..9895045
--- /dev/null
+++ b/client/cmd/config/config.go
@@ -0,0 +1,44 @@
+package config
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+var ConfigCmd = &cobra.Command{
+ Use: "config",
+ Short: "Performs a configuration operation",
+}
+
+var printConfigCmd = &cobra.Command{
+ Use: "print",
+ Short: "Print the current configuration",
+ Run: func(cmd *cobra.Command, args []string) {
+ config, err := utils.LoadClientConfig()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error reading config: %v\n", err)
+ os.Exit(1)
+ }
+
+ // Print the config in a readable format
+ fmt.Printf("Data Directory: %s\n", config.DataDir)
+ fmt.Printf("Symlink Path: %s\n", config.SymlinkPath)
+ fmt.Printf("Signature Check: %v\n", config.SignatureCheck)
+ },
+}
+
+var createDefaultConfigCmd = &cobra.Command{
+ Use: "create-default",
+ Short: "Create a default configuration file",
+ Run: func(cmd *cobra.Command, args []string) {
+ utils.CreateDefaultConfig()
+ },
+}
+
+func init() {
+ ConfigCmd.AddCommand(printConfigCmd)
+ ConfigCmd.AddCommand(createDefaultConfigCmd)
+}
diff --git a/client/cmd/config/signatureCheck.go b/client/cmd/config/signatureCheck.go
new file mode 100644
index 0000000..794a159
--- /dev/null
+++ b/client/cmd/config/signatureCheck.go
@@ -0,0 +1,58 @@
+package config
+
+import (
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+var signatureCheckCmd = &cobra.Command{
+ Use: "signature-check [true|false]",
+ Short: "Set signature check setting",
+ Long: `Set the signature check setting in the client configuration.
+When disabled, signature verification will be bypassed (not recommended for production use).
+Use 'true' to enable or 'false' to disable. If no argument is provided, it toggles the current setting.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ config, err := utils.LoadClientConfig()
+ if err != nil {
+ fmt.Printf("Error loading config: %v\n", err)
+ os.Exit(1)
+ }
+
+ if len(args) > 0 {
+ // Set the signature check based on the provided argument
+ value := strings.ToLower(args[0])
+ if value == "true" {
+ config.SignatureCheck = true
+ } else if value == "false" {
+ config.SignatureCheck = false
+ } else {
+ // If the value is not true or false, print error message and exit
+ fmt.Printf("Error: Invalid value '%s'. Please use 'true' or 'false'.\n", value)
+ os.Exit(1)
+ }
+ } else {
+ // Toggle the signature check setting if no arguments are provided
+ config.SignatureCheck = !config.SignatureCheck
+ }
+
+ // Save the updated config
+ if err := utils.SaveClientConfig(config); err != nil {
+ fmt.Printf("Error saving config: %v\n", err)
+ os.Exit(1)
+ }
+
+ status := "enabled"
+ if !config.SignatureCheck {
+ status = "disabled"
+ }
+ fmt.Printf("Signature check has been set to %s and will be persisted across future commands unless reset.\n", status)
+ },
+}
+
+func init() {
+ ConfigCmd.AddCommand(signatureCheckCmd)
+}
diff --git a/client/cmd/config/utils.go b/client/cmd/config/utils.go
new file mode 100644
index 0000000..d912156
--- /dev/null
+++ b/client/cmd/config/utils.go
@@ -0,0 +1 @@
+package config
diff --git a/client/cmd/download-signatures.go b/client/cmd/download-signatures.go
new file mode 100644
index 0000000..9d1e0a5
--- /dev/null
+++ b/client/cmd/download-signatures.go
@@ -0,0 +1,51 @@
+package cmd
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+ "source.quilibrium.com/quilibrium/monorepo/node/config"
+)
+
+var versionFlag string
+
+var downloadSignaturesCmd = &cobra.Command{
+ Use: "download-signatures",
+ Short: "Download signature files for the current binary",
+ Long: `Download signature files for the current binary. This command will download
+the digest file and all signature files needed for verification. If --version is specified,
+it will download signatures for that version. Otherwise, it will download signatures for
+the latest version.`,
+ Run: func(cmd *cobra.Command, args []string) {
+ var version string
+
+ if versionFlag != "" {
+ // Use specified version
+ version = versionFlag
+ } else {
+ // Get the current version
+ version = config.GetVersionString()
+ }
+
+ // Download signature files
+ if err := utils.DownloadReleaseSignatures(utils.ReleaseTypeQClient, version); err != nil {
+ fmt.Fprintf(os.Stderr, "Error downloading signature files: %v\n", err)
+ os.Exit(1)
+ }
+
+ fmt.Printf("Successfully downloaded signature files for version %s\n", version)
+ },
+}
+
+func init() {
+ downloadSignaturesCmd.Flags().StringVarP(
+ &versionFlag,
+ "version",
+ "v",
+ "",
+ "Version to download signatures for (defaults to latest version)",
+ )
+ rootCmd.AddCommand(downloadSignaturesCmd)
+}
diff --git a/client/cmd/link.go b/client/cmd/link.go
new file mode 100644
index 0000000..21aa579
--- /dev/null
+++ b/client/cmd/link.go
@@ -0,0 +1,100 @@
+package cmd
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+var symlinkPath = "/usr/local/bin/qclient"
+
+var linkCmd = &cobra.Command{
+ Use: "link",
+ Short: "Create a symlink to qclient in PATH",
+ Long: `Create a symlink to the qclient binary in the directory /usr/local/bin/.
+
+Example: qclient link`,
+ 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)
+ }
+
+ IsSudo := utils.IsSudo()
+ if IsSudo {
+ fmt.Println("Running as sudo, creating symlink at /usr/local/bin/qclient")
+ } else {
+ fmt.Println("Cannot create symlink at /usr/local/bin/qclient, please run this command with sudo")
+ os.Exit(1)
+ }
+
+ // Check if the current executable is in the expected location
+ expectedPrefix := utils.ClientDataPath
+
+ // Check if the current executable is in the expected location
+ if !strings.HasPrefix(execPath, expectedPrefix) {
+ fmt.Printf("Current executable is not in the expected location: %s\n", execPath)
+ fmt.Printf("Expected location should start with: %s\n", expectedPrefix)
+
+ // Ask user if they want to move it
+ fmt.Print("Would you like to move the executable to the standard location? (y/n): ")
+ var response string
+ fmt.Scanln(&response)
+
+ if strings.ToLower(response) == "y" || strings.ToLower(response) == "yes" {
+ if err := moveExecutableToStandardLocation(execPath); err != nil {
+ return fmt.Errorf("failed to move executable: %w", err)
+ }
+ // Update execPath to the new location
+ execPath, err = os.Executable()
+ if err != nil {
+ return fmt.Errorf("failed to get new executable path: %w", err)
+ }
+ } else {
+ fmt.Println("Continuing with current location...")
+ }
+ }
+
+ // Create the symlink (handles existing symlinks)
+ if err := utils.CreateSymlink(execPath, symlinkPath); err != nil {
+ return err
+ }
+
+ fmt.Printf("Symlink created at %s\n", symlinkPath)
+ return nil
+ },
+}
+
+func moveExecutableToStandardLocation(execPath string) error {
+ // Get the directory of the current executable
+ version, err := GetVersionInfo(false)
+ if err != nil {
+ return fmt.Errorf("failed to get version info: %w", err)
+ }
+ destDir := filepath.Join(utils.ClientDataPath, "bin", version.Version)
+
+ // Create the standard location directory if it doesn't exist
+ currentUser, err := utils.GetCurrentSudoUser()
+ if err != nil {
+ return fmt.Errorf("failed to get current user: %w", err)
+ }
+ if err := utils.ValidateAndCreateDir(destDir, currentUser); err != nil {
+ return fmt.Errorf("failed to create directory: %w", err)
+ }
+
+ // Move the executable to the standard location
+ if err := os.Rename(execPath, filepath.Join(destDir, StandardizedQClientFileName)); err != nil {
+ return fmt.Errorf("failed to move executable: %w", err)
+ }
+
+ return nil
+}
+
+func init() {
+ rootCmd.AddCommand(linkCmd)
+}
diff --git a/client/cmd/node/autoUpdate.go b/client/cmd/node/autoUpdate.go
new file mode 100644
index 0000000..06816fa
--- /dev/null
+++ b/client/cmd/node/autoUpdate.go
@@ -0,0 +1,296 @@
+package node
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+
+ "github.com/spf13/cobra"
+)
+
+// autoUpdateCmd represents the command to setup automatic updates
+var autoUpdateCmd = &cobra.Command{
+ Use: "auto-update [enable|disable|status]",
+ Short: "Setup automatic update checks",
+ Long: `Setup, remove, or check status of a cron job to automatically check for Quilibrium node updates every 10 minutes.
+
+This command will create, remove, or check a cron entry that runs 'qclient node update' every 10 minutes
+to check for and apply any available updates.
+
+Example:
+ # Setup automatic update checks
+ qclient node auto-update enable
+
+ # Remove automatic update checks
+ qclient node auto-update disable
+
+ # Check if automatic update is enabled
+ qclient node auto-update status`,
+ Run: func(cmd *cobra.Command, args []string) {
+ if len(args) != 1 || (args[0] != "enable" && args[0] != "disable" && args[0] != "status") {
+ fmt.Fprintf(os.Stderr, "Error: must specify 'enable', 'disable', or 'status'\n")
+ cmd.Help()
+ return
+ }
+
+ if args[0] == "enable" {
+ setupCronJob()
+ } else if args[0] == "disable" {
+ removeCronJob()
+ } else if args[0] == "status" {
+ checkAutoUpdateStatus()
+ }
+ },
+}
+
+func init() {
+ NodeCmd.AddCommand(autoUpdateCmd)
+}
+
+func setupCronJob() {
+ // Get full path to qclient executable
+ qclientPath, err := exec.LookPath("qclient")
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: qclient executable not found in PATH: %v\n", err)
+ fmt.Fprintf(os.Stderr, "Please ensure qclient is properly installed and in your PATH (use 'sudo qclient link')\n")
+ return
+ }
+
+ // Absolute path for qclient
+ qclientAbsPath, err := filepath.Abs(qclientPath)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error getting absolute path for qclient: %v\n", err)
+ return
+ }
+
+ // OS-specific setup
+ if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
+ setupUnixCron(qclientAbsPath)
+ } else {
+ fmt.Fprintf(os.Stderr, "Error: auto-update is only supported on macOS and Linux\n")
+ return
+ }
+}
+
+func removeCronJob() {
+ // OS-specific removal
+ if runtime.GOOS == "darwin" || runtime.GOOS == "linux" {
+ removeUnixCron()
+ } else {
+ fmt.Fprintf(os.Stderr, "Error: auto-update is only supported on macOS and Linux\n")
+ return
+ }
+}
+
+func isCrontabInstalled() bool {
+ // Check if crontab is installed
+ _, err := exec.LookPath("crontab")
+ return err == nil
+}
+
+func installCrontab() {
+ fmt.Fprintf(os.Stdout, "Installing cron package...\n")
+ // Install crontab
+ updateCmd := exec.Command("sudo", "apt-get", "update")
+ updateCmd.Stdout = nil
+ updateCmd.Stderr = nil
+ if err := updateCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error updating package lists: %v\n", err)
+ return
+ }
+
+ installCmd := exec.Command("sudo", "apt-get", "install", "-y", "cron")
+ installCmd.Stdout = nil
+ installCmd.Stderr = nil
+ if err := installCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error installing cron package: %v\n", err)
+ return
+ }
+
+ // verify crontab is installed
+ if isCrontabInstalled() {
+ fmt.Fprintf(os.Stdout, "Cron package installed\n")
+ } else {
+ fmt.Fprintf(os.Stderr, "Error: cron package not installed\n")
+ os.Exit(1)
+ }
+}
+
+func setupUnixCron(qclientPath string) {
+ if !isCrontabInstalled() {
+ fmt.Fprintf(os.Stdout, "Crontab command not found, attempting to install cron package...\n")
+ installCrontab()
+ }
+
+ fmt.Fprintf(os.Stdout, "Setting up cron job...\n")
+ // Create cron expression: run every 10 minutes
+ cronExpression := fmt.Sprintf("*/10 * * * * %s node update > /dev/null 2>&1", qclientPath)
+
+ // Check existing crontab
+ checkCmd := exec.Command("crontab", "-l")
+ checkOutput, err := checkCmd.CombinedOutput()
+
+ var currentCrontab string
+ if err != nil {
+ // If there's no crontab yet, this is fine, start with empty crontab
+ currentCrontab = ""
+ } else {
+ currentCrontab = string(checkOutput)
+ }
+
+ // Check if our update command is already in crontab
+ if strings.Contains(currentCrontab, "### qclient-auto-update") {
+ fmt.Fprintf(os.Stdout, "Automatic update check is already configured in crontab\n")
+ return
+ }
+
+ // Add new cron entry with indicators
+ newCrontab := currentCrontab
+ if strings.TrimSpace(newCrontab) != "" && !strings.HasSuffix(newCrontab, "\n") {
+ newCrontab += "\n"
+ }
+ newCrontab += "### qclient-auto-update\n" +
+ cronExpression + "\n" +
+ "### end-qclient-auto-update\n"
+
+ // Write to temporary file
+ tempFile, err := os.CreateTemp("", "qclient-crontab")
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error creating temporary file: %v\n", err)
+ return
+ }
+ defer os.Remove(tempFile.Name())
+
+ if _, err := tempFile.WriteString(newCrontab); err != nil {
+ fmt.Fprintf(os.Stderr, "Error writing to temporary file: %v\n", err)
+ return
+ }
+ tempFile.Close()
+
+ // Install new crontab
+ installCmd := exec.Command("crontab", tempFile.Name())
+ if err := installCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error installing crontab: %v\n", err)
+ return
+ }
+
+ fmt.Fprintf(os.Stdout, "Successfully configured cron job to check for updates every 10 minutes\n")
+ fmt.Fprintf(os.Stdout, "Added: %s\n", cronExpression)
+}
+
+func removeUnixCron() {
+ if !isCrontabInstalled() {
+ fmt.Fprintf(os.Stderr, "Error: crontab command not found\n")
+ return
+ }
+
+ fmt.Fprintf(os.Stdout, "Removing auto-update cron job...\n")
+
+ // Check existing crontab
+ checkCmd := exec.Command("crontab", "-l")
+ checkOutput, err := checkCmd.CombinedOutput()
+
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error checking existing crontab: %v\n", err)
+ return
+ }
+
+ currentCrontab := string(checkOutput)
+
+ // No crontab or doesn't contain our section
+ if currentCrontab == "" || !strings.Contains(currentCrontab, "### qclient-auto-update") {
+ fmt.Fprintf(os.Stdout, "No auto-update job found in crontab\n")
+ return
+ }
+
+ // Remove our section
+ startMarker := "### qclient-auto-update"
+ endMarker := "### end-qclient-auto-update"
+
+ startIdx := strings.Index(currentCrontab, startMarker)
+ endIdx := strings.Index(currentCrontab, endMarker)
+
+ var newCrontab string
+ if startIdx >= 0 && endIdx >= 0 {
+ endIdx += len(endMarker)
+ // Remove the section including markers
+ newCrontab = currentCrontab[:startIdx] + currentCrontab[endIdx:]
+ } else {
+ newCrontab = currentCrontab
+ }
+
+ // Clean up any leftover double newlines
+ newCrontab = strings.ReplaceAll(newCrontab, "\n\n\n", "\n\n")
+ if strings.TrimSpace(newCrontab) != "" && !strings.HasSuffix(newCrontab, "\n") {
+ newCrontab += "\n"
+ }
+
+ // Write to temporary file
+ tempFile, err := os.CreateTemp("", "qclient-crontab")
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error creating temporary file: %v\n", err)
+ return
+ }
+ defer os.Remove(tempFile.Name())
+
+ if _, err := tempFile.WriteString(newCrontab); err != nil {
+ fmt.Fprintf(os.Stderr, "Error writing to temporary file: %v\n", err)
+ return
+ }
+ tempFile.Close()
+
+ // Install new crontab
+ installCmd := exec.Command("crontab", tempFile.Name())
+ if err := installCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error updating crontab: %v\n", err)
+ return
+ }
+
+ fmt.Fprintf(os.Stdout, "Successfully removed auto-update cron job\n")
+}
+
+func checkAutoUpdateStatus() {
+ if !isCrontabInstalled() {
+ fmt.Fprintf(os.Stderr, "Error: crontab command not found\n")
+ fmt.Fprintf(os.Stdout, "Auto-update is not enabled (crontab not installed)\n")
+ return
+ }
+
+ // Check existing crontab
+ checkCmd := exec.Command("crontab", "-l")
+ checkOutput, err := checkCmd.CombinedOutput()
+
+ if err != nil {
+ fmt.Fprintf(os.Stdout, "Auto-update is not enabled (no crontab found)\n")
+ return
+ }
+
+ currentCrontab := string(checkOutput)
+
+ if strings.Contains(currentCrontab, "### qclient-auto-update") {
+ // Extract the cron expression
+ startMarker := "### qclient-auto-update"
+ endMarker := "### end-qclient-auto-update"
+
+ startIdx := strings.Index(currentCrontab, startMarker) + len(startMarker)
+ endIdx := strings.Index(currentCrontab, endMarker)
+
+ if startIdx >= 0 && endIdx >= 0 {
+ cronSection := currentCrontab[startIdx:endIdx]
+ cronLines := strings.Split(strings.TrimSpace(cronSection), "\n")
+ if len(cronLines) > 0 {
+ fmt.Fprintf(os.Stdout, "Auto-update is enabled.")
+ fmt.Fprintf(os.Stdout, "The installed schedule is: %s\n", strings.TrimSpace(cronLines[0]))
+ } else {
+ fmt.Fprintf(os.Stdout, "Auto-update is enabled\n")
+ }
+ } else {
+ fmt.Fprintf(os.Stdout, "Auto-update is enabled\n")
+ }
+ } else {
+ fmt.Fprintf(os.Stdout, "Auto-update is not enabled\n")
+ }
+}
diff --git a/client/cmd/node/clean.go b/client/cmd/node/clean.go
new file mode 100644
index 0000000..6fe3735
--- /dev/null
+++ b/client/cmd/node/clean.go
@@ -0,0 +1,10 @@
+package node
+
+// TODO: Implement a clean command that will remove old versions of the node,
+// signatures, and logs
+// qlient node clean
+// qlient node clean --all (all logs, old node binaries and signatures)
+// qlient node clean --logs (remove all logs)
+// qlient node clean --node (remove all old node binary versions, including signatures)
+
+// to remove even current versions, they must run 'qclient node uninstall'
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..fe56147
--- /dev/null
+++ b/client/cmd/node/install.go
@@ -0,0 +1,185 @@
+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 and create a service to run it.
+
+ ## Service Management
+
+ You can start, stop, and restart the service with:
+
+ qclient node service start
+ qclient node service stop
+ qclient node service restart
+ qclient node service status
+ qclient node service enable
+ qclient node service disable
+
+ ### Mac OS Notes
+
+ On Mac OS, the service is managed by launchd (installed by default)
+
+ ### Linux Notes
+
+ On Linux, the service is managed by systemd (will be installed by this command).
+
+ ## Config Management
+
+ A config directory will be created in the user's home directory at .quilibrium/configs/.
+
+ To create a default config, run the following command:
+
+ qclient node config create-default [name-for-config]
+
+ or, you can import existing configs one at a timefrom a directory:
+
+ qclient node config import [name-for-config] /path/to/src/config/dir/
+
+ You can then select the config to use when running the node with:
+
+ qclient node set-default [name-for-config]
+
+ ## Binary Management
+ Binaries and signatures are installed to /var/quilibrium/bin/node/[version]/
+
+ You can update the node binary with:
+
+ qclient node update [version]
+
+ ### Auto-update
+
+ You can enable auto-update with:
+
+ qclient node auto-update enable
+
+ You can disable auto-update with:
+
+ qclient node auto-update disable
+
+ You can check the auto-update status with:
+
+ qclient node auto-update status
+
+ ## Log Management
+ Logging uses system logging with logrotate installed by default.
+
+ Logs are installed to /var/log/quilibrium
+
+ The logrotate config is installed to /etc/logrotate.d/quilibrium
+
+ You can view the logs with:
+
+ qclient node logs [version]
+
+When installing with this command, if no version is specified, the latest version will be installed.
+
+Sudo is required to install the node to install the node binary, logging,systemd (on Linux), and create the config directory.
+
+Examples:
+
+ # Install the latest version
+ qclient node install
+
+ # Install a specific version
+ qclient node install 2.1.0
+`,
+ 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
+ }
+
+ if !utils.IsSudo() {
+ fmt.Println("This command must be run with sudo: sudo qclient node install")
+ fmt.Println("Sudo is required to install the node binary, logging, systemd (on Linux) service, and create the config directory.")
+ os.Exit(1)
+ }
+
+ // Determine version to install
+ version := determineVersion(args)
+
+ // 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, "Found latest version: %s\n", version)
+ }
+
+ // do a pre-flight check to ensure the release file exists
+ fileName := fmt.Sprintf("%s-%s-%s-%s", utils.ReleaseTypeNode, version, osType, arch)
+ url := fmt.Sprintf("%s/%s", utils.BaseReleaseURL, fileName)
+
+ if !utils.DoesRemoteFileExist(url) {
+ fmt.Printf("The release file %s does not exist on the release server\n", fileName)
+ os.Exit(1)
+ }
+
+ fmt.Fprintf(os.Stdout, "Installing Quilibrium node for %s-%s, version: %s\n", osType, arch, version)
+
+ // Install the node
+ InstallNode(version)
+ },
+}
+
+// installNode installs the Quilibrium node
+func InstallNode(version string) {
+ // Create installation directory
+ if err := utils.ValidateAndCreateDir(utils.NodeDataPath, NodeUser); err != nil {
+ fmt.Fprintf(os.Stderr, "Error creating installation directory: %v\n", err)
+ return
+ }
+
+ if utils.IsExistingNodeVersion(version) {
+ fmt.Fprintf(os.Stderr, "Error: Node version %s already exists\n", version)
+ os.Exit(1)
+ }
+
+ if err := InstallByVersion(version); err != nil {
+ fmt.Fprintf(os.Stderr, "Error installing specific version: %v\n", err)
+ os.Exit(1)
+ }
+
+ createService()
+
+ finishInstallation(version)
+}
+
+// installByVersion installs a specific version of the Quilibrium node
+func InstallByVersion(version string) error {
+
+ versionDir := filepath.Join(utils.NodeDataPath, version)
+ if err := utils.ValidateAndCreateDir(versionDir, NodeUser); 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/log/clean.go b/client/cmd/node/log/clean.go
new file mode 100644
index 0000000..101e149
--- /dev/null
+++ b/client/cmd/node/log/clean.go
@@ -0,0 +1,3 @@
+package log
+
+// TODO: Implement a log subcommand to clean the logs
diff --git a/client/cmd/node/log/log.go b/client/cmd/node/log/log.go
new file mode 100644
index 0000000..9310e83
--- /dev/null
+++ b/client/cmd/node/log/log.go
@@ -0,0 +1,3 @@
+package log
+
+// TODO: Implement a command to view and manage the logs
diff --git a/client/cmd/node/node.go b/client/cmd/node/node.go
new file mode 100644
index 0000000..7ee5e06
--- /dev/null
+++ b/client/cmd/node/node.go
@@ -0,0 +1,83 @@
+package node
+
+import (
+ "fmt"
+ "os"
+ "os/user"
+ "path/filepath"
+
+ "github.com/spf13/cobra"
+ configCmd "source.quilibrium.com/quilibrium/monorepo/client/cmd/node/nodeconfig"
+ proverCmd "source.quilibrium.com/quilibrium/monorepo/client/cmd/node/prover"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+ "source.quilibrium.com/quilibrium/monorepo/node/config"
+)
+
+var (
+ // Base URL for Quilibrium releases
+ baseReleaseURL = "https://releases.quilibrium.com"
+
+ // Default symlink path for the node binary
+ defaultSymlinkPath = "/usr/local/bin/quilibrium-node"
+ logPath = "/var/log/quilibrium"
+
+ ServiceName = "quilibrium-node"
+
+ OsType string
+ Arch string
+
+ configDirectory string
+ NodeConfig *config.Config
+
+ NodeUser *user.User
+ ConfigDirs string
+ NodeConfigToRun string
+)
+
+// NodeCmd represents the node command
+var NodeCmd = &cobra.Command{
+ Use: "node",
+ Short: "Quilibrium node commands",
+ Long: `Run Quilibrium node commands.`,
+ PersistentPreRun: func(cmd *cobra.Command, args []string) {
+ // Store reference to parent's PersistentPreRun to call it first
+ parent := cmd.Parent()
+ if parent != nil && parent.PersistentPreRun != nil {
+ parent.PersistentPreRun(parent, args)
+ }
+
+ // Then run the node-specific initialization
+ var userLookup *user.User
+ var err error
+ userLookup, err = utils.GetCurrentSudoUser()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error getting current user: %v\n", err)
+ os.Exit(1)
+ }
+ NodeUser = userLookup
+ ConfigDirs = filepath.Join(userLookup.HomeDir, ".quilibrium", "configs")
+ NodeConfigToRun = filepath.Join(userLookup.HomeDir, ".quilibrium", "configs", "default")
+ },
+ Run: func(cmd *cobra.Command, args []string) {
+ cmd.Help()
+ },
+}
+
+func init() {
+ NodeCmd.PersistentFlags().StringVar(&configDirectory, "config", ".config", "config directory (default is .config/)")
+
+ // Add subcommands
+ NodeCmd.AddCommand(InstallCmd)
+ NodeCmd.AddCommand(configCmd.ConfigCmd)
+ NodeCmd.AddCommand(updateNodeCmd)
+ NodeCmd.AddCommand(nodeServiceCmd)
+ NodeCmd.AddCommand(proverCmd.ProverCmd)
+ 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/nodeconfig/assign-rewards.go b/client/cmd/node/nodeconfig/assign-rewards.go
new file mode 100644
index 0000000..7d1a917
--- /dev/null
+++ b/client/cmd/node/nodeconfig/assign-rewards.go
@@ -0,0 +1,43 @@
+package nodeconfig
+
+// TODO: Implement a command to assign a config's rewards to a config's rewards address
+// Should be able to assign to a specific address or by name of a config directory
+// e.g. qclient node config assign-rewards my-config my-throwaway-config
+// this finds the address from my-throwaway-config and assigns it to my-config
+// qlient node config assign-rewards my-config --reset will reset the rewards address to the default address
+// or
+// qclient node config assign-rewards my-config --address 0x1234567890abcdef1234567890abcdef1234567890abcdef
+//
+// If no address is provided, the command will prompt for an address
+// the prompt should prompt clearly the user for each part, asking if
+// the user wants to use one of the config files as the source of the address
+// or if they want to enter a new address manually
+// if no configs are found locally, it should prompt the user to create a new config
+// or import one
+
+// i.e. Which config do you want to re-assign rewards?
+// (1) my-config
+// (2) my-other-config
+// (3) my-throwaway-config
+//
+// Enter the number of the config you want to re-assign rewards: 1
+// Finding address from my-config...
+// Successfully found address 0x1234567890abcdef1234567890abcdef1234567890abcdef
+// Which reward address do you want to assign to my-config?
+// (1) my-other-config
+// (2) my-throwaway-config
+// (3) Enter a new address manually
+// (4) Reset to default address
+//
+// Enter the number of the reward address you want to assign: 2
+//
+// Finding address from my-throwaway-config...
+// Successfully found address 0x1234567890abcdef1234567890abcdef1234567890abcdef
+// Assigning rewards from my-config to my-throwaway-config
+// Successfully assigned rewards.
+//
+// Summary:
+// Node address: 0x1234567890abcdef1234567890abcdef1234567890abcdef
+// Rewards address: 0x1234567890abcdef1234567890abcdef1234567890abcdef
+//
+// Successfully updated my-config
diff --git a/client/cmd/node/nodeconfig/config.go b/client/cmd/node/nodeconfig/config.go
new file mode 100644
index 0000000..895646c
--- /dev/null
+++ b/client/cmd/node/nodeconfig/config.go
@@ -0,0 +1,60 @@
+package nodeconfig
+
+import (
+ "fmt"
+ "os"
+ "os/user"
+ "path/filepath"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+var (
+ NodeUser *user.User
+ ConfigDirs string
+ NodeConfigToRun string
+ SetDefault bool
+)
+
+// ConfigCmd represents the node config command
+var ConfigCmd = &cobra.Command{
+ Use: "config",
+ Short: "Manage node configuration",
+ Long: `Manage Quilibrium node configuration.
+
+This command provides utilities for configuring your Quilibrium node, such as:
+- Setting configuration values
+- Setting default configuration
+- Creating default configuration
+- Importing configuration
+`,
+ PersistentPreRun: func(cmd *cobra.Command, args []string) {
+ // Check if the config directory exists
+ user, err := utils.GetCurrentSudoUser()
+ if err != nil {
+ fmt.Println("Error getting current user:", err)
+ os.Exit(1)
+ }
+ ConfigDirs = filepath.Join(user.HomeDir, ".quilibrium", "configs")
+ NodeConfigToRun = filepath.Join(user.HomeDir, ".quilibrium", "configs", "default")
+ if utils.FileExists(ConfigDirs) {
+ utils.ValidateAndCreateDir(ConfigDirs, user)
+ }
+ },
+ Run: func(cmd *cobra.Command, args []string) {
+ cmd.Help()
+ },
+}
+
+func init() {
+ importCmd.Flags().BoolVarP(&SetDefault, "default", "d", false, "Select this config as the default")
+ ConfigCmd.AddCommand(importCmd)
+
+ ConfigCmd.AddCommand(SwitchConfigCmd)
+
+ createCmd.Flags().BoolVarP(&SetDefault, "default", "d", false, "Select this config as the default")
+ ConfigCmd.AddCommand(createCmd)
+ ConfigCmd.AddCommand(setCmd)
+
+}
diff --git a/client/cmd/node/nodeconfig/create.go b/client/cmd/node/nodeconfig/create.go
new file mode 100644
index 0000000..140e55c
--- /dev/null
+++ b/client/cmd/node/nodeconfig/create.go
@@ -0,0 +1,101 @@
+package nodeconfig
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+var createCmd = &cobra.Command{
+ Use: "create [name]",
+ Short: "Create a default configuration file set for a node",
+ Long: fmt.Sprintf(`Create a default configuration by running quilibrium-node with --peer-id and --config flags, then symlink it to the default configuration.
+
+Example:
+ qclient node config create
+ qclient node config create myconfig
+
+ qclient node config create myconfig --default
+
+The first example will create a new configuration at %s/default-config.
+The second example will create a new configuration at %s/myconfig.
+The third example will create a new configuration at %s/myconfig and symlink it so the node will use it.`,
+ ConfigDirs, ConfigDirs, ConfigDirs),
+ Args: cobra.MaximumNArgs(1),
+ Run: func(cmd *cobra.Command, args []string) {
+ // Determine the config name (default-config or user-provided)
+ var configName string
+ if len(args) > 0 {
+ configName = args[0]
+ } else {
+ // Prompt for a name if none provided
+ fmt.Print("Enter a name for the configuration (cannot be 'default'): ")
+ fmt.Scanln(&configName)
+
+ if configName == "" {
+ configName = "default-config"
+ }
+ }
+
+ // Check if trying to use "default" which is reserved for the symlink
+ if configName == "default" {
+ fmt.Println("Error: 'default' is reserved for the symlink. Please use a different name.")
+ os.Exit(1)
+ }
+
+ // Construct the configuration directory path
+ configDir := filepath.Join(ConfigDirs, configName)
+
+ // Create directory if it doesn't exist
+ if err := utils.ValidateAndCreateDir(configDir, NodeUser); err != nil {
+ fmt.Printf("Failed to create config directory: %s\n", err)
+ os.Exit(1)
+ }
+
+ // Run quilibrium-node command to generate config
+ // this is a hack to get the config files to be created
+ // TODO: fix this
+ // to fix this, we need to extrapolate the Node's config and keystore construction
+ // and reuse it for this command
+ nodeCmd := exec.Command("quilibrium-node", "--peer-id", "--config", configDir)
+ nodeCmd.Stdout = os.Stdout
+ nodeCmd.Stderr = os.Stderr
+
+ fmt.Printf("Running quilibrium-node to generate configuration in %s...\n", configName)
+ if err := nodeCmd.Run(); err != nil {
+ // Check if the error is due to the command not being found
+ if exitErr, ok := err.(*exec.ExitError); ok {
+ fmt.Printf("Error running quilibrium-node: %s\n", exitErr)
+ } else if _, ok := err.(*exec.Error); ok {
+ fmt.Printf("Error: quilibrium-node command not found. Please ensure it is installed and in your PATH.\n")
+ } else {
+ fmt.Printf("Error: %s\n", err)
+ }
+ os.RemoveAll(configDir)
+ os.Exit(1)
+ }
+
+ // Check if the configuration was created successfully
+ if !HasConfigFiles(configDir) {
+ fmt.Printf("Failed to generate configuration files in: %s\n", configDir)
+ os.Exit(1)
+ }
+
+ if SetDefault {
+ // Create the symlink
+ if err := utils.CreateSymlink(configDir, NodeConfigToRun); err != nil {
+ fmt.Printf("Failed to create symlink: %s\n", err)
+ os.Exit(1)
+ }
+
+ fmt.Printf("Successfully created %s configuration and symlinked to default\n", configName)
+ } else {
+ fmt.Printf("Successfully created %s configuration\n", configName)
+ }
+ fmt.Println("The keys.yml file will only contain 'null:' until the node is started.")
+ },
+}
diff --git a/client/cmd/node/nodeconfig/import.go b/client/cmd/node/nodeconfig/import.go
new file mode 100644
index 0000000..6b10529
--- /dev/null
+++ b/client/cmd/node/nodeconfig/import.go
@@ -0,0 +1,78 @@
+package nodeconfig
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+var importCmd = &cobra.Command{
+ Use: "import [name] [source_directory]",
+ Short: "Import config.yml and keys.yml from a source directory",
+ Long: `Import config.yml and keys.yml from a source directory to the QuilibriumRoot config folder.
+
+Example:
+ qclient node config import mynode /path/to/source
+
+This will copy config.yml and keys.yml from /path/to/source to /home/quilibrium/configs/mynode/`,
+ Args: cobra.ExactArgs(2),
+ Run: func(cmd *cobra.Command, args []string) {
+ name := args[0]
+ sourceDir := args[1]
+
+ // Check if source directory exists
+ if _, err := os.Stat(sourceDir); os.IsNotExist(err) {
+ fmt.Printf("Source directory does not exist: %s\n", sourceDir)
+ os.Exit(1)
+ }
+
+ if !HasConfigFiles(sourceDir) {
+ fmt.Printf("Source directory does not contain both config.yml and keys.yml files: %s\n", sourceDir)
+ os.Exit(1)
+ }
+
+ // Create target directory in the standard location
+ targetDir := filepath.Join(ConfigDirs, name)
+ if err := utils.ValidateAndCreateDir(targetDir, NodeUser); err != nil {
+ fmt.Printf("Failed to create target directory: %s\n", err)
+ os.Exit(1)
+ }
+
+ // Define source file paths
+ sourceConfigPath := filepath.Join(sourceDir, "config.yml")
+ sourceKeysPath := filepath.Join(sourceDir, "keys.yml")
+
+ // Copy config.yml
+ targetConfigPath := filepath.Join(targetDir, "config.yml")
+ if err := utils.CopyFile(sourceConfigPath, targetConfigPath); err != nil {
+ fmt.Printf("Failed to copy config.yml: %s\n", err)
+ os.Exit(1)
+ }
+
+ // Copy keys.yml
+ targetKeysPath := filepath.Join(targetDir, "keys.yml")
+ if err := utils.CopyFile(sourceKeysPath, targetKeysPath); err != nil {
+ fmt.Printf("Failed to copy keys.yml: %s\n", err)
+ os.Exit(1)
+ }
+
+ if SetDefault {
+ // Create the symlink
+ if err := utils.CreateSymlink(targetDir, NodeConfigToRun); err != nil {
+ fmt.Printf("Failed to create symlink: %s\n", err)
+ os.Exit(1)
+ }
+
+ fmt.Printf("Successfully imported config files to %s and symlinked to default\n", name)
+ } else {
+ fmt.Printf("Successfully imported config files to %s\n", targetDir)
+ }
+ },
+}
+
+func init() {
+
+}
diff --git a/client/cmd/node/nodeconfig/set.go b/client/cmd/node/nodeconfig/set.go
new file mode 100644
index 0000000..fac7435
--- /dev/null
+++ b/client/cmd/node/nodeconfig/set.go
@@ -0,0 +1,82 @@
+package nodeconfig
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/node/config"
+)
+
+var setCmd = &cobra.Command{
+ Use: "set [name] [key] [value]",
+ Short: "Set a configuration value",
+ Long: `Set a configuration value in the node config.yml file.
+
+Example:
+ qclient node config set mynode engine.statsMultiaddr /dns/stats.quilibrium.com/tcp/443
+`,
+ Args: cobra.ExactArgs(3),
+ Run: func(cmd *cobra.Command, args []string) {
+ name := args[0]
+ key := args[1]
+ value := args[2]
+
+ // Construct the config directory path
+ configDir := filepath.Join(ConfigDirs, name)
+ configFile := filepath.Join(configDir, "config.yml")
+
+ // Check if config directory exists
+ if _, err := os.Stat(configDir); os.IsNotExist(err) {
+ fmt.Printf("Config directory does not exist: %s\n", configDir)
+ os.Exit(1)
+ }
+
+ // Check if config file exists
+ if _, err := os.Stat(configFile); os.IsNotExist(err) {
+ fmt.Printf("Config file does not exist: %s\n", configFile)
+ os.Exit(1)
+ }
+
+ // Load the config
+ cfg, err := config.LoadConfig(configDir, "", false)
+ if err != nil {
+ fmt.Printf("Failed to load config: %s\n", err)
+ os.Exit(1)
+ }
+
+ // Update the config based on the key
+ switch key {
+ case "engine.statsMultiaddr":
+ cfg.Engine.StatsMultiaddr = value
+ case "p2p.listenMultiaddr":
+ cfg.P2P.ListenMultiaddr = value
+ case "listenGrpcMultiaddr":
+ cfg.ListenGRPCMultiaddr = value
+ case "listenRestMultiaddr":
+ cfg.ListenRestMultiaddr = value
+ case "engine.autoMergeCoins":
+ if value == "true" {
+ cfg.Engine.AutoMergeCoins = true
+ } else if value == "false" {
+ cfg.Engine.AutoMergeCoins = false
+ } else {
+ fmt.Printf("Invalid value for %s: must be 'true' or 'false'\n", key)
+ os.Exit(1)
+ }
+ default:
+ fmt.Printf("Unsupported configuration key: %s\n", key)
+ fmt.Println("Supported keys: engine.statsMultiaddr, p2p.listenMultiaddr, listenGrpcMultiaddr, listenRestMultiaddr, engine.autoMergeCoins")
+ os.Exit(1)
+ }
+
+ // Save the updated config
+ if err := config.SaveConfig(configDir, cfg); err != nil {
+ fmt.Printf("Failed to save config: %s\n", err)
+ os.Exit(1)
+ }
+
+ fmt.Printf("Successfully updated %s to %s in %s\n", key, value, configFile)
+ },
+}
diff --git a/client/cmd/node/nodeconfig/switch.go b/client/cmd/node/nodeconfig/switch.go
new file mode 100644
index 0000000..4545bd0
--- /dev/null
+++ b/client/cmd/node/nodeconfig/switch.go
@@ -0,0 +1,82 @@
+package nodeconfig
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+var SwitchConfigCmd = &cobra.Command{
+ Use: "switch [name]",
+ Short: "Switch the config to be run by the node",
+ Long: fmt.Sprintf(`Switch the configuration to be run by the node by creating a symlink.
+
+Example:
+ qclient node config switch mynode
+
+This will symlink %s/mynode to %s`, ConfigDirs, NodeConfigToRun),
+ Args: cobra.ExactArgs(1),
+ Run: func(cmd *cobra.Command, args []string) {
+ var name string
+ if len(args) > 0 {
+ name = args[0]
+ } else {
+ // List available configurations
+ configs, err := ListConfigurations()
+ if err != nil {
+ fmt.Printf("Error listing configurations: %s\n", err)
+ os.Exit(1)
+ }
+
+ if len(configs) == 0 {
+ fmt.Println("No configurations found. Create one with 'qclient node config create'")
+ os.Exit(1)
+ }
+
+ fmt.Println("Available configurations:")
+ for i, config := range configs {
+ fmt.Printf("%d. %s\n", i+1, config)
+ }
+
+ // Prompt for choice
+ var choice int
+ fmt.Print("Enter the number of the configuration to set as default: ")
+ _, err = fmt.Scanf("%d", &choice)
+ if err != nil || choice < 1 || choice > len(configs) {
+ fmt.Println("Invalid choice. Please enter a valid number.")
+ os.Exit(1)
+ }
+
+ name = configs[choice-1]
+ }
+
+ // Construct the source directory path
+ sourceDir := filepath.Join(ConfigDirs, name)
+
+ // Check if source directory exists
+ if _, err := os.Stat(sourceDir); os.IsNotExist(err) {
+ fmt.Printf("Config directory does not exist: %s\n", sourceDir)
+ os.Exit(1)
+ }
+
+ // Check if the source directory has both config.yml and keys.yml files
+ if !HasConfigFiles(sourceDir) {
+ fmt.Printf("Source directory does not contain both config.yml and keys.yml files: %s\n", sourceDir)
+ os.Exit(1)
+ }
+
+ // Construct the default directory path
+ defaultDir := filepath.Join(ConfigDirs, "default")
+
+ // Create the symlink
+ if err := utils.CreateSymlink(sourceDir, defaultDir); err != nil {
+ fmt.Printf("Failed to create symlink: %s\n", err)
+ os.Exit(1)
+ }
+
+ fmt.Printf("Successfully set %s as the default configuration\n", name)
+ },
+}
diff --git a/client/cmd/node/nodeconfig/utils.go b/client/cmd/node/nodeconfig/utils.go
new file mode 100644
index 0000000..a255c89
--- /dev/null
+++ b/client/cmd/node/nodeconfig/utils.go
@@ -0,0 +1,42 @@
+package nodeconfig
+
+import (
+ "os"
+ "path/filepath"
+)
+
+// HasConfigFiles checks if a directory contains both config.yml and keys.yml files
+func HasConfigFiles(dirPath string) bool {
+ configPath := filepath.Join(dirPath, "config.yml")
+ keysPath := filepath.Join(dirPath, "keys.yml")
+
+ // Check if both files exist
+ configExists := false
+ keysExists := false
+
+ if _, err := os.Stat(configPath); !os.IsNotExist(err) {
+ configExists = true
+ }
+
+ if _, err := os.Stat(keysPath); !os.IsNotExist(err) {
+ keysExists = true
+ }
+
+ return configExists && keysExists
+}
+
+func ListConfigurations() ([]string, error) {
+ files, err := os.ReadDir(ConfigDirs)
+ if err != nil {
+ return nil, err
+ }
+
+ configs := make([]string, 0)
+ for _, file := range files {
+ if file.IsDir() {
+ configs = append(configs, file.Name())
+ }
+ }
+
+ return configs, nil
+}
diff --git a/client/cmd/bridged.json b/client/cmd/node/prover/premainnet-data/bridged.json
similarity index 100%
rename from client/cmd/bridged.json
rename to client/cmd/node/prover/premainnet-data/bridged.json
diff --git a/client/cmd/first_retro.json b/client/cmd/node/prover/premainnet-data/first_retro.json
similarity index 100%
rename from client/cmd/first_retro.json
rename to client/cmd/node/prover/premainnet-data/first_retro.json
diff --git a/client/cmd/fourth_retro.json b/client/cmd/node/prover/premainnet-data/fourth_retro.json
similarity index 100%
rename from client/cmd/fourth_retro.json
rename to client/cmd/node/prover/premainnet-data/fourth_retro.json
diff --git a/client/cmd/second_retro.json b/client/cmd/node/prover/premainnet-data/second_retro.json
similarity index 100%
rename from client/cmd/second_retro.json
rename to client/cmd/node/prover/premainnet-data/second_retro.json
diff --git a/client/cmd/third_retro.json b/client/cmd/node/prover/premainnet-data/third_retro.json
similarity index 100%
rename from client/cmd/third_retro.json
rename to client/cmd/node/prover/premainnet-data/third_retro.json
diff --git a/client/cmd/node/prover/prover.go b/client/cmd/node/prover/prover.go
new file mode 100644
index 0000000..4d19f2c
--- /dev/null
+++ b/client/cmd/node/prover/prover.go
@@ -0,0 +1,19 @@
+package prover
+
+import (
+ "github.com/spf13/cobra"
+)
+
+var ConfigDirectory string
+
+var ProverCmd = &cobra.Command{
+ Use: "prover",
+ Short: "Performs a configuration operation for given prover info",
+ Run: func(cmd *cobra.Command, args []string) {},
+}
+
+func init() {
+ ProverCmd.PersistentFlags().StringVar(&ConfigDirectory, "config", ".config", "config directory (default is .config/)")
+ ProverCmd.AddCommand(proverPauseCmd)
+ ProverCmd.AddCommand(proverConfigMergeCmd)
+}
diff --git a/client/cmd/proverMerge.go b/client/cmd/node/prover/proverMerge.go
similarity index 63%
rename from client/cmd/proverMerge.go
rename to client/cmd/node/prover/proverMerge.go
index d527554..968f210 100644
--- a/client/cmd/proverMerge.go
+++ b/client/cmd/node/prover/proverMerge.go
@@ -1,22 +1,21 @@
-package cmd
+package prover
import (
_ "embed"
- "encoding/hex"
"encoding/json"
"fmt"
"os"
"path"
"strconv"
- "github.com/libp2p/go-libp2p/core/crypto"
- "github.com/libp2p/go-libp2p/core/peer"
- "github.com/pkg/errors"
"github.com/shopspring/decimal"
"github.com/spf13/cobra"
- "source.quilibrium.com/quilibrium/monorepo/node/config"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+ nodeConfig "source.quilibrium.com/quilibrium/monorepo/node/config"
)
+var DryRun bool
+
var proverConfigMergeCmd = &cobra.Command{
Use: "merge",
Short: "Merges config data for prover seniority",
@@ -32,35 +31,35 @@ var proverConfigMergeCmd = &cobra.Command{
os.Exit(1)
}
- primaryConfig, err := config.LoadConfig(args[0], "", true)
+ primaryConfig, err := nodeConfig.LoadConfig(args[0], "", true)
if err != nil {
fmt.Printf("invalid config directory: %s\n", args[0])
os.Exit(1)
}
otherPaths := []string{}
- peerIds := []string{GetPeerIDFromConfig(primaryConfig).String()}
+ peerIds := []string{utils.GetPeerIDFromConfig(primaryConfig).String()}
for _, p := range args[1:] {
if !path.IsAbs(p) {
fmt.Printf("%s is not an absolute path\n", p)
}
- cfg, err := config.LoadConfig(p, "", true)
+ cfg, err := nodeConfig.LoadConfig(p, "", true)
if err != nil {
fmt.Printf("invalid config directory: %s\n", p)
os.Exit(1)
}
- peerId := GetPeerIDFromConfig(cfg).String()
+ peerId := utils.GetPeerIDFromConfig(cfg).String()
peerIds = append(peerIds, peerId)
otherPaths = append(otherPaths, p)
}
if DryRun {
- bridged := []*BridgedPeerJson{}
- firstRetro := []*FirstRetroJson{}
- secondRetro := []*SecondRetroJson{}
- thirdRetro := []*ThirdRetroJson{}
- fourthRetro := []*FourthRetroJson{}
+ bridged := []*utils.BridgedPeerJson{}
+ firstRetro := []*utils.FirstRetroJson{}
+ secondRetro := []*utils.SecondRetroJson{}
+ thirdRetro := []*utils.ThirdRetroJson{}
+ fourthRetro := []*utils.FourthRetroJson{}
err = json.Unmarshal(bridgedPeersJsonBinary, &bridged)
if err != nil {
@@ -210,7 +209,7 @@ var proverConfigMergeCmd = &cobra.Command{
p,
)
}
- err := config.SaveConfig(args[0], primaryConfig)
+ err := nodeConfig.SaveConfig(args[0], primaryConfig)
if err != nil {
panic(err)
}
@@ -218,82 +217,22 @@ var proverConfigMergeCmd = &cobra.Command{
},
}
-func GetPrivKeyFromConfig(cfg *config.Config) (crypto.PrivKey, error) {
- peerPrivKey, err := hex.DecodeString(cfg.P2P.PeerPrivKey)
- if err != nil {
- panic(errors.Wrap(err, "error unmarshaling peerkey"))
- }
-
- privKey, err := crypto.UnmarshalEd448PrivateKey(peerPrivKey)
- return privKey, err
-}
-
-func GetPeerIDFromConfig(cfg *config.Config) peer.ID {
- peerPrivKey, err := hex.DecodeString(cfg.P2P.PeerPrivKey)
- if err != nil {
- panic(errors.Wrap(err, "error unmarshaling peerkey"))
- }
-
- privKey, err := crypto.UnmarshalEd448PrivateKey(peerPrivKey)
- if err != nil {
- panic(errors.Wrap(err, "error unmarshaling peerkey"))
- }
-
- pub := privKey.GetPublic()
- id, err := peer.IDFromPublicKey(pub)
- if err != nil {
- panic(errors.Wrap(err, "error getting peer id"))
- }
-
- return id
-}
-
-type BridgedPeerJson struct {
- Amount string `json:"amount"`
- Identifier string `json:"identifier"`
- Variant string `json:"variant"`
-}
-
-type FirstRetroJson struct {
- PeerId string `json:"peerId"`
- Reward string `json:"reward"`
-}
-
-type SecondRetroJson struct {
- PeerId string `json:"peerId"`
- Reward string `json:"reward"`
- JanPresence bool `json:"janPresence"`
- FebPresence bool `json:"febPresence"`
- MarPresence bool `json:"marPresence"`
- AprPresence bool `json:"aprPresence"`
- MayPresence bool `json:"mayPresence"`
-}
-
-type ThirdRetroJson struct {
- PeerId string `json:"peerId"`
- Reward string `json:"reward"`
-}
-
-type FourthRetroJson struct {
- PeerId string `json:"peerId"`
- Reward string `json:"reward"`
-}
-
-//go:embed bridged.json
+//go:embed premainnet-data/bridged.json
var bridgedPeersJsonBinary []byte
-//go:embed first_retro.json
+//go:embed premainnet-data/first_retro.json
var firstRetroJsonBinary []byte
-//go:embed second_retro.json
+//go:embed premainnet-data/second_retro.json
var secondRetroJsonBinary []byte
-//go:embed third_retro.json
+//go:embed premainnet-data/third_retro.json
var thirdRetroJsonBinary []byte
-//go:embed fourth_retro.json
+//go:embed premainnet-data/fourth_retro.json
var fourthRetroJsonBinary []byte
func init() {
- proverCmd.AddCommand(proverConfigMergeCmd)
+ proverConfigMergeCmd.Flags().BoolVar(&DryRun, "dry-run", false, "Evaluate seniority score without merging configs")
+ ProverCmd.AddCommand(proverConfigMergeCmd)
}
diff --git a/client/cmd/proverPause.go b/client/cmd/node/prover/proverPause.go
similarity index 87%
rename from client/cmd/proverPause.go
rename to client/cmd/node/prover/proverPause.go
index 91b0a4f..30c780b 100644
--- a/client/cmd/proverPause.go
+++ b/client/cmd/node/prover/proverPause.go
@@ -1,8 +1,9 @@
-package cmd
+package prover
import (
"bytes"
"fmt"
+ "os"
"strings"
"time"
@@ -13,12 +14,16 @@ import (
"go.uber.org/zap"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/types/known/anypb"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
"source.quilibrium.com/quilibrium/monorepo/go-libp2p-blossomsub/pb"
+ nodeConfig "source.quilibrium.com/quilibrium/monorepo/node/config"
"source.quilibrium.com/quilibrium/monorepo/node/execution/intrinsics/token/application"
"source.quilibrium.com/quilibrium/monorepo/node/p2p"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
)
+var NodeConfig *nodeConfig.Config
+
var proverPauseCmd = &cobra.Command{
Use: "pause",
Short: "Pauses a prover",
@@ -28,13 +33,23 @@ var proverPauseCmd = &cobra.Command{
`,
Run: func(cmd *cobra.Command, args []string) {
logger, err := zap.NewProduction()
+ if err != nil {
+ panic(err)
+ }
+
+ NodeConfig, err = utils.LoadNodeConfig(ConfigDirectory)
+ if err != nil {
+ fmt.Printf("invalid config directory: %s\n", ConfigDirectory)
+ os.Exit(1)
+ }
+
pubsub := p2p.NewBlossomSub(NodeConfig.P2P, logger)
intrinsicFilter := p2p.GetBloomFilter(application.TOKEN_ADDRESS, 256, 3)
pubsub.Subscribe(
append([]byte{0x00}, intrinsicFilter...),
func(message *pb.Message) error { return nil },
)
- key, err := GetPrivKeyFromConfig(NodeConfig)
+ key, err := utils.GetPrivKeyFromConfig(NodeConfig)
if err != nil {
panic(err)
}
@@ -145,5 +160,5 @@ func publishMessage(
}
func init() {
- proverCmd.AddCommand(proverPauseCmd)
+ ProverCmd.AddCommand(proverPauseCmd)
}
diff --git a/client/cmd/node/service.go b/client/cmd/node/service.go
new file mode 100644
index 0000000..1984acb
--- /dev/null
+++ b/client/cmd/node/service.go
@@ -0,0 +1,493 @@
+package node
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "text/template"
+
+ "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" {
+ // launchctl is already installed on macOS by default, so no need to check for it
+ installMacOSService()
+ } else if OsType == "linux" {
+ // systemd is not installed on linux by default, so we need to check for it
+ if !utils.CheckForSystemd() {
+ // install systemd if not found
+ installSystemd()
+ }
+ 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")
+}
+
+func installSystemd() {
+ fmt.Fprintf(os.Stdout, "Installing systemd...\n")
+ updateCmd := exec.Command("sudo", "apt-get", "update")
+ updateCmd.Stdout = nil
+ updateCmd.Stderr = nil
+ if err := updateCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error updating package lists: %v\n", err)
+ return
+ }
+
+ installCmd := exec.Command("sudo", "apt-get", "install", "-y", "systemd")
+ installCmd.Stdout = nil
+ installCmd.Stderr = nil
+ if err := installCmd.Run(); err != nil {
+ fmt.Fprintf(os.Stderr, "Error installing systemd: %v\n", err)
+ return
+ }
+}
+
+// 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")
+}
+
+func createService() {
+ // 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
+ }
+}
+
+// createSystemdServiceFile creates the systemd service file with environment file support
+func createSystemdServiceFile() error {
+ if !utils.CheckForSystemd() {
+ installSystemd()
+ }
+
+ // 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 := `# Quilibrium Node Environment`
+
+ // Write environment file
+ envPath := filepath.Join(utils.RootQuilibriumPath, "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 := utils.ChownPath(envPath, NodeUser, false)
+ if chownCmd != nil {
+ return fmt.Errorf("failed to set environment file ownership: %w", chownCmd)
+ }
+
+ // 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 %s
+Restart=on-failure
+RestartSec=10
+LimitNOFILE=65535
+
+[Install]
+WantedBy=multi-user.target
+`, ConfigDirs+"/default")
+
+ // Write service file
+ servicePath := "/etc/systemd/system/quilibrium-node.service"
+ if err := utils.WriteFileAuto(servicePath, serviceContent); err != nil {
+ return fmt.Errorf("failed to create service file: %w", err)
+ }
+
+ // Reload systemd daemon
+ reloadCmd := exec.Command("sudo", "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: utils.NodeDataPath,
+ 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/shared.go b/client/cmd/node/shared.go
new file mode 100644
index 0000000..4fe6a89
--- /dev/null
+++ b/client/cmd/node/shared.go
@@ -0,0 +1,152 @@
+package node
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "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"
+// }
+
+// setOwnership sets the ownership of directories to the node user
+func setOwnership() {
+
+ // Change ownership of installation directory
+ err := utils.ChownPath(utils.NodeDataPath, NodeUser, true)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: Failed to change ownership of %s: %v\n", utils.NodeDataPath, 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.Username, NodeUser.Username)
+
+ // Write the configuration file
+ configPath := "/etc/logrotate.d/quilibrium-node"
+ if err := utils.WriteFile(configPath, configContent); err != nil {
+ return fmt.Errorf("failed to create logrotate configuration: %w", err)
+ }
+
+ // Create log directory with proper permissions
+ if err := utils.ValidateAndCreateDir(logPath, NodeUser); err != nil {
+ return fmt.Errorf("failed to create log directory: %w", err)
+ }
+
+ // Set ownership of log directory
+ err := utils.ChownPath(logPath, NodeUser, true)
+ if 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(version string) {
+ setOwnership()
+
+ normalizedBinaryName := "node-" + version + "-" + OsType + "-" + Arch
+
+ // Finish installation
+ nodeBinaryPath := filepath.Join(utils.NodeDataPath, version, normalizedBinaryName)
+ fmt.Printf("Making binary executable: %s\n", nodeBinaryPath)
+ // Make the binary executable
+ if err := utils.ChmodPath(nodeBinaryPath, 0755, "executable"); err != nil {
+ fmt.Fprintf(os.Stderr, "Warning: Failed to make binary executable: %v\n", err)
+ }
+
+ // 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)
+ }
+
+ // 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, "Binary download directory: %s\n", filepath.Join(utils.NodeDataPath, version))
+ 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, "\nConfiguration:\n")
+ fmt.Fprintf(os.Stdout, " To create a new configuration:\n")
+ fmt.Fprintf(os.Stdout, " qclient node config create [name] --default\n")
+ fmt.Fprintf(os.Stdout, " quilibrium-node --peer-id %s/default-config\n", ConfigDirs)
+
+ fmt.Fprintf(os.Stdout, "\n To use an existing configuration:\n")
+ fmt.Fprintf(os.Stdout, " cp -r /path/to/your/existing/config %s/default-config\n", ConfigDirs)
+ fmt.Fprintf(os.Stdout, " # Or modify the service file to point to your existing config:\n")
+ fmt.Fprintf(os.Stdout, " sudo nano /etc/systemd/system/quilibrium-node.service\n")
+ fmt.Fprintf(os.Stdout, " # Then reload systemd:\n")
+ fmt.Fprintf(os.Stdout, " sudo systemctl daemon-reload\n")
+
+ fmt.Fprintf(os.Stdout, "\nTo select a configuration:\n")
+ fmt.Fprintf(os.Stdout, " qclient node config switch \n")
+ fmt.Fprintf(os.Stdout, " # Or use the --default flag when creating a config to automatically select it:\n")
+ fmt.Fprintf(os.Stdout, " qclient node config create --default\n")
+
+ fmt.Fprintf(os.Stdout, "\nTo manually start the node (must create a config first), you can run:\n")
+ fmt.Fprintf(os.Stdout, " %s --config %s/myconfig/\n",
+ ServiceName, ConfigDirs)
+ fmt.Fprintf(os.Stdout, " # Or use systemd service using the default config:\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")
+}
diff --git a/client/cmd/node/uninstall.go b/client/cmd/node/uninstall.go
new file mode 100644
index 0000000..9c93775
--- /dev/null
+++ b/client/cmd/node/uninstall.go
@@ -0,0 +1,10 @@
+package node
+
+// TODO: Implement a command to uninstall the current and previous versions
+// of the node and all files, not including user data
+// this should NEVER delete the user data, only the node files and logs
+
+// prompt the user for confirmation or a --force flag to skip the confirmation
+
+// qlient node uninstall
+// qlient node uninstall --force (skip the confirmation)
diff --git a/client/cmd/node/update.go b/client/cmd/node/update.go
new file mode 100644
index 0000000..718becc
--- /dev/null
+++ b/client/cmd/node/update.go
@@ -0,0 +1,68 @@
+package node
+
+import (
+ "fmt"
+ "os"
+
+ "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)
+
+ // 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, "Found latest version: %s\n", version)
+ }
+
+ if utils.IsExistingNodeVersion(version) {
+ fmt.Fprintf(os.Stderr, "Error: Node version %s already exists\n", version)
+ os.Exit(1)
+ }
+
+ 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", utils.NodeDataPath)); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ return
+ }
+
+ InstallNode(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/prover.go b/client/cmd/prover.go
deleted file mode 100644
index fdf91cd..0000000
--- a/client/cmd/prover.go
+++ /dev/null
@@ -1,12 +0,0 @@
-package cmd
-
-import "github.com/spf13/cobra"
-
-var proverCmd = &cobra.Command{
- Use: "prover",
- Short: "Performs a configuration operation for given prover info",
-}
-
-func init() {
- configCmd.AddCommand(proverCmd)
-}
diff --git a/client/cmd/root.go b/client/cmd/root.go
index 292a816..6548bef 100644
--- a/client/cmd/root.go
+++ b/client/cmd/root.go
@@ -1,37 +1,62 @@
package cmd
import (
+ "bufio"
"bytes"
- "crypto/tls"
"encoding/hex"
"fmt"
"os"
+ "path/filepath"
"strconv"
"strings"
"github.com/cloudflare/circl/sign/ed448"
- "github.com/multiformats/go-multiaddr"
- mn "github.com/multiformats/go-multiaddr/net"
"github.com/spf13/cobra"
"golang.org/x/crypto/sha3"
- "google.golang.org/grpc"
- "google.golang.org/grpc/credentials"
- "google.golang.org/grpc/credentials/insecure"
+
+ clientConfig "source.quilibrium.com/quilibrium/monorepo/client/cmd/config"
+ "source.quilibrium.com/quilibrium/monorepo/client/cmd/node"
+ "source.quilibrium.com/quilibrium/monorepo/client/cmd/token"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
"source.quilibrium.com/quilibrium/monorepo/node/config"
)
var configDirectory string
var signatureCheck bool = true
+var byPassSignatureCheck bool = false
var NodeConfig *config.Config
var simulateFail bool
-var LightNode bool = false
var DryRun bool = false
-var publicRPC bool = false
+var ClientConfig *utils.ClientConfig
+
+var StandardizedQClientFileName string = "qclient-" + config.GetVersionString() + "-" + osType + "-" + arch
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 cmd.Name() == "help" || cmd.Name() == "download-signatures" {
+ return
+ }
+
+ if !utils.FileExists(utils.GetConfigPath()) {
+ fmt.Println("Client config not found, creating default config")
+ utils.CreateDefaultConfig()
+ }
+
+ clientConfig, err := utils.LoadClientConfig()
+ if err != nil {
+ fmt.Printf("Error loading client config: %v\n", err)
+ os.Exit(1)
+ }
+
+ if !clientConfig.SignatureCheck || byPassSignatureCheck {
+ signatureCheck = false
+ }
+
if signatureCheck {
ex, err := os.Executable()
if err != nil {
@@ -48,114 +73,111 @@ var rootCmd = &cobra.Command{
}
checksum := sha3.Sum256(b)
- digest, err := os.ReadFile(ex + ".dgst")
+
+ // First check var data path for signatures
+ varDataPath := filepath.Join(utils.ClientDataPath, config.GetVersionString())
+ digestPath := filepath.Join(varDataPath, StandardizedQClientFileName+".dgst")
+
+ fmt.Printf("Checking signature for %s\n", digestPath)
+
+ // Try to read digest from var data path first
+ digest, err := os.ReadFile(digestPath)
if err != nil {
- fmt.Println("Digest file not found")
- os.Exit(1)
- }
-
- parts := strings.Split(string(digest), " ")
- if len(parts) != 2 {
- fmt.Println("Invalid digest file format")
- os.Exit(1)
- }
-
- 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)
+ // Fall back to checking next to executable
+ digest, err = os.ReadFile(ex + ".dgst")
if err != nil {
- continue
- }
+ fmt.Println("")
+ fmt.Println("The digest file was not found. Do you want to continue without signature verification? (y/n)")
- pubkey, _ := hex.DecodeString(config.Signatories[i-1])
- if !ed448.Verify(pubkey, digest, sig, "") {
- fmt.Printf("Failed signature check for signatory #%d\n", i)
+ reader := bufio.NewReader(os.Stdin)
+ response, _ := reader.ReadString('\n')
+ response = strings.ToLower(strings.TrimSpace(response))
+
+ if response != "y" && response != "yes" {
+ fmt.Println("Exiting due to missing digest file")
+ fmt.Println("The signature files (if they exist) can be downloaded with the 'qclient download-signatures' command")
+ fmt.Println("You can also skip this prompt manually by using the --signature-check=false flag or to permanently disable signature checks running 'qclient config signature-check false'")
+
+ os.Exit(1)
+ }
+ // Check if the user is trying to run the config command to disable/enable signature checks
+ if len(os.Args) >= 3 && os.Args[1] == "config" && os.Args[2] != "signature-check" {
+ fmt.Println("The signature files (if they exist) can be downloaded with the 'qclient download-signatures' command")
+ fmt.Println("You can also skip this prompt manually by using the --signature-check=false flag or to permanently disable signature checks running 'qclient config signature-check false'")
+ }
+
+ fmt.Println("Continuing without signature verification")
+
+ signatureCheck = false
+ }
+ }
+
+ if signatureCheck {
+ parts := strings.Split(string(digest), " ")
+ if len(parts) != 2 {
+ fmt.Println("Invalid digest file format")
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)
- }
+ digestBytes, err := hex.DecodeString(parts[1][:64])
+ if err != nil {
+ fmt.Println("Invalid digest file format")
+ os.Exit(1)
+ }
- fmt.Println("Signature check passed")
+ if !bytes.Equal(checksum[:], digestBytes) {
+ fmt.Println("Invalid digest for node")
+ os.Exit(1)
+ }
+
+ count := 0
+
+ for i := 1; i <= len(config.Signatories); i++ {
+ // Try var data path first for signature files
+ signatureFile := filepath.Join(varDataPath, fmt.Sprintf("%s.dgst.sig.%d", filepath.Base(ex), i))
+ sig, err := os.ReadFile(signatureFile)
+ if err != nil {
+ // Fall back to checking next to executable
+ 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")
+ fmt.Println("----------------------------------------------------------")
+ fmt.Println("")
}
-
- _, 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)
- }
-
- 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
- }
+ },
+ PersistentPostRun: func(cmd *cobra.Command, args []string) {
+ fmt.Println("")
},
}
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)
}
}
-func GetGRPCClient() (*grpc.ClientConn, error) {
- addr := "rpc.quilibrium.com:8337"
- credentials := credentials.NewTLS(&tls.Config{InsecureSkipVerify: false})
- if !LightNode {
- ma, err := multiaddr.NewMultiaddr(NodeConfig.ListenGRPCMultiaddr)
- if err != nil {
- panic(err)
- }
-
- _, addr, err = mn.DialArgs(ma)
- if err != nil {
- panic(err)
- }
- credentials = insecure.NewCredentials()
- }
-
- return grpc.Dial(
- addr,
- grpc.WithTransportCredentials(
- credentials,
- ),
- grpc.WithDefaultCallOptions(
- grpc.MaxCallSendMsgSize(600*1024*1024),
- grpc.MaxCallRecvMsgSize(600*1024*1024),
- ),
- )
-}
-
func signatureCheckDefault() bool {
envVarValue, envVarExists := os.LookupEnv("QUILIBRIUM_SIGNATURE_CHECK")
if envVarExists {
@@ -171,28 +193,23 @@ func signatureCheckDefault() bool {
}
func init() {
- rootCmd.PersistentFlags().StringVar(
- &configDirectory,
- "config",
- ".config/",
- "config directory (default is .config/)",
- )
- rootCmd.PersistentFlags().BoolVar(
- &DryRun,
- "dry-run",
- false,
- "runs the command (if applicable) without actually mutating state (printing effect output)",
- )
rootCmd.PersistentFlags().BoolVar(
&signatureCheck,
"signature-check",
signatureCheckDefault(),
"bypass signature check (not recommended for binaries) (default true or value of QUILIBRIUM_SIGNATURE_CHECK env var)",
)
- rootCmd.PersistentFlags().BoolVar(
- &publicRPC,
- "public-rpc",
+
+ rootCmd.PersistentFlags().BoolVarP(
+ &byPassSignatureCheck,
+ "yes",
+ "y",
false,
- "uses the public RPC",
+ "auto-approve and bypass signature check (sets signature-check=false)",
)
+
+ // Add the node command
+ rootCmd.AddCommand(node.NodeCmd)
+ rootCmd.AddCommand(clientConfig.ConfigCmd)
+ rootCmd.AddCommand(token.TokenCmd)
}
diff --git a/client/cmd/split.go b/client/cmd/split.go
deleted file mode 100644
index 1274ce5..0000000
--- a/client/cmd/split.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package cmd
-
-import (
- "context"
- "encoding/hex"
- "fmt"
- "math/big"
- "os"
- "strings"
-
- "github.com/shopspring/decimal"
- "github.com/spf13/cobra"
- "source.quilibrium.com/quilibrium/monorepo/node/protobufs"
-)
-
-var splitCmd = &cobra.Command{
- Use: "split",
- Short: "Splits a coin into multiple coins",
- Long: `Splits a coin into multiple coins:
-
- split ...
-
- OfCoin - the address of the coin to split
- Amounts - the sets of amounts to split
- `,
- Run: func(cmd *cobra.Command, args []string) {
- if len(args) < 3 {
- fmt.Println("invalid command")
- os.Exit(1)
- }
-
- coinaddrHex, _ := strings.CutPrefix(args[0], "0x")
- coinaddr, err := hex.DecodeString(coinaddrHex)
- if err != nil {
- panic(err)
- }
- coin := &protobufs.CoinRef{
- Address: coinaddr,
- }
-
- conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
- amounts := [][]byte{}
- for _, amt := range args[1:] {
- amount, err := decimal.NewFromString(amt)
- if err != nil {
- fmt.Println("invalid amount")
- os.Exit(1)
- }
- amount = amount.Mul(decimal.NewFromBigInt(conversionFactor, 0))
- amountBytes := amount.BigInt().FillBytes(make([]byte, 32))
- amounts = append(amounts, amountBytes)
- }
-
- conn, err := GetGRPCClient()
- if err != nil {
- panic(err)
- }
- defer conn.Close()
-
- client := protobufs.NewNodeServiceClient(conn)
- privKey, err := GetPrivKeyFromConfig(NodeConfig)
- if err != nil {
- panic(err)
- }
- pubKeyBytes, err := privKey.GetPublic().Raw()
- if err != nil {
- panic(err)
- }
-
- split := &protobufs.SplitCoinRequest{
- OfCoin: coin,
- Amounts: amounts,
- }
- if err := split.SignED448(pubKeyBytes, privKey.Sign); err != nil {
- panic(err)
- }
- if err := split.Validate(); err != nil {
- panic(err)
- }
-
- _, err = client.SendMessage(
- context.Background(),
- split.TokenRequest(),
- )
- if err != nil {
- panic(err)
- }
- },
-}
-
-func init() {
- tokenCmd.AddCommand(splitCmd)
-}
diff --git a/client/cmd/token.go b/client/cmd/token.go
deleted file mode 100644
index 496256c..0000000
--- a/client/cmd/token.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package cmd
-
-import (
- "github.com/spf13/cobra"
-)
-
-var tokenCmd = &cobra.Command{
- Use: "token",
- Short: "Performs a token operation",
-}
-
-func init() {
- rootCmd.AddCommand(tokenCmd)
-}
diff --git a/client/cmd/accept.go b/client/cmd/token/accept.go
similarity index 89%
rename from client/cmd/accept.go
rename to client/cmd/token/accept.go
index b79119d..eafe564 100644
--- a/client/cmd/accept.go
+++ b/client/cmd/token/accept.go
@@ -1,4 +1,4 @@
-package cmd
+package token
import (
"fmt"
@@ -21,5 +21,5 @@ var acceptCmd = &cobra.Command{
}
func init() {
- tokenCmd.AddCommand(acceptCmd)
+ TokenCmd.AddCommand(acceptCmd)
}
diff --git a/client/cmd/token/account.go b/client/cmd/token/account.go
new file mode 100644
index 0000000..1d911cb
--- /dev/null
+++ b/client/cmd/token/account.go
@@ -0,0 +1,28 @@
+package token
+
+import (
+ "fmt"
+
+ "github.com/iden3/go-iden3-crypto/poseidon"
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+)
+
+var accountCmd = &cobra.Command{
+ Use: "account",
+ Short: "Shows the account address of the managing account",
+ Run: func(cmd *cobra.Command, args []string) {
+ peerId := utils.GetPeerIDFromConfig(NodeConfig)
+ addr, err := poseidon.HashBytes([]byte(peerId))
+ if err != nil {
+ panic(err)
+ }
+
+ addrBytes := addr.FillBytes(make([]byte, 32))
+ fmt.Printf("Account: 0x%x\n", addrBytes)
+ },
+}
+
+func init() {
+ TokenCmd.AddCommand(accountCmd)
+}
diff --git a/client/cmd/all.go b/client/cmd/token/all.go
similarity index 97%
rename from client/cmd/all.go
rename to client/cmd/token/all.go
index 4f623c8..32986d1 100644
--- a/client/cmd/all.go
+++ b/client/cmd/token/all.go
@@ -1,4 +1,4 @@
-package cmd
+package token
import (
"bytes"
@@ -16,6 +16,7 @@ import (
"github.com/spf13/cobra"
"go.uber.org/zap"
"google.golang.org/grpc"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
"source.quilibrium.com/quilibrium/monorepo/node/config"
"source.quilibrium.com/quilibrium/monorepo/node/execution/intrinsics/token/application"
"source.quilibrium.com/quilibrium/monorepo/node/p2p"
@@ -44,8 +45,8 @@ var allCmd = &cobra.Command{
db := store.NewPebbleDB(NodeConfig.DB)
logger, _ := zap.NewProduction()
dataProofStore := store.NewPebbleDataProofStore(db, logger)
- peerId := GetPeerIDFromConfig(NodeConfig)
- privKey, err := GetPrivKeyFromConfig(NodeConfig)
+ peerId := utils.GetPeerIDFromConfig(NodeConfig)
+ privKey, err := utils.GetPrivKeyFromConfig(NodeConfig)
if err != nil {
panic(err)
}
diff --git a/client/cmd/balance.go b/client/cmd/token/balance.go
similarity index 89%
rename from client/cmd/balance.go
rename to client/cmd/token/balance.go
index ab1fc57..a1b97d0 100644
--- a/client/cmd/balance.go
+++ b/client/cmd/token/balance.go
@@ -1,4 +1,4 @@
-package cmd
+package token
import (
"context"
@@ -7,6 +7,7 @@ import (
"github.com/iden3/go-iden3-crypto/poseidon"
"github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
)
@@ -21,8 +22,8 @@ var balanceCmd = &cobra.Command{
defer conn.Close()
client := protobufs.NewNodeServiceClient(conn)
- peerId := GetPeerIDFromConfig(NodeConfig)
- privKey, err := GetPrivKeyFromConfig(NodeConfig)
+ peerId := utils.GetPeerIDFromConfig(NodeConfig)
+ privKey, err := utils.GetPrivKeyFromConfig(NodeConfig)
if err != nil {
panic(err)
}
@@ -88,5 +89,5 @@ var balanceCmd = &cobra.Command{
}
func init() {
- tokenCmd.AddCommand(balanceCmd)
+ TokenCmd.AddCommand(balanceCmd)
}
diff --git a/client/cmd/coins.go b/client/cmd/token/coins.go
similarity index 93%
rename from client/cmd/coins.go
rename to client/cmd/token/coins.go
index 854a888..0f2e7d2 100644
--- a/client/cmd/coins.go
+++ b/client/cmd/token/coins.go
@@ -1,4 +1,4 @@
-package cmd
+package token
import (
"context"
@@ -8,6 +8,7 @@ import (
"github.com/iden3/go-iden3-crypto/poseidon"
"github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
)
@@ -35,8 +36,8 @@ var coinsCmd = &cobra.Command{
defer conn.Close()
client := protobufs.NewNodeServiceClient(conn)
- peerId := GetPeerIDFromConfig(NodeConfig)
- privKey, err := GetPrivKeyFromConfig(NodeConfig)
+ peerId := utils.GetPeerIDFromConfig(NodeConfig)
+ privKey, err := utils.GetPrivKeyFromConfig(NodeConfig)
if err != nil {
panic(err)
}
@@ -136,5 +137,5 @@ var coinsCmd = &cobra.Command{
}
func init() {
- tokenCmd.AddCommand(coinsCmd)
+ TokenCmd.AddCommand(coinsCmd)
}
diff --git a/client/cmd/merge.go b/client/cmd/token/merge.go
similarity index 74%
rename from client/cmd/merge.go
rename to client/cmd/token/merge.go
index aa8a7da..2500a6d 100644
--- a/client/cmd/merge.go
+++ b/client/cmd/token/merge.go
@@ -1,12 +1,14 @@
-package cmd
+package token
import (
"context"
"encoding/hex"
+ "fmt"
"strings"
"github.com/iden3/go-iden3-crypto/poseidon"
"github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
"source.quilibrium.com/quilibrium/monorepo/node/protobufs"
)
@@ -31,8 +33,8 @@ var mergeCmd = &cobra.Command{
defer conn.Close()
client := protobufs.NewNodeServiceClient(conn)
- peerId := GetPeerIDFromConfig(NodeConfig)
- privKey, err := GetPrivKeyFromConfig(NodeConfig)
+ peerId := utils.GetPeerIDFromConfig(NodeConfig)
+ privKey, err := utils.GetPrivKeyFromConfig(NodeConfig)
if err != nil {
panic(err)
}
@@ -112,6 +114,29 @@ var mergeCmd = &cobra.Command{
merge := &protobufs.MergeCoinRequest{
Coins: coinaddrs,
}
+
+ // Display merge details and confirmation prompt
+ fmt.Printf("\nMerge Transaction Details:\n")
+ fmt.Printf("Number of coins to merge: %d\n", len(coinaddrs))
+ fmt.Println("Coins to be merged:")
+ for i, coin := range coinaddrs {
+ fmt.Printf("%d. 0x%x\n", i+1, coin.Address)
+ }
+ fmt.Print("\nDo you want to proceed with this merge? (yes/no): ")
+
+ var response string
+ fmt.Scanln(&response)
+
+ if strings.ToLower(response) != "yes" {
+ fmt.Println("Merge transaction cancelled by user.")
+ return
+ }
+
+ // Create payload for merge operation
+ payload := []byte("merge")
+ for _, coinRef := range coinaddrs {
+ payload = append(payload, coinRef.Address...)
+ }
if err := merge.SignED448(pubKeyBytes, privKey.Sign); err != nil {
panic(err)
}
@@ -128,10 +153,10 @@ var mergeCmd = &cobra.Command{
panic(err)
}
- println("Merge request sent successfully")
+ fmt.Println("Merge transaction sent successfully.")
},
}
func init() {
- tokenCmd.AddCommand(mergeCmd)
+ TokenCmd.AddCommand(mergeCmd)
}
diff --git a/client/cmd/mint.go b/client/cmd/token/mint.go
similarity index 76%
rename from client/cmd/mint.go
rename to client/cmd/token/mint.go
index 0ad82c7..1149e5c 100644
--- a/client/cmd/mint.go
+++ b/client/cmd/token/mint.go
@@ -1,4 +1,4 @@
-package cmd
+package token
import (
"github.com/spf13/cobra"
@@ -10,5 +10,5 @@ var mintCmd = &cobra.Command{
}
func init() {
- tokenCmd.AddCommand(mintCmd)
+ TokenCmd.AddCommand(mintCmd)
}
diff --git a/client/cmd/mutualReceive.go b/client/cmd/token/mutualReceive.go
similarity index 88%
rename from client/cmd/mutualReceive.go
rename to client/cmd/token/mutualReceive.go
index 0971589..6695760 100644
--- a/client/cmd/mutualReceive.go
+++ b/client/cmd/token/mutualReceive.go
@@ -1,4 +1,4 @@
-package cmd
+package token
import (
"fmt"
@@ -21,5 +21,5 @@ var mutualReceiveCmd = &cobra.Command{
}
func init() {
- tokenCmd.AddCommand(mutualReceiveCmd)
+ TokenCmd.AddCommand(mutualReceiveCmd)
}
diff --git a/client/cmd/mutualTransfer.go b/client/cmd/token/mutualTransfer.go
similarity index 91%
rename from client/cmd/mutualTransfer.go
rename to client/cmd/token/mutualTransfer.go
index c65b1bb..310abca 100644
--- a/client/cmd/mutualTransfer.go
+++ b/client/cmd/token/mutualTransfer.go
@@ -1,4 +1,4 @@
-package cmd
+package token
import (
"fmt"
@@ -25,5 +25,5 @@ var mutualTransferCmd = &cobra.Command{
}
func init() {
- tokenCmd.AddCommand(mutualTransferCmd)
+ TokenCmd.AddCommand(mutualTransferCmd)
}
diff --git a/client/cmd/reject.go b/client/cmd/token/reject.go
similarity index 89%
rename from client/cmd/reject.go
rename to client/cmd/token/reject.go
index c120668..ab972f9 100644
--- a/client/cmd/reject.go
+++ b/client/cmd/token/reject.go
@@ -1,4 +1,4 @@
-package cmd
+package token
import (
"fmt"
@@ -21,5 +21,5 @@ var rejectCmd = &cobra.Command{
}
func init() {
- tokenCmd.AddCommand(rejectCmd)
+ TokenCmd.AddCommand(rejectCmd)
}
diff --git a/client/cmd/token/split.go b/client/cmd/token/split.go
new file mode 100644
index 0000000..12ed100
--- /dev/null
+++ b/client/cmd/token/split.go
@@ -0,0 +1,318 @@
+package token
+
+import (
+ "bytes"
+ "context"
+ "encoding/hex"
+ "fmt"
+ "math/big"
+ "os"
+ "strings"
+
+ "github.com/iden3/go-iden3-crypto/poseidon"
+ "github.com/shopspring/decimal"
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+ "source.quilibrium.com/quilibrium/monorepo/node/protobufs"
+)
+
+var parts int
+var partAmount string
+var splitCmd = &cobra.Command{
+ Use: "split",
+ Short: "Splits a coin into multiple coins",
+ Long: `Splits a coin into multiple coins:
+
+ split ...
+ split <--parts PARTS> [--part-amount AMOUNT]
+
+ OfCoin - the address of the coin to split
+ Amounts - the sets of amounts to split
+
+ Example - Split a coin into the specified amounts:
+ $ qclient token coins
+ 1.000000000000 QUIL (Coin 0x1234)
+ $ qclient token split 0x1234 0.5 0.25 0.25
+ $ qclient token coins
+ 0.250000000000 QUIL (Coin 0x1111)
+ 0.250000000000 QUIL (Coin 0x2222)
+ 0.500000000000 QUIL (Coin 0x3333)
+
+ Example - Split a coin into three parts:
+ $ qclient token coins
+ 1.000000000000 QUIL (Coin 0x1234)
+ $ qclient token split 0x1234 --parts 3
+ $ qclient token coins
+ 0.000000000250 QUIL (Coin 0x1111)
+ 0.333333333250 QUIL (Coin 0x2222)
+ 0.333333333250 QUIL (Coin 0x3333)
+ 0.333333333250 QUIL (Coin 0x4444)
+
+ **Note:** Coin 0x1111 is the remainder.
+
+ Example - Split a coin into two parts using the specified amounts:
+ $ qclient token coins
+ 1.000000000000 QUIL (Coin 0x1234)
+ $ qclient token split 0x1234 --parts 2 --part-amount 0.35
+ $ qclient token coins
+ 0.300000000000 QUIL (Coin 0x1111)
+ 0.350000000000 QUIL (Coin 0x2222)
+ 0.350000000000 QUIL (Coin 0x3333)
+
+ **Note:** Coin 0x1111 is the remainder.
+ `,
+ Run: func(cmd *cobra.Command, args []string) {
+ if len(args) < 3 && parts == 1 {
+ fmt.Println("did you forget to specify and ?")
+ os.Exit(1)
+ }
+ if len(args) < 1 && parts > 1 {
+ fmt.Println("did you forget to specify ?")
+ os.Exit(1)
+ }
+ if len(args) > 1 && parts > 1 {
+ fmt.Println("-p/--parts can't be combined with ")
+ os.Exit(1)
+ }
+ if len(args) > 1 && partAmount != "" {
+ fmt.Println("-a/--part-amount can't be combined with ")
+ os.Exit(1)
+ }
+ if parts > 100 {
+ fmt.Println("too many parts, maximum is 100")
+ os.Exit(1)
+ }
+
+ payload := []byte("split")
+ coinaddrHex, _ := strings.CutPrefix(args[0], "0x")
+ coinaddr, err := hex.DecodeString(coinaddrHex)
+ if err != nil {
+ panic(err)
+ }
+ coin := &protobufs.CoinRef{
+ Address: coinaddr,
+ }
+ payload = append(payload, coinaddr...)
+
+ // Get the amount of the coin to be split
+ totalAmount := getCoinAmount(coinaddr)
+
+ amounts := [][]byte{}
+
+ // Split the coin into the user specified amounts
+ if parts == 1 {
+ amounts, payload, err = Split(args[1:], amounts, payload, totalAmount)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ }
+
+ // Split the coin into parts
+ if parts > 1 && partAmount == "" {
+ amounts, payload = SplitIntoParts(amounts, payload, totalAmount, parts)
+ }
+
+ // Split the coin into parts of the user specified amount
+ if parts > 1 && partAmount != "" {
+ amounts, payload, err = SplitIntoPartsAmount(amounts, payload, totalAmount, parts, partAmount)
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ }
+
+ conn, err := GetGRPCClient()
+ if err != nil {
+ panic(err)
+ }
+ defer conn.Close()
+
+ client := protobufs.NewNodeServiceClient(conn)
+ key, err := utils.GetPrivKeyFromConfig(NodeConfig)
+ if err != nil {
+ panic(err)
+ }
+
+ sig, err := key.Sign(payload)
+ if err != nil {
+ panic(err)
+ }
+
+ pub, err := key.GetPublic().Raw()
+ if err != nil {
+ panic(err)
+ }
+
+ _, err = client.SendMessage(
+ context.Background(),
+ &protobufs.TokenRequest{
+ Request: &protobufs.TokenRequest_Split{
+ Split: &protobufs.SplitCoinRequest{
+ OfCoin: coin,
+ Amounts: amounts,
+ Signature: &protobufs.Ed448Signature{
+ Signature: sig,
+ PublicKey: &protobufs.Ed448PublicKey{
+ KeyValue: pub,
+ },
+ },
+ },
+ },
+ },
+ )
+ if err != nil {
+ panic(err)
+ }
+ },
+}
+
+func init() {
+ splitCmd.Flags().IntVarP(&parts, "parts", "p", 1, "number of parts to split the coin into")
+ splitCmd.Flags().StringVarP(&partAmount, "part-amount", "a", "", "amount of each part")
+ TokenCmd.AddCommand(splitCmd)
+}
+
+func Split(args []string, amounts [][]byte, payload []byte, totalAmount *big.Int) ([][]byte, []byte, error) {
+ conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
+ inputAmount := new(big.Int)
+ for _, amt := range args {
+ amount, err := decimal.NewFromString(amt)
+ if err != nil {
+ return nil, nil, fmt.Errorf("invalid amount, must be a decimal number like 0.02 or 2")
+ }
+ amount = amount.Mul(decimal.NewFromBigInt(conversionFactor, 0))
+ inputAmount = inputAmount.Add(inputAmount, amount.BigInt())
+ amountBytes := amount.BigInt().FillBytes(make([]byte, 32))
+ amounts = append(amounts, amountBytes)
+ payload = append(payload, amountBytes...)
+ }
+
+ // Check if the user specified amounts sum to the total amount of the coin
+ if inputAmount.Cmp(totalAmount) != 0 {
+ return nil, nil, fmt.Errorf("the specified amounts must sum to the total amount of the coin")
+ }
+ return amounts, payload, nil
+}
+
+func SplitIntoParts(amounts [][]byte, payload []byte, totalAmount *big.Int, parts int) ([][]byte, []byte) {
+ amount := new(big.Int).Div(totalAmount, big.NewInt(int64(parts)))
+ amountBytes := amount.FillBytes(make([]byte, 32))
+ for i := int64(0); i < int64(parts); i++ {
+ amounts = append(amounts, amountBytes)
+ payload = append(payload, amountBytes...)
+ }
+
+ // If there is a remainder, we need to add it as a separate amount
+ // because the amounts must sum to the original coin amount.
+ remainder := new(big.Int).Mod(totalAmount, big.NewInt(int64(parts)))
+ if remainder.Cmp(big.NewInt(0)) != 0 {
+ remainderBytes := remainder.FillBytes(make([]byte, 32))
+ amounts = append(amounts, remainderBytes)
+ payload = append(payload, remainderBytes...)
+ }
+ return amounts, payload
+}
+
+func SplitIntoPartsAmount(amounts [][]byte, payload []byte, totalAmount *big.Int, parts int, partAmount string) ([][]byte, []byte, error) {
+ conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
+ amount, err := decimal.NewFromString(partAmount)
+ if err != nil {
+ return nil, nil, fmt.Errorf("invalid amount, must be a decimal number like 0.02 or 2")
+ }
+ amount = amount.Mul(decimal.NewFromBigInt(conversionFactor, 0))
+ inputAmount := new(big.Int).Mul(amount.BigInt(), big.NewInt(int64(parts)))
+ amountBytes := amount.BigInt().FillBytes(make([]byte, 32))
+ for i := int64(0); i < int64(parts); i++ {
+ amounts = append(amounts, amountBytes)
+ payload = append(payload, amountBytes...)
+ }
+
+ // If there is a remainder, we need to add it as a separate amount
+ // because the amounts must sum to the original coin amount.
+ remainder := new(big.Int).Sub(totalAmount, inputAmount)
+ if remainder.Cmp(big.NewInt(0)) != 0 {
+ remainderBytes := remainder.FillBytes(make([]byte, 32))
+ amounts = append(amounts, remainderBytes)
+ payload = append(payload, remainderBytes...)
+ }
+
+ // Check if the user specified amounts sum to the total amount of the coin
+ if new(big.Int).Add(inputAmount, new(big.Int).Abs(remainder)).Cmp(totalAmount) != 0 {
+ return nil, nil, fmt.Errorf("the specified amounts must sum to the total amount of the coin")
+ }
+ return amounts, payload, nil
+}
+
+func getCoinAmount(coinaddr []byte) *big.Int {
+ conn, err := GetGRPCClient()
+ if err != nil {
+ panic(err)
+ }
+ defer conn.Close()
+
+ client := protobufs.NewNodeServiceClient(conn)
+ peerId := utils.GetPeerIDFromConfig(NodeConfig)
+ privKey, err := utils.GetPrivKeyFromConfig(NodeConfig)
+ if err != nil {
+ panic(err)
+ }
+
+ pub, err := privKey.GetPublic().Raw()
+ if err != nil {
+ panic(err)
+ }
+
+ addr, err := poseidon.HashBytes([]byte(peerId))
+ if err != nil {
+ panic(err)
+ }
+
+ addrBytes := addr.FillBytes(make([]byte, 32))
+ resp, err := client.GetTokensByAccount(
+ context.Background(),
+ &protobufs.GetTokensByAccountRequest{
+ Address: addrBytes,
+ },
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ if len(resp.Coins) != len(resp.FrameNumbers) {
+ panic("invalid response from RPC")
+ }
+
+ altAddr, err := poseidon.HashBytes([]byte(pub))
+ if err != nil {
+ panic(err)
+ }
+
+ altAddrBytes := altAddr.FillBytes(make([]byte, 32))
+ resp2, err := client.GetTokensByAccount(
+ context.Background(),
+ &protobufs.GetTokensByAccountRequest{
+ Address: altAddrBytes,
+ },
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ if len(resp.Coins) != len(resp.FrameNumbers) {
+ panic("invalid response from RPC")
+ }
+
+ var amount *big.Int
+ for i, coin := range resp.Coins {
+ if bytes.Equal(resp.Addresses[i], coinaddr) {
+ amount = new(big.Int).SetBytes(coin.Amount)
+ }
+ }
+ for i, coin := range resp2.Coins {
+ if bytes.Equal(resp.Addresses[i], coinaddr) {
+ amount = new(big.Int).SetBytes(coin.Amount)
+ }
+ }
+ return amount
+}
diff --git a/client/cmd/token/tests/split_test.go b/client/cmd/token/tests/split_test.go
new file mode 100644
index 0000000..a5412dc
--- /dev/null
+++ b/client/cmd/token/tests/split_test.go
@@ -0,0 +1,232 @@
+package token_test
+
+import (
+ "encoding/hex"
+ "math/big"
+ "reflect"
+ "strings"
+ "testing"
+
+ "github.com/shopspring/decimal"
+ "source.quilibrium.com/quilibrium/monorepo/client/cmd/token"
+)
+
+func TestSplit(t *testing.T) {
+ tests := []struct {
+ name string
+ args []string
+ totalAmount string
+ amounts [][]byte
+ payload []byte
+ expectError bool
+ }{
+ {
+ name: "Valid split - specified amounts",
+ args: []string{"0x1234", "0.5", "0.25", "0.25"},
+ totalAmount: "1.0",
+ amounts: [][]byte{
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 238, 107, 40, 0},
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 53, 148, 0},
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 53, 148, 0},
+ },
+ payload: []byte{
+ 115, 112, 108, 105, 116,
+ 18, 52,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 238, 107, 40, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 53, 148, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 119, 53, 148, 0,
+ },
+ expectError: false,
+ },
+ {
+ name: "Invalid split - amounts do not sum to the total amount of the coin",
+ args: []string{"0x1234", "0.5", "0.25"},
+ totalAmount: "1.0",
+ amounts: [][]byte{},
+ payload: []byte{},
+ expectError: true,
+ },
+ {
+ name: "Invalid split - amounts exceed total amount of the coin",
+ args: []string{"0x1234", "0.5", "1"},
+ totalAmount: "1.0",
+ amounts: [][]byte{},
+ payload: []byte{},
+ expectError: true,
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ payload := []byte("split")
+ coinaddrHex, _ := strings.CutPrefix(tc.args[0], "0x")
+ coinaddr, err := hex.DecodeString(coinaddrHex)
+ if err != nil {
+ panic(err)
+ }
+ payload = append(payload, coinaddr...)
+
+ conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
+ totalAmount, _ := decimal.NewFromString(tc.totalAmount)
+ totalAmount = totalAmount.Mul(decimal.NewFromBigInt(conversionFactor, 0))
+
+ amounts := [][]byte{}
+
+ if tc.expectError {
+ _, _, err = token.Split(tc.args[1:], amounts, payload, totalAmount.BigInt())
+ if err == nil {
+ t.Errorf("want error for invalid split, got nil")
+ }
+ } else {
+ amounts, payload, err = token.Split(tc.args[1:], amounts, payload, totalAmount.BigInt())
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !reflect.DeepEqual(tc.amounts, amounts) {
+ t.Errorf("expected amounts: %v, got: %v", tc.amounts, amounts)
+ }
+ if !reflect.DeepEqual(tc.payload, payload) {
+ t.Errorf("expected payloads: %v, got: %v", tc.payload, payload)
+ }
+ }
+ })
+ }
+}
+
+func TestSplitParts(t *testing.T) {
+ tests := []struct {
+ name string
+ args []string
+ parts int
+ totalAmount string
+ amounts [][]byte
+ payload []byte
+ }{
+ {
+ name: "Valid split - into parts",
+ args: []string{"0x1234"},
+ parts: 3,
+ totalAmount: "1.0",
+ amounts: [][]byte{
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 242, 26, 170},
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 242, 26, 170},
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 242, 26, 170},
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2},
+ },
+ payload: []byte{
+ 115, 112, 108, 105, 116,
+ 18, 52,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 242, 26, 170,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 242, 26, 170,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158, 242, 26, 170,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
+ },
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ payload := []byte("split")
+ coinaddrHex, _ := strings.CutPrefix(tc.args[0], "0x")
+ coinaddr, err := hex.DecodeString(coinaddrHex)
+ if err != nil {
+ panic(err)
+ }
+ payload = append(payload, coinaddr...)
+
+ conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
+ totalAmount, _ := decimal.NewFromString(tc.totalAmount)
+ totalAmount = totalAmount.Mul(decimal.NewFromBigInt(conversionFactor, 0))
+
+ amounts := [][]byte{}
+
+ amounts, payload = token.SplitIntoParts(amounts, payload, totalAmount.BigInt(), tc.parts)
+ if !reflect.DeepEqual(tc.amounts, amounts) {
+ t.Errorf("expected amounts: %v, got: %v", tc.amounts, amounts)
+ }
+ if !reflect.DeepEqual(tc.payload, payload) {
+ t.Errorf("expected payloads: %v, got: %v", tc.payload, payload)
+ }
+ })
+ }
+}
+
+func TestSplitIntoPartsAmount(t *testing.T) {
+ tests := []struct {
+ name string
+ args []string
+ parts int
+ partAmount string
+ totalAmount string
+ amounts [][]byte
+ payload []byte
+ expectError bool
+ }{
+ {
+ name: "Valid split - into parts of specified amount",
+ args: []string{"0x1234"},
+ parts: 2,
+ partAmount: "0.35",
+ totalAmount: "1.0",
+ amounts: [][]byte{
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166, 228, 156, 0},
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166, 228, 156, 0},
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 13, 24, 0},
+ },
+ payload: []byte{
+ 115, 112, 108, 105, 116,
+ 18, 52,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166, 228, 156, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166, 228, 156, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 143, 13, 24, 0,
+ },
+ expectError: false,
+ },
+ {
+ name: "Invalid split - amounts exceed total amount of the coin",
+ args: []string{"0x1234"},
+ parts: 3,
+ partAmount: "0.5",
+ totalAmount: "1.0",
+ amounts: [][]byte{},
+ payload: []byte{},
+ expectError: true,
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ payload := []byte("split")
+ coinaddrHex, _ := strings.CutPrefix(tc.args[0], "0x")
+ coinaddr, err := hex.DecodeString(coinaddrHex)
+ if err != nil {
+ panic(err)
+ }
+ payload = append(payload, coinaddr...)
+
+ conversionFactor, _ := new(big.Int).SetString("1DCD65000", 16)
+ totalAmount, _ := decimal.NewFromString(tc.totalAmount)
+ totalAmount = totalAmount.Mul(decimal.NewFromBigInt(conversionFactor, 0))
+
+ amounts := [][]byte{}
+
+ if tc.expectError {
+ _, _, err = token.SplitIntoPartsAmount(amounts, payload, totalAmount.BigInt(), tc.parts, tc.partAmount)
+ if err == nil {
+ t.Errorf("want error for invalid split, got nil")
+ }
+ } else {
+ amounts, payload, err = token.SplitIntoPartsAmount(amounts, payload, totalAmount.BigInt(), tc.parts, tc.partAmount)
+ if err != nil {
+ t.Fatalf("unexpected error: %v", err)
+ }
+ if !reflect.DeepEqual(tc.amounts, amounts) {
+ t.Errorf("expected amounts: %v, got: %v", tc.amounts, amounts)
+ }
+ if !reflect.DeepEqual(tc.payload, payload) {
+ t.Errorf("expected payloads: %v, got: %v", tc.payload, payload)
+ }
+ }
+ })
+ }
+}
diff --git a/client/cmd/token/token.go b/client/cmd/token/token.go
new file mode 100644
index 0000000..b3caff4
--- /dev/null
+++ b/client/cmd/token/token.go
@@ -0,0 +1,49 @@
+package token
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/node/config"
+)
+
+var LightNode bool = false
+var publicRPC bool = false
+var NodeConfig *config.Config
+var configDirectory string
+
+var TokenCmd = &cobra.Command{
+ Use: "token",
+ Short: "Performs a token operation",
+ Run: func(cmd *cobra.Command, args []string) {
+
+ // 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)
+ }
+
+ 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 init() {
+ TokenCmd.PersistentFlags().BoolVar(&publicRPC, "public-rpc", false, "Use public RPC for token operations")
+ TokenCmd.PersistentFlags().StringVar(&configDirectory, "config", ".config", "config directory (default is .config/)")
+}
diff --git a/client/cmd/token/transfer.go b/client/cmd/token/transfer.go
new file mode 100644
index 0000000..5594084
--- /dev/null
+++ b/client/cmd/token/transfer.go
@@ -0,0 +1,116 @@
+package token
+
+import (
+ "context"
+ "encoding/hex"
+ "fmt"
+ "strings"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+ "source.quilibrium.com/quilibrium/monorepo/node/protobufs"
+)
+
+var transferCmd = &cobra.Command{
+ Use: "transfer",
+ Short: "Creates a pending transfer of coin",
+ Long: `Creates a pending transfer of coin:
+transfer
+ToAccount – account address, must be specified
+OfCoin – the address of the coin to send in whole`,
+ Run: func(cmd *cobra.Command, args []string) {
+ if len(args) != 2 {
+ panic("invalid arguments")
+ }
+
+ conn, err := GetGRPCClient()
+ if err != nil {
+ panic(err)
+ }
+ defer conn.Close()
+
+ client := protobufs.NewNodeServiceClient(conn)
+ privKey, err := utils.GetPrivKeyFromConfig(NodeConfig)
+ if err != nil {
+ panic(err)
+ }
+
+ var coinaddr *protobufs.CoinRef
+ payload := []byte("transfer")
+ toaddr := []byte{}
+
+ for i, arg := range args {
+ addrHex, _ := strings.CutPrefix(arg, "0x")
+ addr, err := hex.DecodeString(addrHex)
+ if err != nil {
+ panic(err)
+ }
+ if i == 0 {
+ toaddr = addr
+ continue
+ }
+ coinaddr = &protobufs.CoinRef{
+ Address: addr,
+ }
+ payload = append(payload, addr...)
+ }
+ payload = append(payload, coinaddr.Address...)
+
+ // Display transaction details and confirmation prompt
+ fmt.Printf("\nTransaction Details:\n")
+ fmt.Printf("To Address: 0x%x\n", toaddr)
+ fmt.Printf("Coin Address: 0x%x\n", coinaddr.Address)
+ fmt.Print("\nDo you want to proceed with this transaction? (yes/no): ")
+
+ var response string
+ fmt.Scanln(&response)
+
+ if strings.ToLower(response) != "yes" {
+ fmt.Println("Transaction cancelled by user.")
+ return
+ }
+
+ sig, err := privKey.Sign(payload)
+ if err != nil {
+ panic(err)
+ }
+
+ pub, err := privKey.GetPublic().Raw()
+ if err != nil {
+ panic(err)
+ }
+
+ _, err = client.SendMessage(
+ context.Background(),
+ &protobufs.TokenRequest{
+ Request: &protobufs.TokenRequest_Transfer{
+ Transfer: &protobufs.TransferCoinRequest{
+ OfCoin: coinaddr,
+ ToAccount: &protobufs.AccountRef{
+ Account: &protobufs.AccountRef_ImplicitAccount{
+ ImplicitAccount: &protobufs.ImplicitAccount{
+ Address: toaddr,
+ },
+ },
+ },
+ Signature: &protobufs.Ed448Signature{
+ Signature: sig,
+ PublicKey: &protobufs.Ed448PublicKey{
+ KeyValue: pub,
+ },
+ },
+ },
+ },
+ },
+ )
+ if err != nil {
+ panic(err)
+ }
+
+ fmt.Println("Transaction sent successfully.")
+ },
+}
+
+func init() {
+ TokenCmd.AddCommand(transferCmd)
+}
diff --git a/client/cmd/token/utils.go b/client/cmd/token/utils.go
new file mode 100644
index 0000000..c711652
--- /dev/null
+++ b/client/cmd/token/utils.go
@@ -0,0 +1,40 @@
+package token
+
+import (
+ "crypto/tls"
+
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials"
+ "google.golang.org/grpc/credentials/insecure"
+
+ "github.com/multiformats/go-multiaddr"
+ mn "github.com/multiformats/go-multiaddr/net"
+)
+
+func GetGRPCClient() (*grpc.ClientConn, error) {
+ addr := "rpc.quilibrium.com:8337"
+ credentials := credentials.NewTLS(&tls.Config{InsecureSkipVerify: false})
+ if !LightNode {
+ ma, err := multiaddr.NewMultiaddr(NodeConfig.ListenGRPCMultiaddr)
+ if err != nil {
+ panic(err)
+ }
+
+ _, addr, err = mn.DialArgs(ma)
+ if err != nil {
+ panic(err)
+ }
+ credentials = insecure.NewCredentials()
+ }
+
+ return grpc.Dial(
+ addr,
+ grpc.WithTransportCredentials(
+ credentials,
+ ),
+ grpc.WithDefaultCallOptions(
+ grpc.MaxCallSendMsgSize(600*1024*1024),
+ grpc.MaxCallRecvMsgSize(600*1024*1024),
+ ),
+ )
+}
diff --git a/client/cmd/transfer.go b/client/cmd/transfer.go
deleted file mode 100644
index f87dbb0..0000000
--- a/client/cmd/transfer.go
+++ /dev/null
@@ -1,90 +0,0 @@
-package cmd
-
-import (
- "context"
- "encoding/hex"
- "strings"
-
- "github.com/spf13/cobra"
- "source.quilibrium.com/quilibrium/monorepo/node/protobufs"
-)
-
-var transferCmd = &cobra.Command{
- Use: "transfer",
- Short: "Creates a pending transfer of coin",
- Long: `Creates a pending transfer of coin:
-
- transfer
-
- ToAccount – account address, must be specified
- OfCoin – the address of the coin to send in whole
- `,
- Run: func(cmd *cobra.Command, args []string) {
- if len(args) != 2 {
- panic("invalid arguments")
- }
-
- conn, err := GetGRPCClient()
- if err != nil {
- panic(err)
- }
- defer conn.Close()
-
- client := protobufs.NewNodeServiceClient(conn)
- privKey, err := GetPrivKeyFromConfig(NodeConfig)
- if err != nil {
- panic(err)
- }
- pubKeyBytes, err := privKey.GetPublic().Raw()
- if err != nil {
- panic(err)
- }
-
- var coinaddr *protobufs.CoinRef
- toaddr := []byte{}
- for i, arg := range args {
- addrHex, _ := strings.CutPrefix(arg, "0x")
- addr, err := hex.DecodeString(addrHex)
- if err != nil {
- panic(err)
- }
- if i == 0 {
- toaddr = addr
- continue
- }
-
- coinaddr = &protobufs.CoinRef{
- Address: addr,
- }
- }
-
- transfer := &protobufs.TransferCoinRequest{
- OfCoin: coinaddr,
- ToAccount: &protobufs.AccountRef{
- Account: &protobufs.AccountRef_ImplicitAccount{
- ImplicitAccount: &protobufs.ImplicitAccount{
- Address: toaddr,
- },
- },
- },
- }
- if err := transfer.SignED448(pubKeyBytes, privKey.Sign); err != nil {
- panic(err)
- }
- if err := transfer.Validate(); err != nil {
- panic(err)
- }
-
- _, err = client.SendMessage(
- context.Background(),
- transfer.TokenRequest(),
- )
- if err != nil {
- panic(err)
- }
- },
-}
-
-func init() {
- tokenCmd.AddCommand(transferCmd)
-}
diff --git a/client/cmd/update.go b/client/cmd/update.go
new file mode 100644
index 0000000..a4e2ec1
--- /dev/null
+++ b/client/cmd/update.go
@@ -0,0 +1,167 @@
+package cmd
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "runtime"
+ "strings"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+ "source.quilibrium.com/quilibrium/monorepo/node/config"
+)
+
+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.
+
+If the current version is already the latest version, the command will exit with a message.
+
+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) {
+
+ currentVersion := config.GetVersionString()
+
+ // 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)
+ }
+
+ if version == currentVersion {
+ fmt.Fprintf(os.Stdout, "Already on version %s\n", currentVersion)
+ return
+ }
+
+ // 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
+ }
+
+ // 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) {
+
+ // Construct executable path
+ execPath := filepath.Join(utils.ClientDataPath, version, "qclient-"+version+"-"+osType+"-"+arch)
+
+ // 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
+
+ // 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..8d03beb
--- /dev/null
+++ b/client/cmd/version.go
@@ -0,0 +1,89 @@
+package cmd
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "regexp"
+
+ "github.com/spf13/cobra"
+ "source.quilibrium.com/quilibrium/monorepo/client/utils"
+ "source.quilibrium.com/quilibrium/monorepo/node/config"
+)
+
+// Version information - fallback if executable name doesn't contain version
+var (
+ DefaultVersion = config.GetVersionString()
+)
+
+// 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("%s\n", info.Version)
+
+ if showChecksum {
+ 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..a3e0ffd
--- /dev/null
+++ b/client/test/Dockerfile
@@ -0,0 +1,31 @@
+# Use build argument to specify the base image
+ARG DISTRO=ubuntu
+ARG VERSION=24.04
+
+# Base stage with common setup
+FROM --platform=$BUILDPLATFORM ${DISTRO}:${VERSION} AS base
+
+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 \
+ bash-completion \
+ 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
+
+# Final test stage
+FROM base AS qclient-test
+WORKDIR /app
+
+
+CMD ["/app/test_install.sh"]
\ No newline at end of file
diff --git a/client/test/README.md b/client/test/README.md
new file mode 100644
index 0000000..18aaac4
--- /dev/null
+++ b/client/test/README.md
@@ -0,0 +1,165 @@
+# 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
+sudo task build_qclient_amd64_linux
+sudo task build_qclient_arm64_linux
+# for mac, you will need to build on a mac
+```
+
+## Run a test container
+
+```
+sudo docker run -it \
+ -v "/home/user/ceremonyclient/client/test/:/app" \
+ -v "/home/user/ceremenoyclient/client/build/amd64_linux/qclient:/opt/quilibrium/bin/qclient" \
+ quil-test /bin/bash
+
+
+This command builds the Docker image with the qclient binary according to the specifications in `Dockerfile.source`. 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
diff --git "a/client/test/home/testuser\n/.quilibrium/config/qclient.yaml" "b/client/test/home/testuser\n/.quilibrium/config/qclient.yaml"
new file mode 100644
index 0000000..2beed71
--- /dev/null
+++ "b/client/test/home/testuser\n/.quilibrium/config/qclient.yaml"
@@ -0,0 +1,3 @@
+dataDir: /var/quilibrium/data/qclient
+symlinkPath: /usr/local/bin/qclient
+signatureCheck: true
diff --git a/client/test/run_tests.sh b/client/test/run_tests.sh
new file mode 100755
index 0000000..d8a4ef5
--- /dev/null
+++ b/client/test/run_tests.sh
@@ -0,0 +1,124 @@
+#!/bin/bash
+set -e
+CLIENT_DIR="${CLIENT_DIR:-$( cd "$(dirname "$(realpath "$( dirname "${BASH_SOURCE[0]}" )")")" >/dev/null 2>&1 && pwd )}"
+
+echo "CLIENT_DIR: $CLIENT_DIR"
+
+# 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 " --no-cache Disable all Docker build cache"
+ echo ""
+ echo "If no arguments are provided, runs tests on all supported distributions"
+ exit 0
+}
+
+# Parse command line arguments
+DISTRO=""
+VERSION=""
+TAG=""
+NO_CACHE=""
+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
+ ;;
+ --no-cache)
+ NO_CACHE="--no-cache"
+ shift
+ ;;
+ -h|--help)
+ show_help
+ ;;
+ *)
+ echo "Unknown option: $1"
+ show_help
+ ;;
+ esac
+done
+
+# 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..."
+
+ # Build the base stage first (this can be cached)
+ docker build \
+ $NO_CACHE \
+ --build-arg DISTRO=$distro \
+ --build-arg VERSION=$version \
+ -t quil-test-$tag-base \
+ --target base \
+ -f client/test/Dockerfile .
+
+ # Build the final test stage
+ docker build \
+ --build-arg DISTRO=$distro \
+ --build-arg VERSION=$version \
+ -t quil-test-$tag \
+ --target qclient-test \
+ -f client/test/Dockerfile .
+
+ # Ensure test files are executable
+ chmod +x "$CLIENT_DIR/test/test_install.sh"
+ chmod +x "$CLIENT_DIR/test/test_utils.sh"
+ chmod +x "$CLIENT_DIR/build/amd64_linux/qclient"
+
+ # Set ownership to match testuser (uid:gid 1000:1000)
+ chown 1000:1000 "$CLIENT_DIR/build/amd64_linux/qclient"
+
+ # Run the container with mounted test directory and binary
+ docker run --rm \
+ -v "$CLIENT_DIR/test:/app" \
+ -v "$CLIENT_DIR/build/amd64_linux/qclient:/opt/quilibrium/bin/qclient" \
+ 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 100755
index 0000000..955694a
--- /dev/null
+++ b/client/test/test_install.sh
@@ -0,0 +1,127 @@
+#!/bin/bash
+set -e
+
+
+# Source the test utilities
+source "$(dirname "$0")/test_utils.sh"
+
+# 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: Link the qclient binary to ensure it's in the PATH
+echo "Linking qclient binary for testing..."
+if [ -f "/opt/quilibrium/bin/qclient" ]; then
+ echo "qclient binary already exists at /opt/quilibrium/bin/qclient"
+else
+ echo "qclient binary not found at /opt/quilibrium/bin/qclient"
+ exit 1
+fi
+
+# Test: Link the qclient binary to the system PATH
+echo "Testing qclient link command..."
+run_test_with_format "sudo /opt/quilibrium/bin/qclient link"
+
+# Verify qclient is now in PATH
+echo "Verifying qclient is in PATH after link command..."
+run_test_with_format "which qclient" | grep -q "/usr/local/bin/qclient" && echo "SUCCESS: qclient found in PATH" || echo "FAILURE: qclient not found in PATH"
+
+# Test qclient can be executed directly
+echo "Testing qclient can be executed directly..."
+run_test_with_format "qclient --help" | grep -q "Usage:" && echo "SUCCESS: qclient executable works" || echo "FAILURE: qclient executable not working properly"
+
+
+# Test: Ensure no config file exists initially
+echo "Testing no config file exists initially..."
+run_test_with_format "test ! -f /etc/quilibrium/config/qclient.yaml"
+
+# Test: Create default config
+echo "Testing default config creation..."
+run_test_with_format "qclient config create-default --signature-check=false"
+
+# Test: Verify config file was created
+echo "Verifying config file was created..."
+run_test_with_format "test -f /etc/quilibrium/config/qclient.yaml"
+
+# Test: Excec arbitrary qclient command and verify signature check
+echo "Testing config print command..."
+run_test_with_format "qclient config print" | grep -v "Checking signature for"
+
+# Test: Toggle signature check
+echo "Testing toggle-signature-check command..."
+run_test_with_format "qclient config toggle-signature-check --signature-check=false"
+run_test_with_format "qclient config print" | grep -v "Checking signature for"
+
+
+# Test: Ensure qclient is in the PATH
+echo "Testing qclient in PATH..."
+run_test_with_format "sudo /opt/quilibrium/bin/qclient link"
+run_test_with_format "which qclient"
+run_test_with_format "qclient version"
+
+run_test_with_format "qclient config print"
+
+
+# Test 0: Install latest version
+# Check if download-signatures command exists in qclient help
+run_test_with_format "qclient help | grep -q 'download-signatures'"
+
+# Test downloading signatures
+run_test_with_format "sudo qclient download-signatures"
+
+# Test 1: Install latest version
+run_test_with_format "sudo 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
+run_test_with_format "test -f /opt/quilibrium/$LATEST_VERSION/node-$LATEST_VERSION-linux-amd64"
+
+# Verify latest version matches
+run_test_with_format "get_latest_version"
+
+# Test 2: Install specific version
+run_test_with_format "qclient node install '2.0.6.2' --signature-check=false"
+
+# Verify specific version installation
+run_test_with_format "test -f /opt/quilibrium/2.0.6.2/node-2.0.6.2-linux-amd64"
+
+# Test 3: Verify service file creation
+run_test_with_format "test -f /etc/systemd/system/quilibrium-node.service"
+
+# Verify service file content
+run_test_with_format "grep -q 'EnvironmentFile=/etc/default/quilibrium-node' /etc/systemd/system/quilibrium-node.service"
+
+# Test 4: Verify environment file
+run_test_with_format "test -f /etc/default/quilibrium-node"
+
+# Verify environment file permissions
+run_test_with_format "test '$(stat -c %a /etc/default/quilibrium-node)' = '640'"
+
+# Test 5: Verify data directory
+run_test_with_format "test -d /var/lib/quilibrium"
+
+# Verify data directory permissions
+run_test_with_format "test '$(stat -c %a /var/lib/quilibrium)' = '755'"
+
+# Test 6: Verify config file
+run_test_with_format "test -f /var/lib/quilibrium/config/node.yaml"
+
+# Verify config file permissions
+run_test_with_format "test '$(stat -c %a /var/lib/quilibrium/config/node.yaml)' = '644'"
+
+# Test 7: Verify binary symlink
+run_test_with_format "test -L /usr/local/bin/quilibrium-node"
+
+# Test 8: Verify binary execution
+run_test_with_format "quilibrium-node --version"
+
+echo "All tests passed successfully on $DISTRO $VERSION!"
\ No newline at end of file
diff --git a/client/test/test_utils.sh b/client/test/test_utils.sh
new file mode 100755
index 0000000..37f97b4
--- /dev/null
+++ b/client/test/test_utils.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+
+# ANSI color codes
+GREEN='\033[0;32m'
+BLUE='\033[0;34m'
+RED='\033[0;31m'
+NC='\033[0m' # No Color
+
+# Function to run a test command and format its output
+run_test_with_format() {
+ local test_cmd="$1"
+ local indent=" " # 4 spaces for indentation
+
+ echo -e "${BLUE}Running test: $test_cmd${NC}"
+ echo "----------------------------------------"
+
+ # Run the command and capture stdout and stderr separately
+ local stdout
+ local stderr
+ stdout=$(eval "$test_cmd" 2> >(tee /dev/stderr))
+ exit_code=$?
+ stderr=$(cat)
+
+ # Format and print the stdout with indentation
+ if [ -n "$stdout" ]; then
+ echo "$stdout" | while IFS= read -r line; do
+ echo -e "${GREEN}$indent$line${NC}"
+ done
+ fi
+
+ # Check for stderr output and exit code
+ if [ -n "$stderr" ] || [ $exit_code -ne 0 ]; then
+ echo -e "${RED}${indent}Test failed:${NC}"
+ if [ -n "$stderr" ]; then
+ echo "$stderr" | while IFS= read -r line; do
+ echo -e "${RED}${indent}$line${NC}"
+ done
+ fi
+ if [ $exit_code -ne 0 ]; then
+ echo -e "${RED}${indent}Exit code: $exit_code${NC}"
+ fi
+ echo "----------------------------------------"
+ return 1
+ fi
+
+ echo -e "${GREEN}${indent}Test completed successfully${NC}"
+ echo "----------------------------------------"
+ return 0
+}
\ No newline at end of file
diff --git a/client/utils/config.go b/client/utils/config.go
new file mode 100644
index 0000000..93be350
--- /dev/null
+++ b/client/utils/config.go
@@ -0,0 +1,91 @@
+package utils
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+
+ "gopkg.in/yaml.v2"
+)
+
+var ClientConfigDir = filepath.Join(os.Getenv("HOME"), ".quilibrium")
+var ClientConfigFile = string(ReleaseTypeQClient) + "-config.yaml"
+var ClientConfigPath = filepath.Join(ClientConfigDir, ClientConfigFile)
+
+func CreateDefaultConfig() {
+ configPath := GetConfigPath()
+
+ fmt.Printf("Creating default config: %s\n", configPath)
+ SaveClientConfig(&ClientConfig{
+ DataDir: ClientDataPath,
+ SymlinkPath: DefaultQClientSymlinkPath,
+ SignatureCheck: true,
+ })
+
+ sudoUser, err := GetCurrentSudoUser()
+ if err != nil {
+ fmt.Println("Error getting current sudo user")
+ os.Exit(1)
+ }
+ ChownPath(GetUserQuilibriumDir(), sudoUser, true)
+}
+
+// LoadClientConfig loads the client configuration from the config file
+func LoadClientConfig() (*ClientConfig, error) {
+ configPath := GetConfigPath()
+
+ // Create default config if it doesn't exist
+ if _, err := os.Stat(configPath); os.IsNotExist(err) {
+ config := &ClientConfig{
+ DataDir: ClientDataPath,
+ SymlinkPath: filepath.Join(ClientDataPath, "current"),
+ SignatureCheck: true,
+ }
+ if err := SaveClientConfig(config); err != nil {
+ return nil, err
+ }
+ return config, nil
+ }
+
+ // Read existing config
+ data, err := os.ReadFile(configPath)
+ if err != nil {
+ return nil, err
+ }
+
+ config := &ClientConfig{}
+ if err := yaml.Unmarshal(data, config); err != nil {
+ return nil, err
+ }
+
+ return config, nil
+}
+
+// SaveClientConfig saves the client configuration to the config file
+func SaveClientConfig(config *ClientConfig) error {
+ data, err := yaml.Marshal(config)
+ if err != nil {
+ return err
+ }
+
+ // Ensure the config directory exists
+ if err := os.MkdirAll(GetConfigDir(), 0755); err != nil {
+ return fmt.Errorf("failed to create config directory: %w", err)
+ }
+
+ return os.WriteFile(GetConfigPath(), data, 0644)
+}
+
+// GetConfigPath returns the path to the client configuration file
+func GetConfigPath() string {
+ return filepath.Join(GetConfigDir(), ClientConfigFile)
+}
+
+func GetConfigDir() string {
+ return filepath.Join(GetUserQuilibriumDir())
+}
+
+// IsClientConfigured checks if the client is configured
+func IsClientConfigured() bool {
+ return FileExists(ClientConfigPath)
+}
diff --git a/client/utils/download.go b/client/utils/download.go
new file mode 100644
index 0000000..b4cd51d
--- /dev/null
+++ b/client/utils/download.go
@@ -0,0 +1,208 @@
+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)
+ fmt.Printf("Getting binary %s...\n", fileName)
+ fmt.Println("Will save to", filepath.Join(BinaryPath, string(releaseType), version))
+ url := fmt.Sprintf("%s/%s", BaseReleaseURL, fileName)
+
+ if !DoesRemoteFileExist(url) {
+ fmt.Printf("the release file %s does not exist on the release server\n", fileName)
+ os.Exit(1)
+ }
+
+ 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)
+ destDir := filepath.Join(BinaryPath, string(releaseType), version)
+ os.MkdirAll(destDir, 0755)
+ destPath := filepath.Join(destDir, fileName)
+
+ fmt.Printf("Downloading %s...", 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.Print(" done\n")
+ 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)
+ fmt.Printf("Searching for signatures for %s from %s\n", baseName, BaseReleaseURL)
+ fmt.Println("Will save to", filepath.Join(BinaryPath, string(releaseType), version))
+
+ // 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 {
+ // Check if the remote signature file exists
+ sigFile := fmt.Sprintf("%s.dgst.sig.%d", baseName, num)
+ remoteURL := fmt.Sprintf("%s/%s", BaseReleaseURL, sigFile)
+
+ if !DoesRemoteFileExist(remoteURL) {
+ continue
+ }
+ fmt.Printf("Found signature file %s\n", sigFile)
+ files = append(files, fmt.Sprintf("%s.dgst.sig.%d", baseName, num))
+ }
+
+ if len(files) == 0 {
+ fmt.Printf("No signature files found for %s\n", baseName)
+ return nil
+ }
+
+ 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
+}
+
+func DoesRemoteFileExist(url string) bool {
+ resp, err := http.Head(url)
+ if err != nil || resp.StatusCode != http.StatusOK {
+ return false
+ }
+ if resp != nil && resp.Body != nil {
+ resp.Body.Close()
+ }
+ return true
+}
diff --git a/client/utils/fileUtils.go b/client/utils/fileUtils.go
new file mode 100644
index 0000000..62887d3
--- /dev/null
+++ b/client/utils/fileUtils.go
@@ -0,0 +1,237 @@
+package utils
+
+import (
+ "crypto/md5"
+ "crypto/sha256"
+ "encoding/hex"
+ "fmt"
+ "io"
+ "os"
+ "os/exec"
+ "os/user"
+ "path/filepath"
+ "runtime"
+)
+
+var ClientInstallPath = filepath.Join("/opt/quilibrium/", string(ReleaseTypeQClient))
+var RootQuilibriumPath = filepath.Join("/var/quilibrium/")
+var BinaryPath = filepath.Join(RootQuilibriumPath, "bin")
+var ClientDataPath = filepath.Join(BinaryPath, string(ReleaseTypeQClient))
+var NodeDataPath = filepath.Join(BinaryPath, string(ReleaseTypeNode))
+var DefaultSymlinkDir = "/usr/local/bin"
+var DefaultNodeSymlinkPath = filepath.Join(DefaultSymlinkDir, string(ReleaseTypeNode))
+var DefaultQClientSymlinkPath = filepath.Join(DefaultSymlinkDir, string(ReleaseTypeQClient))
+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)
+ }
+ }
+
+ fmt.Printf("Creating symlink %s -> %s\n", targetPath, execPath)
+
+ // Create the symlink
+ if err := os.Symlink(execPath, targetPath); err != nil {
+ return fmt.Errorf("failed to create symlink: %w", err)
+ }
+
+ return nil
+}
+
+// ValidateAndCreateDir validates a directory path and creates it if it doesn't exist
+func ValidateAndCreateDir(path string, user *user.User) 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) {
+ fmt.Printf("Creating directory %s\n", path)
+ if err := os.MkdirAll(path, 0755); err != nil {
+ return fmt.Errorf("failed to create directory %s: %v", path, err)
+ }
+ if user != nil {
+ ChownPath(path, user, false)
+ }
+ 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)
+}
+
+func IsSudo() bool {
+ user, err := user.Current()
+ if err != nil {
+ return false
+ }
+ return user.Username == "root"
+}
+
+// ChownPath changes the owner of a file or directory to the specified user
+func ChownPath(path string, user *user.User, isRecursive bool) error {
+ // Change ownership of the path
+ if isRecursive {
+ fmt.Printf("Changing ownership of %s (recursive) to %s\n", path, user.Username)
+ if err := exec.Command("chown", "-R", user.Uid+":"+user.Gid, path).Run(); err != nil {
+ return fmt.Errorf("failed to change ownership of %s to %s (requires sudo): %v", path, user.Uid, err)
+ }
+ } else {
+ fmt.Printf("Changing ownership of %s to %s\n", path, user.Username)
+ if err := exec.Command("chown", user.Uid+":"+user.Gid, path).Run(); err != nil {
+ return fmt.Errorf("failed to change ownership of %s to %s (requires sudo): %v", path, user.Uid, err)
+ }
+ }
+
+ return nil
+}
+
+func ChmodPath(path string, mode os.FileMode, description string) error {
+ fmt.Printf("Changing path: %s to %s (%s)\n", path, mode, description)
+ return os.Chmod(path, mode)
+}
+
+func WriteFile(path string, content string) error {
+ return os.WriteFile(path, []byte(content), 0644)
+}
+
+// WriteFileAuto writes content to a file, automatically using sudo only if necessary
+func WriteFileAuto(path string, content string) error {
+ // First check if file exists and is writable
+ if FileExists(path) {
+ // Try to open the file for writing to check permissions
+ file, err := os.OpenFile(path, os.O_WRONLY, 0)
+ if err == nil {
+ // File is writable, close it and write normally
+ file.Close()
+ fmt.Printf("Writing to file %s using normal permissions\n", path)
+ return os.WriteFile(path, []byte(content), 0644)
+ }
+ } else {
+ // Check if parent directory is writable
+ dir := filepath.Dir(path)
+ if IsWritable(dir) {
+ fmt.Printf("Writing to file %s using normal permissions\n", path)
+ return os.WriteFile(path, []byte(content), 0644)
+ }
+ }
+
+ // If we reach here, sudo is needed
+ fmt.Printf("Writing to file %s using sudo\n", path)
+ cmd := exec.Command("sudo", "tee", path)
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ return fmt.Errorf("failed to get stdin pipe: %w", err)
+ }
+
+ // Start the command
+ if err := cmd.Start(); err != nil {
+ return fmt.Errorf("failed to start sudo command: %w", err)
+ }
+
+ // Write content to stdin
+ if _, err := io.WriteString(stdin, content); err != nil {
+ return fmt.Errorf("failed to write to stdin: %w", err)
+ }
+ stdin.Close()
+
+ // Wait for the command to finish
+ if err := cmd.Wait(); err != nil {
+ return fmt.Errorf("sudo tee command failed: %w", err)
+ }
+
+ return nil
+}
+
+// CopyFile copies a file from src to dst
+func CopyFile(src, dst string) error {
+ fmt.Printf("Copying file from %s to %s\n", src, dst)
+ sourceData, err := os.ReadFile(src)
+ if err != nil {
+ return err
+ }
+
+ return os.WriteFile(dst, sourceData, 0600)
+}
diff --git a/client/utils/node.go b/client/utils/node.go
new file mode 100644
index 0000000..67e4440
--- /dev/null
+++ b/client/utils/node.go
@@ -0,0 +1,64 @@
+package utils
+
+import (
+ "encoding/hex"
+ "fmt"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ "github.com/pkg/errors"
+
+ "github.com/libp2p/go-libp2p/core/crypto"
+ "github.com/libp2p/go-libp2p/core/peer"
+ "source.quilibrium.com/quilibrium/monorepo/node/config"
+)
+
+func GetPeerIDFromConfig(cfg *config.Config) peer.ID {
+ peerPrivKey, err := hex.DecodeString(cfg.P2P.PeerPrivKey)
+ if err != nil {
+ panic(errors.Wrap(err, "error unmarshaling peerkey"))
+ }
+
+ privKey, err := crypto.UnmarshalEd448PrivateKey(peerPrivKey)
+ if err != nil {
+ panic(errors.Wrap(err, "error unmarshaling peerkey"))
+ }
+
+ pub := privKey.GetPublic()
+ id, err := peer.IDFromPublicKey(pub)
+ if err != nil {
+ panic(errors.Wrap(err, "error getting peer id"))
+ }
+
+ return id
+}
+
+func GetPrivKeyFromConfig(cfg *config.Config) (crypto.PrivKey, error) {
+ peerPrivKey, err := hex.DecodeString(cfg.P2P.PeerPrivKey)
+ if err != nil {
+ panic(errors.Wrap(err, "error unmarshaling peerkey"))
+ }
+
+ privKey, err := crypto.UnmarshalEd448PrivateKey(peerPrivKey)
+ return privKey, err
+}
+
+func IsExistingNodeVersion(version string) bool {
+ return FileExists(filepath.Join(NodeDataPath, version))
+}
+
+func CheckForSystemd() bool {
+ // Check if systemctl command exists
+ _, err := exec.LookPath("systemctl")
+ return err == nil
+}
+
+func LoadNodeConfig(configDirectory string) (*config.Config, error) {
+ NodeConfig, err := config.LoadConfig(configDirectory, "", false)
+ if err != nil {
+ fmt.Printf("invalid config directory: %s\n", configDirectory)
+ os.Exit(1)
+ }
+ return NodeConfig, nil
+}
diff --git a/client/utils/system.go b/client/utils/system.go
new file mode 100644
index 0000000..a87ee6b
--- /dev/null
+++ b/client/utils/system.go
@@ -0,0 +1,66 @@
+package utils
+
+import (
+ "bytes"
+ "fmt"
+ "os"
+ "os/exec"
+ "os/user"
+ "path/filepath"
+ "runtime"
+ "strings"
+)
+
+// 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
+}
+
+func GetCurrentSudoUser() (*user.User, error) {
+ if os.Geteuid() != 0 {
+ return user.Current()
+ }
+
+ cmd := exec.Command("sh", "-c", "env | grep SUDO_USER | cut -d= -f2 | cut -d\\n -f1")
+ var out bytes.Buffer
+ var stderr bytes.Buffer
+ cmd.Stdout = &out
+ cmd.Stderr = &stderr
+ err := cmd.Run()
+ if err != nil {
+ return nil, fmt.Errorf("failed to get current sudo user: %v", err)
+ }
+
+ userLookup, err := user.Lookup(strings.TrimSpace(out.String()))
+ if err != nil {
+ return nil, fmt.Errorf("failed to get current sudo user: %v", err)
+ }
+ return userLookup, nil
+}
+
+func GetUserQuilibriumDir() string {
+ sudoUser, err := GetCurrentSudoUser()
+ if err != nil {
+ fmt.Println("Error getting current sudo user")
+ os.Exit(1)
+ }
+
+ return filepath.Join(sudoUser.HomeDir, ".quilibrium")
+}
diff --git a/client/utils/types.go b/client/utils/types.go
new file mode 100644
index 0000000..dec318d
--- /dev/null
+++ b/client/utils/types.go
@@ -0,0 +1,55 @@
+package utils
+
+type ClientConfig struct {
+ DataDir string `yaml:"dataDir"`
+ SymlinkPath string `yaml:"symlinkPath"`
+ SignatureCheck bool `yaml:"signatureCheck"`
+}
+
+type NodeConfig struct {
+ ClientConfig
+ RewardsAddress string `yaml:"rewardsAddress"`
+ AutoUpdateInterval string `yaml:"autoUpdateInterval"`
+}
+
+const (
+ DefaultAutoUpdateInterval = "*/10 * * * *"
+)
+
+type ReleaseType string
+
+const (
+ ReleaseTypeQClient ReleaseType = "qclient"
+ ReleaseTypeNode ReleaseType = "node"
+)
+
+type BridgedPeerJson struct {
+ Amount string `json:"amount"`
+ Identifier string `json:"identifier"`
+ Variant string `json:"variant"`
+}
+
+type FirstRetroJson struct {
+ PeerId string `json:"peerId"`
+ Reward string `json:"reward"`
+}
+
+type SecondRetroJson struct {
+ PeerId string `json:"peerId"`
+ Reward string `json:"reward"`
+ JanPresence bool `json:"janPresence"`
+ FebPresence bool `json:"febPresence"`
+ MarPresence bool `json:"marPresence"`
+ AprPresence bool `json:"aprPresence"`
+ MayPresence bool `json:"mayPresence"`
+}
+
+type ThirdRetroJson struct {
+ PeerId string `json:"peerId"`
+ Reward string `json:"reward"`
+}
+
+type FourthRetroJson struct {
+ PeerId string `json:"peerId"`
+ Reward string `json:"reward"`
+}
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/crates/channel/Cargo.toml b/crates/channel/Cargo.toml
index 9d42125..745e060 100644
--- a/crates/channel/Cargo.toml
+++ b/crates/channel/Cargo.toml
@@ -9,7 +9,6 @@ name = "channel"
[dependencies]
base64 = "0.22.1"
-hex = "0.4.3"
serde_json = "1.0.117"
ed448-goldilocks-plus = "0.11.2"
rand = "0.8.5"
diff --git a/crates/classgroup/src/gmp_classgroup/ffi.rs b/crates/classgroup/src/gmp_classgroup/ffi.rs
index 76d94d2..b5492fb 100644
--- a/crates/classgroup/src/gmp_classgroup/ffi.rs
+++ b/crates/classgroup/src/gmp_classgroup/ffi.rs
@@ -23,11 +23,11 @@ use super::super::gmp::mpz::{mp_bitcnt_t, mp_limb_t};
use libc::{c_int, c_long, c_ulong, c_void, size_t};
// pub use c_ulong;
use std::{mem, usize};
-// We use the unsafe versions to avoid unecessary allocations.
+// We use the unsafe versions to avoid unnecessary allocations.
extern "C" {
fn adapted_nudupl(a: *mut Mpz, b: *mut Mpz, c: *mut Mpz, times: c_ulong);
}
-// We use the unsafe versions to avoid unecessary allocations.
+// We use the unsafe versions to avoid unnecessary allocations.
#[link(name = "gmp")]
extern "C" {
fn __gmpz_gcdext(gcd: *mut Mpz, s: *mut Mpz, t: *mut Mpz, a: *const Mpz, b: *const Mpz);
diff --git a/crates/rpm/Cargo.toml b/crates/rpm/Cargo.toml
index 848ad00..78004a5 100644
--- a/crates/rpm/Cargo.toml
+++ b/crates/rpm/Cargo.toml
@@ -8,15 +8,11 @@ crate-type = ["lib", "staticlib"]
name = "rpm"
[dependencies]
-hex = "0.4.3"
-serde_json = "1.0.117"
uniffi = { version= "0.25", features = ["cli"]}
curve25519-dalek = "4.1.3"
rand = "0.8.5"
num = "0.4.3"
-lazy_static = "1.5.0"
subtle = "2.6.1"
-rayon = "1.10"
[dev-dependencies]
criterion = { version = "0.4", features = ["html_reports"] }
diff --git a/crates/vdf/src/lib.rs b/crates/vdf/src/lib.rs
index 4156829..8b84688 100644
--- a/crates/vdf/src/lib.rs
+++ b/crates/vdf/src/lib.rs
@@ -12,13 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#![deny(warnings)]
-//! # Rust implementations of class groups and verifyable delay functions
+//! # Rust implementations of class groups and verifiable delay functions
//!
//! This repo includes three crates
//!
//! * `classgroup`, which includes a class group implementation, as well as a
//! trait for class groups.
-//! * `vdf`, which includes a Verifyable Delay Function (VDF) trait, as well as
+//! * `vdf`, which includes a Verifiable Delay Function (VDF) trait, as well as
//! an implementation of that trait.
//! * `vdf-cli`, which includes a command-line interface to the `vdf` crate. It
//! also includes additional commands, which are deprecated and will later be
@@ -155,7 +155,7 @@ pub trait VDFParams: Clone + Eq {
/// consumers of this trait **MUST NOT** expect this.
///
/// Instances of this trait are *not* expected to be `Sync`. This allows them
-/// to reuse allocations (such as scratch memory) accross invocations without
+/// to reuse allocations (such as scratch memory) across invocations without
/// the need for locking. However, they **MUST** be `Send` and `Clone`, so that
/// consumers can duplicate them and send them across threads.
pub trait VDF: Send + Debug {
@@ -170,7 +170,7 @@ pub trait VDF: Send + Debug {
/// The challenge is an opaque byte string of arbitrary length.
/// Implementors **MUST NOT** make any assumptions about its contents,
/// and **MUST** produce distinct outputs for distinct challenges
- /// (except with negiligible probability).
+ /// (except with negligible probability).
///
/// This can be most easily implemented by using the challenge as part of
/// the input of a cryptographic hash function. The VDFs provided in this
@@ -209,7 +209,7 @@ pub trait VDF: Send + Debug {
///
/// # Rationale
///
- /// It would be more ideomatic Rust to use the type system to enforce that a
+ /// It would be more idiomatic Rust to use the type system to enforce that a
/// difficulty has been validated before use. However, I (Demi) have not
/// yet figured out an object-safe way to do so.
fn check_difficulty(&self, difficulty: u64) -> Result<(), InvalidIterations>;
diff --git a/crates/vdf/src/proof_pietrzak.rs b/crates/vdf/src/proof_pietrzak.rs
index 786cecb..bea6b1d 100644
--- a/crates/vdf/src/proof_pietrzak.rs
+++ b/crates/vdf/src/proof_pietrzak.rs
@@ -454,7 +454,7 @@ mod test {
assert_eq!(calculate_final_t(Iterations(100), 8), 100);
}
#[test]
- fn check_assuptions_about_stdlib() {
+ fn check_assumptions_about_stdlib() {
assert_eq!(62 - u64::leading_zeros(1024u64), 9);
let mut q: Vec<_> = (1..4).step_by(2).collect();
assert_eq!(q[..], [1, 3]);
diff --git a/crates/vdf/src/proof_wesolowski.rs b/crates/vdf/src/proof_wesolowski.rs
index bbc72b5..2d306e2 100644
--- a/crates/vdf/src/proof_wesolowski.rs
+++ b/crates/vdf/src/proof_wesolowski.rs
@@ -91,7 +91,7 @@ pub fn approximate_parameters(t: f64) -> (usize, u8, u64) {
fn u64_to_bytes(q: u64) -> [u8; 8] {
if false {
- // This use of `std::mem::transumte` is correct, but still not justified.
+ // This use of `std::mem::transmute` is correct, but still not justified.
unsafe { std::mem::transmute(q.to_be()) }
} else {
[
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
+);
+
+
+