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
80 changes: 80 additions & 0 deletions key-wallet-ffi/src/account_derivation_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,4 +246,84 @@ mod tests {
wallet::wallet_free(wallet);
}
}

/// Provider operator (BLS) and platform node (Ed25519) keys derived from a
/// seed through the FFI must match the DashSync/dashbls reference vectors
/// (see rust-dashcore issue #878 and
/// key-wallet/src/tests/provider_key_derivation_tests.rs).
#[test]
#[cfg(all(feature = "bls", feature = "eddsa"))]
fn test_provider_key_derivation_matches_dashsync_reference() {
let mut error = FFIError::default();

let mnemonic = std::ffi::CString::new(MNEMONIC).unwrap();
let passphrase = std::ffi::CString::new("").unwrap();

let wallet = unsafe {
wallet::wallet_create_from_mnemonic(mnemonic.as_ptr(), FFINetwork::Mainnet, &mut error)
};
assert!(!wallet.is_null());

let mut seed = [0u8; 64];
let ok = unsafe {
crate::mnemonic::mnemonic_to_seed(
mnemonic.as_ptr(),
passphrase.as_ptr(),
seed.as_mut_ptr(),
&mut (seed.len()),
&mut error,
)
};
assert!(ok);

let collection =
unsafe { crate::account_collection::wallet_get_account_collection(wallet, &mut error) };
assert!(!collection.is_null());

unsafe {
// BLS operator key 0 at m/9'/5'/3'/3'/0 (legacy HD chain).
let operator_account =
crate::account_collection::account_collection_get_provider_operator_keys(collection)
as *mut crate::account::FFIBLSAccount;
assert!(!operator_account.is_null());

let sk0_hex = super::super::bls_account_derive_private_key_from_seed(
operator_account,
seed.as_ptr(),
seed.len(),
0,
&mut error,
);
assert!(!sk0_hex.is_null(), "BLS derivation failed: {:?}", error.code);
let sk0 = std::ffi::CStr::from_ptr(sk0_hex).to_str().unwrap();
assert_eq!(sk0, "11122e1ad656d0610ce0f80d40da874d67ea656a3e66ed371c915ec3a488a43a");
crate::utils::string_free(sk0_hex);
crate::account::bls_account_free(operator_account);

// Ed25519 platform node key 0 at m/9'/5'/3'/4'/0' (SLIP-0010).
let platform_account =
crate::account_collection::account_collection_get_provider_platform_keys(collection)
as *mut crate::account::FFIEdDSAAccount;
assert!(!platform_account.is_null());

let node_sk0_hex = super::super::eddsa_account_derive_private_key_from_seed(
platform_account,
seed.as_ptr(),
seed.len(),
0,
&mut error,
);
assert!(!node_sk0_hex.is_null(), "Ed25519 derivation failed: {:?}", error.code);
let node_sk0 = std::ffi::CStr::from_ptr(node_sk0_hex).to_str().unwrap();
assert_eq!(
node_sk0,
"5fa238b12be77347abf9b5957bd902d16c6aaca28d25c4267ffacbd7458dceb1"
);
crate::utils::string_free(node_sk0_hex);
crate::account::eddsa_account_free(platform_account);

crate::account_collection::account_collection_free(collection);
wallet::wallet_free(wallet);
}
}
}
8 changes: 4 additions & 4 deletions key-wallet/examples/account_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let bls_account = BLSAccount::from_seed(
None,
AccountType::ProviderVotingKeys,
bls_seed,
&bls_seed,
Network::Testnet,
)?;

Expand Down Expand Up @@ -110,7 +110,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let eddsa_account = EdDSAAccount::from_seed(
None,
AccountType::IdentityRegistration,
ed25519_seed,
&ed25519_seed,
Network::Testnet,
)?;

