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
1,402 changes: 678 additions & 724 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ members = [
"packages/rs-dapi",
"packages/rs-dash-event-bus",
"packages/rs-platform-wallet",
"packages/rs-platform-wallet-ffi",
"packages/rs-platform-encryption",
"packages/wasm-sdk",
]

Expand Down
4 changes: 2 additions & 2 deletions packages/rs-dapi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ prometheus = "0.14"
once_cell = "1.19"

# Dash Core RPC client
dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "e7792c431c55c0d28efb0344b3a1948f576be5ce" }
dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "e7792c431c55c0d28efb0344b3a1948f576be5ce" }
dashcore-rpc = { path = "../../../rust-dashcore/rpc-client" }
dash-spv = { path = "../../../rust-dashcore/dash-spv" }
Comment on lines +90 to +91
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify local path dependencies exist and are properly referenced.

The change from remote git references to local path dependencies (../../../rust-dashcore/rpc-client and ../../../rust-dashcore/dash-spv) represents a significant shift in dependency management. While this aligns with the PR's stated objective to prefer local path dependencies, cross-workspace references can be fragile.

Please verify:

  1. Both paths exist relative to the repository root and are correctly resolved
  2. The local rust-dashcore workspace is properly initialized and discoverable by Cargo
  3. No circular dependency chains are introduced
  4. Feature flags and crate visibility are preserved when using local paths vs. remote git references

Run the following script to confirm the paths are accessible:


🏁 Script executed:

#!/bin/bash
# Description: Verify local path dependencies exist and can be built

# Check if path dependencies exist
echo "=== Checking path existence ==="
test -d "rust-dashcore/rpc-client" && echo "✓ rpc-client path exists" || echo "✗ rpc-client path NOT found"
test -d "rust-dashcore/dash-spv" && echo "✓ dash-spv path exists" || echo "✗ dash-spv path NOT found"

# Verify Cargo can resolve the dependencies without errors
echo -e "\n=== Verifying Cargo can resolve dependencies ==="
cd packages/rs-dapi
cargo check --lib 2>&1 | head -20

Length of output: 245


</shell_calls>


🏁 Script executed:

#!/bin/bash
# Discover the actual repository structure and locate the missing directories

echo "=== Repository root structure ==="
ls -la | head -20

echo -e "\n=== Looking for 'rpc-client' directory anywhere in repo ==="
find . -maxdepth 3 -type d -name "rpc-client" 2>/dev/null | head -10

echo -e "\n=== Looking for 'dash-spv' directory anywhere in repo ==="
find . -maxdepth 3 -type d -name "dash-spv" 2>/dev/null | head -10

echo -e "\n=== Looking for 'rust-dashcore' directory ==="
find . -maxdepth 2 -type d -name "rust-dashcore" 2>/dev/null

echo -e "\n=== Content of packages/rs-dapi/Cargo.toml (lines 80-100) ==="
sed -n '80,100p' packages/rs-dapi/Cargo.toml

Length of output: 2049


🏁 Script executed:

#!/bin/bash
# Check repository root structure and workspace configuration

echo "=== Repository root listing ==="
ls -la

echo -e "\n=== Top-level Cargo.toml (if exists) ==="
if [ -f "Cargo.toml" ]; then
  cat Cargo.toml | head -30
else
  echo "No Cargo.toml at repository root"
fi

echo -e "\n=== Looking for all directories named 'rust-dashcore' ==="
find . -maxdepth 2 -type d -name "*dashcore*" 2>/dev/null

echo -e "\n=== Checking packages directory structure ==="
ls -la packages/ 2>/dev/null | head -20

echo -e "\n=== Looking for Cargo.toml files at top level and subdirs ==="
find . -maxdepth 2 -name "Cargo.toml" -type f 2>/dev/null | head -20

Length of output: 4539


Incorrect path references will cause build failures.

The local path dependencies in lines 88-89 reference non-existent directories:

  1. dashcore-rpc (line 88): Path ../../../rust-dashcore/rpc-client does not exist. The rpc-client directory is not present anywhere in the repository.

  2. dash-spv (line 89): While dash-spv exists in the repository at packages/dash-spv, the path is incorrectly specified as ../../../rust-dashcore/dash-spv. The correct relative path from packages/rs-dapi/ is ../dash-spv (matching the pattern used for other workspace dependencies like dpp and dapi-grpc).

Both dependencies will fail to resolve during build. Correct the paths to either:

  • Point to the actual locations within the workspace (e.g., ../dash-spv for dash-spv)
  • Or remove dashcore-rpc entirely if it cannot be sourced from the repository
