Streamlit App – Digital Signature demo

I demo ECDSA public/private keys with digital signatures using Streamlit

The private key gets generated, and stays private. The public key is given to the person who want’s to verify the message. Using the public key plus the signed message they can be sure that the only the owner of the matching private key signed it.

Python360 Digital Signtatures Streamlit App – Code on GitHub

ChatGPT helped, but I had to remind it to use “state”…Just one word! This is where the experience/knowledge of an experience programmer helps. As soon as I asked it to use “state” it resolved the bug. State pattern is a common design pattern in programming.


Start. Enter first prompt to chatGPT :

I am going to give you some instructions to build a python streamlit app ok?

Certainly! I’d be happy to help you with building a Python Streamlit app. Streamlit is a great framework for creating web applications with minimal code. Please provide me with the specific instructions or requirements you have in mind, and I’ll do my best to guide you through the process. If you have a particular goal or functionality you want in your app, let me know so I can tailor the assistance accordingly.

Certainly! To create a Streamlit app that progressively shows each step and asks the user to input their message to be signed, you can follow the steps below. We’ll use Streamlit’s simple syntax to build the app.

Firstly, make sure you have Streamlit installed. If not, you can install it using:

pip install streamlit

Now, create a Python script (e.g., app.py) and use the following code:

import streamlit as st
from ecdsa import SigningKey, SECP256k1

st.title("ECDSA Signature Generator")

# Step 1: Generate a private key
st.header("Step 1: Generate a Private Key")
private_key = SigningKey.generate(curve=SECP256k1)
private_key_string = private_key.to_string().hex()

st.code(f"Private Key: {private_key_string}")

# Step 2: Get the corresponding public key
st.header("Step 2: Get the Corresponding Public Key")
public_key = private_key.get_verifying_key()
public_key_string = public_key.to_string().hex()

st.code(f"Public Key: {public_key_string}")

# Step 3: User Input - Message to Sign
st.header("Step 3: User Input - Message to Sign")
message_to_sign = st.text_area("Enter your message to sign:")

# Step 4: Sign the message
if message_to_sign:
    st.header("Step 4: Sign the Message")
    message = message_to_sign.encode("utf-8")
    signature = private_key.sign(message)
    signature_string = signature.hex()

    st.code(f"Message: {message_to_sign}")
    st.code(f"Signature: {signature_string}")

    # Step 5: Verify the signature
    st.header("Step 5: Verify the Signature")
    if public_key.verify(signature, message):
        st.success("Signature is valid.")
    else:
        st.error("Signature is not valid.")

# Note: You can run the app using the command 'streamlit run app.py'

Save the script and run it using the following command in your terminal:

streamlit run app.py

This will start a local development server and open the app in your default web browser. The app will guide the user through each step and allow them to input a message for signing.

Great, all good. Can you modify step 5 and ask the user to input the public key of the private key that was used to create the signature, and then verify it is valid for the message/signature combination

Certainly! I’ve modified step 5 to ask the user to input the public key corresponding to the private key used to create the signature. The app will then verify whether the provided public key is valid for the given message and signature combination. Here’s the updated code:

import streamlit as st
from ecdsa import SigningKey, VerifyingKey, SECP256k1

st.title("ECDSA Signature Generator and Verifier")

# Step 1: Generate a private key
st.header("Step 1: Generate a Private Key")
private_key = SigningKey.generate(curve=SECP256k1)
private_key_string = private_key.to_string().hex()

st.code(f"Private Key: {private_key_string}")

# Step 2: Get the corresponding public key
st.header("Step 2: Get the Corresponding Public Key")
public_key = private_key.get_verifying_key()
public_key_string = public_key.to_string().hex()

st.code(f"Public Key: {public_key_string}")

# Step 3: User Input - Message to Sign
st.header("Step 3: User Input - Message to Sign")
message_to_sign = st.text_area("Enter your message to sign:")

