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
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,44 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
serialization traits as Platform. Existing bincode encodings and C interfaces
are unchanged, but Rust consumers must use the fork for compatible `Encode`/`Decode` traits.
The dependency switch does not automatically opt types into `DecodeUntrusted`.
- **Breaking:** `dashcore_hashes` re-exports the hash primitives (`sha1`,
`sha256`, `sha256d`, `sha512`, `sha512_256`, `ripemd160`, `hash160`, `hmac`,
`siphash24`, `cmp`, `hex`) from `bitcoin_hashes` 0.14. The raw types no longer
implement bincode or accept byte and sequence forms in serde, `Error` is
`FromSliceError`, and `forward_hex`, `backward_hex` and the `schemars` feature
are removed
- **Breaking:** hashes honour the format precision, so `{:.8}` prints 8 digits
- **Breaking:** `hash_x11::Hash` loses `forward_hex`, `backward_hex`,
`from_bytes_ref` and `from_bytes_mut`, `hash_x11::Midstate` is replaced by
`[u8; 32]`, and `n_bytes_hashed()` returns the bytes written
- **Breaking:** `EcdsaSighashType` comes from `bitcoin-crypto` and gains a
`NonStandard` variant, which `from_consensus` returns for non-standard flags
and which displays, parses and serializes as `0xNN`. The `from_standard` error
is `NonStandardSighashTypeError`, `TapSighashType` converts through `TryFrom`,
and `sighash::Error` no longer derives `Copy`, `PartialOrd`, `Ord` or `Hash`
- **Breaking:** the taproot types come from `bitcoin-crypto`. `Signature`
fields are renamed to `signature` and `sighash_type`, and `taproot::Error` is
`SigFromSliceError`, without a `Secp256k1` variant
- **Breaking:** `dashcore::eddsa` uses the `dash-pkc` types, so `EddsaPkBytes`
and `EddsaSkBytes` no longer implement `BaseCodec` and `EddsaError` changes
shape. Use `EddsaPublicKey::try_from`, `EddsaPkHash::from` and
`EddsaSecretKey::public_key` instead of `EddsaPkBytes::validate`,
`EddsaPkBytes::hash` and `EddsaSkBytes::public_key`
- **Breaking:** `QualifiedMasternodeListEntry::entry_hash` and
`MessageVerificationError::ThresholdSignatureNotValid` hold `Sha256dHash`
- **Breaking:** `dashcore::base58` is `base58ck` 0.5.0, so `Base58CkString`
replaces the `encode_*` functions and the `Base58` error variants carry
`base58::DecodeCheckError`
- Relicense `dash-network`, `dash-network-seeds`, `dash-spv`, `dash-spv-ffi`,
`dash-spv-bench`, `git-state` and `masternode-seeds-fetcher` to CC0-1.0
- All workspace crates declare a MSRV of 1.89

### Fixed

- Treat `SIGHASH_SINGLE` flags with `ANYONECANPAY` set, such as `0x83`, as
single when checking for the legacy `SIGHASH_SINGLE` bug
- `transaction_sighash` in `key-wallet-ffi` hashes non-standard flags as given
and rejects flags above `0xff`

## 0.44.0 - 2026-07-01