🤖 Prompt for AI Agents
In packages/rs-dapi/Cargo.toml around lines 88 to 89, the two local path
dependencies point to non-existent locations; update dash-spv to the correct
workspace relative path and remove or fix dashcore-rpc: change the dash-spv path
from "../../../rust-dashcore/dash-spv" to "../dash-spv" to match the workspace
layout, and for dashcore-rpc either remove that dependency if the rpc-client
crate is not present in the repository or replace its path with the actual
relative path to the crate if it exists elsewhere in the repo; ensure Cargo.toml
paths are valid and run cargo metadata/build to verify resolution.


rs-dash-event-bus = { path = "../rs-dash-event-bus" }

Expand Down
10 changes: 5 additions & 5 deletions packages/rs-dpp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,18 @@ chrono = { version = "0.4.35", default-features = false, features = [
] }
chrono-tz = { version = "0.8", optional = true }
ciborium = { version = "0.2.2", optional = true }
dashcore = { git = "https://github.com/dashpay/rust-dashcore", rev = "e7792c431c55c0d28efb0344b3a1948f576be5ce", features = [
dashcore = { path = "../../../rust-dashcore/dash", features = [
"std",
"secp-recovery",
"rand",
"signer",
"serde",
"eddsa",
], default-features = false }
key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "e7792c431c55c0d28efb0344b3a1948f576be5ce", optional = true }
key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "e7792c431c55c0d28efb0344b3a1948f576be5ce", optional = true }
dash-spv = { git = "https://github.com/dashpay/rust-dashcore", rev = "e7792c431c55c0d28efb0344b3a1948f576be5ce", optional = true }
dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "e7792c431c55c0d28efb0344b3a1948f576be5ce", optional = true }
key-wallet = { path = "../../../rust-dashcore/key-wallet", optional = true }
key-wallet-manager = { path = "../../../rust-dashcore/key-wallet-manager", optional = true }
dash-spv = { path = "../../../rust-dashcore/dash-spv", optional = true }
dashcore-rpc = { path = "../../../rust-dashcore/rpc-client", optional = true }

env_logger = { version = "0.11" }
getrandom = { version = "0.2", features = ["js"] }
Expand Down
18 changes: 18 additions & 0 deletions packages/rs-platform-encryption/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[package]
name = "platform-encryption"
version = "2.1.1"
edition = "2021"
authors = ["Dash Core Team"]
license = "MIT"
description = "Cryptographic utilities for Dash Platform (DIP-15 DashPay encryption)"

[dependencies]
# Cryptography
dashcore = { path = "../../../rust-dashcore/dash" }
aes = "0.8"
cbc = "0.1"
sha2 = "0.10"
thiserror = "1.0"

[dev-dependencies]
hex = "0.4"
278 changes: 278 additions & 0 deletions packages/rs-platform-encryption/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
//! Cryptographic utilities for Dash Platform (DIP-15)
//!
//! This crate implements the Diffie-Hellman key exchange and encryption/decryption
//! operations as specified in DIP-15 for secure communication between Dash identities.

use aes::cipher::{block_padding::Pkcs7, KeyIvInit};
use aes::Aes256;
use dashcore::secp256k1::{PublicKey, SecretKey};

type Aes256CbcEnc = cbc::Encryptor<Aes256>;
type Aes256CbcDec = cbc::Decryptor<Aes256>;

/// Derive a shared secret key using ECDH as specified in DIP-15
///
/// This uses libsecp256k1_ecdh which computes: SHA256((y[31]&0x1|0x2) || x)
/// where (x, y) is the EC point result of scalar multiplication
///
/// # Arguments
/// * `private_key` - The private key for this side of the exchange
/// * `public_key` - The public key from the other party
///
/// # Returns
/// A 32-byte shared secret key
pub fn derive_shared_key_ecdh(private_key: &SecretKey, public_key: &PublicKey) -> [u8; 32] {
use dashcore::secp256k1::ecdh::SharedSecret;

// Use secp256k1's built-in ECDH which matches libsecp256k1_ecdh
// This computes SHA256((y[31]&0x1|0x2) || x) internally
let shared_secret = SharedSecret::new(public_key, private_key);

let mut key = [0u8; 32];
key.copy_from_slice(shared_secret.as_ref());
key
}