# Step 4: Sign the message
if message_to_sign:
    st.header("Step 4: Sign the Message")
    message = message_to_sign.encode("utf-8")
    signature = private_key.sign(message)
    signature_string = signature.hex()

    st.code(f"Message: {message_to_sign}")
    st.code(f"Signature: {signature_string}")

    # Step 5: Verify the signature with user input public key
    st.header("Step 5: Verify the Signature")
    input_public_key = st.text_input("Enter the public key (hex format):")

    if input_public_key:
        try:
            input_public_key_bytes = bytes.fromhex(input_public_key)
            user_public_key = VerifyingKey.from_string(input_public_key_bytes, curve=SECP256k1)

            if user_public_key.verify(signature, message):
                st.success("Signature is valid for the provided public key.")
            else:
                st.error("Signature is not valid for the provided public key.")
        except ValueError:
            st.error("Invalid public key format. Please enter a valid hex-encoded public key.")

Now, the app will prompt the user to input the public key in hexadecimal format, and it will verify whether the provided public key is valid for the given message and signature combination.

Uh oh…..ERROR! 😱

The public key is invalid?? WTF!

I think this fails because there is no “State” – so when I run step 5 it creates a new private/public key pair, different to the original. Can you check and fix if required?

You’re correct; my apologies for the oversight. Streamlit is stateful, and the issue here is that each time the script is run, it generates a new private key. To maintain the state across different steps of the app, you can use Streamlit’s st.session_state.

Here’s the modified code that addresses this issue:

import streamlit as st
from ecdsa import SigningKey, VerifyingKey, SECP256k1

# Define session state to store variables across sessions
if 'private_key' not in st.session_state:
    st.session_state.private_key = SigningKey.generate(curve=SECP256k1)

st.title("ECDSA Signature Generator and Verifier")

# Step 1: Generate a private key
st.header("Step 1: Generate a Private Key")
private_key_string = st.session_state.private_key.to_string().hex()

st.code(f"Private Key: {private_key_string}")

# Step 2: Get the corresponding public key
st.header("Step 2: Get the Corresponding Public Key")
public_key = st.session_state.private_key.get_verifying_key()
public_key_string = public_key.to_string().hex()

st.code(f"Public Key: {public_key_string}")

# Step 3: User Input - Message to Sign
st.header("Step 3: User Input - Message to Sign")
message_to_sign = st.text_area("Enter your message to sign:")

# Step 4: Sign the message
if message_to_sign:
    st.header("Step 4: Sign the Message")
    message = message_to_sign.encode("utf-8")
    signature = st.session_state.private_key.sign(message)
    signature_string = signature.hex()

    st.code(f"Message: {message_to_sign}")
    st.code(f"Signature: {signature_string}")

    # Step 5: Verify the signature with user input public key
    st.header("Step 5: Verify the Signature")
    input_public_key = st.text_input("Enter the public key (hex format):")

    if input_public_key:
        try:
            input_public_key_bytes = bytes.fromhex(input_public_key)
            user_public_key = VerifyingKey.from_string(input_public_key_bytes, curve=SECP256k1)

            if user_public_key.verify(signature, message):
                st.success("Signature is valid for the provided public key.")
            else:
                st.error("Signature is not valid for the provided public key.")
        except ValueError:
            st.error("Invalid public key format. Please enter a valid hex-encoded public key.")

This modification ensures that the same private key is used throughout the app session.

Successful!

streamlit run app.py
signing a document with a signature

Try the app here @ Streamlit.io

Refresh the page if you want to sign a new message. Not how changing just 1 character produces an entirely new signature?

valid signature

The size limit for the message you can sign with ECDSA depends on the specific implementation and the cryptographic library you are using. In the case of the ecdsa library in Python, which you are using in the provided code, the maximum size of the message is influenced by the underlying elliptic curve.

For the SECP256k1 curve, which is commonly used in Bitcoin, the maximum message size is limited to the size of the curve’s order, which is 256 bits. This corresponds to 32 bytes.

In practice, it’s recommended to hash larger messages and sign the hash value. This is not only because of size limitations but also for security reasons. Hashing reduces the risk associated with using longer messages, and it helps standardize the size of the input for the ECDSA algorithm.

Therefore, if you have larger messages, it’s a good practice to hash them using a secure hash function (such as SHA-256) and then sign the resulting hash. This ensures that the message size is within the limits of the elliptic curve and provides a consistent and secure approach for signing messages of any size.

This video helped me massively when I started learning more about Digital Signatures, big thanks to Matt Thomas.

Matt Thomas Explaining Digital Signatures as used in Bitcoin

https://youtu.be/UQrKOtW5Nao – Digital Signatures ⚡ | Rust Language