1. Einleitung
Digitale Signaturen werden zur Gewährleistung von Authentizität und Integrität verwendet. Wie in diesem Artikel beschrieben, „gibt eine gültige digitale Signatur, bei der die Voraussetzungen erfüllt sind, einem Empfänger ein sehr hohes Maß an Vertrauen, dass die Nachricht von einem bekannten Absender erstellt wurde (Authentizität) und dass die Nachricht während des Transports nicht verändert wurde (Integrität).“
Digitale Signaturen werden häufig in Smart Contracts eingesetzt, z. B. bei Allowlist-Minting und in Orderbuch-NFT-Marktplätzen. Dies liegt daran, dass sie zur Einsparung von Transaktionskosten beitragen (Signatur off-chain, Verifizierung on-chain). Der Missbrauch durch Entwickler birgt jedoch auch Risiken in NFT-Marktplätzen. In diesem Blogbeitrag möchten wir über den Missbrauch digitaler Signaturen im NFT-Ökosystem sprechen.
2. Anwendungen
Digitale Signaturen werden häufig für das Allowlist-Minting (nur Benutzer mit gültigen Signaturen können NFTs prägen) in NFT-Verträgen und in NFT-Märkten zur Bestellverifizierung (nur Bestellungen mit erwarteten Signaturen können ausgeführt werden) verwendet. Die Signatur der Daten erfolgt off-chain, um Gas zu sparen. Im Folgenden werden diese beiden Anwendungsszenarien erläutert.
2.1. Allowlist-Minting
„NFT-Minting“ ist der Prozess der Erstellung eines NFTs auf der Blockchain. Die meisten NFT-Projekte möchten ihre Produkte verbreiten; sie ziehen es vor, Benutzer durch Allowlist-Minting (auch Presale usw. genannt) zu motivieren. Personen, die die Plätze gewinnen, können Token zu einem niedrigeren Preis (sogar kostenlos) prägen. Eine digitale Signatur wird verwendet, um die Allowlist-Minters von öffentlichen (gewöhnlichen) Mintern zu unterscheiden. Unten ist ein Beispiel für die Implementierung des Allowlist-Mintings dargestellt.
function mint_approved(
vData memory info,
uint256 number_of_items_requested,
uint16 _batchNumber
) external {
...
require(verify(info), "Unauthorised access secret");
...
}
function verify(vData memory info) public view returns (bool) {
require(info.from != address(0), "INVALID_SIGNER");
bytes memory cat =
abi.encode(
info.from,
info.start,
info.end,
info.eth_price,
info.dust_price,
info.max_mint,
info.mint_free
);
bytes32 hash = keccak256(cat);
require(info.signature.length == 65, "Invalid signature length");
bytes32 sigR;
bytes32 sigS;
uint8 sigV;
bytes memory signature = info.signature; assembly {
sigR := mload(add(signature, 0x20))
sigS := mload(add(signature, 0x40))
sigV := byte(0, mload(add(signature, 0x60)))
} bytes32 data =
keccak256(
abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)
);
address recovered = ecrecover(data, sigV, sigR, sigS);
return signer == recovered;
}
Dieser Codeausschnitt stammt aus dem Association NFT (das eine Schwachstelle aufweist – kopieren Sie diesen Code nicht). Die Funktion mint_approved() beabsichtigt, das Allowlist-Minting zu implementieren: Der Projektbesitzer signiert eine Mint-Nachricht (Variable info) und sendet die Nachricht an den berechtigten Mintern (der NFTs prägen kann). Dann kann der Minter approved_mint mit der signierten Variablen aufrufen. Der Vertrag wird dann überprüfen, ob die Nachricht vom Projekt signiert wurde (signer == recovered). Wenn ja, ist die Person, die die Funktion aufruft, berechtigt, NFTs zu prägen (was NICHT sicher ist, da keine Überprüfung stattfindet, ob die Person, die die Funktion aufruft, die tatsächliche Person auf der Allowlist ist).
2.2. Bestellverifizierung
Die Bestellverifizierung ist eine weitere Anwendung der digitalen Signatur im NFT-Ökosystem. NFT-Marktplätze spielen eine wesentliche Rolle im NFT-Ökosystem, da sie die Handelsfunktionalität für NFTs bereitstellen. Da jeder NFT-Token nicht fungibel ist, ist die automatisierten Market Maker (AMM) Handelspolitik in NFT-Märkten schwer anzuwenden. Daher verwenden die meisten NFT-Marktplätze, z. B. OpenSea, LooksRare und X2Y2, das Orderbuch-Handelsmodell.
Der Orderbuchhandel ist einfach. Es gibt einen Maker, auch eine Person, die ein Asset zu einem bestimmten Preis verkaufen möchte, und einen Taker, auch eine Person, die das Asset zum Preis des Verkäufers kaufen möchte. In diesem Fall ist die Bestellung abgeglichen. Der Prozess ist in den Orderbuch-NFT-Marktplätzen derselbe. Der einzige Unterschied ist der Prozess der Order-Angebotsabgabe: Die NFT-Marktplätze verwenden digitale Signaturen zur Bestellverifizierung. Abbildung 1 beschreibt ein Beispiel für den gesamten Handelsprozess eines der Orderbuch-Marktplätze: OpenSea.

