Identity
Ownership Hierarchy
Every Make ID is owned by an EVM wallet address on Tempo:
| Layer | Role |
|---|---|
EVM Wallet / Passkey (owner_address) | Canonical account owner. Pays gas for registration, funds storage, manages keys. Supports EOAs, smart wallets, and WebAuthn passkeys. |
Make ID (mid) | On-chain account identifier (uint64). Assigned by the registry contract. Ownership is transferable. |
| Ed25519 Keys | Delegated signing keys for fast off-chain operations (signing messages, pushing commits). |
Registration costs gas on Tempo, providing natural spam resistance — no one can create unlimited accounts for free. MID ownership is transferable onchain for social recovery and account migration. A custodial recovery address can be authorized for safe transfer in case of key loss.
Accounts
An account is identified by a unique Make ID (mid, uint64) assigned by an onchain registry contract. Each account has an owner_address (20-byte EVM address) set on its first KEY_ADD message.
Ownership transfer happens onchain via the registry contract's transferOwnership() function, which emits an event relayed into Makechain consensus as an OWNERSHIP_TRANSFER message. This updates owner_address in the state layer. The OWNERSHIP_TRANSFER message includes both the new and previous owner addresses — the previous address must match the current state as a defense-in-depth check.
Keys
All keys are Ed25519. Each account has one or more registered keys with explicit scopes:
| Scope | Capabilities |
|---|---|
OWNER | Full account control: manage keys, transfer projects, delete account |
SIGNING | Push commits, update refs, manage collaborators on authorized projects |
AGENT | Automated actions (CI/CD, AI agents) — scoped to specific projects/refs |
Keys are registered onchain and relayed into the consensus layer as KEY_ADD / KEY_REMOVE messages (2P set) so validators can verify signatures without querying the chain. The KEY_ADD message carries an owner_address field set by the registry relay on registration.
Registry Contracts
Four contracts on Tempo manage MID lifecycle. Validators watch the MakeRegistry for events and relay them into Makechain consensus.
MakeBundler → MakeIdGateway → MakeRegistry ← RecoveryRouter
(atomic ops) (registration (core: MIDs, (social recovery
policy) keys, transfer, via multisig)
recovery)MakeRegistry
The core contract. Manages MID ownership, Ed25519 key delegation, ownership transfer, and time-locked recovery.
| Function | Relay Message | Description |
|---|---|---|
register(to, key, scope) | KEY_ADD + owner_address | Create a new MID (gateway-only) |
addKey(mid, key, scope, ...) | KEY_ADD | Add a delegated signing key |
removeKey(mid, key) | KEY_REMOVE | Revoke a key (permanent — no re-add) |
transferOwnership(mid, newOwner) | OWNERSHIP_TRANSFER | Transfer MID ownership |
setRecovery(mid, recovery) | — | Set time-locked recovery address |
initiateRecovery(mid, newOwner) | — | Start recovery timelock (recovery address only) |
cancelRecovery(mid) | — | Cancel during timelock (owner only) |
executeRecovery(mid) | OWNERSHIP_TRANSFER | Complete recovery after timelock (anyone) |
Key state machine: NULL → ADDED → REMOVED (irreversible — prevents key recycling attacks). Recovery address is preserved through direct transfers so recovery can still function if an attacker transfers the MID.
MakeIdGateway
Controls registration policy without changing the registry address validators watch.
| Mode | Behavior |
|---|---|
OPEN | Anyone can register |
INVITE_ONLY | Requires a valid invite code |
CLOSED | Registration disabled |
MakeBundler
Atomic multi-step operations in a single transaction: register + add multiple keys + set recovery address. While Tempo's native batch calls (type 0x76) can achieve this, the Bundler provides a typed API.
RecoveryRouter
A social recovery proxy owned by a multisig (e.g., Gnosis Safe 2/3). Users set the router's address as their recovery address. The multisig can then initiate time-locked recovery on their behalf if they lose access. Different user types can choose different recovery strategies:
| User Type | Recovery Address | Controller |
|---|---|---|
| Self-custody | Personal hardware wallet | User directly |
| Hosted (Makechain app) | RecoveryRouter | 2/3 ops multisig |
| Enterprise | Own RecoveryRouter | Organization's 3/5 multisig |
Passkeys and Tempo Transactions
Tempo Transactions (EIP-2718 type 0x76) enable native support for:
- Passkey authentication (secp256r1/WebAuthn) — Tempo validates P256 signatures at the chain level before EVM execution, so
msg.senderis already authenticated when it reaches the registry contracts. No onchain P256 verification is needed. - Batch calls — deploy smart account + register MID + add keys in one atomic transaction
- Fee sponsorship — Makechain can cover registration gas for new users
This means passkey users follow the same code path as EOA users — Tempo handles the cryptographic difference transparently. The EIP-712 meta-transaction path (registerFor) uses secp256k1 ECDSA for offline/relayer signing scenarios.
The contracts are in contracts/src/.
Signature Scheme
- Ed25519 — fast verification (~60k verifications/sec on commodity hardware), compact signatures (64 bytes), deterministic signing (no nonce reuse risk)
- BLAKE3 — 32-byte digests for message hashing, commit hashing, and merkle tree construction
External Address Verification
Accounts can prove ownership of external blockchain addresses via VERIFICATION_ADD / VERIFICATION_REMOVE messages (2P set). Each verification requires a claim_signature proving the external key signed a deterministic challenge message.
Challenge Message
The message to sign is:
makechain:verify:<mid>Where <mid> is the decimal string representation of the account's Make ID. For example, account 42 signs the UTF-8 bytes of makechain:verify:42.
Ethereum (ETH_ADDRESS)
Sign the challenge using EIP-191 personal_sign:
keccak256("\x19Ethereum Signed Message:\n" + len(message) + message)The claim_signature is 65 bytes: r (32) || s (32) || v (1) where v is the recovery ID (0 or 1).
The address field is the 20-byte Ethereum address. The protocol recovers the public key from the signature, derives the address via keccak256(pubkey)[12..], and verifies it matches.
Solana (SOL_ADDRESS)
Sign the challenge using standard Ed25519:
ed25519_sign(keypair, "makechain:verify:<mid>")The claim_signature is 64 bytes (standard Ed25519 signature). The address field is the 32-byte Solana public key. The protocol verifies the signature directly against the address.