Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(platform)!: add owner keys to identities, fixed verification of use of owner keys #2215

Merged
merged 4 commits into from
Oct 7, 2024
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
4 changes: 2 additions & 2 deletions packages/platform-test-suite/test/e2e/withdrawals.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ describe('Withdrawals', function withdrawalsTest() {
)).to.be.rejectedWith(`Withdrawal amount "${amountToWithdraw}" is bigger that identity balance "${identityBalanceBefore}"`);
});

it('should not allow to create withdrawal with wrong security key type', async () => {
it('should not allow to create withdrawal with authentication key purpose', async () => {
const account = await client.getWalletAccount();
const identityBalanceBefore = identity.getBalance();
const withdrawTo = await account.getUnusedAddress();
Expand All @@ -196,7 +196,7 @@ describe('Withdrawals', function withdrawalsTest() {
toAddress: withdrawTo.address,
signingKeyIndex: 1,
},
)).to.be.rejectedWith('Error conversion not implemented: Invalid public key security level HIGH. The state transition requires one of MASTER');
)).to.be.rejectedWith('Error conversion not implemented: Invalid identity key purpose AUTHENTICATION. This state transition requires TRANSFER | OWNER');
});

// TODO: Figure out how to overcome client-side validation and implement
Expand Down
3 changes: 1 addition & 2 deletions packages/rs-dpp/src/core_types/validator/v0/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use dashcore::{ProTxHash, PubkeyHash};
use std::fmt;
use std::fmt::{Debug, Display, Formatter};
use std::fmt::{Debug, Formatter};

use crate::bls_signatures::PublicKey as BlsPublicKey;
#[cfg(feature = "core-types-serde-conversion")]
Expand Down
7 changes: 6 additions & 1 deletion packages/rs-dpp/src/errors/consensus/basic/basic_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ use crate::consensus::basic::identity::{
InvalidIdentityUpdateTransitionDisableKeysError, InvalidIdentityUpdateTransitionEmptyError,
InvalidInstantAssetLockProofError, InvalidInstantAssetLockProofSignatureError,
MissingMasterPublicKeyError, NotImplementedIdentityCreditWithdrawalTransitionPoolingError,
TooManyMasterPublicKeyError,
TooManyMasterPublicKeyError, WithdrawalOutputScriptNotAllowedWhenSigningWithOwnerKeyError,
};
use crate::consensus::basic::invalid_identifier_error::InvalidIdentifierError;
use crate::consensus::basic::state_transition::{
Expand Down Expand Up @@ -335,6 +335,11 @@ pub enum BasicError {
InvalidIdentityCreditWithdrawalTransitionOutputScriptError,
),

#[error(transparent)]
WithdrawalOutputScriptNotAllowedWhenSigningWithOwnerKeyError(
WithdrawalOutputScriptNotAllowedWhenSigningWithOwnerKeyError,
),

#[error(transparent)]
InvalidIdentityCreditWithdrawalTransitionCoreFeeError(
InvalidIdentityCreditWithdrawalTransitionCoreFeeError,
Expand Down
2 changes: 2 additions & 0 deletions packages/rs-dpp/src/errors/consensus/basic/identity/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub use invalid_instant_asset_lock_proof_signature_error::*;
pub use missing_master_public_key_error::*;
pub use not_implemented_identity_credit_withdrawal_transition_pooling_error::*;
pub use too_many_master_public_key_error::*;
pub use withdrawal_output_script_not_allowed_when_signing_with_owner_key::*;

mod data_contract_bounds_not_present_error;
mod disabling_key_id_also_being_added_in_same_transition_error;
Expand Down Expand Up @@ -62,3 +63,4 @@ mod invalid_instant_asset_lock_proof_signature_error;
mod missing_master_public_key_error;
mod not_implemented_identity_credit_withdrawal_transition_pooling_error;
mod too_many_master_public_key_error;
mod withdrawal_output_script_not_allowed_when_signing_with_owner_key;
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use crate::errors::ProtocolError;
use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize};
use thiserror::Error;

use crate::consensus::basic::BasicError;
use crate::consensus::ConsensusError;
use crate::identity::core_script::CoreScript;

use crate::identity::KeyID;
use bincode::{Decode, Encode};

#[derive(
Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize,
)]
#[error("Withdrawal output script not allowed when signing with owner key {key_id}")]
#[platform_serialize(unversioned)]
pub struct WithdrawalOutputScriptNotAllowedWhenSigningWithOwnerKeyError {
output_script: CoreScript,
key_id: KeyID,
}

/*

DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION

*/

