ECDSA Signature
Sign messages with ECDSA to obtain your AES key and to verify results returned by the User Interactor.
Bubble uses the ECDSA signature scheme to obtain the user's AES key for encrypting and decrypting data and also for verifying User Interactor results.
Signing a message
The SDK provides a signing functionality to facilitate this process.
Below are the function signatures for signing, provided in Python and JavaScript languages:
def sign(message, key)
def sign_eip191(message, key)The function signs the message using the given ECDSA private key.
Input parameters:
- message: The message to sign
- key: ECDSA private key
Output:
- The generated signature
Signature verification
When requesting the User Interactor to onboard a user or to encrypt data for a specific user, the user must verify that the results are really obtained by bubble system. We do that using the ECDSA signature scheme.
The SDK provides a verification functionality to facilitate the verify process.
Below are the function signatures for verification, provided in Python and JavaScript languages:
def verify_signatures(message, signatures, signers)The function executes several crucial checks: it retrieves the signer for each signature to confirm that the signer is included in the authorized signers' list. Furthermore, it ensures that each signature originates from a distinct signer to maintain the uniqueness of all signers involved.
Parameters:
message: The message that has been signed.signatures: The signatures to verify.signers: List of signers to validate against the signatures.
Output:
- Returns
trueif all signatures are validated; otherwise,false.
Example usage - Onboard user
As explained earlier, executing the onboardUser script retrieves the user's AES key. During the request phase, it's crucial to sign the RSA public key. In the response phase, verifying the evaluators' signatures is necessary.
Below are example demonstrating the usage of the sign and verify function in Python language:
def onboard_user(client, signing_private_key):
signers = get_signers_addresses()
# Create RSA key pair
rsa_private_key, rsa_public_key = generate_rsa_keypair()
# Get the Ethereum address from private key
account = Account.from_key(signing_private_key)
user_address = to_bytes(hexstr=account.address)
message = rsa_public_key + user_address
# Sign the rsa public key
signature = sign_eip191(message, bytes.fromhex(signing_private_key[2:]))
print(f"Onboarding user with address: {account.address}")
# Call the gRPC service
request = pb.OnboardUserRequest(
rsa_public_key=rsa_public_key,
address=user_address,
user_signature=signature
)
response = client.OnboardUser(request)
logging.info(f"OnboardUser returned {len(response.rsa_ciphertexts)} bytes")
if len(response.rsa_ciphertexts) != 2 * RSA_CIPHERTEXT_SIZE:
raise ValueError(f"Invalid response size: {len(response.rsa_ciphertexts)}")
if len(response.mpc_signatures) != NUM_EVALUATORS:
raise ValueError(f"Invalid number of signatures: {len(response.mpc_signatures)}")
# Verify the signatures
if not verify_signatures(response.rsa_ciphertexts, response.mpc_signatures, signers): # returns true if the signatures are valid, false otherwise
raise ValueError(f"Signatures verification failed")
# Split the response into two ciphers
cipher0 = response.rsa_ciphertexts[:RSA_CIPHERTEXT_SIZE]
cipher1 = response.rsa_ciphertexts[RSA_CIPHERTEXT_SIZE:]
# Decrypt the ciphers
share0 = decrypt_rsa(rsa_private_key, cipher0)
share1 = decrypt_rsa(rsa_private_key, cipher1)
# XOR the key shares to get the user AES key
user_aes_key = bytes(a ^ b for a, b in zip(share0, share1))
return user_aes_key