User InterfaceHttp proxy
Onboard a user
Onboard a user through the Bubble HTTP API and get the AES user key, in Python or JavaScript.
A minimal copy-paste example that onboards a user through the Bubble HTTP API and returns the AES user key. Available in Python and JavaScript.
Quick start
- Requirements: a wallet private key. Override the proxy with
BUBBLE_PROXY_URLif needed. - Install dependencies:
Python 3.10+. Use a virtualenv — the SDK pins web3==6.11.2.
pip install soda-bubble-sdkExample script
onboard.py
import base64, json, os, urllib.request
from soda_python_sdk.crypto import generate_rsa_keypair, recover_user_key, sign_eip191, verify_signatures
from eth_account import Account
from web3 import Web3
PRIVATE_KEY = os.environ["PRIVATE_KEY"]
PROXY_URL = os.environ.get("BUBBLE_PROXY_URL", "https://proxy.bubble.sodalabs.net")
RPC_URL = os.environ.get("RPC_URL", "https://ethereum-sepolia-rpc.publicnode.com")
# GCDecryptionVerifier for your chain — see Contract addresses
VERIFIER = os.environ.get("VERIFIER", "0x336646CF32aD1EdB82a8e31eE94DB1Be91932aea")
account = Account.from_key(PRIVATE_KEY)
# 1) Generate an RSA keypair for the onboarding request
private_key_bytes, public_key_bytes = generate_rsa_keypair()
# 2) Sign rsa_public_key + user_address
message = public_key_bytes + bytes.fromhex(account.address[2:])
signature = sign_eip191(message, bytes.fromhex(PRIVATE_KEY[2:]))
# 3) Call /onboard
body = {
"rsa_public_key": base64.b64encode(public_key_bytes).decode(),
"user_signature": base64.b64encode(signature).decode(),
"address": account.address,
}
req = urllib.request.Request(f"{PROXY_URL}/onboard", data=json.dumps(body).encode(),
headers={"Content-Type": "application/json"}, method="POST")
with urllib.request.urlopen(req, timeout=60) as res:
data = json.load(res)
ciphertext = base64.b64decode(data["rsa_ciphertexts"])
# 4) Verify the evaluators signed these key shares, before deriving anything from them.
# Without this a hostile proxy could return shares it chose, giving you a key it knows.
w3 = Web3(Web3.HTTPProvider(RPC_URL))
abi = [{"inputs": [], "name": "getSigners", "outputs": [{"type": "address[]"}],
"stateMutability": "view", "type": "function"}]
signers = w3.eth.contract(address=Web3.to_checksum_address(VERIFIER), abi=abi).functions.getSigners().call()
sigs = [base64.b64decode(s) for s in data.get("mpc_signatures", [])]
if len(sigs) != len(signers) or not verify_signatures(ciphertext, sigs, signers):
raise SystemExit("onboard response failed signature verification — refusing to derive a key")
# 5) Reconstruct the AES user key from the MPC shares
aes_key = recover_user_key(private_key_bytes, ciphertext[:256], ciphertext[256:])
print("User AES key (hex):", aes_key.hex())Run it
PRIVATE_KEY=0xabc123... python onboard.pyThe script signs the RSA public key plus your address, posts to /onboard, and prints the reconstructed AES user key you need for future encrypt and decrypt operations. The response also carries mpc_signatures, one per MPC evaluator — see OnboardUser for what they attest to.