Address & Key Formats

Concrete data formats for every key and address type in the CB-MPC application

Bold = Static / deterministic (same key material always produces this output)
Italic = Mutable / ephemeral (regenerated via MPC ceremony, refresh, or derivation)
Red border = Secret material (never leaves device)
Blue border = Public material (safe to share)

1. Public Keys

All public keys in CB-MPC use compressed SEC1 encoding. The first byte signals the parity of the Y coordinate, followed by the 32-byte X coordinate.

K
secp256k1 Compressed Public Key ECDSA STATIC

The canonical public key format throughout the app. Produced by cbmpc_ecdsa2p_key_pubkey() and stored in ManagedKey.publicKey as a hex string.

33 bytes -- compressed SEC1 (hex-encoded, 66 characters) 02a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2 [prefix 02|03] + [32-byte X]
02 Prefix
a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2 X-Coordinate (32 bytes)
Curvesecp256k1 (OpenSSL NID 714)
Size33 bytes raw / 66 hex chars
Prefix02 (even Y) or 03 (odd Y)
EncodingHex string, lowercase, no 0x prefix in storage
Recognizable ByStarts with 02 or 03, exactly 66 hex chars
Display Truncation

In the app UI, public keys are truncated to two formats:

publicKeyDisplay: first 16 hex chars + "..." → 02a1b2c3d4e5f6a7...

shortAddress: "0x" + first 4 + "..." + last 4 → 0x02a1...a1b2

P
prime256v1 (NIST P-256) Compressed Public Key ECDSA STATIC

Identical SEC1 format as secp256k1 but on a different curve. Supported by the C API but not the primary curve for blockchain use.

33 bytes -- compressed SEC1 (hex-encoded, 66 characters) 03f4d8e2a1c6b3e7f9d0a5c8b2e4f1a3d6c9b5e8f2a4d7c1b6e9f3a5d8c2b7e0a3 [prefix 02|03] + [32-byte X]
Curveprime256v1 / P-256 (OpenSSL NID 415)
Size33 bytes raw / 66 hex chars
Recognizable BySame format as secp256k1 -- requires metadata to distinguish curves
Curve Ambiguity

Compressed public keys for secp256k1 and prime256v1 are visually identical (both 33 bytes, same prefix bytes). The app stores curveCode (714 or 415) as metadata in ManagedKey to disambiguate. Without this metadata, the curve cannot be determined from the key alone.

E
Ed25519 Public Key EdDSA STATIC

Used for multi-party EdDSA signing via cbmpc_eddsamp_sign(). Raw 32-byte compressed point on Curve25519.

32 bytes -- raw compressed point (hex-encoded, 64 characters) d75a981cfe45f47d3b2e10c8b5a263570d95cffc9a4d08cef4d36c8dbab1f2a4 [32-byte compressed point, no prefix byte]
CurveEd25519 (Curve25519, twisted Edwards)
Size32 bytes raw / 64 hex chars
Recognizable By64 hex chars (no 02/03 prefix) -- 2 chars shorter than secp256k1
Used ForMulti-party threshold signing (N-of-M via cbmpc_eckey_mp_t)

2. Ethereum Addresses

0x
Ethereum Address ECDSA STATIC

Derived from the secp256k1 public key: decompress to 64-byte (X,Y), Keccak-256 hash, take the last 20 bytes. The app currently stores the last 40 hex chars of the compressed public key as a display address.

20 bytes -- Keccak-256 derived (hex-encoded, 42 characters with 0x prefix) 0x71C7656EC7ab88b098defB751B7401B5f6d8976F [0x prefix] + [20-byte address]
0x Prefix
71C7656EC7ab88b098defB751B7401B5f6d8976F Keccak-256(uncompressed_pubkey)[12:32]
DerivationKeccak-256(04 || X || Y)[12:32]
Size20 bytes / 40 hex chars + "0x" prefix
ChecksumEIP-55 mixed-case (not currently enforced in app)
NormalizationAddressBook auto-prepends "0x" if missing
Display0x71C7...976F (first 4 + last 4 hex chars)
Same Key, Same Address

An Ethereum address is deterministically derived from a public key. Since the public key is static (unchanged by MPC share refresh), the Ethereum address is also static. Refreshing key shares does not change the address.

3. MPC Key Shares (Private Material)

Key shares are the secret half of MPC -- each party holds a share that is never combined. Shares are regenerated during key refresh ceremonies, making them inherently mutable.

2
2-of-2 ECDSA Key Share ECDSA MUTABLE SECRET

Created by cbmpc_ecdsa2p_dkg(). Each device holds one share of a 2-party ECDSA key. Serialized as an opaque binary blob via cbmpc_ecdsa2p_key_serialize().

