Pen & paper protocol walkthrough — click each phase to step through
swapId(swapId, R_A, enc_salt_A, pk_meta_B, noteDetails_A) to TEE.(swapId, R_B, enc_salt_B, pk_meta_A, noteDetails_B) to TEE.h_swap, h_R, h_meta, h_enc. Recompute swapId from noteDetails. Verify terms match. No EC operations — only hashes.announceSwap(swapId, R_A, R_B, enc_salt_A, enc_salt_B) — single tx, both or neither.timeout, each party refunds their own note using fallbackOwner key. Same nullifier either way → no double spend.
r_A (secret), R_A = r_A·G, random salt_Ass = r_A · pk_meta_Bpk_stealth_B = pk_meta_B + H("stealth", ss)·Genc_salt = salt_A ⊕ H("salt_enc", ss)
pk_stealth_Bpk_Acommitment, chainId, timeout, pk_stealth_Bh_swap, h_R, h_meta, h_enc
(swapId, nonce, R_A, enc_salt_A, pk_meta_B, noteDetails_A)r_B (secret), R_B = r_B·G, random salt_Bss = r_B · pk_meta_Apk_stealth_A = pk_meta_A + H("stealth", ss)·Genc_salt = salt_B ⊕ H("salt_enc", ss)
pk_stealth_Apk_Bcommitment, chainId, timeout, pk_stealth_Ah_swap, h_R, h_meta, h_enc
(swapId, nonce, R_B, enc_salt_B, pk_meta_A, noteDetails_B)H("bind_swap", swapId, noteDetails_A.salt) == h_swap_AH("bind_swap", swapId, noteDetails_B.salt) == h_swap_BH("commitment", noteDetails_A...) == commitment_AH("commitment", noteDetails_B...) == commitment_Bh_R, h_meta, h_encexpected_swapId from noteDetails values + timeout + pk_metas + nonce.expected_swapId == swapId.announceSwap(swapId, R_A, R_B, enc_salt_A, enc_salt_B)
require(!announcements[swapId].revealed) — prevents duplicate announcements even if TEE state is rolled back.
R_A and enc_salt_Ass = sk_meta_B · R_Asalt_A = enc_salt_A ⊕ H("salt_enc", ss)sk_stealth_B = sk_meta_B + H("stealth", ss)R_B and enc_salt_Bss = sk_meta_A · R_Bsalt_B = enc_salt_B ⊕ H("salt_enc", ss)sk_stealth_A = sk_meta_A + H("stealth", ss)block.timestamp > timeoutsk_A·G == fallbackOwnertimeout is public output → verifier checks block.timestamp > timeout
H("nullifier", commitment, salt). This is the core safety invariant.
| Data | Party A | Party B | TEE | On-chain |
|---|---|---|---|---|
| sk_meta_A | ✓ | ✗ | ✗ | ✗ |
| r_A (ephemeral secret) | ✓ | ✗ | ✗ | ✗ |
| R_A (ephemeral public) | ✓ | after P3 | ✓ | H(R_A) at P1, R_A at P3 |
| salt_A | ✓ | after P3 (decrypt) | ✓ (plaintext) | ✗ (encrypted) |
| swap amounts/assets | ✓ | ✓ | ✓ | ✗ |
| pk_stealth | ✓ | ✓ | ✓ | ✓ |
| link(pk_stealth → identity) | ✓ | ✓ | ✓ | ✗ (needs R) |