impl WithdrawalOutputScriptNotAllowedWhenSigningWithOwnerKeyError {
pub fn new(output_script: CoreScript, key_id: KeyID) -> Self {
Self {
output_script,
key_id,
}
}

pub fn output_script(&self) -> &CoreScript {
&self.output_script
}

pub fn key_id(&self) -> KeyID {
self.key_id
}
}
impl From<WithdrawalOutputScriptNotAllowedWhenSigningWithOwnerKeyError> for ConsensusError {
fn from(err: WithdrawalOutputScriptNotAllowedWhenSigningWithOwnerKeyError) -> Self {
Self::BasicError(
BasicError::WithdrawalOutputScriptNotAllowedWhenSigningWithOwnerKeyError(err),
)
}
}
1 change: 1 addition & 0 deletions packages/rs-dpp/src/errors/consensus/codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ impl ErrorWithCode for BasicError {
Self::MasterPublicKeyUpdateError(_) => 10529,
Self::IdentityAssetLockTransactionOutPointNotEnoughBalanceError(_) => 10530,
Self::IdentityAssetLockStateTransitionReplayError(_) => 10531,
Self::WithdrawalOutputScriptNotAllowedWhenSigningWithOwnerKeyError(_) => 10532,

// State Transition Errors: 10600-10699
Self::InvalidStateTransitionTypeError { .. } => 10600,
Expand Down
24 changes: 17 additions & 7 deletions packages/rs-dpp/src/identity/identity_public_key/purpose.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::identity::Purpose::{AUTHENTICATION, DECRYPTION, ENCRYPTION, SYSTEM, TRANSFER, VOTING};
use crate::identity::Purpose::{
AUTHENTICATION, DECRYPTION, ENCRYPTION, OWNER, SYSTEM, TRANSFER, VOTING,
};
use anyhow::bail;
use bincode::{Decode, Encode};
#[cfg(feature = "cbor")]
Expand Down Expand Up @@ -37,6 +39,8 @@ pub enum Purpose {
SYSTEM = 4,
/// this key cannot be used for signing documents
VOTING = 5,
/// this key is used to prove ownership of a masternode or evonode
OWNER = 6,
}

impl From<Purpose> for [u8; 1] {
Expand All @@ -54,6 +58,7 @@ impl From<Purpose> for &'static [u8; 1] {
TRANSFER => &[3],
SYSTEM => &[4],
VOTING => &[5],
OWNER => &[6],
}
}
}
Expand All @@ -68,6 +73,7 @@ impl TryFrom<u8> for Purpose {
3 => Ok(TRANSFER),
4 => Ok(SYSTEM),
5 => Ok(VOTING),
6 => Ok(OWNER),
value => bail!("unrecognized purpose: {}", value),
}
}
Expand All @@ -83,6 +89,7 @@ impl TryFrom<i32> for Purpose {
3 => Ok(TRANSFER),
4 => Ok(SYSTEM),
5 => Ok(VOTING),
6 => Ok(OWNER),
value => bail!("unrecognized purpose: {}", value),
}
}
Expand All @@ -102,8 +109,15 @@ impl std::fmt::Display for Purpose {

impl Purpose {
/// The full range of purposes
pub fn full_range() -> [Purpose; 5] {
[AUTHENTICATION, ENCRYPTION, DECRYPTION, TRANSFER, VOTING]
pub fn full_range() -> [Purpose; 6] {
[
AUTHENTICATION,
ENCRYPTION,
DECRYPTION,
TRANSFER,
VOTING,
OWNER,
]
}
/// Just the authentication and withdraw purposes
pub fn searchable_purposes() -> [Purpose; 3] {
Expand All @@ -113,8 +127,4 @@ impl Purpose {
pub fn encryption_decryption() -> [Purpose; 2] {
[ENCRYPTION, DECRYPTION]
}
/// The last purpose
pub fn last() -> Purpose {
Self::TRANSFER
}
}
143 changes: 143 additions & 0 deletions packages/rs-dpp/src/identity/identity_public_key/random.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,149 @@ impl IdentityPublicKey {
}
}

/// Generates a random ECDSA critical-level authentication key for a masternode owner.
///
/// This function generates a random key that can be used for owner authentication in a masternode context.
/// The function accepts an optional seed for deterministic key generation, or uses entropy-based randomness if no seed is provided.
///
/// # Parameters
///
/// * `id`: The identifier (`KeyID`) for the masternode owner key.
/// * `seed`: An optional `u64` value used to seed the random number generator. If `None`, the RNG will be seeded from entropy.
/// * `platform_version`: A reference to the `PlatformVersion` struct, which is used to determine the correct key structure version.
///
/// # Returns
///
/// Returns a tuple containing the generated `IdentityPublicKey` for the masternode owner and the corresponding private key as a byte vector.
///
/// # Errors
///
/// Returns a `ProtocolError` if the platform version is not supported.
pub fn random_masternode_owner_key(
id: KeyID,
seed: Option<u64>,
platform_version: &PlatformVersion,
) -> Result<(Self, Vec<u8>), ProtocolError> {
let mut rng = match seed {
None => StdRng::from_entropy(),
Some(seed_value) => StdRng::seed_from_u64(seed_value),
};
Self::random_masternode_owner_key_with_rng(id, &mut rng, platform_version)
}

/// Generates a random ECDSA critical-level authentication key for a masternode owner using a custom RNG.
///
/// This function generates a random key using a given random number generator (RNG). This is useful when specific control over the randomness is needed.
///
/// # Parameters
///
/// * `id`: The identifier (`KeyID`) for the masternode owner key.
/// * `rng`: A mutable reference to a `StdRng` instance used to generate randomness.
/// * `platform_version`: A reference to the `PlatformVersion` struct, which is used to determine the correct key structure version.
///
/// # Returns
///
/// Returns a tuple containing the generated `IdentityPublicKey` for the masternode owner and the corresponding private key as a byte vector.
///
/// # Errors
///
/// Returns a `ProtocolError` if the platform version is not supported.
pub fn random_masternode_owner_key_with_rng(
id: KeyID,
rng: &mut StdRng,
platform_version: &PlatformVersion,
) -> Result<(Self, Vec<u8>), ProtocolError> {
match platform_version
.dpp
.identity_versions
.identity_key_structure_version
{
0 => {
let (key, private_key) =
IdentityPublicKeyV0::random_owner_key_with_rng(id, rng, platform_version)?;
Ok((key.into(), private_key))
}
version => Err(ProtocolError::UnknownVersionMismatch {
method: "IdentityPublicKey::random_masternode_owner_key_with_rng".to_string(),
known_versions: vec![0],
received: version,
}),
}
}

/// Generates a random ECDSA critical-level transfer key for a masternode.
///
/// This function generates a random key for use in transferring ownership of a masternode. An optional seed can be provided for deterministic key generation, or entropy-based randomness is used if no seed is given.
///
/// # Parameters
///
/// * `id`: The identifier (`KeyID`) for the masternode transfer key.
/// * `seed`: An optional `u64` value used to seed the random number generator. If `None`, the RNG will be seeded from entropy.
/// * `platform_version`: A reference to the `PlatformVersion` struct, which is used to determine the correct key structure version.
///
/// # Returns
///
/// Returns a tuple containing the generated `IdentityPublicKey` for the masternode transfer key and the corresponding private key as a byte vector.
///
/// # Errors
///
/// Returns a `ProtocolError` if the platform version is not supported.
pub fn random_masternode_transfer_key(
id: KeyID,
seed: Option<u64>,
platform_version: &PlatformVersion,
) -> Result<(Self, Vec<u8>), ProtocolError> {
let mut rng = match seed {
None => StdRng::from_entropy(),
Some(seed_value) => StdRng::seed_from_u64(seed_value),
};
Self::random_masternode_transfer_key_with_rng(id, &mut rng, platform_version)
}

/// Generates a random ECDSA critical-level transfer key for a masternode using a custom RNG.
///
/// This function generates a random key for masternode transfers using a given random number generator (RNG).
///
/// # Parameters
///
/// * `id`: The identifier (`KeyID`) for the masternode transfer key.
/// * `rng`: A mutable reference to a `StdRng` instance used to generate randomness.
/// * `platform_version`: A reference to the `PlatformVersion` struct, which is used to determine the correct key structure version.
///
/// # Returns
///
/// Returns a tuple containing the generated `IdentityPublicKey` for the masternode transfer key and the corresponding private key as a byte vector.
///
/// # Errors
///
/// Returns a `ProtocolError` if the platform version is not supported.
pub fn random_masternode_transfer_key_with_rng(
id: KeyID,
rng: &mut StdRng,
platform_version: &PlatformVersion,
) -> Result<(Self, Vec<u8>), ProtocolError> {
match platform_version
.dpp
.identity_versions
.identity_key_structure_version
{
0 => {
let (key, private_key) =
IdentityPublicKeyV0::random_masternode_transfer_key_with_rng(
id,
rng,
platform_version,
)?;
Ok((key.into(), private_key))
}
version => Err(ProtocolError::UnknownVersionMismatch {
method: "IdentityPublicKey::random_masternode_transfer_key_with_rng".to_string(),
known_versions: vec![0],
received: version,
}),
}
}
shumkov marked this conversation as resolved.
Show resolved Hide resolved

/// Generates a random ECDSA high-level authentication public key along with its corresponding private key.
///
/// This method constructs a random ECDSA (using the secp256k1 curve) high-level authentication public key
Expand Down
Loading
Loading