Konkret signiert der Verkäufer eine Verkaufsorder und speichert sie auf dem Server von OpenSea. Der Käufer kann die signierte Verkaufsorder-Informationen vom Server von OpenSea abrufen und dann den NFT-Marktplatzvertrag mit der signierten Verkaufsorder als Parameter aufrufen. Der Marktplatzvertrag validiert die Order, um sicherzustellen, dass der Verkäufer die Verkaufsorder signiert hat (da der Käufer die Transaktion initiiert) – um zu verhindern, dass der Käufer ein Asset ohne Zustimmung des Verkäufers kauft.
3. Sicherheitsvorfälle
Das Horton-Prinzip ist ein Maximen für kryptographische Systeme und kann ausgedrückt werden als „Authentifizieren Sie, was gemeint ist, nicht was gesagt wird“ oder „meinen Sie, was Sie unterschreiben, und unterschreiben Sie, was Sie meinen“. Es erfordert, die Aktion vollständig und präzise zu signieren. Wenn die Signatur teilweise oder ungenau ist, sind die Folgen katastrophal.
3.1. Association NFT
Erinnern wir uns an den NBA NFT-Vertrag in Abschnitt 2.1. Die Funktion verify führt eine Standard-Signaturprüfung durch, aber es fehlt eine KRITISCHE Komponente. Die Signaturprüfung stellt nur sicher, dass die Nachricht vom Projekt signiert wurde. Es gibt jedoch keine Durchsetzung, dass die Person, die die Signatur dem Vertrag zur Verfügung stellt, mit dem Allowlist-Mintern in der signierten Nachricht übereinstimmt. Folglich kann jeder dieselbe Signatur verwenden, um die Verifizierung zu bestehen und NFTs zu prägen.
3.2. OpenSea
Ein weiteres Sicherheitsproblem betrifft OpenSea. Anfang 2022 deckten Forscher eine potenzielle Schwachstelle im OpenSea-Marktplatzvertrag (Version: wyvern 2.2) auf, der die Kernfunktionalität des NFT-Handels implementiert.
Im Wyvern-Protokoll erstellen Benutzer Angebote (Verkaufsangebote) oder Kaufangebote off-chain, und Signaturen von Angeboten werden on-chain verifiziert. Wyvern-Angebote enthalten viele Parameter, und die Parameter werden zu einer einzigen Bytes-Zeichenkette zusammengefasst, um den Digest des Angebots zu berechnen. Dann validiert der Vertrag die Signatur des Digests. Die Parameteraggregation erfolgt einfach durch das Zusammenfügen der Parameter zu einer Bytes-Zeichenkette mit den folgenden Methoden.
index = ArrayUtils.unsafeWriteAddress(index, order.target);
index = ArrayUtils.unsafeWriteUint8(index, uint8(order.howToCall));
index = ArrayUtils.unsafeWriteBytes(index, order.calldata);
index = ArrayUtils.unsafeWriteBytes(index, order.replacementPattern);
index = ArrayUtils.unsafeWriteAddress(index, order.staticTarget);
index = ArrayUtils.unsafeWriteBytes(index, order.staticExtradata);
index = ArrayUtils.unsafeWriteAddress(index, order.paymentToken);
Wenn die Parameter beispielsweise aus 2 Komponenten bestehen: (address, bytes), und die Parameter sind (0x9a534628b4062e123ce7ee2222ec20b86e16ca8f, "0xc098"), dann sind die aggregierten Bytes 0x0000000000000000000000009a534628b4062e123ce7ee2222ec20b86e16ca8fc098, einfach address + bytes. Scheint einfach und klar, oder?
Betrachten Sie nun ein komplexeres Beispiel: Die Struktur der Parameter ist (address, bytes, bytes).
Parameter 1 ist
_(0x9a534628b4062e123ce7ee2222ec20b86e16ca8f, "0xab", "0xcdef")_.Parameter 2 ist
_(0x9a534628b4062e123ce7ee2222ec20b86e16ca8f, "0xabcd", "0xef")_.
Die aggregierten Bytes sind:
Parameter 1:
_0x0000000000000000000000009a534628b4062e123ce7ee2222ec20b86e16ca8fabcdef_.Parameter 2:
_0x0000000000000000000000009a534628b4062e123ce7ee2222ec20b86e16ca8fabcdef_.
Wow! Zwei unterschiedliche Parameter haben das gleiche aggregierte Ergebnis, was bedeutet, dass ihre Digests GLEICH sind, was dazu führt, dass eine Signatur die beiden unterschiedlichen Parameter verifizieren kann.
Dies liegt daran, dass viele variablenlange Komponenten in den Parametern vorhanden sind. Ein Angreifer kann einen Teil der Variablen abschneiden und die abgeschnittenen Teile an seine früheren oder späteren Komponenten anhängen. Leider haben Wyvern-Verträge viele variablelange Parameter, wie unten gezeigt.
......
address target;
/* HowToCall. */
AuthenticatedProxy.HowToCall howToCall;
/* Calldata. */
bytes calldata;
/* Calldata replacement pattern, or an empty byte array for no replacement. */
bytes replacementPattern;
/* Static call target, zero-address for no static call. */
address staticTarget;
/* Static call extra data. */
bytes staticExtradata;
......
Die Auswirkung der Schwachstelle besteht darin, dass ein Angreifer (wenn möglich) die Konten des Opfers kontrollieren könnte, um einige bösartige Aktionen auszuführen. Eine detaillierte Analyse der Schwachstelle finden Sie hier.
Beide in diesem Abschnitt genannten Sicherheitsvorfälle verstoßen gegen das Horton-Prinzip. Insbesondere enthält der NBA-Vertrag den Mintern nicht in der signierten Nachricht (oder prüft nicht die Übereinstimmung der Informationen in der signierten Nachricht mit dem tatsächlichen Aufrufer), und der Wyvern-Vertrag signiert strukturlose Parameter, sodass die Bedeutung der Aktion geändert werden kann, während die Darstellung (Aussage) der Parameter erhalten bleibt.
4. Vorschläge
Befolgen Sie das Horton-Prinzip: Signieren Sie, was Sie meinen, nicht, was Sie sagen. Die Signatur sollte umfassende und genaue Informationen enthalten, die benötigt werden.
- Fügen Sie alle zu verifizierenden Informationen in die Signatur auf. Prüfen Sie die Konsistenz der Daten in der signierten Nachricht mit dem Laufzeitwert (z. B. der beabsichtigte Benutzer in der signierten Nachricht und der tatsächliche Benutzer).
- Die zu signierende Nachricht muss deterministisch kodiert werden, d. h. es dürfen keine Nachrichten existieren, die unterschiedliche Strukturen aufweisen, aber das gleiche Kodierungsergebnis haben.