/// Encrypt data using CBC-AES-256
///
/// # Arguments
/// * `key` - 32-byte encryption key
/// * `iv` - 16-byte initialization vector (must be randomly generated and unique)
/// * `data` - Data to encrypt
///
/// # Returns
/// Encrypted data with PKCS7 padding
pub fn encrypt_aes_256_cbc(key: &[u8; 32], iv: &[u8; 16], data: &[u8]) -> Vec<u8> {
use aes::cipher::BlockEncryptMut;

let cipher = Aes256CbcEnc::new(key.into(), iv.into());
let mut buffer = Vec::new();
buffer.extend_from_slice(data);

// Add padding
let padding_needed = 16 - (data.len() % 16);
buffer.resize(data.len() + padding_needed, padding_needed as u8);

cipher
.encrypt_padded_mut::<Pkcs7>(&mut buffer, data.len())
.expect("encryption failed")
.to_vec()
}

/// Decrypt data using CBC-AES-256
///
/// # Arguments
/// * `key` - 32-byte encryption key
/// * `iv` - 16-byte initialization vector
/// * `ciphertext` - Encrypted data to decrypt
///
/// # Returns
/// Decrypted data with padding removed
pub fn decrypt_aes_256_cbc(
key: &[u8; 32],
iv: &[u8; 16],
ciphertext: &[u8],
) -> Result<Vec<u8>, CryptoError> {
use aes::cipher::BlockDecryptMut;

let cipher = Aes256CbcDec::new(key.into(), iv.into());
let mut buffer = ciphertext.to_vec();

let decrypted = cipher
.decrypt_padded_mut::<Pkcs7>(&mut buffer)
.map_err(|_| CryptoError::DecryptionFailed)?;

Ok(decrypted.to_vec())
}

/// Encrypt an extended public key for DashPay contact requests (DIP-15)
///
/// # Arguments
/// * `shared_key` - 32-byte shared secret from ECDH
/// * `iv` - 16-byte initialization vector (must be randomly generated)
/// * `xpub` - Extended public key bytes to encrypt
///
/// # Returns
/// Encrypted extended public key with IV prepended (96 bytes: 16-byte IV + 80-byte encrypted data)
pub fn encrypt_extended_public_key(shared_key: &[u8; 32], iv: &[u8; 16], xpub: &[u8]) -> Vec<u8> {
let encrypted_data = encrypt_aes_256_cbc(shared_key, iv, xpub);

// Prepend IV to encrypted data as per DIP-15
let mut result = Vec::with_capacity(16 + encrypted_data.len());
result.extend_from_slice(iv);
result.extend_from_slice(&encrypted_data);
result
}

/// Decrypt an extended public key from DashPay contact requests (DIP-15)
///
/// # Arguments
/// * `shared_key` - 32-byte shared secret from ECDH
/// * `encrypted_data` - Encrypted extended public key with IV prepended (96 bytes total)
///
/// # Returns
/// Decrypted extended public key bytes
pub fn decrypt_extended_public_key(
shared_key: &[u8; 32],
encrypted_data: &[u8],
) -> Result<Vec<u8>, CryptoError> {
if encrypted_data.len() < 16 {
return Err(CryptoError::InvalidCiphertextLength);
}

// Extract IV from first 16 bytes
let iv: [u8; 16] = encrypted_data[..16].try_into().unwrap();
let ciphertext = &encrypted_data[16..];

decrypt_aes_256_cbc(shared_key, &iv, ciphertext)
}

/// Encrypt an account label for DashPay (DIP-15)
///
/// # Arguments
/// * `shared_key` - 32-byte shared secret from ECDH
/// * `iv` - 16-byte initialization vector (must be randomly generated, different from xpub IV)
/// * `label` - Account label string to encrypt
///
/// # Returns
/// Encrypted label with IV prepended (48-80 bytes: 16-byte IV + 32-64 byte encrypted data)
pub fn encrypt_account_label(shared_key: &[u8; 32], iv: &[u8; 16], label: &str) -> Vec<u8> {
let encrypted_data = encrypt_aes_256_cbc(shared_key, iv, label.as_bytes());

// Prepend IV to encrypted data as per DIP-15
let mut result = Vec::with_capacity(16 + encrypted_data.len());
result.extend_from_slice(iv);
result.extend_from_slice(&encrypted_data);
result
}

/// Decrypt an account label from DashPay (DIP-15)
///
/// # Arguments
/// * `shared_key` - 32-byte shared secret from ECDH
/// * `encrypted_data` - Encrypted label with IV prepended (48-80 bytes total)
///
/// # Returns
/// Decrypted label string
pub fn decrypt_account_label(
shared_key: &[u8; 32],
encrypted_data: &[u8],
) -> Result<String, CryptoError> {
if encrypted_data.len() < 16 {
return Err(CryptoError::InvalidCiphertextLength);
}

// Extract IV from first 16 bytes
let iv: [u8; 16] = encrypted_data[..16].try_into().unwrap();
let ciphertext = &encrypted_data[16..];

let decrypted = decrypt_aes_256_cbc(shared_key, &iv, ciphertext)?;
String::from_utf8(decrypted).map_err(|_| CryptoError::InvalidUtf8)
}