Expand Down Expand Up @@ -164,7 +164,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let bls_account = BLSAccount::from_seed(
None,
AccountType::ProviderVotingKeys,
bls_seed,
&bls_seed,
Network::Testnet,
)?;
let watch_only_bls = bls_account.to_watch_only();
Expand All @@ -177,7 +177,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let eddsa_account = EdDSAAccount::from_seed(
None,
AccountType::IdentityRegistration,
ed25519_seed,
&ed25519_seed,
Network::Testnet,
)?;
let watch_only_eddsa = eddsa_account.to_watch_only();
Expand Down
8 changes: 4 additions & 4 deletions key-wallet/src/account/account_collection_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ mod tests {
let bls_account = BLSAccount::from_seed(
None,
AccountType::ProviderOperatorKeys,
[42u8; 32],
&[42u8; 32],
Network::Testnet,
)
.unwrap();
Expand All @@ -70,7 +70,7 @@ mod tests {
let eddsa_account = EdDSAAccount::from_seed(
None,
AccountType::ProviderPlatformKeys,
[99u8; 32],
&[99u8; 32],
Network::Testnet,
)
.unwrap();
Expand Down Expand Up @@ -118,7 +118,7 @@ mod tests {
let bls_account = BLSAccount::from_seed(
None,
AccountType::ProviderVotingKeys, // Wrong! Should be ProviderOperatorKeys
[42u8; 32],
&[42u8; 32],
Network::Testnet,
)
.unwrap();
Expand All @@ -136,7 +136,7 @@ mod tests {
let eddsa_account = EdDSAAccount::from_seed(
None,
AccountType::IdentityRegistration, // Wrong! Should be ProviderPlatformKeys
[99u8; 32],
&[99u8; 32],
Network::Testnet,
)
.unwrap();
Expand Down
43 changes: 28 additions & 15 deletions key-wallet/src/account/bls_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,23 @@ impl BLSAccount {
})
}

/// Create a BLS account from raw private key bytes (seed)
/// Create a BLS account from a wallet seed (e.g. the 64-byte BIP39 seed).
///
/// The seed is fed directly into the BLS HD master key (dashbls
/// `ExtendedPrivateKey::FromSeed`) and the account's derivation path
/// (e.g. `m/9'/5'/3'/3'` for mainnet operator keys) is applied in the BLS
/// scheme, matching DashSync. The stored extended public key is the
/// account-level key, so key `i` is its `i`th child.
pub fn from_seed(
parent_wallet_id: Option<Vec<u8>>,
account_type: AccountType,
seed: [u8; 32],
seed: &[u8],
network: Network,
) -> Result<Self> {
let bls_private_key = ExtendedBLSPrivKey::new_master(network, &seed)?;
let bls_public_key = ExtendedBLSPubKey::from_private_key(&bls_private_key);
let master = ExtendedBLSPrivKey::new_master(network, seed)?;
let path = account_type.derivation_path(network)?;
let account_xpriv = master.derive_path_legacy(&path)?;
let bls_public_key = ExtendedBLSPubKey::from_private_key(&account_xpriv);

Ok(Self {
parent_wallet_id,
Expand All @@ -139,7 +147,7 @@ impl BLSAccount {
return Err(Error::WatchOnly);
}
let child_num = ChildNumber::from_normal_idx(index)?;
current_key = current_key.ckd_pub(child_num)?;
current_key = current_key.derive_pub_legacy(child_num)?;
}

Ok(current_key)
Expand Down Expand Up @@ -237,7 +245,11 @@ impl
}

fn has_internal_and_external(&self) -> bool {
true
// Provider operator keys live in a single pool (`account/i`, matching
// DashSync) — there are no separate external/internal chains. This also
// keeps the chain-agnostic seed derivation helpers usable, so operator
// key `i` derived from the seed is `m/9'/coin'/3'/3'/i`.
false
}

fn has_intermediate_derivation(&self) -> Option<ChildNumber> {
Expand Down Expand Up @@ -266,9 +278,10 @@ impl
// Get the derivation path for this account type
let path = self.account_type.derivation_path(self.network)?;

// Derive the account private key from master
// Derive the account private key from master (legacy mode, matching
// dashbls/DashSync for provider operator keys)
master_xpriv
.derive_path(&path)
.derive_path_legacy(&path)
.map_err(|e| Error::InvalidParameter(format!("BLS derivation error: {}", e)))
}

Expand All @@ -284,9 +297,9 @@ impl
return Err(Error::WatchOnly);
}

// Derive the child private key from account private key
// Derive the child private key from account private key (legacy mode)
account_xpriv
.derive_path(child_path)
.derive_path_legacy(child_path)
.map_err(|e| Error::InvalidParameter(format!("BLS child derivation error: {}", e)))
}

Expand All @@ -303,9 +316,9 @@ impl
}
}

