From 11a99131f9941977d3c6366c1628a8d6282d5f5a Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 14 Oct 2025 15:06:50 +0200 Subject: [PATCH 1/7] Node/account/tests: move to a specific file --- node/account/src/secret_key.rs | 67 --------------------------- node/account/tests/test_secret_key.rs | 50 ++++++++++++++++++++ 2 files changed, 50 insertions(+), 67 deletions(-) create mode 100644 node/account/tests/test_secret_key.rs diff --git a/node/account/src/secret_key.rs b/node/account/src/secret_key.rs index 5be8c7b658..d4c28865d6 100644 --- a/node/account/src/secret_key.rs +++ b/node/account/src/secret_key.rs @@ -193,70 +193,3 @@ impl<'de> serde::Deserialize<'de> for AccountSecretKey { b58.parse().map_err(serde::de::Error::custom) } } - -#[cfg(test)] -mod tests { - use std::env; - - use super::*; - - #[test] - fn test_account_secret_key_bs58check_decode() { - let parsed: AccountSecretKey = "EKFWgzXsoMYcP1Hnj7dBhsefxNucZ6wyz676Qg5uMFNzytXAi2Ww" - .parse() - .unwrap(); - assert_eq!( - parsed.0.get_address(), - "B62qjVQLxt9nYMWGn45mkgwYfcz8e8jvjNCBo11VKJb7vxDNwv5QLPS" - ); - } - - #[test] - fn test_account_secret_key_display() { - let parsed: AccountSecretKey = "EKFWgzXsoMYcP1Hnj7dBhsefxNucZ6wyz676Qg5uMFNzytXAi2Ww" - .parse() - .unwrap(); - assert_eq!( - &parsed.to_string(), - "EKFWgzXsoMYcP1Hnj7dBhsefxNucZ6wyz676Qg5uMFNzytXAi2Ww" - ); - } - - #[test] - fn test_encrypt_decrypt() { - let password = "not-very-secure-pass"; - - let new_key = AccountSecretKey::rand(); - let tmp_dir = env::temp_dir(); - let tmp_path = format!("{}/{}-key", tmp_dir.display(), new_key.public_key()); - - // dump encrypted file - new_key - .to_encrypted_file(&tmp_path, password) - .expect("Failed to encrypt secret key"); - - // load and decrypt - let decrypted = AccountSecretKey::from_encrypted_file(&tmp_path, password) - .unwrap_or_else(|_| panic!("Failed to decrypt secret key file: {}", tmp_path)); - - assert_eq!( - new_key.public_key(), - decrypted.public_key(), - "Encrypted and decrypted public keys do not match" - ); - } - - #[test] - fn test_ocaml_key_decrypt() { - let password = "not-very-secure-pass"; - let key_path = "../tests/files/accounts/test-key-1"; - let expected_public_key = "B62qmg7n4XqU3SFwx9KD9B7gxsKwxJP5GmxtBpHp1uxyN3grujii9a1"; - let decrypted = AccountSecretKey::from_encrypted_file(key_path, password) - .unwrap_or_else(|_| panic!("Failed to decrypt secret key file: {}", key_path)); - - assert_eq!( - expected_public_key.to_string(), - decrypted.public_key().to_string() - ) - } -} diff --git a/node/account/tests/test_secret_key.rs b/node/account/tests/test_secret_key.rs new file mode 100644 index 0000000000..63d4ed5dee --- /dev/null +++ b/node/account/tests/test_secret_key.rs @@ -0,0 +1,50 @@ +use std::env; + +use mina_node_account::AccountSecretKey; + +#[test] +fn test_account_secret_key_bs58check_decode() { + let parsed: AccountSecretKey = "EKFWgzXsoMYcP1Hnj7dBhsefxNucZ6wyz676Qg5uMFNzytXAi2Ww" + .parse() + .unwrap(); + // Test by comparing the public key + assert_eq!( + parsed.public_key().to_string(), + "B62qjVQLxt9nYMWGn45mkgwYfcz8e8jvjNCBo11VKJb7vxDNwv5QLPS" + ); +} + +#[test] +fn test_account_secret_key_display() { + let parsed: AccountSecretKey = "EKFWgzXsoMYcP1Hnj7dBhsefxNucZ6wyz676Qg5uMFNzytXAi2Ww" + .parse() + .unwrap(); + assert_eq!( + &parsed.to_string(), + "EKFWgzXsoMYcP1Hnj7dBhsefxNucZ6wyz676Qg5uMFNzytXAi2Ww" + ); +} + +#[test] +fn test_encrypt_decrypt() { + let password = "not-very-secure-pass"; + + let new_key = AccountSecretKey::rand(); + let tmp_dir = env::temp_dir(); + let tmp_path = format!("{}/{}-key", tmp_dir.display(), new_key.public_key()); + + // dump encrypted file + new_key + .to_encrypted_file(&tmp_path, password) + .expect("Failed to encrypt secret key"); + + // load and decrypt + let decrypted = AccountSecretKey::from_encrypted_file(&tmp_path, password) + .unwrap_or_else(|_| panic!("Failed to decrypt secret key file: {}", tmp_path)); + + assert_eq!( + new_key.public_key(), + decrypted.public_key(), + "Encrypted and decrypted public keys do not match" + ); +} From 0d2ed0164d0df3bafeae2baf8a36ee78873a46ed Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 14 Oct 2025 15:17:38 +0200 Subject: [PATCH 2/7] mina-node-account: document more the library, and move tests --- node/account/Cargo.toml | 1 + node/account/src/lib.rs | 67 +++++++++++++++++++++++++++++++++- node/account/src/public_key.rs | 6 +-- node/account/src/secret_key.rs | 6 +-- 4 files changed, 70 insertions(+), 10 deletions(-) diff --git a/node/account/Cargo.toml b/node/account/Cargo.toml index 0e6839012d..f75dd276a1 100644 --- a/node/account/Cargo.toml +++ b/node/account/Cargo.toml @@ -3,6 +3,7 @@ name = "mina-node-account" version = "0.17.0" edition = "2021" license = "Apache-2.0" +description = "Account management for Mina nodes, including key generation, encryption, and address handling" [dependencies] anyhow = { workspace = true } diff --git a/node/account/src/lib.rs b/node/account/src/lib.rs index 2862ada6fe..15a6f84d4d 100644 --- a/node/account/src/lib.rs +++ b/node/account/src/lib.rs @@ -1,5 +1,68 @@ -mod secret_key; -pub use secret_key::AccountSecretKey; +//! Account management for Mina nodes +//! +//! This crate provides a high-level interface for managing Mina accounts, +//! built on top of the +//! [`mina-signer`](https://github.com/o1-labs/proof-systems/tree/master/signer) +//! crate. It handles cryptographic key generation, encryption/decryption of +//! secret keys, and address handling. +//! +//! # Overview +//! +//! The crate exports two main types: +//! - [`AccountSecretKey`] - Represents a private key that can be used to +//! sign transactions +//! - [`AccountPublicKey`] - Represents a public key/address for receiving +//! transactions +//! +//! # Key Features +//! +//! - **Key Generation**: Generate new random keypairs for Mina accounts +//! - **Key Encryption**: Encrypt and decrypt secret keys using password-based +//! encryption +//! - **Address Format**: Encode and decode Mina addresses using the standard +//! Base58Check format +//! - **Key Import/Export**: Read and write encrypted keys from/to files +//! +//! # Example Usage +//! +//! ``` +//! use mina_node_account::{AccountSecretKey, AccountPublicKey}; +//! use std::env; +//! +//! // Generate a new keypair +//! let secret_key = AccountSecretKey::rand(); +//! let public_key = secret_key.public_key(); +//! +//! // Save encrypted key to file in temp directory +//! let temp_dir = env::temp_dir(); +//! let path = temp_dir.join(format!("test-wallet-{}", public_key)); +//! let password = "secure-password"; +//! secret_key.to_encrypted_file(&path, password) +//! .expect("Failed to save key"); +//! +//! // Load encrypted key from file +//! let loaded_key = AccountSecretKey::from_encrypted_file(&path, password) +//! .expect("Failed to load key"); +//! +//! // Get the public address +//! let address = AccountPublicKey::from(loaded_key.public_key()); +//! println!("Address: {}", address); +//! +//! // Verify the keys match +//! assert_eq!(secret_key.public_key().to_string(), +//! loaded_key.public_key().to_string()); +//! +//! // Clean up +//! std::fs::remove_file(&path).ok(); +//! ``` +//! +//! # Cryptography +//! +//! Mina uses the Pasta curves (Pallas and Vesta) for its cryptographic +//! operations. These curves are specifically designed for efficient +//! recursive zero-knowledge proof composition. mod public_key; +mod secret_key; pub use public_key::AccountPublicKey; +pub use secret_key::AccountSecretKey; diff --git a/node/account/src/public_key.rs b/node/account/src/public_key.rs index a72b994916..df1439f54a 100644 --- a/node/account/src/public_key.rs +++ b/node/account/src/public_key.rs @@ -1,5 +1,3 @@ -use std::{fmt, str::FromStr}; - use mina_p2p_messages::{ b58::FromBase58CheckError, binprot::{ @@ -8,9 +6,9 @@ use mina_p2p_messages::{ }, v2::{NonZeroCurvePoint, NonZeroCurvePointUncompressedStableV1}, }; -use serde::{Deserialize, Serialize}; - use mina_signer::{CompressedPubKey, PubKey}; +use serde::{Deserialize, Serialize}; +use std::{fmt, str::FromStr}; #[derive( BinProtWrite, BinProtRead, Serialize, Deserialize, Debug, Ord, PartialOrd, Eq, PartialEq, Clone, diff --git a/node/account/src/secret_key.rs b/node/account/src/secret_key.rs index d4c28865d6..aeaa8a029b 100644 --- a/node/account/src/secret_key.rs +++ b/node/account/src/secret_key.rs @@ -1,5 +1,4 @@ -use std::{fmt, fs, io, path::Path, str::FromStr}; - +use super::AccountPublicKey; use mina_core::{ constants::GENESIS_PRODUCER_SK, EncryptedSecretKey, EncryptedSecretKeyFile, EncryptionError, }; @@ -7,8 +6,7 @@ use mina_p2p_messages::{bigint::BigInt, v2::SignatureLibPrivateKeyStableV1}; use mina_signer::{keypair::KeypairError, seckey::SecKeyError, CompressedPubKey, Keypair}; use rand::{rngs::StdRng, CryptoRng, Rng, SeedableRng}; use serde::{Deserialize, Serialize}; - -use super::AccountPublicKey; +use std::{fmt, fs, io, path::Path, str::FromStr}; #[derive(Clone)] pub struct AccountSecretKey(Keypair); From db6e50a8628980db26a7c6bf01e11b7b195de27b Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 14 Oct 2025 15:18:27 +0200 Subject: [PATCH 3/7] Makefile: wrap test-vrf at 80 chars + add test-account target --- Makefile | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 1753da2892..0c19b43472 100644 --- a/Makefile +++ b/Makefile @@ -302,7 +302,12 @@ test-release: ## Run tests in release mode .PHONY: test-vrf test-vrf: ## Run VRF tests, requires nightly Rust - @cd vrf && cargo +$(NIGHTLY_RUST_VERSION) test --release -- -Z unstable-options --report-time + @cd vrf && cargo +$(NIGHTLY_RUST_VERSION) test --release -- \ + -Z unstable-options --report-time + +.PHONY: test-account +test-account: ## Run account tests + @cargo test -p mina-node-account .PHONY: test-p2p-messages test-p2p-messages: From 88508b11434981a30f9517906a2f72e4267714ab Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 14 Oct 2025 15:19:09 +0200 Subject: [PATCH 4/7] CI/tests: run test-account --- .github/workflows/tests.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 5619c12082..d347d9512c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -222,6 +222,25 @@ jobs: path: target/release/mina retention-days: 7 + account-tests: + timeout-minutes: 20 + runs-on: ubuntu-24.04 + steps: + - name: Git checkout + uses: actions/checkout@v5 + + - name: Setup build dependencies + uses: ./.github/actions/setup-build-deps + + - name: Setup Rust + uses: ./.github/actions/setup-rust + with: + toolchain: 1.84 + cache-prefix: build-v0 + + - name: Run account tests + run: make test-account + build-tests: timeout-minutes: 60 runs-on: ubuntu-22.04 From 0be9e4a689dd8c493090439b1a3cc88c3ed6e900 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 14 Oct 2025 15:21:18 +0200 Subject: [PATCH 5/7] CHANGELOG: add description for patch 1540 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0202e0531..c0d09da06f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 dummy values ([#1514](https://github.com/o1-labs/mina-rust/pull/1514)) - **CI/Documentation**: add a script to check the references to the OCaml code ([#1525](https://github.com/o1-labs/mina-rust/pull/1525)). +- **mina-node-account**: move tests into `node/account/tests`, document the + library and run the tests in CI + ([#1540](https://github.com/o1-labs/mina-rust/pull/1540)). ### Changed From d8340841eb716b01e7705a020c4710b156f68863 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 14 Oct 2025 15:57:22 +0200 Subject: [PATCH 6/7] CI/tests: use env vars instead of plain text --- .github/workflows/tests.yaml | 39 +++++++++++++++++------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index d347d9512c..f48d3f36d2 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -17,6 +17,9 @@ env: MINA_PANIC_ON_BUG: true CARGO_INCREMENTAL: 1 RUSTFLAGS: "-C overflow-checks=off -C debug-assertions=off" + RUST_STABLE_VERSION: "1.84" + RUST_NIGHTLY_VERSION: "nightly" + OCAML_VERSION: "4.14.2" concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -31,7 +34,7 @@ jobs: - name: Setup Rust uses: ./.github/actions/setup-rust with: - toolchain: 1.84 + toolchain: ${{ env.RUST_STABLE_VERSION }} enable-cache: false - name: Clean cargo cache run: cargo clean @@ -39,9 +42,6 @@ jobs: ledger-tests: timeout-minutes: 20 runs-on: ubuntu-24.04 - strategy: - matrix: - ocaml_version: [4.14.2] steps: - name: Git checkout uses: actions/checkout@v5 @@ -52,12 +52,12 @@ jobs: - name: Use shared OCaml setting up steps uses: ./.github/actions/setup-ocaml with: - ocaml_version: ${{ matrix.ocaml_version }} + ocaml_version: ${{ env.OCAML_VERSION }} - name: Setup Rust uses: ./.github/actions/setup-rust with: - toolchain: nightly + toolchain: ${{ env.RUST_NIGHTLY_VERSION }} cache-prefix: ledger-v0 - name: Download circuits files @@ -82,7 +82,7 @@ jobs: - name: Setup Rust uses: ./.github/actions/setup-rust with: - toolchain: nightly + toolchain: ${{ env.RUST_NIGHTLY_VERSION }} cache-prefix: p2p-messages-v0 - name: Download circuits files @@ -127,9 +127,6 @@ jobs: vrf-tests: timeout-minutes: 20 runs-on: ubuntu-24.04 - strategy: - matrix: - ocaml_version: [4.14.2] steps: - name: Git checkout uses: actions/checkout@v5 @@ -140,12 +137,12 @@ jobs: - name: Use shared OCaml setting up steps uses: ./.github/actions/setup-ocaml with: - ocaml_version: ${{ matrix.ocaml_version }} + ocaml_version: ${{ env.OCAML_VERSION }} - name: Setup Rust uses: ./.github/actions/setup-rust with: - toolchain: nightly + toolchain: ${{ env.RUST_NIGHTLY_VERSION }} cache-prefix: vrf-v0 - name: Build vrf tests @@ -167,7 +164,7 @@ jobs: - name: Setup Rust uses: ./.github/actions/setup-rust with: - toolchain: 1.84 + toolchain: ${{ env.RUST_STABLE_VERSION }} cache-prefix: p2p-v0 - name: Test p2p crate @@ -187,12 +184,12 @@ jobs: - name: Use shared OCaml setting up steps uses: ./.github/actions/setup-ocaml with: - ocaml_version: 4.14.2 + ocaml_version: ${{ env.OCAML_VERSION }} - name: Setup Rust uses: ./.github/actions/setup-rust with: - toolchain: 1.84 + toolchain: ${{ env.RUST_STABLE_VERSION }} cache-prefix: build-v0 - name: Release build @@ -235,7 +232,7 @@ jobs: - name: Setup Rust uses: ./.github/actions/setup-rust with: - toolchain: 1.84 + toolchain: ${{ env.RUST_STABLE_VERSION }} cache-prefix: build-v0 - name: Run account tests @@ -254,12 +251,12 @@ jobs: - name: Use shared OCaml setting up steps uses: ./.github/actions/setup-ocaml with: - ocaml_version: 4.14.2 + ocaml_version: ${{ env.OCAML_VERSION }} - name: Setup Rust uses: ./.github/actions/setup-rust with: - toolchain: 1.84 + toolchain: ${{ env.RUST_STABLE_VERSION }} cache-prefix: build-tests-v0 - name: Build tests @@ -285,12 +282,12 @@ jobs: - name: Use shared OCaml setting up steps uses: ./.github/actions/setup-ocaml with: - ocaml_version: 4.14.2 + ocaml_version: ${{ env.OCAML_VERSION }} - name: Setup Rust uses: ./.github/actions/setup-rust with: - toolchain: 1.84 + toolchain: ${{ env.RUST_STABLE_VERSION }} cache-prefix: build-tests-webrtc-v0 - name: Build tests @@ -316,7 +313,7 @@ jobs: - name: Setup Rust uses: ./.github/actions/setup-rust with: - toolchain: nightly + toolchain: ${{ env.RUST_NIGHTLY_VERSION }} cache-prefix: build-wasm-v0 - name: Setup wasm tooling From 14fdc1ed576b0907532ff31446a2ca282def22c7 Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 14 Oct 2025 16:15:27 +0200 Subject: [PATCH 7/7] CI/tests: use env var for versioning --- .github/workflows/tests.yaml | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index f48d3f36d2..d3d10ccb9e 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -20,6 +20,7 @@ env: RUST_STABLE_VERSION: "1.84" RUST_NIGHTLY_VERSION: "nightly" OCAML_VERSION: "4.14.2" + CACHE_VERSION: "v0" concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -58,7 +59,7 @@ jobs: uses: ./.github/actions/setup-rust with: toolchain: ${{ env.RUST_NIGHTLY_VERSION }} - cache-prefix: ledger-v0 + cache-prefix: ledger-${{ env.CACHE_VERSION }} - name: Download circuits files uses: ./.github/actions/setup-circuits @@ -83,7 +84,7 @@ jobs: uses: ./.github/actions/setup-rust with: toolchain: ${{ env.RUST_NIGHTLY_VERSION }} - cache-prefix: p2p-messages-v0 + cache-prefix: p2p-messages-${{ env.CACHE_VERSION }} - name: Download circuits files uses: ./.github/actions/setup-circuits @@ -143,7 +144,7 @@ jobs: uses: ./.github/actions/setup-rust with: toolchain: ${{ env.RUST_NIGHTLY_VERSION }} - cache-prefix: vrf-v0 + cache-prefix: vrf-${{ env.CACHE_VERSION }} - name: Build vrf tests run: make build-vrf @@ -165,7 +166,7 @@ jobs: uses: ./.github/actions/setup-rust with: toolchain: ${{ env.RUST_STABLE_VERSION }} - cache-prefix: p2p-v0 + cache-prefix: p2p-${{ env.CACHE_VERSION }} - name: Test p2p crate run: make test-p2p @@ -190,7 +191,7 @@ jobs: uses: ./.github/actions/setup-rust with: toolchain: ${{ env.RUST_STABLE_VERSION }} - cache-prefix: build-v0 + cache-prefix: build-${{ env.CACHE_VERSION }} - name: Release build run: make build-release @@ -233,7 +234,7 @@ jobs: uses: ./.github/actions/setup-rust with: toolchain: ${{ env.RUST_STABLE_VERSION }} - cache-prefix: build-v0 + cache-prefix: build-${{ env.CACHE_VERSION }} - name: Run account tests run: make test-account @@ -257,7 +258,7 @@ jobs: uses: ./.github/actions/setup-rust with: toolchain: ${{ env.RUST_STABLE_VERSION }} - cache-prefix: build-tests-v0 + cache-prefix: build-tests-${{ env.CACHE_VERSION }} - name: Build tests run: make build-tests @@ -288,7 +289,7 @@ jobs: uses: ./.github/actions/setup-rust with: toolchain: ${{ env.RUST_STABLE_VERSION }} - cache-prefix: build-tests-webrtc-v0 + cache-prefix: build-tests-webrtc-${{ env.CACHE_VERSION }} - name: Build tests run: make build-tests-webrtc @@ -314,7 +315,7 @@ jobs: uses: ./.github/actions/setup-rust with: toolchain: ${{ env.RUST_NIGHTLY_VERSION }} - cache-prefix: build-wasm-v0 + cache-prefix: build-wasm-${{ env.CACHE_VERSION }} - name: Setup wasm tooling run: make setup-wasm