diff --git a/cryptography/utils.sh b/cryptography/cryptography.sh similarity index 66% rename from cryptography/utils.sh rename to cryptography/cryptography.sh index 5c04947..cbcaa63 100644 --- a/cryptography/utils.sh +++ b/cryptography/cryptography.sh @@ -98,63 +98,20 @@ ecdsa_verify() { fi } -# This function generates an ID signature based on the provided challenge data, -# ephemeral public key, destination node ID, and private key. -id_sign() { - local challenge_data="$1" - local ephemeral_public_key="$2" - local dest_node_id="$3" - local private_key="$4" - - # Compute SHA256 hash of the concatenated binary data - local id_signature_text="discovery v5 identity proof" - local id_signature_hash=$( (printf "%s" "$id_signature_text"; printf "%s%s%s" "$challenge_data" "$ephemeral_public_key" "$dest_node_id" | hex_to_bin) | sha256sum | awk '{print $1}') - - # Use the Python script to generate the signature - local signature=$(ecdsa_sign "$id_signature_hash" "$private_key") - if [ $? -ne 0 ] || [ -z "$signature" ]; then - printf "Error: Failed to generate signature\n" >&2 - return 1 - fi - - # Ensure the signature is the correct length (64 bytes in hex) - if [ ${#signature} -ne 128 ]; then - printf "Error: Signature has incorrect length: expected 128, got %d\n" "${#signature}" >&2 - return 1 - fi - - # Return the signature - printf "%s" "$signature" -} - -id_verify() { - local challenge_data="$1" - local ephemeral_public_key="$2" - local dest_node_id="$3" - local signature="$4" - local static_public_key="$5" +# This function computes the Keccak-256 hash of the input. +# Note: Inputs are expected to be hex encoded +keccak_256() { + local input - # Compute SHA256 hash of the concatenated binary data - local id_signature_text="discovery v5 identity proof" - local id_signature_hash=$( (printf "%s" "$id_signature_text"; printf "%s%s%s" "$challenge_data" "$ephemeral_public_key" "$dest_node_id" | hex_to_bin) | sha256sum | awk '{print $1}') - - # Verify the signature - if ecdsa_verify "$id_signature_hash" "$signature" "$static_public_key"; then - return 0 - else - return 1 - fi -} - -# This function generates a SHA256 hash of the input -sha256() { # if file descriptor 0 (stdin) is associated with a terminal if [ -t 0 ]; then - openssl dgst -sha256 -binary "$1" - + # Use command-line argument + input="$1" + # otherwise, the input is being piped from another command or redirected from a file else - # If input is piped, read from stdin - cat - | openssl dgst -sha256 -binary + # Read from pipe + input=$(cat) fi + python cryptography/keccak_256.py "$input" } \ No newline at end of file diff --git a/cryptography/derive_public_key.py b/cryptography/derive_public_key.py new file mode 100644 index 0000000..00ca53b --- /dev/null +++ b/cryptography/derive_public_key.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +import sys +import binascii +from eth_keys import keys + +def derive_public_key(private_key_hex): + try: + # Convert hex input to bytes + private_key_bytes = binascii.unhexlify(private_key_hex) + + # Create a PrivateKey object + private_key = keys.PrivateKey(private_key_bytes) + + # Get the public key + public_key = private_key.public_key + + # Get the uncompressed public key + uncompressed_public_key = public_key.to_bytes() + + # Get the compressed public key + compressed_public_key = public_key.to_compressed_bytes() + + return { + "uncompressed": uncompressed_public_key.hex(), + "compressed": compressed_public_key.hex() + } + except binascii.Error: + return "Error: Invalid hexadecimal in private key" + except Exception as e: + return f"Error: {str(e)}" + +if __name__ == "__main__": + if len(sys.argv) != 2: + print("Usage: python derive_public_key.py ") + sys.exit(1) + + private_key_hex = sys.argv[1] + result = derive_public_key(private_key_hex) + + if isinstance(result, dict): + print(f"Uncompressed Public Key: {result['uncompressed']}") + print(f"Compressed Public Key: {result['compressed']}") + else: + print(result) \ No newline at end of file diff --git a/cryptography/ecdsa_sign.py b/cryptography/ecdsa_sign.py index 9cad6ec..9972615 100644 --- a/cryptography/ecdsa_sign.py +++ b/cryptography/ecdsa_sign.py @@ -16,11 +16,6 @@ def ecdsa_sign(message_hash_hex, private_key_hex): # Get the signature as r || s (64 bytes) signature_bytes = signature.r.to_bytes(32, 'big') + signature.s.to_bytes(32, 'big') - - # Verify the signature immediately - public_key = private_key.public_key - is_valid = public_key.verify_msg_hash(message_hash_bytes, signature) - return signature_bytes.hex() if __name__ == '__main__': diff --git a/cryptography/keccak_256.py b/cryptography/keccak_256.py new file mode 100644 index 0000000..6d58f68 --- /dev/null +++ b/cryptography/keccak_256.py @@ -0,0 +1,30 @@ +import sys +import binascii +from Crypto.Hash import keccak + +def keccak_hash(data): + data = binascii.unhexlify(data) + + # Create a new Keccak hash object + k = keccak.new(digest_bits=256) + + # Update the hash object with the data + k.update(data) + + # Return the hexadecimal representation of the hash + return k.hexdigest() + +if __name__ == "__main__": + if len(sys.argv) < 1: + print("Usage: keccak_hash.py [hash_bits]") + print(" : The data to hash (string or hex)") + sys.exit(1) + + input_data = sys.argv[1] + + try: + result = keccak_hash(input_data) + print(result) + except Exception as e: + print(f"Error: {str(e)}") + sys.exit(1) \ No newline at end of file diff --git a/cryptography/tests/run_tests.sh b/cryptography/tests/run_tests.sh index 9687ed0..64dd85e 100755 --- a/cryptography/tests/run_tests.sh +++ b/cryptography/tests/run_tests.sh @@ -2,7 +2,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" source $DIR/../../discv5/utils.sh -source $DIR/../utils.sh +source $DIR/../cryptography.sh test_generate_secp256k1_keypair() { # Call the function @@ -54,52 +54,4 @@ test_generate_secp256k1_keypair() { fi } -test_id_sign(){ - static_private_key="fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736" - challenge_data="000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000" - ephemeral_public_key="039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231" - dest_node_id="bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9" - - local calculated_id_signature=$(id_sign "$challenge_data" "$ephemeral_public_key" "$dest_node_id" "$static_private_key") - local expected_id_signature="94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6" - - if [[ "$calculated_id_signature" != "$expected_id_signature" ]]; then - printf "test_id_sign: Test FAILED: signature mismatch.\n" - printf "Calculated: %s\n" "$calculated_id_signature" - printf "Expected: %s\n" "$expected_id_signature" - else - printf "test_id_sign: Test PASSED: signature matches.\n" - fi -} - -test_id_sign_and_verify() { - # Test inputs - static_private_key="fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736" - static_public_key="030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235" - challenge_data="000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000" - ephemeral_pubkey="039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231" - dest_node_id="bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9" - - # Generate the signature using id_sign - local calculated_id_signature - calculated_id_signature=$(id_sign "$challenge_data" "$ephemeral_pubkey" "$dest_node_id" "$static_private_key") - if [ $? -ne 0 ]; then - printf "test_id_sign_and_verify: Test FAILED: id_sign function returned an error.\n" - return 1 - fi - - # Verify the signature using id_verify, passing static_public_key - if id_verify "$challenge_data" "$ephemeral_pubkey" "$dest_node_id" "$calculated_id_signature" "$static_public_key"; then - printf "test_id_sign_and_verify: Test PASSED: Signature verified successfully.\n" - return 0 - else - printf "test_id_sign_and_verify: Test FAILED: Signature verification failed.\n" - return 1 - fi -} - test_generate_secp256k1_keypair -printf "\n" -test_id_sign -printf "\n" -test_id_sign_and_verify \ No newline at end of file diff --git a/discv5/discv5_codec.sh b/discv5/codec.sh similarity index 99% rename from discv5/discv5_codec.sh rename to discv5/codec.sh index 7aee794..1acf8a5 100755 --- a/discv5/discv5_codec.sh +++ b/discv5/codec.sh @@ -2,7 +2,7 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" source $DIR/utils.sh -source $DIR/../rlp/rlp_codec.sh +source $DIR/../rlp/rlp.sh # Function to encode header encrypt_masked_header() { diff --git a/discv5/discv5.sh b/discv5/discv5.sh index 156929c..c3b54dd 100755 --- a/discv5/discv5.sh +++ b/discv5/discv5.sh @@ -1,7 +1,7 @@ #!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -source $DIR/discv5_codec.sh +source $DIR/codec.sh source $DIR/../discv5/udp_duplex.sh # Set up the trap for Ctrl+C (SIGINT) diff --git a/discv5/tests/run_tests.sh b/discv5/tests/run_tests.sh index 5a843be..523d33a 100755 --- a/discv5/tests/run_tests.sh +++ b/discv5/tests/run_tests.sh @@ -1,8 +1,8 @@ #!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -source $DIR/../../cryptography/utils.sh -source $DIR/../discv5_codec.sh +source $DIR/../../cryptography/cryptography.sh +source $DIR/../codec.sh # Test encoding and decoding of PING message test_ping_message() { @@ -297,6 +297,50 @@ test_handshake_message() { fi } +test_id_sign(){ + static_private_key="fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736" + challenge_data="000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000" + ephemeral_public_key="039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231" + dest_node_id="bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9" + + local calculated_id_signature=$(id_sign "$challenge_data" "$ephemeral_public_key" "$dest_node_id" "$static_private_key") + local expected_id_signature="94852a1e2318c4e5e9d422c98eaf19d1d90d876b29cd06ca7cb7546d0fff7b484fe86c09a064fe72bdbef73ba8e9c34df0cd2b53e9d65528c2c7f336d5dfc6e6" + + if [[ "$calculated_id_signature" != "$expected_id_signature" ]]; then + printf "test_id_sign: Test FAILED: signature mismatch.\n" + printf "Calculated: %s\n" "$calculated_id_signature" + printf "Expected: %s\n" "$expected_id_signature" + else + printf "test_id_sign.\n Test PASSED: signature matches.\n" + fi +} + +test_id_sign_and_verify() { + # Test inputs + static_private_key="fb757dc581730490a1d7a00deea65e9b1936924caaea8f44d476014856b68736" + static_public_key="030e2cb74241c0c4fc8e8166f1a79a05d5b0dd95813a74b094529f317d5c39d235" + challenge_data="000000000000000000000000000000006469736376350001010102030405060708090a0b0c00180102030405060708090a0b0c0d0e0f100000000000000000" + ephemeral_pubkey="039961e4c2356d61bedb83052c115d311acb3a96f5777296dcf297351130266231" + dest_node_id="bbbb9d047f0488c0b5a93c1c3f2d8bafc7c8ff337024a55434a0d0555de64db9" + + # Generate the signature using id_sign + local calculated_id_signature + calculated_id_signature=$(id_sign "$challenge_data" "$ephemeral_pubkey" "$dest_node_id" "$static_private_key") + if [ $? -ne 0 ]; then + printf "test_id_sign_and_verify.\n Test FAILED: id_sign function returned an error.\n" + return 1 + fi + + # Verify the signature using id_verify, passing static_public_key + if id_verify "$challenge_data" "$ephemeral_pubkey" "$dest_node_id" "$calculated_id_signature" "$static_public_key"; then + printf "test_id_sign_and_verify.\n Test PASSED: Signature verified successfully.\n" + return 0 + else + printf "test_id_sign_and_verify.\n Test FAILED: Signature verification failed.\n" + return 1 + fi +} + test_ping_message printf "\n" test_pong_message @@ -307,4 +351,8 @@ test_nodes_message printf "\n" test_whoareyou_message printf "\n" -test_handshake_message \ No newline at end of file +test_handshake_message +printf "\n" +test_id_sign +printf "\n" +test_id_sign_and_verify \ No newline at end of file diff --git a/discv5/tests/run_tests_from_file.sh b/discv5/tests/run_tests_from_file.sh index fe87346..1b4bc15 100755 --- a/discv5/tests/run_tests_from_file.sh +++ b/discv5/tests/run_tests_from_file.sh @@ -1,7 +1,7 @@ #!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -source $DIR/../discv5_codec.sh +source $DIR/../codec.sh # Function to parse JSON parse_json() { diff --git a/discv5/udp_duplex.sh b/discv5/udp_duplex.sh index 31aa144..733d6b3 100755 --- a/discv5/udp_duplex.sh +++ b/discv5/udp_duplex.sh @@ -1,8 +1,8 @@ #!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -source $DIR/../cryptography/utils.sh -source $DIR/discv5_codec.sh +source $DIR/../cryptography/cryptography.sh +source $DIR/codec.sh # Create temporary files for our queues INCOMING_QUEUE=$(mktemp /tmp/discv5_incoming.XXXXXX) diff --git a/discv5/utils.sh b/discv5/utils.sh index 76a9aa9..529865c 100644 --- a/discv5/utils.sh +++ b/discv5/utils.sh @@ -32,3 +32,51 @@ generate_random_bytes() { done printf "%s" "$result" } + +# This function generates an ID signature based on the provided challenge data, +# ephemeral public key, destination node ID, and private key. +id_sign() { + local challenge_data="$1" + local ephemeral_public_key="$2" + local dest_node_id="$3" + local private_key="$4" + + # Compute SHA256 hash of the concatenated binary data + local id_signature_text="discovery v5 identity proof" + local id_signature_hash=$( (printf "%s" "$id_signature_text"; printf "%s%s%s" "$challenge_data" "$ephemeral_public_key" "$dest_node_id" | hex_to_bin) | sha256sum | cut -d' ' -f1) + + # Use the Python script to generate the signature + local signature=$(ecdsa_sign "$id_signature_hash" "$private_key") + if [ $? -ne 0 ] || [ -z "$signature" ]; then + printf "Error: Failed to generate signature\n" >&2 + return 1 + fi + + # Ensure the signature is the correct length (64 bytes in hex) + if [ ${#signature} -ne 128 ]; then + printf "Error: Signature has incorrect length: expected 128, got %d\n" "${#signature}" >&2 + return 1 + fi + + # Return the signature + printf "%s" "$signature" +} + +id_verify() { + local challenge_data="$1" + local ephemeral_public_key="$2" + local dest_node_id="$3" + local signature="$4" + local static_public_key="$5" + + # Compute SHA256 hash of the concatenated binary data + local id_signature_text="discovery v5 identity proof" + local id_signature_hash=$( (printf "%s" "$id_signature_text"; printf "%s%s%s" "$challenge_data" "$ephemeral_public_key" "$dest_node_id" | hex_to_bin) | sha256sum | cut -d' ' -f1) + + # Verify the signature + if ecdsa_verify "$id_signature_hash" "$signature" "$static_public_key"; then + return 0 + else + return 1 + fi +} diff --git a/enr/enr.sh b/enr/enr.sh new file mode 100644 index 0000000..c261f2f --- /dev/null +++ b/enr/enr.sh @@ -0,0 +1,62 @@ +#!/bin/bash + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +source $DIR/../rlp/rlp.sh +source $DIR/../discv5/utils.sh +source $DIR/../cryptography/cryptography.sh + +# Function to encode ENR +encode_enr() { + local seq="$1" + local public_key="$2" + local private_key="$3" + local ip="$4" + local udp="$5" + + # Convert IP address to its hexadecimal representation + local ip_hex=$(printf '%02x%02x%02x%02x' $(printf $ip | tr '.' ' ')) + + # Convert UDP port to hexadecimal representation + local udp_hex=$(printf '%04x' $udp) + + # Prepare key-value pairs as a string, sorted by key + # Keys to include: "id", "ip", "secp256k1", "udp" + # Sorted order: id, ip, secp256k1, udp + + # Build the content string + local content_items + content_items="${seq}," # Sequence number + content_items="${content_items}id,v4," # id key and value + content_items="${content_items}ip,${ip}," # ip key and hex value + content_items="${content_items}secp256k1,${public_key}," # secp256k1 key and public key + content_items="${content_items}udp,${udp}" # udp key and hex value + + # RLP encode the content as a list + local rlp_content + rlp_content=$(rlp_encode "[$content_items]") + + # Hash the RLP-encoded content using keccak256 + local rlp_content_hash + rlp_content_hash=$(printf "$rlp_content" | keccak_256) + + # Sign the hash + local signature + signature=$(ecdsa_sign "$rlp_content_hash" "$private_key") + + # Combine signature and content + local signed_content_items + signed_content_items="${signature},${content_items}" + + # RLP encode the signed content as a list + local rlp_signed_content + rlp_signed_content=$(rlp_encode "[$signed_content_items]") + + # Base64 encode using URL-safe base64 without padding + local encoded + encoded=$(printf "$rlp_signed_content" | hex_to_bin | base64 | tr -d '=' | tr '/+' '_-' | tr -d '\n') + + printf "enr:-%s" "$encoded" +} + + + diff --git a/enr/tests/run_tests.sh b/enr/tests/run_tests.sh new file mode 100755 index 0000000..b6c7c47 --- /dev/null +++ b/enr/tests/run_tests.sh @@ -0,0 +1,17 @@ +#!/bin/bash +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +source $DIR/../enr.sh + +test_enr(){ + local seq="1" + local public_key="03ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd3138" + local private_key="b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291" + local ip="127.0.0.1" + local udp=30303 + + local encoded=$(encode_enr "$seq" "$public_key" "$private_key" "$ip" "$udp") + printf "%s\n" "$encoded" +} + + +test_enr diff --git a/rlp/README.md b/rlp/README.md index 30709b7..1b4da39 100644 --- a/rlp/README.md +++ b/rlp/README.md @@ -9,7 +9,7 @@ This code implements the RLP specification in Bash. This uses pure Bash where po ### Encoding ```bash -$ source rlp/rlp_codec.sh +$ source rlp/codec.sh $ rlp_encode ["dog","god","cat"] cc83646f6783676f6483636174 ``` @@ -17,7 +17,7 @@ cc83646f6783676f6483636174 ### Decoding ```bash -$ source rlp/rlp_codec.sh +$ source rlp/codec.sh $ rlp_decode cc83646f6783676f6483636174 [dog,god,cat] ``` @@ -29,7 +29,7 @@ The following code demonstrates how to encode/decode a message to/from RLP: ```bash #!/bin/bash -source rlp_codec.sh +source codec.sh # Encode message into RLP local encoded_message=$(rlp_encode "[\"dog\",\"god\",\"cat\"]") diff --git a/rlp/rlp_codec.sh b/rlp/rlp.sh similarity index 100% rename from rlp/rlp_codec.sh rename to rlp/rlp.sh diff --git a/rlp/tests/run_tests.sh b/rlp/tests/run_tests.sh index c7378e4..429d5a1 100755 --- a/rlp/tests/run_tests.sh +++ b/rlp/tests/run_tests.sh @@ -1,7 +1,7 @@ #!/bin/bash DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -source $DIR/../rlp_codec.sh +source $DIR/../rlp.sh # Colors for output GREEN='\033[0;32m'