Expand Down
20 changes: 20 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,32 @@ members = ["dash", "crypto", "dash-network", "hashes", "internals", "fuzz", "rpc
resolver = "2"

[workspace.dependencies]
# Packages shared across crates
bincode = { package = "grovedb-bincode", version = "=2.1.0" }
bincode_derive = { package = "grovedb-bincode-derive", version = "=2.1.0" }
futures = "0.3"
hex = "0.4"
hex_lit = "0.1.1"
rayon = "1.11"
secp256k1 = "0.33.1" # must match with version used in `bitcoin-crypto` and `dash-pkc`
serde = { version = "1.0.219", default-features = false }
zeroize = { version = "1.8", features = ["derive"] }

# Packages from `dashpay/base-sdk`, MUST share same version ACROSS crates
dash-pkc = { git = "https://github.com/dashpay/base-sdk", rev = "e6402ced257c370a586ade9840ebbf545a4b7926", default-features = false }
dash-types = { git = "https://github.com/dashpay/base-sdk", rev = "e6402ced257c370a586ade9840ebbf545a4b7926", default-features = false }

# Packages from `rust-bitcoin/rust-bitcoin`, `bitcoin-crypto` uses revision due to `secp256k1`
base58ck = { version = "0.5.0" }
bitcoin_hashes = { version = "0.14.101", default-features = false }
bitcoin-crypto = { git = "https://github.com/rust-bitcoin/rust-bitcoin", rev = "7ba35c7c1dd63892bfb9c9395bbd80e2ab5df516", default-features = false }

[workspace.package]
authors = ["The rust-dashcore Developers"]
edition = "2024"
license = "CC0-1.0"
repository = "https://github.com/dashpay/rust-dashcore"
rust-version = "1.89.0"
version = "0.45.0"

[patch.crates-io]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<p>
<a href="https://crates.io/crates/dash"><img alt="Crate Info" src="https://img.shields.io/crates/v/dash.svg"/></a>
<a href="https://github.com/dashpay/rust-dashcore/blob/main/LICENSE"><img alt="MIT or Apache-2.0 Licensed" src="https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg"/></a>
<a href="https://github.com/dashpay/rust-dashcore/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/github/license/dashpay/rust-dashcore"/></a>
<a href="https://github.com/dashpay/rust-dashcore/actions?query=workflow%3AContinuous%20integration"><img alt="CI Status" src="https://github.com/dashpay/rust-dashcore/workflows/Continuous%20integration/badge.svg"></a>
<a href="https://codecov.io/gh/dashpay/rust-dashcore/branch/main"><img alt="Coverage (main)" src="https://codecov.io/gh/dashpay/rust-dashcore/branch/main/graph/badge.svg"/></a>
<a href="https://codecov.io/gh/dashpay/rust-dashcore/branch/dev"><img alt="Coverage (dev)" src="https://codecov.io/gh/dashpay/rust-dashcore/branch/dev/graph/badge.svg"/></a>
Expand Down
30 changes: 16 additions & 14 deletions crypto/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,40 +1,42 @@
[package]
name = "dashcore-crypto"
version = { workspace = true }
authors = ["The Dash Core Developers"]
license = "CC0-1.0"
repository = "https://github.com/dashpay/rust-dashcore/"
description = "Cryptographic primitives shared by rust-dashcore crates."
edition = "2021"
categories = ["cryptography::cryptocurrencies"]
keywords = ["crypto", "dash", "bls", "secp256k1"]
edition = "2021"
authors.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true
version.workspace = true

[features]
default = []

bincode = ["dep:bincode", "dep:bincode_derive", "dashcore_hashes/bincode", "dash-network/bincode"]
bls = ["dep:dash-pkc", "dash-pkc/bls", "dep:tracing"]
eddsa = ["dep:dash-pkc", "dash-pkc/eddsa"]
serde = ["dep:serde", "dash-types/serde", "dashcore_hashes/serde", "dash-network/serde", "secp256k1/serde"]
bls = ["dash-pkc/bls", "dep:tracing"]
eddsa = ["dash-pkc/eddsa"]
serde = ["dep:serde", "bitcoin-crypto/serde", "dash-types/serde", "dashcore_hashes/serde", "dash-network/serde", "dash-pkc/serde", "secp256k1/serde"]

[dependencies]
base58ck = { version = "0.1.0" }
base58ck = { workspace = true }
bitcoin-crypto = { workspace = true, features = ["std", "hex"] }
bincode = { workspace = true, optional = true }
bincode_derive = { workspace = true, optional = true }
dash-network = { path = "../dash-network" }
dash-pkc = { workspace = true, features = ["std"], optional = true }
dash-pkc = { workspace = true, features = ["std"] }
dash-types = { workspace = true, features = ["codec"] }
dashcore_hashes = { path = "../hashes" }
hex = { version = "0.4" }
hex = { workspace = true }
internals = { path = "../internals", package = "dashcore-private" }
secp256k1 = { version = "0.33.1" }
serde = { version = "1.0.219", default-features = false, features = [ "derive", "alloc" ], optional = true }
secp256k1 = { workspace = true }
serde = { workspace = true, features = ["derive", "alloc"], optional = true }
thiserror = "2"
tracing = { version = "0.1", optional = true }

[lints.rust]
unexpected_cfgs = { level = "deny", check-cfg = ['cfg(bench)', 'cfg(fuzzing)', 'cfg(kani)'] }

[dev-dependencies]
hex_lit = { version = "0.1.1", features = ["rust_v_1_46"] }
hex_lit = { workspace = true, features = ["rust_v_1_46"] }
serde_json = { version = "1.0" }
32 changes: 16 additions & 16 deletions crypto/src/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use secp256k1;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::sighash::{EcdsaSighashType, NonStandardSighashType};
use crate::sighash::EcdsaSighashType;

const MAX_SIG_LEN: usize = 73;

Expand Down Expand Up @@ -61,7 +61,7 @@ impl Signature {
let mut buf = [0u8; MAX_SIG_LEN];
let signature = self.sig.serialize_der();
buf[..signature.len()].copy_from_slice(&signature);
buf[signature.len()] = self.hash_ty as u8;
buf[signature.len()] = self.hash_ty.to_consensus_u8();
SerializedSignature {
data: buf,
len: signature.len() + 1,
Expand All @@ -74,14 +74,19 @@ impl Signature {
/// [`serialize`](Self::serialize) method instead.
pub fn to_vec(self) -> Vec<u8> {
// TODO: add support to serialize to a writer to SerializedSig
self.sig.serialize_der().iter().copied().chain(iter::once(self.hash_ty as u8)).collect()
self.sig
.serialize_der()
.iter()
.copied()
.chain(iter::once(self.hash_ty.to_consensus_u8()))
.collect()
}
}

impl fmt::Display for Signature {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::LowerHex::fmt(&self.sig.serialize_der().as_hex(), f)?;
fmt::LowerHex::fmt(&[self.hash_ty as u8].as_hex(), f)
fmt::LowerHex::fmt(&[self.hash_ty.to_consensus_u8()].as_hex(), f)
}
}

Expand All @@ -93,7 +98,8 @@ impl FromStr for Signature {
let (sighash_byte, signature) = bytes.split_last().ok_or(Error::EmptySignature)?;
Ok(Signature {
sig: secp256k1::ecdsa::Signature::from_der(signature)?,
hash_ty: EcdsaSighashType::from_standard(*sighash_byte as u32)?,
hash_ty: EcdsaSighashType::from_standard(*sighash_byte as u32)
.map_err(|_| Error::NonStandardSighashType(*sighash_byte as u32))?,
})
}
}
Expand Down Expand Up @@ -217,12 +223,12 @@ impl<'a> IntoIterator for &'a SerializedSignature {
}

/// A key-related error.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[derive(Clone, PartialEq, Eq, Debug)]
#[non_exhaustive]
pub enum Error {
/// Hex encoding error
HexEncoding(hex::Error),
/// Base58 encoding error
HexEncoding(hex::HexToBytesError),
/// Non-standard sighash type
NonStandardSighashType(u32),
/// Empty Signature
EmptySignature,
Expand Down Expand Up @@ -261,14 +267,8 @@ impl From<secp256k1::Error> for Error {
}
}

impl From<NonStandardSighashType> for Error {
fn from(err: NonStandardSighashType) -> Self {
Error::NonStandardSighashType(err.0)
}
}

impl From<hex::Error> for Error {
fn from(err: hex::Error) -> Self {
impl From<hex::HexToBytesError> for Error {
fn from(err: hex::HexToBytesError) -> Self {
Error::HexEncoding(err)
}
}
69 changes: 8 additions & 61 deletions crypto/src/eddsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,33 +7,13 @@
//! Ed25519 keys for Platform node identity.

#[cfg(feature = "eddsa")]
use dash_pkc::eddsa::{
EddsaPkBytes as PkcPkBytes, EddsaPublicKey as PkcPublicKey, EddsaSecretKey as PkcSecretKey,
};
#[cfg(feature = "eddsa")]
use dash_types::Hashable;
use dash_types::{make_bytes, make_sbytes};
#[cfg(feature = "eddsa")]
use thiserror::Error as ThisError;

/// Raw Ed25519 public key length.
pub const EDDSA_PK_LEN: usize = 32;
pub use dash_pkc::eddsa::{EddsaError, EddsaPublicKey, EddsaSecretKey};
pub use dash_pkc::eddsa::{EddsaPkBytes, EddsaSkBytes, EDDSA_PK_LEN, EDDSA_SK_LEN};
use dash_types::{make_bytes, Hashable};

/// Ed25519 public key hash length.
pub const EDDSA_PK_HASH_LEN: usize = 20;

/// Raw Ed25519 secret key (seed) length.
pub const EDDSA_SK_LEN: usize = 32;

/// Errors produced by Ed25519 operations.
#[cfg(feature = "eddsa")]
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ThisError)]
pub enum EddsaError {
/// Public key bytes are not a usable curve point.
#[error("Invalid Ed25519 public key: {0}")]
InvalidPublicKey(String),
}

make_bytes! {
/// Ed25519 public key hash (20 bytes).
EddsaPkHash, EDDSA_PK_HASH_LEN, rev
Expand Down Expand Up @@ -82,42 +62,10 @@ impl<'de, C> bincode::BorrowDecode<'de, C> for EddsaPkHash {
}
}

make_bytes! {
/// Ed25519 public key (32 bytes, unvalidated).
EddsaPkBytes, EDDSA_PK_LEN
}

#[cfg(feature = "eddsa")]
impl EddsaPkBytes {
/// Checks these bytes are a usable curve point.
///
/// # Errors
///
/// Returns `InvalidPublicKey` when the bytes are not on the curve.
pub fn validate(&self) -> Result<(), EddsaError> {
PkcPublicKey::from_bytes(self.as_bytes())
.map(|_| ())
.map_err(|e| EddsaError::InvalidPublicKey(e.to_string()))
}

/// The CometBFT hash of the public key.
pub fn hash(&self) -> EddsaPkHash {
EddsaPkHash::from_bytes(
*Hashable::hash(&PkcPkBytes::from_bytes(*self.as_bytes())).as_bytes(),
)
}
}

make_sbytes! {
/// Ed25519 secret key seed (32 bytes).
EddsaSkBytes, EDDSA_SK_LEN
}

#[cfg(feature = "eddsa")]
impl EddsaSkBytes {
/// Derives the corresponding public key.
pub fn public_key(&self) -> EddsaPkBytes {
EddsaPkBytes::from_bytes(PkcSecretKey::from_bytes(self.as_bytes()).public_key().to_bytes())
/// The CometBFT hash of the public key.
impl From<EddsaPkBytes> for EddsaPkHash {
fn from(public_key: EddsaPkBytes) -> Self {
Self::from_bytes(*Hashable::hash(&public_key).as_bytes())
}
}

Expand Down Expand Up @@ -171,7 +119,6 @@ mod tests {
assert_eq!(back, hash);
}

#[cfg(feature = "eddsa")]
#[test]
fn hash_is_truncated_sha256() {
use dashcore_hashes::{sha256, Hash};
Expand All @@ -180,7 +127,7 @@ mod tests {
let digest = sha256::Hash::hash(&public_key);
// `EddsaPkHash` holds the wire order, which is the byte-reversal of
// the canonical form the digest is read in.
let mut canonical = *EddsaPkBytes::from_bytes(public_key).hash().as_bytes();
let mut canonical = *EddsaPkHash::from(EddsaPkBytes::from_bytes(public_key)).as_bytes();
canonical.reverse();

assert_eq!(canonical[..], digest.to_byte_array()[..20]);
Expand Down
18 changes: 9 additions & 9 deletions crypto/src/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use crate::base58;
#[non_exhaustive]
pub enum Error {
/// Base58 encoding error
Base58(base58::Error),
Base58(base58::DecodeCheckError),
/// secp256k1-related error
Secp256k1(secp256k1::Error),
/// Invalid key prefix error
Expand All @@ -40,7 +40,7 @@ pub enum Error {
/// The base58 decoded correctly but the payload was the wrong length.
InvalidBase58PayloadLength(usize),
/// Hex decoding error
Hex(hex::Error),
Hex(hex::HexToArrayError),
/// `PublicKey` hex should be 66 or 130 digits long.
InvalidHexLength(usize),
/// Something is not supported based on active features
Expand Down Expand Up @@ -88,8 +88,8 @@ impl std::error::Error for Error {
}

#[doc(hidden)]
impl From<base58::Error> for Error {
fn from(e: base58::Error) -> Error {
impl From<base58::DecodeCheckError> for Error {
fn from(e: base58::DecodeCheckError) -> Error {
Error::Base58(e)
}
}
Expand All @@ -102,8 +102,8 @@ impl From<secp256k1::Error> for Error {
}

#[doc(hidden)]
impl From<hex::Error> for Error {
fn from(e: hex::Error) -> Self {
impl From<hex::HexToArrayError> for Error {
fn from(e: hex::HexToArrayError) -> Self {
Error::Hex(e)
}
}
Expand Down Expand Up @@ -400,11 +400,11 @@ impl PrivateKey {
ret[1..33].copy_from_slice(&self.inner[..]);
let privkey = if self.compressed {
ret[33] = 1;
base58::encode_check(&ret[..])
base58::Base58CkString::encode_unbounded(&ret[..])
} else {
base58::encode_check(&ret[..33])
base58::Base58CkString::encode_unbounded(&ret[..33])
};
fmt.write_str(&privkey)
fmt.write_str(privkey.as_str())
}

/// Get WIF encoding of this private key.
Expand Down
Loading
Loading