
Menandatangani buta: apa yang sebenarnya Anda setujui ketika layar tak berkata apa-apa yang berguna
Ada satu saat tertentu di mana sebagian besar kerugian kripto besar terjadi. Bukan ketika sebuah kunci dicuri. Melainkan ketika seseorang menatap permintaan tanda tangan yang tak ia mengerti, memutuskan bahwa mungkin tak apa-apa, lalu mengetuk setujui.
Industri punya nama untuk ini: menandatangani buta. Artinya membubuhkan tanda tangan Anda pada data yang tak bisa Anda baca. Dan bertahun-tahun lamanya, dompet memperlakukan itu sebagai hal biasa.
Apa yang seharusnya dilakukan sebuah layar penandatanganan
Janji keamanan swakelola bertumpu pada satu asumsi: bahwa tak ada yang bergerak tanpa persetujuan Anda. Multisig dua-dari-dua, dompet perangkat keras, penandatangan terisolasi — masing-masing adalah mesin untuk mengubah niat Anda menjadi sebuah tanda tangan.
Mesin itu bernilai persis sebesar pemahaman Anda tentang apa yang Anda setujui.
Kalau layar berkata «Kirim 0,5 ETH ke 0x8f3C…», persetujuan Anda berarti sesuatu. Anda membandingkannya dengan apa yang Anda maksudkan. Kalau layar berkata «Interaksi kontrak — data: 0xa22cb465000000000000000000000000d8dA6…0001», persetujuan Anda tak berarti apa pun. Anda tidak sedang menyetujui sebuah transaksi; Anda sedang menyetujui sebuah persegi panjang berisi heksadesimal.
Penyerang tahu layar mana dari keduanya yang mereka butuhkan agar Anda lihat.
Apa yang bersembunyi di dalam byte
Muatan sebuah transaksi EVM adalah calldata: pemilih fungsi sepanjang empat byte, diikuti argumen yang dikodekan dengan ABI. Sempurna terbaca oleh mesin dan sempurna buram bagi manusia. Segelintir pemilih itulah jalan keluarnya uang.
approve(address,uint256) — pemilih 095ea7b3. Memberi sebuah kontrak izin membelanjakan token Anda. Bukan transfer; melainkan kuasa yang berdiri terus. Ia tak memindahkan apa pun saat Anda menandatanganinya, dan justru karena itulah ia lolos. Pengurasan terjadi belakangan, sesuai jadwal penyerang.
setApprovalForAll(address,bool) — pemilih a22cb465. Padanan untuk NFT, dan lebih buruk: satu tanda tangan memberi seorang operator kuasa atas setiap token dalam koleksi itu yang Anda pegang, sekarang dan nanti. Tak ada kolom jumlah yang bisa menenangkan Anda.
increaseAllowance(address,uint256) — pemilih 39509351. Menambah jatah yang sudah ada. Kerap terlewat karena ia bukan pemilih yang semua orang diberi tahu untuk diawasi.
transferFrom(address,address,uint256) — pemilih 23b872dd. Memindahkan token dari alamat yang sudah memberikan jatah. Tanda tangan yang akhirnya membelanjakan apa yang sebuah approve terdahulu izinkan.
Pola yang layak diresapi: transaksi yang mencuri uang Anda jarang sekali transaksi yang Anda tanda tangani. Anda menandatangani sebuah izin. Pencuriannya adalah transaksi terpisah, dikirim belakangan, yang tak pernah Anda lihat.
Mengapa «tanpa batas» adalah kata yang harus dicari
Hampir setiap penyalahgunaan jatah punya satu ciri sama: jumlahnya secara efektif tak terhingga.
Dapp meminta persetujuan tanpa batas karena praktis — setujui sekali, tak pernah ditanya lagi. Akibatnya, pengguna terlatih memberikan hak belanja tanpa batas sebagai rutinitas, dan antarmuka yang menampilkan 115792089237316195423570985008687907853269984665640564039457584007913129639935 tak memberi tahu siapa pun apa-apa.
Ada satu kehalusan yang menjerat penerapan yang naif. Nilai kanonik dari «persetujuan tak terhingga» persis 2²⁵⁶−1, jadi pemeriksaan yang paling jelas adalah membandingkannya dengan konstanta itu. Tapi dapp rutin memakai bilangan raksasa lain — separuh dari maksimum, 0xff…f0, 10¹⁸ × 10³⁸ — yang tak terbatas dalam segala pengertian praktis namun luput dari uji kesamaan persis. Peringatan yang hanya menyala pada nilai penanda persis adalah peringatan yang bisa dihindari penyerang dengan mengurangi satu.
Ambang yang benar terletak jauh di bawah penanda dan jauh di atas apa pun yang nyata. SSP menandai setiap jatah sebesar 2²⁵⁵ atau lebih — kira-kira 5,8 × 10⁷⁶, yang melampaui pasokan ERC-20 mana pun yang mungkin sejauh puluhan orde besaran. Tak ada yang sah pernah salah ditandai, dan menggerinda nilai tepat di bawah maksimum tak akan mengelak dari peringatan itu.
Ambang itu hanya berlaku bagi panggilan yang memberikan jatah. Pada transfer biasa, «tanpa batas» bukan gagasan yang bermakna — Anda memindahkan jumlah tertentu — maka di sana penanda maksimum persis dibiarkan saja.
Apa yang SSP lakukan
SSP menguraikan calldata menjadi bahasa yang jelas di layar persetujuan. Pemilih yang dikenali digambarkan sebagaimana adanya: siapa mitranya, berapa jumlahnya, apakah hak yang diberikan tanpa batas. Heksadesimal mentah bukan lagi isi utama — ia tinggal di balik bagian Lanjutan, bagi yang menginginkannya.
Tiga keputusan rancangan lebih penting daripada penguraiannya sendiri.
Pengurai itu hanya lapisan tampilan. Ia tak pernah mengubah apa yang ditandatangani. Persetujuan selalu menandatangani muatan asli yang persis; pembantu ini hanya menyajikan ulang byte yang sebelumnya ditampilkan sebagai heksadesimal mentah. Pengurai yang bisa mengubah muatan justru akan menjadi permukaan serangan baru, bukan pertahanan — yang Anda baca dan yang Anda tandatangani harus byte yang sama, selalu.
Ia gagal dengan menutup. Pemilih tak dikenal, panjang keliru, heksadesimal cacat, pengisian alamat tak baku — apa pun yang tak terduga tak mengembalikan apa-apa, dan antarmuka mundur ke aksi umum dengan heksadesimal mentah di Lanjutan. Ia tak pernah menebak. Pengurai yang menebak lebih buruk daripada tak ada pengurai, sebab ringkasan keliru yang percaya diri lebih berbahaya daripada heksadesimal yang terlihat: heksadesimal setidaknya jujur memberi tahu Anda bahwa Anda tak memahaminya.
Naluri gagal-menutup itu menembus lebih dalam daripada sekadar fungsi tak dikenal. Alamat berkode ABI adalah dua belas byte nol lalu dua puluh byte alamat; kata yang berisi apa pun selain itu pada byte-byte awalnya tidak berkode kanonik, dan SSP memperlakukannya sebagai mencurigakan alih-alih mencoba menafsirkannya. Begitu pula boolean — hanya pengodean kanonik yang diterima: seluruhnya nol (salah) dan 31 nol lalu 0x01 (benar). Pengodean non-kanonik pada layar yang memberikan hak belanja adalah bendera merah, bukan teka-teki penguraian.
Simbol dan desimal token tak pernah ditebak. Jumlah yang mudah dibaca hanya ditampilkan bila token itu dikenali dengan yakin dari daftar yang ada di perangkat. Kalau tidak, Anda mendapat angka mentah dalam satuan dasar. Ini sengaja kurang cantik: menampilkan «5,0 USDC» untuk kontrak yang sekadar menamai dirinya USDC akan mengubah pengurai menjadi mesin pembohong, dan itu persis hasil yang diinginkan penyerang.
Lalu ada bagian yang bersifat struktural, bukan kosmetik. Di SSP transaksi dibangun di satu tempat dan disetujui di tempat lain: disusun di ekstensi peramban, diuraikan dan ditampilkan secara mandiri di ponsel Anda, tempat SSP Key menghitung ulang hash transaksi di perangkat dan menolak menandatangani bila ia tak cocok dengan yang ditampilkan. Menandatangani buta di satu perangkat berarti satu layar yang terkompromi sudah cukup. Di sini, layar yang menunjukkan aksi terurai dan perangkat yang memegang kunci kedua adalah perangkat yang sama, dan ia memverifikasi alih-alih memercayai.
Apa yang harus dilakukan dengan persetujuan Anda sendiri
Perlakukan approve dan setApprovalForAll sebagai yang berbahaya. Itulah tanda tangan yang membuat orang kehilangan uang, dan mereka terasa tak berbahaya justru karena tak ada yang bergerak.
Tolak persetujuan tanpa batas bila Anda bisa. Banyak dapp menerima jumlah terbatas kalau Anda menetapkannya. Itu menambah gesekan, dan membatasi kerugian Anda pada apa yang memang hendak Anda belanjakan.
Periksa apa yang sudah pernah Anda berikan. Persetujuan lama tak kedaluwarsa. Izin yang Anda berikan kepada sebuah protokol dua tahun lalu masih hidup, dan bila kontrak itu belakangan terkompromi, ia adalah jalan terbuka menuju token Anda. Mencabut persetujuan berlangsung cepat, dan itulah satu jam beres-beres paling bernilai dalam swakelola.
Ketika layar tak berkata apa-apa kepada Anda, itulah sinyalnya. Kalau dompet Anda tak sanggup menyebutkan apa yang dilakukan sebuah transaksi, itu adalah informasi — bukan kerepotan untuk diklik lewat. Tanggapan yang tepat terhadap permintaan yang tak terbaca adalah berhenti, bukan menyipitkan mata.
Tujuannya tak pernah membuat Anda membaca heksadesimal. Tujuannya adalah memastikan bahwa ketika Anda menyetujui sesuatu, Anda dan dompet Anda sepakat tentang apa itu.