/// Errors that can occur during cryptographic operations
#[derive(Debug, thiserror::Error)]
pub enum CryptoError {
#[error("Decryption failed")]
DecryptionFailed,

#[error("Invalid UTF-8 in decrypted data")]
InvalidUtf8,

#[error("Invalid ciphertext length (must be at least 16 bytes for IV)")]
InvalidCiphertextLength,
}

#[cfg(test)]
mod tests {
use super::*;
use dashcore::secp256k1::rand::{thread_rng, RngCore};
use dashcore::secp256k1::Secp256k1;

#[test]
fn test_ecdh_key_derivation() {
let secp = Secp256k1::new();

// Generate two key pairs
let (secret1, public1) = secp.generate_keypair(&mut thread_rng());
let (secret2, public2) = secp.generate_keypair(&mut thread_rng());

// Derive shared keys from both sides
let shared1 = derive_shared_key_ecdh(&secret1, &public2);
let shared2 = derive_shared_key_ecdh(&secret2, &public1);

// Both sides should derive the same shared key
assert_eq!(shared1, shared2);
}

#[test]
fn test_aes_encryption_decryption() {
let key = [0u8; 32];
let mut iv = [0u8; 16];
thread_rng().fill_bytes(&mut iv);

let plaintext = b"Hello, DashPay!";

let ciphertext = encrypt_aes_256_cbc(&key, &iv, plaintext);
let decrypted = decrypt_aes_256_cbc(&key, &iv, &ciphertext).unwrap();

assert_eq!(plaintext, decrypted.as_slice());
}

#[test]
fn test_extended_public_key_encryption() {
let secp = Secp256k1::new();
let (secret1, _public1) = secp.generate_keypair(&mut thread_rng());
let (_secret2, public2) = secp.generate_keypair(&mut thread_rng());

// Derive shared key
let shared_key = derive_shared_key_ecdh(&secret1, &public2);

// Generate random IV
let mut iv = [0u8; 16];
thread_rng().fill_bytes(&mut iv);

// Mock extended public key data (78 bytes)
let xpub_data = vec![0x04; 78];

// Encrypt and decrypt
let encrypted = encrypt_extended_public_key(&shared_key, &iv, &xpub_data);

// Verify size: 16 bytes (IV) + 80 bytes (encrypted data) = 96 bytes
assert_eq!(encrypted.len(), 96, "Encrypted xpub should be 96 bytes");

let decrypted = decrypt_extended_public_key(&shared_key, &encrypted).unwrap();

assert_eq!(xpub_data, decrypted);
}

#[test]
fn test_account_label_encryption() {
let secp = Secp256k1::new();
let (secret1, _public1) = secp.generate_keypair(&mut thread_rng());
let (_secret2, public2) = secp.generate_keypair(&mut thread_rng());

// Derive shared key
let shared_key = derive_shared_key_ecdh(&secret1, &public2);

// Generate random IV
let mut iv = [0u8; 16];
thread_rng().fill_bytes(&mut iv);

let label = "My DashPay Account";

// Encrypt and decrypt
let encrypted = encrypt_account_label(&shared_key, &iv, label);

// Verify size is in valid range: 48-80 bytes (16-byte IV + 32-64 bytes encrypted)
assert!(
encrypted.len() >= 48 && encrypted.len() <= 80,
"Encrypted label should be 48-80 bytes, got {}",
encrypted.len()
);

let decrypted = decrypt_account_label(&shared_key, &encrypted).unwrap();

assert_eq!(label, decrypted);
}
}
37 changes: 37 additions & 0 deletions packages/rs-platform-wallet-ffi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[package]
name = "platform-wallet-ffi"
version = "2.1.1"
edition = "2021"
authors = ["Dash Core Team"]
license = "MIT"
description = "C FFI bindings for platform-wallet"

[lib]
crate-type = ["staticlib", "cdylib", "rlib"]

[dependencies]
platform-wallet = { path = "../rs-platform-wallet" }
dpp = { path = "../rs-dpp" }

# FFI utilities
once_cell = "1.19"
parking_lot = { version = "0.12", features = ["send_guard"] }
lazy_static = "1.4"

# Serialization
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

# Core dependencies (for Network type)
dashcore = { path = "../../../rust-dashcore/dash" }
key-wallet = { path = "../../../rust-dashcore/key-wallet" }

# Error handling
thiserror = "1.0"

[dev-dependencies]
tempfile = "3.8"

[features]
default = []
mocks = []
Loading
Loading