diff --git a/Cargo.toml b/Cargo.toml index f362e2aa5..b19b39ad4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ resolver = "2" [workspace.dependencies] bincode = { package = "grovedb-bincode", version = "=2.1.0" } bincode_derive = { package = "grovedb-bincode-derive", version = "=2.1.0" } +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 } [workspace.package] version = "0.45.0" diff --git a/crypto/Cargo.toml b/crypto/Cargo.toml index e97a68e9b..896d58bcc 100644 --- a/crypto/Cargo.toml +++ b/crypto/Cargo.toml @@ -13,7 +13,8 @@ edition = "2021" default = [] bincode = ["dep:bincode", "dep:bincode_derive", "dashcore_hashes/bincode", "dash-network/bincode"] -bls = ["dep:dash-pkc", "dep:tracing"] +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"] [dependencies] @@ -21,8 +22,8 @@ base58ck = { version = "0.1.0" } bincode = { workspace = true, optional = true } bincode_derive = { workspace = true, optional = true } dash-network = { path = "../dash-network" } -dash-pkc = { version = "=0.1.0-beta", default-features = false, features = ["bls", "std"], optional = true } -dash-types = { version = "=0.1.0-beta", default-features = false, features = ["codec"] } +dash-pkc = { workspace = true, features = ["std"], optional = true } +dash-types = { workspace = true, features = ["codec"] } dashcore_hashes = { path = "../hashes" } hex = { version = "0.4" } internals = { path = "../internals", package = "dashcore-private" } @@ -36,3 +37,4 @@ unexpected_cfgs = { level = "deny", check-cfg = ['cfg(bench)', 'cfg(fuzzing)', ' [dev-dependencies] hex_lit = { version = "0.1.1", features = ["rust_v_1_46"] } +serde_json = { version = "1.0" } diff --git a/crypto/src/bls.rs b/crypto/src/bls.rs index c1b140339..b9d3dc43a 100644 --- a/crypto/src/bls.rs +++ b/crypto/src/bls.rs @@ -6,8 +6,6 @@ //! BLS12-381 public key and signatures. -use core::str::FromStr; - #[cfg(feature = "bls")] use dash_pkc::__deps::ff::PrimeField; #[cfg(feature = "bls")] @@ -15,7 +13,7 @@ use dash_pkc::bls::{ BlsPublicKey as PkcPublicKey, BlsScChia, BlsScIetf, BlsScheme as PkcScheme, BlsSecretKey as PkcSecretKey, BlsSignature as PkcSignature, Fr, }; -use dash_types::{make_bytes, make_sbytes, type_cvrt}; +use dash_types::{make_bytes, make_sbytes}; use hex::FromHexError; #[cfg(feature = "bls")] use thiserror::Error as ThisError; @@ -107,14 +105,6 @@ impl BlsPkBytes { } } -impl FromStr for BlsPkBytes { - type Err = FromHexError; - - fn from_str(s: &str) -> Result { - Self::from_hex(s) - } -} - #[cfg(feature = "bincode")] impl bincode::Encode for BlsPkBytes { fn encode( @@ -143,12 +133,6 @@ impl<'de, C> bincode::BorrowDecode<'de, C> for BlsPkBytes { } } -type_cvrt!( - for[] TryFrom<&[u8]> for BlsPkBytes, - core::array::TryFromSliceError, - |v| Ok(Self::from_bytes(<[u8; BLS_PK_LEN]>::try_from(*v)?)) -); - /// A [`BlsPkBytes`] paired with the scheme to read it under. #[cfg(feature = "bls")] #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -360,14 +344,6 @@ impl BlsSigBytes { } } -impl FromStr for BlsSigBytes { - type Err = FromHexError; - - fn from_str(s: &str) -> Result { - Self::from_hex(s) - } -} - #[cfg(feature = "bincode")] impl bincode::Encode for BlsSigBytes { fn encode( @@ -396,12 +372,6 @@ impl<'de, C> bincode::BorrowDecode<'de, C> for BlsSigBytes { } } -type_cvrt!( - for[] TryFrom<&[u8]> for BlsSigBytes, - core::array::TryFromSliceError, - |v| Ok(Self::from_bytes(<[u8; BLS_SIG_LEN]>::try_from(*v)?)) -); - /// A [`BlsSigBytes`] paired with the scheme to read it under. #[cfg(feature = "bls")] #[derive(Clone, Copy, Debug, Eq, PartialEq)] diff --git a/crypto/src/eddsa.rs b/crypto/src/eddsa.rs new file mode 100644 index 000000000..6a2ae6582 --- /dev/null +++ b/crypto/src/eddsa.rs @@ -0,0 +1,188 @@ +// +// This file is a part of rust-dashcore. +// SPDX-License-Identifier: CC0-1.0 +// See the accompanying file LICENSE or https://creativecommons.org/publicdomain/zero/1.0 +// + +//! 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; + +/// 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 +} + +impl EddsaPkHash { + /// Wraps the canonical (i.e. Tenderdash) order bytes. + pub fn from_canonical_bytes(mut bytes: [u8; EDDSA_PK_HASH_LEN]) -> Self { + bytes.reverse(); + Self::from_bytes(bytes) + } + + /// Copies out the canonical (i.e. Tenderdash) order bytes. + pub fn to_canonical_bytes(self) -> [u8; EDDSA_PK_HASH_LEN] { + let mut bytes = self.to_bytes(); + bytes.reverse(); + bytes + } +} + +#[cfg(feature = "bincode")] +impl bincode::Encode for EddsaPkHash { + fn encode( + &self, + encoder: &mut E, + ) -> Result<(), bincode::error::EncodeError> { + self.to_canonical_bytes().encode(encoder) + } +} + +#[cfg(feature = "bincode")] +impl bincode::Decode for EddsaPkHash { + fn decode>( + decoder: &mut D, + ) -> Result { + Ok(Self::from_canonical_bytes(<[u8; EDDSA_PK_HASH_LEN]>::decode(decoder)?)) + } +} + +#[cfg(feature = "bincode")] +impl<'de, C> bincode::BorrowDecode<'de, C> for EddsaPkHash { + fn borrow_decode>( + decoder: &mut D, + ) -> Result { + Ok(Self::from_canonical_bytes(<[u8; EDDSA_PK_HASH_LEN]>::borrow_decode(decoder)?)) + } +} + +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()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Persisted masternode-list snapshots were written while the platform + /// node id was typed as `PubkeyHash`; the bincode layout must stay + /// identical so old snapshots keep decoding. + #[cfg(feature = "bincode")] + #[test] + fn bincode_layout_matches_pubkey_hash() { + use dashcore_hashes::Hash as _; + + let bytes = [ + 0x8b, 0xaa, 0xdf, 0xf0, 0x0d, 0x8b, 0xaa, 0xdf, 0xf0, 0x0d, 0x8b, 0xaa, 0xdf, 0xf0, + 0x0d, 0x8b, 0xaa, 0xdf, 0xf0, 0x0d, + ]; + let config = bincode::config::standard(); + let hash_bytes = bincode::encode_to_vec(EddsaPkHash::from_canonical_bytes(bytes), config) + .expect("encode hash"); + let pubkey_hash_bytes = + bincode::encode_to_vec(crate::key::PubkeyHash::from_byte_array(bytes), config) + .expect("encode pubkey hash"); + assert_eq!(hash_bytes, pubkey_hash_bytes); + assert_eq!(hash_bytes, bytes, "the canonical order is the persisted order"); + + let (decoded, _): (EddsaPkHash, _) = + bincode::decode_from_slice(&hash_bytes, config).expect("decode hash"); + assert_eq!(decoded.to_canonical_bytes(), bytes); + } + + #[test] + fn hex_display_and_parse_round_trip() { + let hex = "4cd2ca50b36e0a2bb1b6b29da140448b47eeb7a1"; + let hash: EddsaPkHash = hex.parse().expect("parse hash hex"); + assert_eq!(hash.to_string(), hex); + assert_eq!(format!("{:?}", hash), format!("EddsaPkHash({})", hex)); + assert!("abcd".parse::().is_err(), "wrong-length hex must fail"); + assert!("zz".repeat(20).parse::().is_err(), "non-hex input must fail"); + } + + #[cfg(feature = "serde")] + #[test] + fn serde_json_round_trip_as_hex_string() { + let hash = EddsaPkHash::from_bytes([0x11; 20]); + let json = serde_json::to_string(&hash).expect("serialize hash"); + assert_eq!(json, format!("\"{}\"", "11".repeat(20))); + let back: EddsaPkHash = serde_json::from_str(&json).expect("deserialize hash"); + assert_eq!(back, hash); + } + + #[cfg(feature = "eddsa")] + #[test] + fn hash_is_truncated_sha256() { + use dashcore_hashes::{sha256, Hash}; + + let public_key = [7u8; 32]; + 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(); + canonical.reverse(); + + assert_eq!(canonical[..], digest.to_byte_array()[..20]); + } +} diff --git a/crypto/src/lib.rs b/crypto/src/lib.rs index 6d024877d..bcedd9555 100644 --- a/crypto/src/lib.rs +++ b/crypto/src/lib.rs @@ -25,6 +25,7 @@ pub(crate) mod serde_utils; pub mod bls; pub mod ecdsa; +pub mod eddsa; pub mod key; pub mod sighash; pub mod taproot; diff --git a/dash/Cargo.toml b/dash/Cargo.toml index 0ec437eb2..3d1e40e9b 100644 --- a/dash/Cargo.toml +++ b/dash/Cargo.toml @@ -29,7 +29,7 @@ secp-recovery = ["secp256k1/recovery"] signer = ["secp-recovery", "rand", "base64"] core-block-hash-use-x11 = ["dashcore_hashes/x11"] bls = ["dashcore-crypto/bls"] -eddsa = ["ed25519-dalek"] +eddsa = ["dashcore-crypto/eddsa"] quorum_validation = ["bls"] message_verification = ["bls"] bincode = [ "dep:bincode", "dep:bincode_derive", "dashcore-crypto/bincode", "dashcore_hashes/bincode", "dash-network/bincode" ] @@ -46,7 +46,7 @@ bech32 = { version = "0.9.1" } dashcore_hashes = { path = "../hashes" } dashcore-crypto = { path = "../crypto" } dash-network = { path = "../dash-network" } -dash-types = { version = "=0.1.0-beta", default-features = false, features = ["codec"] } +dash-types = { workspace = true, features = ["codec"] } secp256k1 = { version = "0.33.1" } serde = { version = "1.0.219", default-features = false, features = [ "derive", "alloc", "rc" ], optional = true } @@ -57,7 +57,6 @@ anyhow = { version= "1.0" } hex = { version= "0.4" } bincode = { workspace = true, optional = true } bincode_derive = { workspace = true, optional = true } -ed25519-dalek = { version = "2.1", features = ["rand_core"], optional = true } blake3 = "1.8.1" thiserror = "2" bitvec = "1.0" diff --git a/dash/src/blockdata/transaction/special_transaction/provider_registration.rs b/dash/src/blockdata/transaction/special_transaction/provider_registration.rs index 1f9e31ec3..1973cce76 100644 --- a/dash/src/blockdata/transaction/special_transaction/provider_registration.rs +++ b/dash/src/blockdata/transaction/special_transaction/provider_registration.rs @@ -753,7 +753,7 @@ mod tests { let decoded: ProviderRegistrationPayload = deserialize(&encoded).expect("deserialize zero-filled payload"); - assert_eq!(decoded.platform_node_id, Some(crate::PlatformNodeId::from_byte_array([0; 20]))); + assert_eq!(decoded.platform_node_id, Some(crate::PlatformNodeId::from_bytes([0; 20]))); assert_eq!(decoded.platform_p2p_port, Some(0)); assert_eq!(decoded.platform_http_port, Some(0)); } diff --git a/dash/src/blockdata/transaction/special_transaction/provider_update_service.rs b/dash/src/blockdata/transaction/special_transaction/provider_update_service.rs index 6068886b8..02c73698b 100644 --- a/dash/src/blockdata/transaction/special_transaction/provider_update_service.rs +++ b/dash/src/blockdata/transaction/special_transaction/provider_update_service.rs @@ -406,7 +406,7 @@ mod tests { port: 0, script_payout: ScriptBuf::from(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0]), inputs_hash: InputsHash::all_zeros(), - platform_node_id: Some(PlatformNodeId::from_byte_array([0; 20])), + platform_node_id: Some(PlatformNodeId::from_bytes([0; 20])), platform_p2p_port: Some(0), platform_http_port: Some(0), payload_sig: BLSSignature::from([0; 96]), @@ -448,7 +448,7 @@ mod tests { assert_eq!(encoded.len(), 207); let decoded = ProviderUpdateServicePayload::consensus_decode(&mut &encoded[..]).unwrap(); - assert_eq!(decoded.platform_node_id, Some(PlatformNodeId::from_byte_array([0; 20]))); + assert_eq!(decoded.platform_node_id, Some(PlatformNodeId::from_bytes([0; 20]))); assert_eq!(decoded.platform_p2p_port, Some(0)); assert_eq!(decoded.platform_http_port, Some(0)); } diff --git a/dash/src/hash_types.rs b/dash/src/hash_types.rs index 79f05471a..7dc1dbc54 100644 --- a/dash/src/hash_types.rs +++ b/dash/src/hash_types.rs @@ -67,6 +67,7 @@ macro_rules! impl_asref_push_bytes { pub use newtypes::*; mod newtypes { + pub use dashcore_crypto::eddsa::EddsaPkHash; pub use dashcore_crypto::key::{PubkeyHash, WPubkeyHash}; use core::str::FromStr; @@ -215,6 +216,23 @@ mod newtypes { impl_asref_push_bytes!(PubkeyHash, ScriptHash, WPubkeyHash, WScriptHash); + impl crate::consensus::Encodable for EddsaPkHash { + fn consensus_encode( + &self, + w: &mut W, + ) -> Result { + self.as_bytes().consensus_encode(w) + } + } + + impl crate::consensus::Decodable for EddsaPkHash { + fn consensus_decode( + r: &mut R, + ) -> Result { + Ok(Self::from_bytes(<[u8; 20]>::consensus_decode(r)?)) + } + } + impl Txid { /// Create a Txid from a string pub fn from_hex(s: &str) -> Result { @@ -376,13 +394,53 @@ mod newtypes { } } -#[cfg(all(test, feature = "serde"))] +#[cfg(test)] mod tests { use core::str::FromStr; use super::*; + use crate::consensus::{deserialize, serialize}; + use serde_derive::{Deserialize, Serialize}; + #[test] + fn consensus_round_trip() { + let mut wire = [0u8; 20]; + for (i, byte) in wire.iter_mut().enumerate() { + *byte = i as u8; + } + + let node_id = EddsaPkHash::from_bytes(wire); + let encoded = serialize(&node_id); + assert_eq!(encoded.len(), 20); + assert_eq!(encoded, wire, "the stored order is the wire order"); + let decoded: EddsaPkHash = deserialize(&encoded).expect("decode node id"); + assert_eq!(decoded, node_id); + } + + /// Mainnet evonode example from issue #887: the ProRegTx wire bytes carry + /// the `uint160` internal (reversed) order, while the canonical id — as + /// dashmate/Tenderdash derive and display it — is the byte-reversal. + #[test] + fn consensus_decode_yields_canonical_order() { + let wire_bytes = crate::internal_macros::hex!("8cb97997a418f4814a63d3564b9574a393437968"); + let node_id: EddsaPkHash = deserialize(&wire_bytes).expect("decode node id"); + assert_eq!(node_id.to_string(), "68794393a374954b56d3634a81f418a49779b98c"); + assert_eq!(serialize(&node_id), wire_bytes, "re-encoding restores wire order"); + } + + /// A node id decoded from the wire must compare equal to one derived from + /// the matching Ed25519 public key — the mismatch reported in issue #887. + #[cfg(feature = "eddsa")] + #[test] + fn decoded_wire_id_matches_derived_id() { + let public_key = [7u8; 32]; + let derived = dashcore_crypto::eddsa::EddsaPkBytes::from_bytes(public_key).hash(); + + let decoded: EddsaPkHash = deserialize(&derived.to_bytes()).expect("decode node id"); + assert_eq!(decoded, derived); + } + /// Regression test for the bug where hash newtypes' `Deserialize` errored /// with "bad hex string length 32 (expected 64)" (or similar) when an /// hash-bearing struct was wrapped by an internally-tagged enum and @@ -392,6 +450,7 @@ mod tests { /// replayed into the HR branch as raw bytes — which the previous /// string-only `HexVisitor` rejected because `from_str` saw 32 UTF-8 chars /// instead of the expected 64-char hex form. + #[cfg(feature = "serde")] #[test] fn serde_round_trip_through_internally_tagged_enum() { #[derive(Debug, PartialEq, Serialize, Deserialize)] @@ -469,6 +528,7 @@ mod tests { /// 20-byte hash (PubkeyHash) goes through the same path. Smaller hash /// length exercises a different `raw_len_bytes` / `hex_len_bytes` /// disambiguation in the visitor. + #[cfg(feature = "serde")] #[test] fn serde_round_trip_through_internally_tagged_enum_pubkey_hash() { #[derive(Debug, PartialEq, Serialize, Deserialize)] diff --git a/dash/src/lib.rs b/dash/src/lib.rs index 9939050ca..a59ead5a9 100644 --- a/dash/src/lib.rs +++ b/dash/src/lib.rs @@ -69,8 +69,8 @@ pub extern crate bitcoinconsensus; pub extern crate dashcore_hashes as hashes; pub extern crate secp256k1; -#[cfg(feature = "ed25519-dalek")] -pub use ed25519_dalek; +#[cfg(feature = "eddsa")] +pub use dashcore_crypto::eddsa; #[cfg(feature = "serde")] #[macro_use] @@ -105,7 +105,9 @@ pub mod ephemerealdata; pub mod error; pub mod hash_types; pub mod merkle_tree; -pub mod platform_node_id; +pub mod platform_node_id { + pub use dashcore_crypto::eddsa::EddsaPkHash as PlatformNodeId; +} pub mod policy; pub mod pow; pub mod sign_message; diff --git a/dash/src/platform_node_id.rs b/dash/src/platform_node_id.rs deleted file mode 100644 index d01ef64a2..000000000 --- a/dash/src/platform_node_id.rs +++ /dev/null @@ -1,186 +0,0 @@ -// Rust Dash Library -// Written by -// The Rust Dash developers -// -// To the extent possible under law, the author(s) have dedicated all -// copyright and related and neighboring rights to this software to -// the public domain worldwide. This software is distributed without -// any warranty. -// -// You should have received a copy of the CC0 Public Domain Dedication -// along with this software. -// If not, see . -// - -//! Dash Platform (Tenderdash/CometBFT) node ID. -//! -//! A platform node ID is the first 20 bytes of `SHA256(ed25519_public_key)`, -//! following the CometBFT node-ID convention. Although it is 20 bytes on the -//! wire like a [`PubkeyHash`](crate::PubkeyHash), it is *not* a `hash160` -//! public key hash, so it gets its own type to keep the two conventions from -//! being conflated. -//! -//! The inner bytes are always held in canonical forward order — the order in -//! which Tenderdash and dashmate display and derive the id. Dash Core -//! serializes the field as a `uint160`, whose internal blob order is the -//! byte-reversal of the canonical form, so the consensus encoding reverses on -//! the way in and out. - -#[cfg(feature = "bincode")] -use bincode::{Decode, Encode}; -use hashes::{Hash, sha256}; -use internals::impl_array_newtype; - -use crate::consensus::{Decodable, Encodable, encode}; -use crate::internal_macros::impl_bytes_newtype; -use crate::io; - -/// A Dash Platform (Tenderdash/CometBFT) node ID. -/// -/// This is `SHA256(ed25519_public_key)` truncated to 20 bytes, not a `hash160` -/// public key hash. The inner bytes are always in canonical forward order -/// (as displayed by Tenderdash/dashmate); the consensus encoding is the -/// byte-reversal of that, matching Dash Core's internal `uint160` blob order. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -#[cfg_attr(feature = "bincode", derive(Encode, Decode))] -pub struct PlatformNodeId([u8; 20]); - -impl_array_newtype!(PlatformNodeId, u8, 20); -impl_bytes_newtype!(PlatformNodeId, 20); - -impl PlatformNodeId { - /// Constructs a platform node ID from its 20 canonical (forward-order) bytes. - pub const fn from_byte_array(bytes: [u8; 20]) -> Self { - PlatformNodeId(bytes) - } - - /// Returns the 20 canonical (forward-order) bytes of the node ID. - pub const fn to_byte_array(self) -> [u8; 20] { - self.0 - } - - /// Returns a reference to the 20 canonical (forward-order) bytes of the node ID. - pub const fn as_byte_array(&self) -> &[u8; 20] { - &self.0 - } - - /// Derives the node ID from an Ed25519 public key per the CometBFT - /// convention: `SHA256(public_key)[0..20]`. - pub fn from_ed25519_public_key(public_key_bytes: &[u8; 32]) -> Self { - let digest = sha256::Hash::hash(public_key_bytes); - let mut bytes = [0u8; 20]; - bytes.copy_from_slice(&digest.to_byte_array()[..20]); - PlatformNodeId(bytes) - } -} - -impl Encodable for PlatformNodeId { - fn consensus_encode(&self, w: &mut W) -> Result { - // The wire carries Dash Core's internal `uint160` blob order, which is - // the byte-reversal of the canonical form held in `self.0`. - let mut wire_bytes = self.0; - wire_bytes.reverse(); - wire_bytes.consensus_encode(w) - } -} - -impl Decodable for PlatformNodeId { - fn consensus_decode(r: &mut R) -> Result { - let mut bytes = <[u8; 20]>::consensus_decode(r)?; - bytes.reverse(); - Ok(PlatformNodeId(bytes)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::consensus::{deserialize, serialize}; - - #[test] - fn consensus_round_trip() { - let mut canonical = [0u8; 20]; - for (i, byte) in canonical.iter_mut().enumerate() { - *byte = i as u8; - } - let mut wire = canonical; - wire.reverse(); - - let node_id = PlatformNodeId::from_byte_array(canonical); - let encoded = serialize(&node_id); - assert_eq!(encoded.len(), 20); - assert_eq!(encoded, wire, "wire order is the byte-reversal of canonical order"); - let decoded: PlatformNodeId = deserialize(&encoded).expect("decode node id"); - assert_eq!(decoded, node_id); - } - - /// Mainnet evonode example from issue #887: the ProRegTx wire bytes carry - /// the `uint160` internal (reversed) order, while the canonical id — as - /// dashmate/Tenderdash derive and display it — is the byte-reversal. - #[test] - fn consensus_decode_yields_canonical_order() { - let wire_bytes = crate::internal_macros::hex!("8cb97997a418f4814a63d3564b9574a393437968"); - let node_id: PlatformNodeId = deserialize(&wire_bytes).expect("decode node id"); - assert_eq!(node_id.to_string(), "68794393a374954b56d3634a81f418a49779b98c"); - assert_eq!(serialize(&node_id), wire_bytes, "re-encoding restores wire order"); - } - - /// A node id decoded from the wire must compare equal to one derived from - /// the matching Ed25519 public key — the mismatch reported in issue #887. - #[test] - fn decoded_wire_id_matches_derived_id() { - let public_key = [7u8; 32]; - let derived = PlatformNodeId::from_ed25519_public_key(&public_key); - - let mut wire_bytes = derived.to_byte_array(); - wire_bytes.reverse(); - let decoded: PlatformNodeId = deserialize(&wire_bytes).expect("decode node id"); - assert_eq!(decoded, derived); - } - - /// Persisted masternode-list snapshots were written while - /// `platform_node_id` was typed as `PubkeyHash`; the bincode layout must - /// stay identical so old snapshots keep decoding. - #[cfg(feature = "bincode")] - #[test] - fn bincode_layout_matches_pubkey_hash() { - use hashes::Hash; - - let bytes = [0xCD; 20]; - let config = bincode::config::standard(); - let node_id_bytes = bincode::encode_to_vec(PlatformNodeId::from_byte_array(bytes), config) - .expect("encode node id"); - let pubkey_hash_bytes = - bincode::encode_to_vec(crate::PubkeyHash::from_byte_array(bytes), config) - .expect("encode pubkey hash"); - assert_eq!(node_id_bytes, pubkey_hash_bytes); - } - - #[test] - fn hex_display_and_parse_round_trip() { - let hex = "4cd2ca50b36e0a2bb1b6b29da140448b47eeb7a1"; - let node_id: PlatformNodeId = hex.parse().expect("parse node id hex"); - assert_eq!(node_id.to_string(), hex); - assert_eq!(format!("{:?}", node_id), hex); - assert!("abcd".parse::().is_err(), "wrong-length hex must fail"); - assert!("zz".repeat(20).parse::().is_err(), "non-hex input must fail"); - } - - #[cfg(feature = "serde")] - #[test] - fn serde_json_round_trip_as_hex_string() { - let node_id = PlatformNodeId::from_byte_array([0x11; 20]); - let json = serde_json::to_string(&node_id).expect("serialize node id"); - assert_eq!(json, format!("\"{}\"", "11".repeat(20))); - let back: PlatformNodeId = serde_json::from_str(&json).expect("deserialize node id"); - assert_eq!(back, node_id); - } - - #[test] - fn from_ed25519_public_key_is_truncated_sha256() { - let public_key = [7u8; 32]; - let digest = sha256::Hash::hash(&public_key); - let node_id = PlatformNodeId::from_ed25519_public_key(&public_key); - assert_eq!(node_id.as_byte_array()[..], digest.to_byte_array()[..20]); - } -} diff --git a/key-wallet-ffi/src/special_payload.rs b/key-wallet-ffi/src/special_payload.rs index 495304306..e9ca38411 100644 --- a/key-wallet-ffi/src/special_payload.rs +++ b/key-wallet-ffi/src/special_payload.rs @@ -153,7 +153,10 @@ impl From<&ProviderRegistrationPayload> for FFIProviderRegistrationPayload { script_payout, script_payout_len, has_platform_fields: p.platform_node_id.is_some(), - platform_node_id: p.platform_node_id.map(|h| h.to_byte_array()).unwrap_or([0; 20]), + platform_node_id: p + .platform_node_id + .map(|id| id.to_canonical_bytes()) + .unwrap_or([0; 20]), platform_p2p_port: p.platform_p2p_port.map_or(-1, i32::from), platform_http_port: p.platform_http_port.map_or(-1, i32::from), } @@ -223,7 +226,10 @@ impl From<&ProviderUpdateServicePayload> for FFIProviderUpdateServicePayload { script_payout, script_payout_len, has_platform_fields: p.platform_node_id.is_some(), - platform_node_id: p.platform_node_id.map(|id| id.to_byte_array()).unwrap_or([0; 20]), + platform_node_id: p + .platform_node_id + .map(|id| id.to_canonical_bytes()) + .unwrap_or([0; 20]), platform_p2p_port: p.platform_p2p_port.map_or(-1, i32::from), platform_http_port: p.platform_http_port.map_or(-1, i32::from), } diff --git a/key-wallet/src/account/eddsa_account.rs b/key-wallet/src/account/eddsa_account.rs index 6ad5b2490..673ff4f14 100644 --- a/key-wallet/src/account/eddsa_account.rs +++ b/key-wallet/src/account/eddsa_account.rs @@ -5,10 +5,11 @@ use super::account_trait::AccountTrait; use crate::account::AccountType; -use crate::derivation_slip10::{ExtendedEd25519PrivKey, ExtendedEd25519PubKey, VerifyingKey}; +use crate::derivation_slip10::{ExtendedEd25519PrivKey, ExtendedEd25519PubKey}; use crate::error::{Error, Result}; use crate::{ChildNumber, DerivationPath, Network}; use core::fmt; +use dashcore::eddsa::{EddsaPkBytes, EddsaSkBytes}; use dashcore::Address; #[cfg(feature = "serde")] @@ -62,8 +63,9 @@ impl EdDSAAccount { network: Network, ) -> Result { // Create an extended public key with default metadata - use dashcore::ed25519_dalek::VerifyingKey; - let verifying_key = VerifyingKey::from_bytes(&ed25519_public_key) + let verifying_key = EddsaPkBytes::from_bytes(ed25519_public_key); + verifying_key + .validate() .map_err(|e| Error::InvalidParameter(format!("Invalid Ed25519 public key: {}", e)))?; let extended_key = ExtendedEd25519PubKey { @@ -136,16 +138,12 @@ impl EdDSAAccount { /// (`m/9'/5'/3'/4'` on mainnet, `m/9'/1'/3'/4'` otherwise), matching /// DashSync. Because derivation starts from the raw seed, no account /// state is involved and in particular no `is_watch_only` gate applies. - pub fn platform_node_key_at( - seed: &[u8], - network: Network, - index: u32, - ) -> Result { + pub fn platform_node_key_at(seed: &[u8], network: Network, index: u32) -> Result { let master = ExtendedEd25519PrivKey::new_master(network, seed)?; let path = AccountType::ProviderPlatformKeys.derivation_path(network)?; let account_xpriv = master.derive_priv(&path)?; let child = account_xpriv.derive_priv(&[ChildNumber::from_hardened_idx(index)?])?; - Ok(dashcore::ed25519_dalek::SigningKey::from_bytes(&child.private_key)) + Ok(child.private_key.clone()) } /// Derive an Ed25519 key at a specific path @@ -252,13 +250,8 @@ impl fmt::Display for EdDSAAccount { } } -impl - AccountDerivation< - ExtendedEd25519PrivKey, - ExtendedEd25519PubKey, - VerifyingKey, - dashcore::ed25519_dalek::SigningKey, - > for EdDSAAccount +impl AccountDerivation + for EdDSAAccount { fn defaults_to_hardened_derivation(&self) -> bool { true @@ -346,7 +339,7 @@ impl let ed25519_pubkey = self.derive_public_key_at(address_pool_type, index, use_hardened_with_priv_key)?; - let node_id = crate::derivation_slip10::tenderdash_node_id(&ed25519_pubkey.to_bytes()); + let node_id = ed25519_pubkey.hash().to_canonical_bytes(); use dashcore::address::Payload; use dashcore::hashes::Hash; @@ -362,7 +355,7 @@ impl address_pool_type: AddressPoolType, index: u32, use_hardened_with_priv_key: Option, - ) -> Result { + ) -> Result { let extended_pubkey = self.derive_extended_public_key_at( address_pool_type, index, @@ -413,9 +406,9 @@ impl &self, master_xpriv: &ExtendedEd25519PrivKey, index: u32, - ) -> Result { + ) -> Result { let xpriv = self.derive_from_master_xpriv_extended_xpriv_at(master_xpriv, index)?; - Ok(dashcore::ed25519_dalek::SigningKey::from_bytes(&xpriv.private_key)) + Ok(xpriv.private_key.clone()) } fn derive_from_seed_extended_xpriv_at( @@ -427,13 +420,9 @@ impl self.derive_from_master_xpriv_extended_xpriv_at(&master, index) } - fn derive_from_seed_private_key_at( - &self, - seed: &[u8], - index: u32, - ) -> Result { + fn derive_from_seed_private_key_at(&self, seed: &[u8], index: u32) -> Result { let xpriv = self.derive_from_seed_extended_xpriv_at(seed, index)?; - Ok(dashcore::ed25519_dalek::SigningKey::from_bytes(&xpriv.private_key)) + Ok(xpriv.private_key.clone()) } } diff --git a/key-wallet/src/derivation_slip10.rs b/key-wallet/src/derivation_slip10.rs index 36fd3de44..6314e4fc2 100644 --- a/key-wallet/src/derivation_slip10.rs +++ b/key-wallet/src/derivation_slip10.rs @@ -9,7 +9,7 @@ //! - Different serialization format (no xpub/xprv, custom encoding) use core::fmt; -pub use dashcore::ed25519_dalek::{SigningKey, VerifyingKey}; +use dashcore::eddsa::{EddsaPkBytes, EddsaSkBytes}; use dashcore::Network; use dashcore_hashes::{sha512, Hash, HashEngine, Hmac, HmacEngine}; #[cfg(feature = "serde")] @@ -18,28 +18,9 @@ use std::error; // Re-export ChainCode, Fingerprint and ChildNumber from bip32 use crate::bip32::{ChainCode, ChildNumber, Fingerprint}; -// Re-export ed25519-dalek types as our public API -pub use dashcore::ed25519_dalek::SigningKey as Ed25519PrivateKey; -pub use dashcore::ed25519_dalek::VerifyingKey as Ed25519PublicKey; - // Use DerivationPath from bip32 pub use crate::bip32::DerivationPath; -/// Compute the Tenderdash/CometBFT node ID for an Ed25519 public key: the -/// first 20 bytes of a single SHA-256 of the 32-byte public key. -/// -/// This is the canonical value of the `platform_node_id` field in ProRegTx / -/// ProUpServTx payloads for evonodes: it is what dashmate derives from -/// `node_key.json` and what Tenderdash announces on the platform P2P network, -/// so it is the only value that can match an on-chain registration. -/// -/// Note this is **not** `hash160` (SHA-256 + RIPEMD-160). Dash uses hash160 -/// for the ECDSA owner/voting key hashes in the same payload, but the -/// platform node id follows the CometBFT node-ID convention. -pub fn tenderdash_node_id(ed25519_public_key_bytes: &[u8; 32]) -> [u8; 20] { - dashcore::PlatformNodeId::from_ed25519_public_key(ed25519_public_key_bytes).to_byte_array() -} - /// Extended Ed25519 private key for SLIP-0010 #[derive(Clone)] pub struct ExtendedEd25519PrivKey { @@ -51,24 +32,18 @@ pub struct ExtendedEd25519PrivKey { pub parent_fingerprint: Fingerprint, /// Child number used to derive this key pub child_number: ChildNumber, - /// The Ed25519 private key (seed bytes, not the SigningKey itself) - pub private_key: [u8; 32], + /// The Ed25519 private key (the seed the key expands from) + pub private_key: EddsaSkBytes, /// Chain code for derivation pub chain_code: ChainCode, } -// Hand-written instead of derived so the raw secret bytes are compared in -// constant time (a derived `==` on `[u8; 32]` short-circuits on the first -// differing byte, leaking how much of the secret matches). +// Hand-written so the secret is compared in constant time, which +// `EddsaSkBytes` does; a derived `==` would short-circuit on the first +// differing byte and leak how much of it matches. impl PartialEq for ExtendedEd25519PrivKey { fn eq(&self, other: &Self) -> bool { - let mut diff = 0u8; - for (a, b) in self.private_key.iter().zip(other.private_key.iter()) { - diff |= a ^ b; - } - // black_box keeps the compiler from turning the fold back into an - // early-exit comparison. - core::hint::black_box(diff) == 0 + self.private_key == other.private_key && self.network == other.network && self.depth == other.depth && self.parent_fingerprint == other.parent_fingerprint @@ -116,7 +91,8 @@ impl ExtendedEd25519PrivKey { let hmac_bytes = hmac_result.as_byte_array(); // First 32 bytes are the private key seed - let private_key: [u8; 32] = hmac_bytes[..32].try_into().expect("HMAC output is 64 bytes"); + let private_key = + EddsaSkBytes::from_bytes(hmac_bytes[..32].try_into().expect("HMAC output is 64 bytes")); // Last 32 bytes are the chain code let chain_code = @@ -160,14 +136,15 @@ impl ExtendedEd25519PrivKey { // For Ed25519: data = 0x00 || private_key || index hmac_engine.input(&[0x00]); - hmac_engine.input(self.private_key.as_ref()); + hmac_engine.input(self.private_key.as_bytes()); hmac_engine.input(&u32::from(child).to_be_bytes()); let hmac_result: Hmac = Hmac::from_engine(hmac_engine); let hmac_bytes = hmac_result.as_byte_array(); // First 32 bytes become the new private key seed - let private_key: [u8; 32] = hmac_bytes[..32].try_into().expect("HMAC output is 64 bytes"); + let private_key = + EddsaSkBytes::from_bytes(hmac_bytes[..32].try_into().expect("HMAC output is 64 bytes")); // Last 32 bytes become the new chain code let chain_code = @@ -187,9 +164,8 @@ impl ExtendedEd25519PrivKey { } /// Get the public key for this private key - pub fn public_key(&self) -> Result { - let signing_key = SigningKey::from_bytes(&self.private_key); - Ok(signing_key.verifying_key()) + pub fn public_key(&self) -> Result { + Ok(self.private_key.public_key()) } /// Get the fingerprint of this key @@ -234,7 +210,7 @@ impl ExtendedEd25519PrivKey { // Private key with 0x00 prefix (33 bytes) result.push(0x00); - result.extend_from_slice(self.private_key.as_ref()); + result.extend_from_slice(self.private_key.as_bytes()); result } @@ -286,8 +262,9 @@ impl ExtendedEd25519PrivKey { return Err(Error::InvalidPrivateKeyPrefix); } - let private_key: [u8; 32] = - data[46..78].try_into().map_err(|_| Error::WrongExtendedKeyLength(data.len()))?; + let private_key = EddsaSkBytes::from_bytes( + data[46..78].try_into().map_err(|_| Error::WrongExtendedKeyLength(data.len()))?, + ); Ok(ExtendedEd25519PrivKey { network, @@ -324,7 +301,7 @@ pub struct ExtendedEd25519PubKey { /// Child number used to derive this key pub child_number: ChildNumber, /// The Ed25519 public key - pub public_key: VerifyingKey, + pub public_key: EddsaPkBytes, /// Chain code for derivation pub chain_code: ChainCode, } @@ -346,7 +323,7 @@ impl ExtendedEd25519PubKey { pub fn fingerprint(&self) -> Fingerprint { use dashcore_hashes::{hash160, Hash}; - let hash = hash160::Hash::hash(self.public_key.as_ref()); + let hash = hash160::Hash::hash(self.public_key.as_bytes()); Fingerprint::from_bytes(hash[..4].try_into().expect("hash160 has enough bytes")) } @@ -354,7 +331,7 @@ impl ExtendedEd25519PubKey { pub fn identifier(&self) -> [u8; 20] { use dashcore_hashes::{hash160, Hash}; - let hash = hash160::Hash::hash(self.public_key.as_ref()); + let hash = hash160::Hash::hash(self.public_key.as_bytes()); hash.to_byte_array() } @@ -382,7 +359,7 @@ impl ExtendedEd25519PubKey { // Public key with 0x00 prefix for consistency (33 bytes) result.push(0x00); - result.extend_from_slice(self.public_key.as_ref()); + result.extend_from_slice(self.public_key.as_bytes()); result } @@ -431,8 +408,8 @@ impl ExtendedEd25519PubKey { let public_key_bytes: [u8; 32] = data[46..78].try_into().map_err(|_| Error::WrongExtendedKeyLength(data.len()))?; - let public_key = VerifyingKey::from_bytes(&public_key_bytes) - .map_err(|e| Error::Ed25519Error(e.to_string()))?; + let public_key = EddsaPkBytes::from_bytes(public_key_bytes); + public_key.validate().map_err(|e| Error::Ed25519Error(e.to_string()))?; Ok(ExtendedEd25519PubKey { network, @@ -578,7 +555,7 @@ impl bincode::Encode for ExtendedEd25519PrivKey { self.depth.encode(encoder)?; self.parent_fingerprint.encode(encoder)?; self.child_number.encode(encoder)?; - self.private_key.encode(encoder)?; + self.private_key.as_bytes().encode(encoder)?; self.chain_code.encode(encoder)?; Ok(()) } @@ -594,7 +571,7 @@ impl bincode::Decode for ExtendedEd25519PrivKey { depth: u8::decode(decoder)?, parent_fingerprint: Fingerprint::decode(decoder)?, child_number: ChildNumber::decode(decoder)?, - private_key: <[u8; 32]>::decode(decoder)?, + private_key: EddsaSkBytes::from_bytes(<[u8; 32]>::decode(decoder)?), chain_code: ChainCode::decode(decoder)?, }) } @@ -626,7 +603,9 @@ impl bincode::Decode for ExtendedEd25519PubKey { let parent_fingerprint = Fingerprint::decode(decoder)?; let child_number = ChildNumber::decode(decoder)?; let public_key_bytes = <[u8; 32]>::decode(decoder)?; - let public_key = VerifyingKey::from_bytes(&public_key_bytes) + let public_key = EddsaPkBytes::from_bytes(public_key_bytes); + public_key + .validate() .map_err(|e| bincode::error::DecodeError::OtherString(e.to_string()))?; let chain_code = ChainCode::decode(decoder)?; @@ -797,7 +776,7 @@ mod test { current = current.ckd_priv(child_number).unwrap(); } - current.private_key + *current.private_key.to_bytes() } fn derive_ed25519_private_key_hex(seed_hex: &str, indexes: &[u32]) -> String { @@ -814,12 +793,12 @@ mod test { let seed = hex::decode(CASE_1_SEED).unwrap(); let mut key = ExtendedEd25519PrivKey::new_master(Network::Mainnet, &seed).unwrap(); - assert_ne!(key.private_key, [0u8; 32]); + assert_ne!(*key.private_key.to_bytes(), [0u8; 32]); assert_ne!(key.chain_code.as_ref(), &[0u8; 32]); key.zeroize(); - assert_eq!(key.private_key, [0u8; 32]); + assert_eq!(*key.private_key.to_bytes(), [0u8; 32]); assert_eq!(key.chain_code.as_ref(), &[0u8; 32]); assert_eq!(key.depth, 0); assert_eq!(key.parent_fingerprint, Fingerprint::default()); diff --git a/key-wallet/src/managed_account/address_pool.rs b/key-wallet/src/managed_account/address_pool.rs index bbc1ce59f..c83722dd3 100644 --- a/key-wallet/src/managed_account/address_pool.rs +++ b/key-wallet/src/managed_account/address_pool.rs @@ -547,7 +547,9 @@ impl AddressPool { public_key_bytes.len() )) })?; - let node_id = crate::derivation_slip10::tenderdash_node_id(pubkey_arr); + let node_id = dashcore::eddsa::EddsaPkBytes::from_bytes(*pubkey_arr) + .hash() + .to_canonical_bytes(); use dashcore::address::Payload; use dashcore::hashes::Hash; diff --git a/key-wallet/src/managed_account/managed_account_trait.rs b/key-wallet/src/managed_account/managed_account_trait.rs index 9591f2f18..a933b97f6 100644 --- a/key-wallet/src/managed_account/managed_account_trait.rs +++ b/key-wallet/src/managed_account/managed_account_trait.rs @@ -439,7 +439,7 @@ pub trait ManagedAccountTrait { &mut self, account_xpriv: crate::derivation_slip10::ExtendedEd25519PrivKey, add_to_state: bool, - ) -> Result<(crate::derivation_slip10::VerifyingKey, AddressInfo), &'static str> { + ) -> Result<(dashcore::eddsa::EddsaPkBytes, AddressInfo), &'static str> { match self.managed_account_type_mut() { ManagedAccountType::ProviderPlatformKeys { addresses, @@ -457,10 +457,10 @@ pub trait ManagedAccountTrait { addresses.mark_index_used(info.index); - let verifying_key = crate::derivation_slip10::VerifyingKey::from_bytes( - &pub_key_bytes.try_into().map_err(|_| "Invalid EdDSA public key length")?, - ) - .map_err(|_| "Failed to deserialize EdDSA public key")?; + let verifying_key = dashcore::eddsa::EddsaPkBytes::from_bytes( + pub_key_bytes.try_into().map_err(|_| "Invalid EdDSA public key length")?, + ); + verifying_key.validate().map_err(|_| "Failed to deserialize EdDSA public key")?; Ok((verifying_key, info)) } diff --git a/key-wallet/src/tests/provider_key_derivation_tests.rs b/key-wallet/src/tests/provider_key_derivation_tests.rs index a6abac159..7b5a8e72e 100644 --- a/key-wallet/src/tests/provider_key_derivation_tests.rs +++ b/key-wallet/src/tests/provider_key_derivation_tests.rs @@ -110,10 +110,10 @@ fn ed25519_platform_node_keys_match_slip10_reference() { // The stored account key must be the account-level key at m/9'/5'/3'/4'. let expected_account_sk = hex::decode("80035d9c2f89971a9c9fad826bba8be9328f1686ae555e912949c2c32800c379").unwrap(); - let expected_account_pk = dashcore::ed25519_dalek::SigningKey::from_bytes( + let expected_account_pk = dashcore::eddsa::EddsaSkBytes::from_bytes( expected_account_sk.as_slice().try_into().unwrap(), ) - .verifying_key(); + .public_key(); assert_eq!(account.ed25519_public_key.public_key, expected_account_pk); // Platform node key 0 (hardened child 0') via the seed-based signing path. @@ -133,20 +133,19 @@ fn ed25519_platform_node_keys_match_slip10_reference() { #[test] fn ed25519_platform_node_id_matches_tenderdash_convention() { use crate::account::eddsa_account::EdDSAAccount; - use crate::derivation_slip10::tenderdash_node_id; // Platform node key 0 (m/9'/5'/3'/4'/0') for TEST_SEED — the same key the // SLIP-0010 vector above pins. Public key and node id cross-checked with // an independent Ed25519 implementation (pyca/cryptography). let seed = hex::decode(TEST_SEED_HEX).unwrap(); let sk0 = EdDSAAccount::platform_node_key_at(&seed, Network::Mainnet, 0).unwrap(); - let pubkey = sk0.verifying_key(); + let pubkey = sk0.public_key(); assert_eq!( hex::encode(pubkey.to_bytes()), "3130c14339391cf26a68d86879e180ee9a16b660f5aa91f560f67c0abe8cf789" ); assert_eq!( - hex::encode(tenderdash_node_id(&pubkey.to_bytes())), + hex::encode(pubkey.hash().to_canonical_bytes()), "302f2615e6955cce8ed3cff81e8011bfd3a2991f" ); } diff --git a/key-wallet/src/tests/special_transaction_matching_tests.rs b/key-wallet/src/tests/special_transaction_matching_tests.rs index c1ea71f0b..eec0a03bc 100644 --- a/key-wallet/src/tests/special_transaction_matching_tests.rs +++ b/key-wallet/src/tests/special_transaction_matching_tests.rs @@ -552,7 +552,7 @@ async fn provider_registration_with_platform_node_id_matches_provider_platform_k .expect("provider_platform_keys managed") .next_eddsa_platform_key(eddsa, true) .expect("derive platform"); - let platform_node_id = dashcore::PlatformNodeId::from_byte_array( + let platform_node_id = dashcore::PlatformNodeId::from_canonical_bytes( derive_pubkey_hash(&platform_info.address).to_byte_array(), ); diff --git a/key-wallet/src/tests/special_transaction_tests.rs b/key-wallet/src/tests/special_transaction_tests.rs index 06d3c84f3..1448fce0c 100644 --- a/key-wallet/src/tests/special_transaction_tests.rs +++ b/key-wallet/src/tests/special_transaction_tests.rs @@ -206,7 +206,7 @@ fn create_special_transaction(tx_type: SpecialTransactionType) -> Transaction { script_payout: ScriptBuf::new(), inputs_hash: InputsHash::from_byte_array([6u8; 32]), signature: vec![7u8; 96], - platform_node_id: Some(PlatformNodeId::from_byte_array([8u8; 20])), + platform_node_id: Some(PlatformNodeId::from_bytes([8u8; 20])), platform_p2p_port: Some(26656), platform_http_port: Some(443), }; @@ -231,7 +231,7 @@ fn create_special_transaction(tx_type: SpecialTransactionType) -> Transaction { port: 19999, script_payout: ScriptBuf::new(), inputs_hash: InputsHash::from_byte_array([10u8; 32]), - platform_node_id: Some(PlatformNodeId::from_byte_array([12u8; 20])), + platform_node_id: Some(PlatformNodeId::from_bytes([12u8; 20])), platform_p2p_port: Some(26656), platform_http_port: Some(443), payload_sig: BLSSignature::from([11u8; 96]), diff --git a/key-wallet/src/transaction_checking/account_checker.rs b/key-wallet/src/transaction_checking/account_checker.rs index 5f4183a0b..082600af9 100644 --- a/key-wallet/src/transaction_checking/account_checker.rs +++ b/key-wallet/src/transaction_checking/account_checker.rs @@ -1073,12 +1073,9 @@ impl ManagedCoreFundsAccount { _ => return None, }; - // Check if platform_node_id matches any of our pool entries. - // Pool payloads hold Tenderdash node ids (SHA256(pubkey)[..20]), - // matching the on-chain field's convention. for (address, &addr_index) in &addresses.address_index { if let Payload::PubkeyHash(addr_hash) = address.payload() { - if addr_hash.as_byte_array() == platform_node_id.as_byte_array() { + if *addr_hash.as_byte_array() == platform_node_id.to_canonical_bytes() { // Get the address info if let Some(address_info) = addresses.addresses.get(&addr_index) { return Some(AccountMatch { @@ -1548,7 +1545,7 @@ impl crate::managed_account::ManagedCoreKeysAccount { for (address, &addr_index) in &addresses.address_index { if let Payload::PubkeyHash(addr_hash) = address.payload() { - if addr_hash.as_byte_array() == platform_node_id.as_byte_array() { + if *addr_hash.as_byte_array() == platform_node_id.to_canonical_bytes() { if let Some(address_info) = addresses.addresses.get(&addr_index) { return Some(AccountMatch { account_type_match: diff --git a/key-wallet/src/transaction_checking/transaction_router/tests/provider.rs b/key-wallet/src/transaction_checking/transaction_router/tests/provider.rs index 2c12d861e..7f62244da 100644 --- a/key-wallet/src/transaction_checking/transaction_router/tests/provider.rs +++ b/key-wallet/src/transaction_checking/transaction_router/tests/provider.rs @@ -696,7 +696,7 @@ async fn test_provider_registration_transaction_routing_check_platform_only() { inputs_hash: dashcore::hash_types::InputsHash::from_slice(&[6u8; 32]) .expect("Failed to create inputs hash from bytes"), signature: vec![7u8; 65], // Simplified signature - platform_node_id: Some(dashcore::PlatformNodeId::from_byte_array( + platform_node_id: Some(dashcore::PlatformNodeId::from_canonical_bytes( platform_node_id .payload() .as_pubkey_hash()