Variable length -- opaque binary (base64 shown for illustration) BIGNUMAAAEAAAAgAAAAIKsW7mP8f4qVzRn3bDxTkYf... [party_role + encrypted_share + curve_metadata]
Threshold2-of-2 -- both shares required to sign
PartiesParty 0 (device) + Party 1 (device or server)
FormatOpaque binary -- not human-readable
MutabilityRegenerated on cbmpc_ecdsa2p_refresh() -- new share, same public key
StorageDevice Keychain (kSecAttrAccessibleWhenUnlockedThisDeviceOnly)
Keychain IDcb-mpc.share.{keyId}.{partyId}
Share Refresh Changes the Bytes

After a key refresh ceremony, the binary representation of both shares changes completely. The public key remains identical -- only the internal share randomness is rotated. This is why share data is marked italic/mutable while the public key stays bold/static.

Internal Packing (UserDefaults backup)

When both shares are stored together (e.g., device-to-device key), they use a length-prefixed packing:

4 bytes k0 length (UInt32 BE)
k0 bytes Party 0 Share
k1 bytes Party 1 Share

Layout: [4-byte k0 length][k0 bytes][k1 bytes] -- k1 length is inferred from total size minus header minus k0 length.

N
N-of-M Multi-Party Key Share EdDSA THRESHOLD MUTABLE

Created by threshold DKG with access control structures. Supports arbitrary (t, n) configurations. Serialized as a 5-field structure.

5-field structure -- cbmpc_eckey_mp_t (conceptual layout) { x_share, Q, Qis[], curve, party_name } [share_scalar + joint_pubkey + per-party_pubkeys + curve_id + identity]
Thresholdt-of-n -- configurable via access structure
Fieldsx_share (scalar), Q (joint public key), Qis (array of per-party public keys), curve (NID), party_name
ConversionCan convert to additive shares via cbmpc_eckey_mp_to_additive()
Signingcbmpc_eddsamp_sign() for Ed25519 threshold signatures
Metadata is Required

Unlike 2-of-2 keys where the threshold is implicit, multi-party shares must carry metadata (party count, threshold, access structure) to be usable. The party_name field identifies which party this share belongs to. When transferring shares (QR, Bluetooth), both the share data and the party metadata must travel together.

4. HD Derivation Paths (BIP-32 / BIP-44)

HD
BIP-44 Derivation Paths HD-WALLET

