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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[workspace]
members = ["dash", "dash-network", "dash-network-ffi", "hashes", "internals", "fuzz", "rpc-client", "rpc-json", "rpc-integration-test", "key-wallet", "key-wallet-ffi", "dash-spv", "dash-spv-ffi"]
members = ["dash", "dash-network", "dash-network-ffi", "hashes", "internals", "fuzz", "rpc-client", "rpc-json", "rpc-integration-test", "key-wallet", "key-wallet-ffi", "key-wallet-manager", "dash-spv", "dash-spv-ffi"]
resolver = "2"

[workspace.package]
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Supports (or should support)
* JSONRPC interaction with Dash Core
* FFI bindings for C/Swift integration (dash-spv-ffi, key-wallet-ffi)
* [Unified SDK](UNIFIED_SDK.md) option for iOS that combines Core and Platform functionality
* [High-level wallet management](key-wallet-manager/README.md) with transaction building and UTXO management

# Known limitations

Expand Down Expand Up @@ -79,6 +80,17 @@ fn main() {

See `client/examples/` for more usage examples.

# Wallet Management

This library provides comprehensive wallet functionality through multiple components:

* **key-wallet**: Low-level cryptographic primitives for HD wallets, mnemonic generation, and key derivation
* **[key-wallet-manager](key-wallet-manager/README.md)**: High-level wallet management with transaction building, UTXO tracking, and coin selection
* **key-wallet-ffi**: C/Swift FFI bindings for mobile integration
* **dash-spv**: SPV (Simplified Payment Verification) client implementation

For most applications, start with [key-wallet-manager](key-wallet-manager/README.md) which provides a complete, easy-to-use interface for wallet operations.

# Supported Dash Core Versions
The following versions are officially supported and automatically tested:
* 0.18.0
Expand Down Expand Up @@ -109,6 +121,11 @@ cargo update --package "byteorder" --precise "1.3.4"

Documentation can be found on [dashcore.readme.io/docs](https://dashcore.readme.io/docs).

## Component Documentation

* **[key-wallet-manager](key-wallet-manager/README.md)** - High-level wallet management guide
* **[Unified SDK](UNIFIED_SDK.md)** - iOS SDK combining Core and Platform functionality

# Contributing

Contributions are generally welcome. If you intend to make larger changes please
Expand Down
61 changes: 61 additions & 0 deletions TODOS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# TODOs for Rust Dashcore Key Wallet System

## Critical Issues (Prevent Compilation)

### dashcore crate compilation errors
The underlying `dashcore` crate has pre-existing compilation errors that prevent `key-wallet-manager` from building:

1. **Missing imports in crypto/sighash.rs**: Two unresolved imports are causing E0432 errors
2. **65 warnings in dashcore**: Various deprecated method usage and unused variables

**Impact**: key-wallet-manager cannot compile until dashcore is fixed.
**Priority**: Critical - blocks all high-level wallet functionality.

## Remaining Features (Optional)

### Serialization support
The last pending feature from the original plan:

1. **Create wallet serialization**: Add serde support for saving/loading wallets from disk
2. **Encrypted wallet storage**: Add password protection for saved wallets
3. **Backup and restore**: Implement mnemonic and xprv/xpub backup functionality

**Impact**: Wallets cannot be persisted between application runs.
**Priority**: Medium - useful for production applications.

### Testing improvements
1. **Multi-language mnemonic tests**: Currently marked as `#[ignore]` - need actual multi-language support
2. **Integration tests**: More comprehensive testing of key-wallet + key-wallet-manager integration
3. **Transaction building tests**: Test actual transaction creation and signing

## Known Limitations

### Watch-only wallet derivation
The current watch-only wallet implementation creates its own derivation paths rather than using the exact same addresses as the original wallet. This is due to the separation between account-level xpubs and the AddressPool API requirements.

### dashcore dependency issues
The architecture assumes dashcore will eventually compile. If dashcore continues to have issues, key-wallet-manager may need to:
1. Use a different transaction library
2. Implement transaction types internally
3. Wait for dashcore fixes

## Status Summary

✅ **Completed Successfully:**
- Restructured crate architecture (key-wallet + key-wallet-manager)
- Fixed all key-wallet compilation issues
- Added comprehensive tests for mnemonics and address management
- Created watch-only wallet functionality
- Enhanced derivation module with builder pattern
- Separated low-level primitives from high-level operations

❌ **Blocked by External Issues:**
- key-wallet-manager compilation (blocked by dashcore)
- Transaction building functionality (blocked by dashcore)
- Integration tests (blocked by dashcore)

✅ **Architecture Goals Met:**
- Clean separation of concerns
- No circular dependencies
- Proper use of existing dashcore types
- Extensible design for future features
8 changes: 3 additions & 5 deletions dash/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ default = [ "std", "secp-recovery", "bincode" ]
base64 = [ "base64-compat" ]
rand-std = ["secp256k1/rand"]
rand = ["secp256k1/rand"]
serde = ["actual-serde", "dashcore_hashes/serde", "secp256k1/serde", "key-wallet/serde", "dash-network/serde"]
serde = ["dep:serde", "dashcore_hashes/serde", "secp256k1/serde", "dash-network/serde"]
secp-lowmemory = ["secp256k1/lowmemory"]
secp-recovery = ["secp256k1/recovery"]
signer = ["secp-recovery", "rand", "base64"]
Expand All @@ -39,7 +39,7 @@ bincode = [ "dep:bincode", "dep:bincode_derive", "dashcore_hashes/bincode", "das
# The no-std feature doesn't disable std - you need to turn off the std feature for that by disabling default.
# Instead no-std enables additional features required for this crate to be usable without std.
# As a result, both can be enabled without conflict.
std = ["secp256k1/std", "dashcore_hashes/std", "bech32/std", "internals/std", "key-wallet/std", "dash-network/std"]
std = ["secp256k1/std", "dashcore_hashes/std", "bech32/std", "internals/std", "dash-network/std"]
no-std = ["core2", "dashcore_hashes/alloc", "dashcore_hashes/core2", "secp256k1/alloc", "dash-network/no-std"]

[package.metadata.docs.rs]
Expand All @@ -51,12 +51,10 @@ internals = { path = "../internals", package = "dashcore-private" }
bech32 = { version = "0.9.1", default-features = false }
dashcore_hashes = { path = "../hashes", default-features = false }
secp256k1 = { default-features = false, features = ["hashes"], version= "0.30.0" }
key-wallet = { path = "../key-wallet", default-features = false }
dash-network = { path = "../dash-network", default-features = false }
core2 = { version = "0.4.0", optional = true, features = ["alloc"], default-features = false }
rustversion = { version="1.0.20"}
# Do NOT use this as a feature! Use the `serde` feature instead.
actual-serde = { package = "serde", version = "1.0.219", default-features = false, features = [ "derive", "alloc" ], optional = true }
serde = { version = "1.0.219", default-features = false, features = [ "derive", "alloc" ], optional = true }

base64-compat = { version = "1.0.0", optional = true }
bitcoinconsensus = { version = "0.20.2-0.5.0", default-features = false, optional = true }
Expand Down
112 changes: 107 additions & 5 deletions dash/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,6 @@ use core::fmt;
use core::marker::PhantomData;
use core::str::FromStr;

use bech32;
use hashes::{Hash, HashEngine, sha256};
use internals::write_err;
use secp256k1::{Secp256k1, Verification, XOnlyPublicKey};

use crate::base58;
use crate::blockdata::constants::{
MAX_SCRIPT_ELEMENT_SIZE, PUBKEY_ADDRESS_PREFIX_MAIN, PUBKEY_ADDRESS_PREFIX_TEST,
Expand All @@ -66,7 +61,13 @@ use crate::error::ParseIntError;
use crate::hash_types::{PubkeyHash, ScriptHash};
use crate::prelude::*;
use crate::taproot::TapNodeHash;
use bech32;
use dash_network::Network;
use hashes::{Hash, HashEngine, sha256};
use internals::write_err;
use secp256k1::{Secp256k1, Verification, XOnlyPublicKey};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// Address error.
#[derive(Debug, PartialEq, Eq, Clone)]
Expand Down Expand Up @@ -183,6 +184,7 @@ impl From<bech32::Error> for Error {

/// The different types of addresses.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum AddressType {
/// Pay to pubkey hash.
Expand Down Expand Up @@ -812,6 +814,25 @@ crate::serde_utils::serde_string_serialize_impl!(Address, "a Dash address");
#[cfg(feature = "serde")]
crate::serde_utils::serde_string_deserialize_impl!(Address<NetworkUnchecked>, "a Dash address");

#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Address<NetworkChecked> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use core::str::FromStr;
use serde::de::Error;

let s = String::deserialize(deserializer)?;
let addr_unchecked = Address::<NetworkUnchecked>::from_str(&s).map_err(D::Error::custom)?;

// For NetworkChecked, we need to assume a network. This is a limitation
// of deserializing without network context. Users should use Address<NetworkUnchecked>
// for serde when the network is not known at compile time.
addr_unchecked.require_network(Network::Dash).map_err(D::Error::custom)
}
}

#[cfg(feature = "serde")]
impl serde::Serialize for Address<NetworkUnchecked> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
Expand All @@ -822,6 +843,87 @@ impl serde::Serialize for Address<NetworkUnchecked> {
}
}

#[cfg(feature = "bincode")]
impl bincode::Encode for Address {
fn encode<E: bincode::enc::Encoder>(
&self,
encoder: &mut E,
) -> Result<(), bincode::error::EncodeError> {
self.to_string().encode(encoder)
}
}

#[cfg(feature = "bincode")]
impl bincode::Decode for Address {
fn decode<D: bincode::de::Decoder>(
decoder: &mut D,
) -> Result<Self, bincode::error::DecodeError> {
use core::str::FromStr;
let s = String::decode(decoder)?;
Address::from_str(&s)
.map_err(|e| bincode::error::DecodeError::OtherString(e.to_string()))
.map(|a| a.assume_checked())
}
}

#[cfg(feature = "bincode")]
impl<'de> bincode::BorrowDecode<'de> for Address {
fn borrow_decode<D: bincode::de::BorrowDecoder<'de>>(
decoder: &mut D,
) -> Result<Self, bincode::error::DecodeError> {
use core::str::FromStr;
let s = String::borrow_decode(decoder)?;
Address::from_str(&s)
.map_err(|e| bincode::error::DecodeError::OtherString(e.to_string()))
.map(|a| a.assume_checked())
}
}

#[cfg(feature = "bincode")]
impl bincode::Encode for AddressType {
fn encode<E: bincode::enc::Encoder>(
&self,
encoder: &mut E,
) -> Result<(), bincode::error::EncodeError> {
use bincode::Encode;
(*self as u8).encode(encoder)
}
}

#[cfg(feature = "bincode")]
impl bincode::Decode for AddressType {
fn decode<D: bincode::de::Decoder>(
decoder: &mut D,
) -> Result<Self, bincode::error::DecodeError> {
let val = u8::decode(decoder)?;
match val {
0 => Ok(AddressType::P2pkh),
1 => Ok(AddressType::P2sh),
2 => Ok(AddressType::P2wpkh),
3 => Ok(AddressType::P2wsh),
4 => Ok(AddressType::P2tr),
_ => Err(bincode::error::DecodeError::OtherString("invalid address type".to_string())),
}
}
}

#[cfg(feature = "bincode")]
impl<'de> bincode::BorrowDecode<'de> for AddressType {
fn borrow_decode<D: bincode::de::BorrowDecoder<'de>>(
decoder: &mut D,
) -> Result<Self, bincode::error::DecodeError> {
let val = u8::borrow_decode(decoder)?;
match val {
0 => Ok(AddressType::P2pkh),
1 => Ok(AddressType::P2sh),
2 => Ok(AddressType::P2wpkh),
3 => Ok(AddressType::P2wsh),
4 => Ok(AddressType::P2tr),
_ => Err(bincode::error::DecodeError::OtherString("invalid address type".to_string())),
}
}
}

