Soda Labs
Soda Labs Docs
User InterfaceUser SDK

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 true if 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

On this page