Skip to content
New issue

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

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

Already on GitHub? # to your account

feat(dpp): add a convenience method to get the public key data for a private key depending on the key type #2214

Merged
merged 5 commits into from
Oct 6, 2024

Conversation

QuantumExplorer
Copy link
Member

@QuantumExplorer QuantumExplorer commented Oct 6, 2024

Issue being fixed or feature implemented

It wasn't convenient to get public key data when we knew the private key data and the key type.

What was done?

Added a convenience method to get the public key data for a private key depending on the key type

How Has This Been Tested?

Breaking Changes

Not breaking

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Summary by CodeRabbit

  • New Features
    • Introduced a method to convert private key data into public key data, enhancing key management capabilities.
  • Bug Fixes
    • Improved error handling for invalid input sizes related to key conversion.

@QuantumExplorer QuantumExplorer added this to the v1.4.0 milestone Oct 6, 2024
Copy link
Contributor

coderabbitai bot commented Oct 6, 2024

Walkthrough

The changes introduce a new public method public_key_data_from_private_key_data to the KeyType enum in the key_type.rs file. This method facilitates the conversion of private key data into public key data based on the specified key type. It processes various key types, returning the appropriate public key or a ProtocolError for unsupported types. Additionally, the import statement for ProtocolError has been updated to include InvalidVectorSizeError, indicating potential error handling for input size validation.

Changes

File Path Change Summary
packages/rs-dpp/src/identity/identity_public_key/key_type.rs Added method public_key_data_from_private_key_data to KeyType enum for converting private keys to public keys. Updated import for ProtocolError to include InvalidVectorSizeError.

Poem

In the world of keys, where secrets play,
A new method hops in, brightening the day.
From private to public, it swiftly will glide,
With errors to catch, it stands by your side.
So let's celebrate this change with a cheer,
For key management's magic is now crystal clear! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Outside diff range and nitpick comments (1)
packages/rs-dpp/src/identity/identity_public_key/key_type.rs (1)

254-258: Clarify the unsupported operation in the error message

The error message returned for KeyType::BIP13_SCRIPT_HASH could be more descriptive. Providing additional context can help users understand why the operation isn't supported.

Consider updating the error message:

return Err(ProtocolError::NotSupported(
-    "Converting a private key to a script hash is not supported".to_string(),
+    "Conversion from private key to script hash for BIP13_SCRIPT_HASH key type is not supported".to_string(),
));

This change offers clearer information about the limitation.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 240f6ce and ff64350.

📒 Files selected for processing (1)
  • packages/rs-dpp/src/identity/identity_public_key/key_type.rs (2 hunks)