// Derive the child public key from account public key
// Derive the child public key from account public key (legacy mode)
self.bls_public_key
.derive_path(child_path)
.derive_path_legacy(child_path)
.map_err(|e| Error::InvalidParameter(format!("BLS public key derivation error: {}", e)))
}

Expand Down Expand Up @@ -456,7 +469,7 @@ mod tests {
index: 0,
standard_account_type: StandardAccountType::BIP44Account,
},
seed,
&seed,
Network::Testnet,
)
.expect("Failed to create BLS account from seed");
Expand All @@ -473,7 +486,7 @@ mod tests {
index: 0,
standard_account_type: StandardAccountType::BIP44Account,
},
seed,
&seed,
Network::Testnet,
)
.expect("Failed to create BLS account from seed");
Expand All @@ -492,7 +505,7 @@ mod tests {
index: 0,
standard_account_type: StandardAccountType::BIP44Account,
},
seed,
&seed,
Network::Testnet,
)
.expect("Failed to create BLS account from seed");
Expand Down
21 changes: 14 additions & 7 deletions key-wallet/src/account/eddsa_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,22 @@ impl EdDSAAccount {
})
}

/// Create an EdDSA account from a private key (seed)
/// Create an EdDSA account from a wallet seed (e.g. the 64-byte BIP39 seed).
///
/// The seed is fed directly into the SLIP-0010 Ed25519 master key and the
/// account's derivation path (e.g. `m/9'/5'/3'/4'` for mainnet platform
/// node keys) is applied in the Ed25519 scheme, matching DashSync. The
/// stored extended public key is the account-level key.
pub fn from_seed(
parent_wallet_id: Option<Vec<u8>>,
account_type: AccountType,
ed25519_seed: [u8; 32],
seed: &[u8],
network: Network,
) -> Result<Self> {
let ed25519_private_key = ExtendedEd25519PrivKey::new_master(network, &ed25519_seed)?;
let ed25519_public_key = ExtendedEd25519PubKey::from_priv(&ed25519_private_key)?;
let master = ExtendedEd25519PrivKey::new_master(network, seed)?;
let path = account_type.derivation_path(network)?;
let account_xpriv = master.derive_priv(&path)?;
let ed25519_public_key = ExtendedEd25519PubKey::from_priv(&account_xpriv)?;

Ok(Self {
parent_wallet_id,
Expand Down Expand Up @@ -454,7 +461,7 @@ mod tests {
index: 0,
standard_account_type: StandardAccountType::BIP44Account,
},
seed,
&seed,
Network::Testnet,
)
.expect("Failed to create EdDSA account from seed");
Expand All @@ -471,7 +478,7 @@ mod tests {
index: 0,
standard_account_type: StandardAccountType::BIP44Account,
},
seed,
&seed,
Network::Testnet,
)
.expect("Failed to create EdDSA account from seed");
Expand Down Expand Up @@ -516,7 +523,7 @@ mod tests {
index: 0,
standard_account_type: StandardAccountType::BIP44Account,
},
seed,
&seed,
Network::Testnet,
)
.expect("Failed to create EdDSA account from seed");
Expand Down
Loading
Loading