EIP-1271: Firmare e verificare le firme degli smart contract
Lo standard EIP-1271 (opens in a new tab) consente agli smart contract di verificare le firme.
In questo tutorial, forniamo una panoramica delle firme digitali, del contesto dell'EIP-1271 e dell'implementazione specifica dell'EIP-1271 utilizzata da Safe (opens in a new tab) (precedentemente Gnosis Safe). Tutto questo può fungere da punto di partenza per implementare l'EIP-1271 nei propri contratti.
Cos'è una firma?
In questo contesto, una firma (più precisamente, una "firma digitale") è un messaggio più una sorta di prova che il messaggio provenga da una persona/mittente/indirizzo specifico.
Ad esempio, una firma digitale potrebbe apparire così:
- Messaggio: "Voglio accedere a questo sito web con il mio portafoglio Ethereum."
- Firmatario: Il mio indirizzo è
0x000… - Prova: Ecco una prova che io,
0x000…, ho effettivamente creato l'intero messaggio (di solito si tratta di qualcosa di crittografico).
È importante notare che una firma digitale include sia un "messaggio" che una "firma".
Perché? Ad esempio, se mi dessi un contratto da firmare, e poi io tagliassi la pagina della firma e ti restituissi solo le mie firme senza il resto del contratto, il contratto non sarebbe valido.
Allo stesso modo, una firma digitale non significa nulla senza un messaggio associato!
Perché esiste l'EIP-1271?
Per creare una firma digitale da utilizzare sulle blockchain basate su Ethereum, in genere è necessaria una chiave privata segreta che nessun altro conosce. Questo è ciò che rende la tua firma, tua (nessun altro può creare la stessa firma senza conoscere la chiave segreta).
Il tuo account Ethereum (ovvero, il tuo account di proprietà esterna/EOA) ha una chiave privata associata, e questa è la chiave privata che viene tipicamente utilizzata quando un sito web o un'applicazione decentralizzata (dapp) ti chiede una firma (ad es. per "Accedi con Ethereum").
Un'app può verificare una firma (opens in a new tab) che crei utilizzando una libreria di terze parti come ethers.js senza conoscere la tua chiave privata (opens in a new tab) ed essere sicura che tu sia stato colui che ha creato la firma.
Infatti, poiché le firme digitali degli EOA utilizzano la crittografia a chiave pubblica, possono essere generate e verificate offchain! È così che funziona il voto senza gas delle DAO: invece di inviare i voti onchain, le firme digitali possono essere create e verificate offchain utilizzando librerie crittografiche.
Mentre gli account EOA hanno una chiave privata, gli account degli smart contract non hanno alcun tipo di chiave privata o segreta (quindi "Accedi con Ethereum", ecc. non può funzionare nativamente con gli account degli smart contract).
Il problema che l'EIP-1271 mira a risolvere: come possiamo dire che la firma di uno smart contract è valida se lo smart contract non ha alcun "segreto" da poter incorporare nella firma?
Come funziona l'EIP-1271?
Gli smart contract non hanno chiavi private che possono essere utilizzate per firmare messaggi. Quindi come possiamo dire se una firma è autentica?
Beh, un'idea è che possiamo semplicemente chiedere allo smart contract se una firma è autentica!
Ciò che fa l'EIP-1271 è standardizzare questa idea di "chiedere" a uno smart contract se una determinata firma è valida.
Un contratto che implementa l'EIP-1271 deve avere una funzione chiamata isValidSignature che accetta un messaggio e una firma. Il contratto può quindi eseguire una logica di convalida (la specifica non impone nulla di specifico qui) e poi restituire un valore che indica se la firma è valida o meno.
Se isValidSignature restituisce un risultato valido, è praticamente il contratto che dice "sì, approvo questa firma + messaggio!"
Interfaccia
Ecco l'interfaccia esatta nella specifica EIP-1271 (parleremo del parametro _hash di seguito, ma per ora, pensalo come il messaggio che viene verificato):
pragma solidity ^0.5.0;
contract ERC1271 {
// bytes4(keccak256("isValidSignature(bytes32,bytes)")
bytes4 constant internal MAGICVALUE = 0x1626ba7e;
/**
* @dev Dovrebbe restituire se la firma fornita è valida per l'hash fornito
* @param _hash Hash dei dati da firmare
* @param _signature Array di byte della firma associato a _hash
*
* DEVE restituire il valore magico bytes4 0x1626ba7e quando la funzione ha successo.
* NON DEVE modificare lo stato (usando STATICCALL per solc < 0.5, modificatore view per solc > 0.5)
* DEVE consentire chiamate esterne
*/
function isValidSignature(
bytes32 _hash,
bytes memory _signature)
public
view
returns (bytes4 magicValue);
}
Esempio di implementazione dell'EIP-1271: Safe
I contratti possono implementare isValidSignature in molti modi: la specifica non dice molto sull'implementazione esatta.
Un contratto degno di nota che implementa l'EIP-1271 è Safe (precedentemente Gnosis Safe).
Nel codice di Safe, isValidSignature è implementato (opens in a new tab) in modo che le firme possano essere create e verificate in due modi (opens in a new tab):
- Messaggi onchain
- Creazione: un proprietario del safe crea una nuova transazione del safe per "firmare" un messaggio, passando il messaggio come dati nella transazione. Una volta che un numero sufficiente di proprietari firma la transazione per raggiungere la soglia del multisig, la transazione viene trasmessa ed eseguita. Nella transazione, c'è una funzione del safe chiamata (
signMessage(bytes calldata _data)) che aggiunge il messaggio a un elenco di messaggi "approvati". - Verifica: chiama
isValidSignaturesul contratto Safe e passa il messaggio da verificare come parametro del messaggio e un valore vuoto per il parametro della firma (opens in a new tab) (ovvero,0x). Il Safe vedrà che il parametro della firma è vuoto e, invece di verificare crittograficamente la firma, saprà di dover semplicemente procedere e controllare se il messaggio è nell'elenco dei messaggi "approvati".
- Creazione: un proprietario del safe crea una nuova transazione del safe per "firmare" un messaggio, passando il messaggio come dati nella transazione. Una volta che un numero sufficiente di proprietari firma la transazione per raggiungere la soglia del multisig, la transazione viene trasmessa ed eseguita. Nella transazione, c'è una funzione del safe chiamata (
- Messaggi offchain:
- Creazione: un proprietario del safe crea un messaggio offchain, quindi fa in modo che altri proprietari del safe firmino il messaggio individualmente fino a quando non ci sono abbastanza firme per superare la soglia di approvazione del multisig.
- Verifica: chiama
isValidSignature. Nel parametro del messaggio, passa il messaggio da verificare. Nel parametro della firma, passa le singole firme di ciascun proprietario del safe tutte concatenate insieme, una dopo l'altra. Il Safe verificherà che ci siano abbastanza firme per soddisfare la soglia e che ogni firma sia valida. In tal caso, restituirà un valore che indica l'avvenuta verifica della firma.
Cos'è esattamente il parametro _hash? Perché non passare l'intero messaggio?
Potresti aver notato che la funzione isValidSignature nell'interfaccia EIP-1271 (opens in a new tab) non accetta il messaggio stesso, ma un parametro _hash. Ciò significa che invece di passare l'intero messaggio di lunghezza arbitraria a isValidSignature, passiamo un hash di 32 byte del messaggio (generalmente keccak256).
Ogni byte dei dati di chiamata — ovvero, i dati dei parametri della funzione passati a una funzione dello smart contract — costa 16 gas (4 gas se è un byte zero) (opens in a new tab), quindi questo può far risparmiare molto gas se un messaggio è lungo.
Specifiche EIP-1271 precedenti
Ci sono specifiche EIP-1271 in circolazione che hanno una funzione isValidSignature con un primo parametro di tipo bytes (lunghezza arbitraria, invece di un bytes32 a lunghezza fissa) e nome del parametro message. Questa è una versione precedente (opens in a new tab) dello standard EIP-1271.
Come dovrebbe essere implementato l'EIP-1271 nei miei contratti?
La specifica è molto flessibile al riguardo. L'implementazione di Safe ha alcune buone idee:
- Puoi considerare valide le firme EOA del "proprietario" del contratto.
- Potresti memorizzare un elenco di messaggi approvati e considerare validi solo quelli.
Alla fine, spetta a te come sviluppatore del contratto!
Conclusione
L'EIP-1271 (opens in a new tab) è uno standard versatile che consente agli smart contract di verificare le firme. Apre le porte agli smart contract per agire in modo più simile agli EOA — ad esempio fornendo un modo per far funzionare "Accedi con Ethereum" con gli smart contract — e può essere implementato in molti modi (Safe ha un'implementazione non banale e interessante da considerare).