Comment on lines 214 to 222
KeyType::ECDSA_SECP256K1 => {
let secp = Secp256k1::new();
let secret_key =
dashcore::secp256k1::SecretKey::from_slice(private_key_bytes.as_slice())
.map_err(|e| ProtocolError::Generic(e.to_string()))?;
let private_key = dashcore::PrivateKey::new(secret_key, network);

Ok(private_key.public_key(&secp).to_bytes())
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor to avoid code duplication in ECDSA key types

The match arms for KeyType::ECDSA_SECP256K1 and KeyType::ECDSA_HASH160 share similar code for handling ECDSA keys. Consider extracting the common logic into a helper function to enhance maintainability and reduce duplication.

Create a helper function:

fn get_ecdsa_private_key(
    private_key_bytes: &[u8],
    network: Network,
) -> Result<dashcore::PrivateKey, ProtocolError> {
    let secp = Secp256k1::new();
    let secret_key = dashcore::secp256k1::SecretKey::from_slice(private_key_bytes)
        .map_err(|e| ProtocolError::Generic(e.to_string()))?;
    Ok(dashcore::PrivateKey::new(secret_key, network))
}

Update the match arms:

For KeyType::ECDSA_SECP256K1:

let private_key = get_ecdsa_private_key(private_key_bytes, network)?;
let secp = Secp256k1::new();
Ok(private_key.public_key(&secp).to_bytes())

For KeyType::ECDSA_HASH160:

let private_key = get_ecdsa_private_key(private_key_bytes, network)?;
let secp = Secp256k1::new();
Ok(ripemd160_sha256(private_key.public_key(&secp).to_bytes().as_slice()).to_vec())

This refactor eliminates redundancy and streamlines the code.

Comment on lines 208 to 212
pub fn public_key_data_from_private_key_data(
&self,
private_key_bytes: Vec<u8>,
network: Network,
) -> Result<Vec<u8>, ProtocolError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using a slice for the private_key_bytes parameter

Currently, the method takes ownership of private_key_bytes as a Vec<u8>, but it doesn't modify it internally. Using a slice &[u8] would avoid unnecessary cloning and improve efficiency.

Apply this diff to change the parameter type:

-pub fn public_key_data_from_private_key_data(
-    &self,
-    private_key_bytes: Vec<u8>,
-    network: Network,
-) -> Result<Vec<u8>, ProtocolError> {
+pub fn public_key_data_from_private_key_data(
+    &self,
+    private_key_bytes: &[u8],
+    network: Network,
+) -> Result<Vec<u8>, ProtocolError> {

Adjust subsequent code by removing .as_slice() calls since private_key_bytes is already a slice.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn public_key_data_from_private_key_data(
&self,
private_key_bytes: Vec<u8>,
network: Network,
) -> Result<Vec<u8>, ProtocolError> {
pub fn public_key_data_from_private_key_data(
&self,
private_key_bytes: &[u8],
network: Network,
) -> Result<Vec<u8>, ProtocolError> {

Comment on lines 228 to 231
.g1_element()
.expect("expected to get a public key from a bls private key")
.to_bytes()
.to_vec();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle potential errors without using expect

Using .expect() can lead to panics if g1_element() returns an error. Instead, propagate the error using ? to maintain robustness.

Apply this diff to handle the error properly:

-let public_key_bytes = private_key
-    .g1_element()
-    .expect("expected to get a public key from a bls private key")
-    .to_bytes()
-    .to_vec();
+let public_key_bytes = private_key
+    .g1_element()?
+    .to_bytes()
+    .to_vec();

Ensure that g1_element() returns Result and any errors are appropriately mapped to ProtocolError.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.g1_element()
.expect("expected to get a public key from a bls private key")
.to_bytes()
.to_vec();
.g1_element()?
.to_bytes()
.to_vec();

Comment on lines 244 to 251
let key_pair = ed25519_dalek::SigningKey::from_bytes(
&private_key_bytes.as_slice().try_into().map_err(|_| {
ProtocolError::InvalidVectorSizeError(InvalidVectorSizeError::new(
32,
private_key_bytes.len(),
))
})?,
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Missing proper error handling for SigningKey::from_bytes

  • In packages/rs-dpp/src/identity/identity_public_key/key_type.rs lines 244-251, the SigningKey::from_bytes call does not handle potential errors using the ? operator or .map_err.
🔗 Analysis chain

Ensure correct error handling for invalid key sizes

In the KeyType::EDDSA_25519_HASH160 arm, the conversion of private_key_bytes to a fixed-size array could fail if the length is incorrect. Verify that the error handling accurately reflects the issue.

Run the following script to check if the from_bytes method handles varying key sizes correctly:

This script simulates keys of different sizes to ensure that the error handling works as expected.



🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that the ED25519 key handling correctly detects invalid key sizes.

# Test: Attempt to create a SigningKey with invalid key sizes.
# Expect: The method should return an error for sizes other than 32 bytes.

for size in 31 32 33; do
  echo "Testing with key size: $size bytes"
  python3 -c "
import os
key_bytes = os.urandom($size)
try:
    from ed25519_dalek import SigningKey
    SigningKey.from_bytes(key_bytes)
    print('Key accepted')
except Exception as e:
    print(f'Error: {e}')
"
done

Length of output: 1181


Script:

#!/bin/bash
# Description: Verify that all usages of SigningKey::from_bytes have proper error handling.

# Search for all instances of SigningKey::from_bytes in the codebase
ast-grep --lang rust --pattern $'SigningKey::from_bytes($_)' > signing_key_usages.txt

# Check each usage for error handling
echo "Verifying error handling for each SigningKey::from_bytes usage:"
while IFS= read -r line; do
  # Extract file and line number
  file=$(echo "$line" | awk '{print $1}')
  code=$(echo "$line" | cut -d' ' -f2-)
  
  # Check if error handling is present using regex
  if echo "$code" | grep -qE '\?|\.map_err'; then
    echo "[$file]: Proper error handling found."
  else
    echo "[$file]: Missing error handling for SigningKey::from_bytes."
  fi
done < signing_key_usages.txt

Length of output: 220


Script:

#!/bin/bash
# Description: Verify that all usages of SigningKey::from_bytes in key_type.rs have proper error handling.

# Define the file to inspect
FILE_PATH="packages/rs-dpp/src/identity/identity_public_key/key_type.rs"

# Check if the file exists
if [ ! -f "$FILE_PATH" ]; then
  echo "Error: File $FILE_PATH does not exist."
  exit 1
fi

# Search for all instances of SigningKey::from_bytes in the specified file
echo "Searching for SigningKey::from_bytes usages in $FILE_PATH..."
matches=$(rg 'SigningKey::from_bytes' "$FILE_PATH")

# Check if any matches were found
if [ -z "$matches" ]; then
  echo "No instances of SigningKey::from_bytes found in $FILE_PATH."
  exit 0
fi

# Iterate over each match and verify error handling
echo "Verifying error handling for each SigningKey::from_bytes usage:"
echo "$matches" | while IFS= read -r line; do
  # Extract the line number and code
  line_number=$(echo "$line" | cut -d':' -f2)
  code=$(echo "$line" | cut -d':' -f3-)

  # Extract surrounding lines for context (2 lines before and after)
  context=$(rg -C 2 "SigningKey::from_bytes" "$FILE_PATH" | grep -A2 "SigningKey::from_bytes" | grep -v "SigningKey::from_bytes")

  # Check for error handling using '?' or '.map_err'
  if echo "$code" | grep -qE '\?'; then
    echo "Line $line_number: Proper error handling found using '?'."
  elif echo "$code" | grep -qE '\.map_err'; then
    echo "Line $line_number: Proper error handling found using '.map_err'."
  else
    echo "Line $line_number: **Missing** proper error handling for SigningKey::from_bytes."
  fi
done

Length of output: 1731

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between c285445 and 4d838f0.

📒 Files selected for processing (1)
  • packages/rs-dpp/src/identity/identity_public_key/key_type.rs (2 hunks)
🧰 Additional context used
🔇 Additional comments (7)
packages/rs-dpp/src/identity/identity_public_key/key_type.rs (7)

17-17: Import change looks good

The addition of InvalidVectorSizeError to the import statement is appropriate, as it's used in the new public_key_data_from_private_key_data method. This change enhances code clarity by explicitly importing the error type.


205-209: Method signature and structure look good

The method signature is well-defined, taking a slice of private key bytes and a network parameter. The return type Result<Vec<u8>, ProtocolError> is appropriate for this operation, allowing for error propagation.


211-218: ECDSA_SECP256K1 implementation is correct

The implementation for ECDSA_SECP256K1 keys is well-structured and includes proper error handling. It correctly creates a SecretKey from the input bytes, constructs a PrivateKey with the given network, and returns the corresponding public key bytes.


237-244: ECDSA_HASH160 implementation is correct

The implementation for ECDSA_HASH160 keys is well-structured and includes proper error handling. It correctly creates a SecretKey from the input bytes, constructs a PrivateKey with the given network, and applies the ripemd160_sha256 hash function to the public key bytes.


245-262: EDDSA_25519_HASH160 implementation is correct and well-handled

The implementation for EDDSA_25519_HASH160 keys is well-structured and includes proper error handling:

  1. The use of the ed25519-dalek feature flag is appropriate for conditional compilation.
  2. The error handling for invalid input size using InvalidVectorSizeError is well-implemented.
  3. The application of ripemd160_sha256 to the verifying key bytes is correct.
  4. The error handling for the not(feature = "ed25519-dalek") case is good, returning a NotSupported error.

263-267: BIP13_SCRIPT_HASH handling is appropriate

The implementation for BIP13_SCRIPT_HASH keys correctly returns a NotSupported error. This is an appropriate approach for a key type that doesn't support conversion from private to public key data.


205-269: Overall implementation of public_key_data_from_private_key_data is well-structured and comprehensive

The new method public_key_data_from_private_key_data is a valuable addition to the KeyType enum. It provides a unified way to derive public key data from private key data for various key types. Key points:

  1. The method signature is well-defined, taking appropriate parameters and returning a Result type.
  2. Implementation for each key type is handled separately, allowing for type-specific logic.
  3. Error handling is generally well-implemented, with appropriate use of ProtocolError variants.
  4. Feature flags are used effectively to handle conditional compilation for BLS and EdDSA implementations.
  5. Unsupported key types (BIP13_SCRIPT_HASH) are handled explicitly.

The only suggested improvement is in the error handling for the BLS12_381 case, where expect() could be replaced with map_err() for more consistent error propagation.

Overall, this implementation enhances the functionality of the KeyType enum and provides a robust method for deriving public key data.

Comment on lines +219 to +236
KeyType::BLS12_381 => {
#[cfg(feature = "bls-signatures")]
{
let private_key =
bls_signatures::PrivateKey::from_bytes(private_key_bytes, false)
.map_err(|e| ProtocolError::Generic(e.to_string()))?;
let public_key_bytes = private_key
.g1_element()
.expect("expected to get a public key from a bls private key")
.to_bytes()
.to_vec();
Ok(public_key_bytes)
}
#[cfg(not(feature = "bls-signatures"))]
return Err(ProtocolError::NotSupported(
"Converting a private key to a bls public key is not supported without the bls-signatures feature".to_string(),
));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve error handling in BLS12_381 implementation

The BLS12_381 implementation is generally correct, but there's room for improvement in error handling:

  1. The use of expect() when calling g1_element() could lead to a panic. Consider using map_err() to convert any error to a ProtocolError instead.

  2. The error handling for the not(feature = "bls-signatures") case is good, returning a NotSupported error.

Consider replacing the expect() call with proper error handling:

-    let public_key_bytes = private_key
-        .g1_element()
-        .expect("expected to get a public key from a bls private key")
-        .to_bytes()
-        .to_vec();
+    let public_key_bytes = private_key
+        .g1_element()
+        .map_err(|e| ProtocolError::Generic(e.to_string()))?
+        .to_bytes()
+        .to_vec();

This change will propagate any errors from g1_element() as a ProtocolError instead of panicking.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
KeyType::BLS12_381 => {
#[cfg(feature = "bls-signatures")]
{
let private_key =
bls_signatures::PrivateKey::from_bytes(private_key_bytes, false)
.map_err(|e| ProtocolError::Generic(e.to_string()))?;
let public_key_bytes = private_key
.g1_element()
.expect("expected to get a public key from a bls private key")
.to_bytes()
.to_vec();
Ok(public_key_bytes)
}
#[cfg(not(feature = "bls-signatures"))]
return Err(ProtocolError::NotSupported(
"Converting a private key to a bls public key is not supported without the bls-signatures feature".to_string(),
));
}
KeyType::BLS12_381 => {
#[cfg(feature = "bls-signatures")]
{
let private_key =
bls_signatures::PrivateKey::from_bytes(private_key_bytes, false)
.map_err(|e| ProtocolError::Generic(e.to_string()))?;
let public_key_bytes = private_key
.g1_element()
.map_err(|e| ProtocolError::Generic(e.to_string()))?
.to_bytes()
.to_vec();
Ok(public_key_bytes)
}
#[cfg(not(feature = "bls-signatures"))]
return Err(ProtocolError::NotSupported(
"Converting a private key to a bls public key is not supported without the bls-signatures feature".to_string(),
));
}

@QuantumExplorer QuantumExplorer changed the title feat: add a convenience method to get the public key data for a private key depending on the key type feat(dpp): add a convenience method to get the public key data for a private key depending on the key type Oct 6, 2024
@QuantumExplorer QuantumExplorer merged commit b584227 into v1.4-dev Oct 6, 2024
65 checks passed
@QuantumExplorer QuantumExplorer deleted the feat/addKeyTypePrivateKeyToPublicKey branch October 6, 2024 08:48
# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant