Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 45 additions & 28 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ 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"
CACHE_VERSION: "v0"

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
Expand All @@ -31,17 +35,14 @@ 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

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
Expand All @@ -52,13 +53,13 @@ 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
cache-prefix: ledger-v0
toolchain: ${{ env.RUST_NIGHTLY_VERSION }}
cache-prefix: ledger-${{ env.CACHE_VERSION }}

- name: Download circuits files
uses: ./.github/actions/setup-circuits
Expand All @@ -82,8 +83,8 @@ jobs:
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
toolchain: nightly
cache-prefix: p2p-messages-v0
toolchain: ${{ env.RUST_NIGHTLY_VERSION }}
cache-prefix: p2p-messages-${{ env.CACHE_VERSION }}

- name: Download circuits files
uses: ./.github/actions/setup-circuits
Expand Down Expand Up @@ -127,9 +128,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
Expand All @@ -140,13 +138,13 @@ 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
cache-prefix: vrf-v0
toolchain: ${{ env.RUST_NIGHTLY_VERSION }}
cache-prefix: vrf-${{ env.CACHE_VERSION }}

- name: Build vrf tests
run: make build-vrf
Expand All @@ -167,8 +165,8 @@ jobs:
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
toolchain: 1.84
cache-prefix: p2p-v0
toolchain: ${{ env.RUST_STABLE_VERSION }}
cache-prefix: p2p-${{ env.CACHE_VERSION }}

- name: Test p2p crate
run: make test-p2p
Expand All @@ -187,13 +185,13 @@ 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
cache-prefix: build-v0
toolchain: ${{ env.RUST_STABLE_VERSION }}
cache-prefix: build-${{ env.CACHE_VERSION }}

- name: Release build
run: make build-release
Expand Down Expand Up @@ -222,6 +220,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: ${{ env.RUST_STABLE_VERSION }}
cache-prefix: build-${{ env.CACHE_VERSION }}

- name: Run account tests
run: make test-account

build-tests:
timeout-minutes: 60
runs-on: ubuntu-22.04
Expand All @@ -235,13 +252,13 @@ 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
cache-prefix: build-tests-v0
toolchain: ${{ env.RUST_STABLE_VERSION }}
cache-prefix: build-tests-${{ env.CACHE_VERSION }}

- name: Build tests
run: make build-tests
Expand All @@ -266,13 +283,13 @@ 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
cache-prefix: build-tests-webrtc-v0
toolchain: ${{ env.RUST_STABLE_VERSION }}
cache-prefix: build-tests-webrtc-${{ env.CACHE_VERSION }}

- name: Build tests
run: make build-tests-webrtc
Expand All @@ -297,8 +314,8 @@ jobs:
- name: Setup Rust
uses: ./.github/actions/setup-rust
with:
toolchain: nightly
cache-prefix: build-wasm-v0
toolchain: ${{ env.RUST_NIGHTLY_VERSION }}
cache-prefix: build-wasm-${{ env.CACHE_VERSION }}

- name: Setup wasm tooling
run: make setup-wasm
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions node/account/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
67 changes: 65 additions & 2 deletions node/account/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
6 changes: 2 additions & 4 deletions node/account/src/public_key.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::{fmt, str::FromStr};

use mina_p2p_messages::{
b58::FromBase58CheckError,
binprot::{
Expand All @@ -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,
Expand Down
73 changes: 2 additions & 71 deletions node/account/src/secret_key.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
use std::{fmt, fs, io, path::Path, str::FromStr};

use super::AccountPublicKey;
use mina_core::{
constants::GENESIS_PRODUCER_SK, EncryptedSecretKey, EncryptedSecretKeyFile, EncryptionError,
};
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);
Expand Down Expand Up @@ -193,70 +191,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()
)
}
}
Loading
Loading