
Firma cieca: che cosa stai approvando davvero quando lo schermo non dice nulla di utile
C'è un momento preciso in cui avvengono quasi tutte le grandi perdite cripto. Non è quando viene rubata una chiave. È quando qualcuno guarda una richiesta di firma che non capisce, decide che probabilmente va bene, e tocca approva.
Il settore ha un nome per questo: firma cieca. Significa apporre la propria firma su dati che non si possono leggere. E per anni i wallet l'hanno trattata come una cosa normale.
Che cosa dovrebbe fare una schermata di firma
La promessa di sicurezza dell'autocustodia poggia su un presupposto: che nulla si muova senza la tua approvazione. Il multisig due-di-due, gli hardware wallet, i firmatari isolati: ognuno di essi è una macchina per trasformare la tua intenzione in una firma.
Quel meccanismo vale esattamente quanto vale la tua comprensione di ciò che hai approvato.
Se lo schermo dice «Invia 0,5 ETH a 0x8f3C…», la tua approvazione significa qualcosa. L'hai confrontata con quello che intendevi fare. Se lo schermo dice «Interazione con contratto — dati: 0xa22cb465000000000000000000000000d8dA6…0001», la tua approvazione non significa proprio nulla. Non stai acconsentendo a una transazione; stai acconsentendo a un rettangolo di esadecimale.
Gli attaccanti sanno quale delle due schermate hanno bisogno che tu veda.
Che cosa si nasconde nei byte
Il carico utile di una transazione EVM sono i calldata: un selettore di funzione di quattro byte seguito da argomenti codificati in ABI. Perfettamente leggibile da una macchina e perfettamente opaco per una persona. Una manciata di quei selettori è la strada da cui esce il denaro.
approve(address,uint256) — selettore 095ea7b3. Concede a un contratto il permesso di spendere i tuoi token. Non è un trasferimento; è un'autorizzazione permanente. Quando la firmi non muove nulla, ed è esattamente per questo che passa. Lo svuotamento avviene dopo, secondo i tempi dell'attaccante.
setApprovalForAll(address,bool) — selettore a22cb465. L'equivalente per gli NFT, e peggiore: una sola firma dà a un operatore autorità su tutti i token di quella collezione che possiedi, ora e in futuro. Non c'è alcun campo importo che possa rassicurarti.
increaseAllowance(address,uint256) — selettore 39509351. Ricarica un'autorizzazione esistente. Spesso trascurato perché non è il selettore che a tutti è stato detto di sorvegliare.
transferFrom(address,address,uint256) — selettore 23b872dd. Sposta token da un indirizzo che ha già concesso un'autorizzazione. È la firma che finalmente spende ciò che un approve precedente aveva permesso.
Lo schema da interiorizzare: la transazione che ti ruba i soldi raramente è la transazione che hai firmato. Hai firmato un permesso. Il furto è una transazione distinta, inviata dopo, che tu non vedi mai.
Perché «illimitato» è la parola da cercare
Quasi tutti gli abusi di autorizzazione condividono un tratto: l'importo è di fatto infinito.
Le dapp chiedono approvazioni illimitate perché è comodo: approvi una volta e non ti chiedono più nulla. Il risultato è che gli utenti vengono addestrati a concedere diritti di spesa illimitati per abitudine, e un'interfaccia che mostra 115792089237316195423570985008687907853269984665640564039457584007913129639935 non sta dicendo niente a nessuno.
C'è una sottigliezza che frega le implementazioni ingenue. Il valore canonico dell'«approvazione infinita» è esattamente 2²⁵⁶−1, quindi il controllo ovvio è confrontarlo con quella costante. Ma le dapp usano abitualmente altri numeri astronomicamente grandi — la metà del massimo, 0xff…f0, 10¹⁸ × 10³⁸ — che sono illimitati in ogni senso pratico e tuttavia falliscono un test di uguaglianza esatta. Un avviso che scatta solo sul sentinella esatto è un avviso che un attaccante aggira sottraendo uno.
La soglia giusta sta ben sotto il sentinella e ben sopra qualunque cosa reale. SSP segnala qualsiasi autorizzazione pari o superiore a 2²⁵⁵ — circa 5,8 × 10⁷⁶, che supera qualunque offerta ERC-20 possibile di decine di ordini di grandezza. Nulla di legittimo viene mai segnalato per errore, e limare un valore appena sotto il massimo non schiva l'avviso.
Quella soglia si applica solo alle chiamate che concedono autorizzazioni. In un semplice trasferimento «illimitato» non è un'idea sensata — stai muovendo un importo preciso — quindi lì il sentinella del massimo esatto viene lasciato stare.
Che cosa fa SSP
SSP decodifica i calldata in linguaggio piano sulla schermata di approvazione. I selettori riconosciuti vengono resi per quello che sono: chi è la controparte, qual è l'importo, se i diritti concessi sono illimitati. L'esadecimale grezzo non è più il contenuto principale: vive dietro una sezione Avanzate, per chi lo vuole.
Tre scelte di progetto contano più della decodifica in sé.
Il decodificatore è solo presentazione. Non cambia mai ciò che viene firmato. L'approvazione firma sempre l'esatto carico originale; l'aiutante si limita a ripresentare byte che prima erano mostrati come esadecimale grezzo. Un decodificatore in grado di alterare il carico sarebbe una nuova superficie d'attacco anziché una difesa: ciò che leggi e ciò che firmi devono essere gli stessi byte, sempre.
Fallisce chiudendosi. Selettore sconosciuto, lunghezza errata, esadecimale malformato, riempimento di indirizzo non standard: qualsiasi cosa inattesa non restituisce nulla, e l'interfaccia ripiega su un'azione generica con l'esadecimale grezzo in Avanzate. Non tira mai a indovinare. Un decodificatore che indovina è peggio di nessun decodificatore, perché un riassunto sbagliato ma sicuro di sé è più pericoloso dell'esadecimale visibile: l'esadecimale almeno ti dice onestamente che non lo capisci.
Quell'istinto di fallire chiudendosi va più a fondo delle funzioni sconosciute. Un indirizzo codificato in ABI sono dodici byte a zero seguiti da venti byte di indirizzo; una parola con qualsiasi altra cosa in quei byte iniziali non è codificata canonicamente, e SSP la tratta come sospetta invece di provare a interpretarla. Lo stesso per i booleani: si accettano solo le codifiche canoniche, tutto zero (falso) e 31 zeri seguiti da 0x01 (vero). Codifiche non canoniche su una schermata che concede diritti di spesa sono un campanello d'allarme, non una sfida di analisi.
Simboli e decimali dei token non vengono mai indovinati. Un importo leggibile viene mostrato solo quando il token è noto con certezza dal registro presente sul dispositivo. Altrimenti ottieni il numero grezzo in unità base. È deliberatamente meno bello: mostrare «5,0 USDC» per un contratto che semplicemente si fa chiamare USDC trasformerebbe il decodificatore in una macchina per mentire, che è esattamente il risultato voluto da un attaccante.
Poi c'è la parte strutturale anziché cosmetica. In SSP la transazione viene costruita in un posto e approvata in un altro: composta nell'estensione del browser, decodificata e mostrata in modo indipendente sul tuo telefono, dove SSP Key ricalcola l'hash della transazione sul dispositivo e si rifiuta di firmare se non corrisponde a ciò che è stato mostrato. Firmare alla cieca su un solo dispositivo significa che basta una schermata compromessa. Qui la schermata che ti mostra l'azione decodificata e il dispositivo che custodisce la seconda chiave sono lo stesso dispositivo, e quel dispositivo verifica invece di fidarsi.
Che cosa fare con le tue approvazioni
Tratta approve e setApprovalForAll come quelle pericolose. Sono le firme che costano soldi alla gente, e sembrano innocue proprio perché non muovono nulla.
Rifiuta le approvazioni illimitate quando puoi. Molte dapp accettano un importo limitato se lo imposti tu. È attrito, e tiene il tuo danno entro ciò che intendevi davvero spendere.
Verifica che cosa hai già concesso. Le vecchie approvazioni non scadono. Un permesso dato a un protocollo due anni fa è ancora attivo, e se quel contratto viene compromesso più avanti, è una strada aperta verso i tuoi token. Revocare le approvazioni è veloce, ed è l'ora di manutenzione più redditizia dell'autocustodia.
Quando lo schermo non ti dice nulla, quello è il segnale. Se il tuo wallet non sa dire che cosa fa una transazione, quella è un'informazione, non un fastidio da superare a clic. La risposta giusta a una richiesta illeggibile è fermarsi, non strizzare gli occhi.
L'obiettivo non è mai stato farti leggere esadecimale. È assicurarsi che, quando approvi qualcosa, tu e il tuo wallet siate d'accordo su che cosa fosse.