/// Methods on [`Address`] that can be called on both `Address<NetworkChecked>` and
/// `Address<NetworkUnchecked>`.
impl<V: NetworkValidation> Address<V> {
Expand Down
13 changes: 6 additions & 7 deletions dash/src/amount.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ fn unsigned_abs(x: i8) -> u8 {
x.wrapping_abs() as u8
}

fn repeat_char(f: &mut dyn fmt::Write, c: char, count: usize) -> fmt::Result {
fn repeat_char(f: &mut dyn Write, c: char, count: usize) -> fmt::Result {
for _ in 0..count {
f.write_char(c)?;
}
Expand All @@ -369,7 +369,7 @@ fn repeat_char(f: &mut dyn fmt::Write, c: char, count: usize) -> fmt::Result {
fn fmt_satoshi_in(
satoshi: u64,
negative: bool,
f: &mut dyn fmt::Write,
f: &mut dyn Write,
denom: Denomination,
show_denom: bool,
options: FormatOptions,
Expand Down Expand Up @@ -1264,7 +1264,7 @@ pub mod serde {
//! use dash::Amount;
//!
//! #[derive(Serialize, Deserialize)]
//! # #[serde(crate = "actual_serde")]
//! # #[serde(crate = "serde")]
//! pub struct HasAmount {
//! #[serde(with = "dash::amount::serde::as_btc")]
//! pub amount: Amount,
Expand Down Expand Up @@ -2151,7 +2151,6 @@ mod tests {
#[test]
fn serde_as_sat() {
#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(crate = "actual_serde")]
struct T {
#[serde(with = "crate::amount::serde::as_sat")]
pub amt: Amount,
Expand Down Expand Up @@ -2185,7 +2184,7 @@ mod tests {
use serde_json;

#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(crate = "actual_serde")]

struct T {
#[serde(with = "crate::amount::serde::as_btc")]
pub amt: Amount,
Expand Down Expand Up @@ -2221,7 +2220,7 @@ mod tests {
use serde_json;

#[derive(Serialize, Deserialize, PartialEq, Debug, Eq)]
#[serde(crate = "actual_serde")]

struct T {
#[serde(default, with = "crate::amount::serde::as_btc::opt")]
pub amt: Option<Amount>,
Expand Down Expand Up @@ -2266,7 +2265,7 @@ mod tests {
use serde_json;

#[derive(Serialize, Deserialize, PartialEq, Debug, Eq)]
#[serde(crate = "actual_serde")]

struct T {
#[serde(default, with = "crate::amount::serde::as_sat::opt")]
pub amt: Option<Amount>,
Expand Down
6 changes: 3 additions & 3 deletions dash/src/blockdata/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use crate::{VarInt, io, merkle_tree};
/// * [CBlockHeader definition](https://github.com/bitcoin/bitcoin/blob/345457b542b6a980ccfbc868af0970a6f91d1b82/src/primitives/block.h#L20)
#[derive(Copy, PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]

pub struct Header {
/// Block version, now repurposed for soft fork signalling.
pub version: Version,
Expand Down Expand Up @@ -113,7 +113,7 @@ impl Header {
/// * [BIP34 - Block v2, Height in Coinbase](https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki)
#[derive(Copy, PartialEq, Eq, Clone, Debug, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]

pub struct Version(i32);

impl Version {
Expand Down Expand Up @@ -199,7 +199,7 @@ impl Decodable for Version {
/// * [CBlock definition](https://github.com/bitcoin/bitcoin/blob/345457b542b6a980ccfbc868af0970a6f91d1b82/src/primitives/block.h#L62)
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]

pub struct Block {
/// The block header
pub header: Header,
Expand Down
1 change: 0 additions & 1 deletion dash/src/blockdata/fee_rate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use crate::prelude::*;
/// up the types as well as basic formatting features.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "actual_serde"))]
#[cfg_attr(feature = "serde", serde(transparent))]
pub struct FeeRate(u64);

Expand Down
Loading
Loading