Hierarchical deterministic keys use BIP-44 paths to derive child keys from a master. The path is a string of indices; hardened indices (marked with ') use the top bit of a 32-bit integer.

Path Format

BIP-44 Ethereum path string m/44'/60'/0'/0/0 m / purpose' / coin_type' / account' / change / address_index
m Root
44' Purpose (BIP-44)
60' Coin (Ethereum)
0' Account
0 Change
0 Index

Derivation Presets

Preset Path Pattern Varies Example Sequence
MetaMask m/44'/60'/0'/0/X address_index .../0/0, .../0/1, .../0/2
MetaMask (imported) m/44'/60'/0'/0 none (root only) m/44'/60'/0'/0
Ledger Live m/44'/60'/X'/0/0 account .../0'/0/0, .../1'/0/0, .../2'/0/0
Custom m/44'/60'/* any user-defined

Binary Index Encoding

Parsed by KeyStore.parseBIP44Path() into an array of UInt32 values:

Non-hardened index (e.g., 0) 0x00000000 raw index value
Hardened index (e.g., 44') 0x8000002C index | 0x80000000
44'0x8000002C (44 | 0x80000000)
60'0x8000003C (60 | 0x80000000)
0'0x80000000 (0 | 0x80000000)
0 (non-hardened)0x00000000
M
HD Master Key vs. Child Key HD-WALLET

HD-MASTER (root keyset)

Serialized HD keyset -- opaque binary cbmpc_hd_key_t { root_share + chain_code + ... }
Pathm (root)
Display TagHD-MASTER
Can DeriveYes -- produces HD-CHILD keys
SharesMPC shares of master key (mutable)
Public KeyRoot compressed SEC1 (static)

HD-CHILD (derived key)

Derived ECDSA 2-party key cbmpc_ecdsa2p_key_t (from cbmpc_hd_ecdsa2p_derive)
Pathm/44'/60'/0'/0/0
Display TagHD-CHILD
Links toparentKeyId (UUID of master)
Public KeyDeterministic from master + path (static)
AddressUnique Ethereum address per path

5. Signature Formats

S
ECDSA Signature ECDSA
DER-encoded (variable length, ~70-72 bytes) 3045022100f7a8b3c2d1e4f5a6b7c8d9e0f1a2b3c4d5e6f7...02200a1b2c3d4... [30 || len || 02 || r_len || r || 02 || s_len || s]
Input32-byte SHA-256 hash
EncodingDER (ASN.1 SEQUENCE of two INTEGERs)
Size~70-72 bytes (variable)
Produced Bycbmpc_ecdsa2p_sign()
E
EdDSA Signature EdDSA
Fixed 64 bytes (R || S) a1b2c3d4e5f6...32_bytes_R...e7f8a9b0c1d2...32_bytes_S... [32-byte R] + [32-byte S]
InputRaw message bytes (not pre-hashed)
EncodingRaw concatenation (R || S)
Size64 bytes (fixed)
Produced Bycbmpc_eddsamp_sign()

6. EIP-712 Typed Data Hashing

712
EIP-712 Structured Hash ECDSA STATIC

The app implements EIP-712 typed data signing with a pure-Swift Keccak-256 (Ethereum variant, NOT NIST SHA-3).

32 bytes -- signing hash Keccak-256( 0x19 || 0x01 || domainSeparator || structHash )
0x19 EIP-191
0x01 Version
domainSeparator Keccak-256(EIP712Domain type + values)
structHash Keccak-256(typeHash + encoded fields)
Hash FunctionKeccak-256 (Ethereum variant, 32-byte output)
Domain FieldschainId, verifyingContract, name, version
Type Encoding"TypeName(type1 name1,type2 name2,...)"
Output32-byte hash signed by ECDSA

7. QR Transfer Frame Format

QR
Multi-Part QR Frame MUTABLE

Key shares exported via QR use base64-encoded binary frames. Each frame contains a magic header, part index, and payload chunk. Maximum 450 bytes per binary frame (~600 chars base64).

Base64-encoded binary frame (text QR code) Q0JNUEMAAAEAAwABa2V5X3NoYXJlX2RhdGFfcGFydF8xXy4uLg== [magic + part_index + total_parts + part_number + payload]
CBMPC Magic (5 bytes)
UInt16 BE Total Parts
UInt16 BE Part Number
payload bytes Encrypted Share Data (up to ~440 bytes)
Max Frame450 bytes binary / ~600 chars base64
QR Version9-10 (reliable phone-to-phone scanning)
SplittingData split evenly across parts (not fill-first)
Integer ReadsreadUInt16BE / readUInt32BE (manual byte reads, no load(as:))
ScannerAVCaptureMetadataOutput.stringValueData(base64Encoded:)

8. Key Metadata & Storage Identifiers

ID
ManagedKey Metadata Schema

Every key in the app wraps raw cryptographic material with structured metadata stored in Core Data. This metadata is what allows the UI to distinguish key types and display meaningful information.

Field Type Example Notes
id UUID A1B2C3D4-E5F6-7890-ABCD-EF1234567890 Primary key, links to Keychain entries
publicKey String (hex) 02a1b2c3d4...f0a1b2 66-char compressed SEC1
keyType Enum simple | hdMaster | hdChild Display: ECDSA, HD-MASTER, HD-CHILD
curveCode Int32 714 or 415 OpenSSL NID -- disambiguates curves
derivationPath String? m/44'/60'/0'/0/0 nil for simple keys, "m" for HD master
parentKeyId UUID? ... HD-CHILD → its HD-MASTER
storageLocation Enum secureEnclave | keychain Display: Device Keychain, iCloud Keychain
ceremonyType String device_device | device_server In KeyShare metadata (Keychain)
Why Metadata Matters

A compressed secp256k1 public key and a compressed P-256 public key are byte-for-byte indistinguishable without the curveCode field. Similarly, a 2-of-2 key share and an N-of-M key share are both opaque binary -- only the keyType and ceremonyType metadata tells the app (and the user) what threshold scheme applies. Always transfer metadata alongside key material.

9. Quick Reference: All Formats at a Glance

#
Size & Encoding Summary
Data Type Raw Size Encoding Display Persistence
secp256k1 pubkey 33 bytes Compressed SEC1 hex Bold (static) Core Data
P-256 pubkey 33 bytes Compressed SEC1 hex Bold (static) Core Data
Ed25519 pubkey 32 bytes Raw hex Bold (static) Core Data
Ethereum address 20 bytes 0x + hex Bold (static) Derived on display
2-of-2 ECDSA share variable Opaque binary Italic (mutable) Device Keychain
N-of-M threshold share variable 5-field struct Italic (mutable) Device Keychain
HD master keyset variable Opaque binary Italic (mutable shares) Device Keychain
BIP-44 path 5 x UInt32 String m/44'/60'/... Bold (static) Core Data
ECDSA signature ~70-72 bytes DER Italic (per-message) Signing records
EdDSA signature 64 bytes Raw R||S Italic (per-message) Signing records
QR frame ≤450 bytes Base64 text Italic (ephemeral) Not persisted