1. Einleitung
Digitale Signaturen werden verwendet, um Authentizität und Integrität zu gewährleisten. Wie in diesem Artikel beschrieben, „gibt eine gültige digitale Signatur, bei der die Voraussetzungen erfüllt sind, dem Empfänger ein sehr hohes 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 verwendet, z. B. beim Allowlist-Minting und in NFT-Marktplätzen mit Orderbuch. Dies liegt daran, dass sie zur Kosteneinsparung bei Transaktionen beitragen (Off-Chain-Signatur und On-Chain-Verifizierung). Allerdings führen auch Missbräuche durch Entwickler zu Risiken auf NFT-Marktplätzen. In diesem Blog 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 auf NFT-Märkten für die 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 Vorgang der Erstellung eines NFTs auf der Blockchain. Die meisten NFT-Projekte möchten ihre Produkte verbreiten; sie bevorzugen es, Benutzer durch Allowlist-Minting (auch Presale usw. genannt) zu motivieren. Personen, die sich einen Platz sichern, können Tokens zu einem niedrigeren Preis (sogar kostenlos) prägen. Eine digitale Signatur wird verwendet, um die Allowlist-Minters von öffentlichen (gewöhnlichen) Mintern zu unterscheiden. Nachfolgend finden Sie ein Beispiel für die Implementierung des Allowlist-Mintings.
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 Code-Schnipsel stammt vom Association NFT (der eine Schwachstelle aufweist – kopieren Sie diesen Code nicht). Die Funktion mint_approved() soll das Allowlist-Minting implementieren: Der Projektbesitzer signiert eine Mint-Nachricht (Variable info) und sendet die Nachricht an den zugelassenen Minter (der NFTs prägen kann). Der Minter kann dann approved_mint mit der signierten Variable aufrufen. Der Vertrag prüft dann, ob die Nachricht vom Projekt signiert wurde (signer == recovered). Wenn ja, darf die Person, die die Funktion aufruft, NFTs 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 digitaler Signaturen im NFT-Ökosystem. NFT-Marktplätze spielen eine wesentliche Rolle im NFT-Ökosystem, da sie die Handelsfunktionalität für NFTs bereitstellen. Da jedes NFT-Token nicht-fungibel ist, ist die Automated 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 Orderbuch-Handel ist einfach. Es gibt einen Maker, also eine Person, die ein Asset zu einem bestimmten Preis verkaufen möchte, und einen Taker, also eine Person, die das Asset zum Preis des Verkäufers kaufen möchte. In diesem Fall kommt die Bestellung zustande. Der Prozess ist in den Orderbuch-NFT-Marktplätzen derselbe. Der einzige Unterschied ist der Prozess der Angebotsabgabe: Die NFT-Marktplätze verwenden digitale Signaturen zur Bestellverifizierung. Abbildung 1 beschreibt ein Beispiel für den gesamten Handelsprozess eines Orderbuch-Marktplatzes: OpenSea.

Insbesondere signiert der Verkäufer eine Verkaufsorder und speichert sie auf dem Server von OpenSea. Der Käufer kann die signierten Verkaufsorder-Informationen vom Server von OpenSea abrufen und dann den NFT-Marktplatzvertrag mit der signierten Verkaufsorder als Parameter aufrufen. Der Marktvertrag validiert die Bestellung, 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 kryptografische 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 Handlung vollständig und präzise zu signieren. Wenn die Signatur teilweise oder ungenau ist, sind die Folgen katastrophal.
3.1. Association NFT
Zurück zum 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 der Übereinstimmung zwischen der Person, die die Signatur dem Vertrag vorlegt, und dem Allowlist-Minter in der signierten Nachricht. Infolgedessen 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 des OpenSea-Marktplatzvertrags (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 Byte-Zeichenkette zusammengefasst, um den Digest des Angebots zu berechnen. Dann validiert der Vertrag die Signatur des Digests. Die Methode zur Parameterzusammenfassung packt die Parameter einfach mit den folgenden Methoden in eine Byte-Zeichenkette.
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 zusammengefassten Bytes 0x0000000000000000000000009a534628b4062e123ce7ee2222ec20b86e16ca8fc098, einfach address + bytes. Scheint einfach und klar zu sein, oder?
Betrachten wir 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 zusammengefassten Bytes sind:
Parameter 1:
_0x0000000000000000000000009a534628b4062e123ce7ee2222ec20b86e16ca8fabcdef_.Parameter 2:
_0x0000000000000000000000009a534628b4062e123ce7ee2222ec20b86e16ca8fabcdef_.
Wow! Zwei verschiedene Parameter haben dasselbe zusammengefasste Ergebnis, was bedeutet, dass ihre Digests GLEICH sind, was dazu führt, dass eine Signatur die beiden verschiedenen Parameter verifizieren kann.
Dies liegt daran, dass viele variable Längenkomponenten in den Parametern vorhanden sind. Ein Angreifer kann einen Teil der Variablen kürzen und die gekürzten Teile an ihre vorherigen oder nachfolgenden Komponenten anhängen. Leider enthalten Wyvern-Verträge viele variable Längenparameter, wie die folgende zeigt.
......
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 Auswirkungen der Schwachstelle sind, dass ein Angreifer (wenn möglich) die Konten des Opfers kontrollieren könnte, um bösartige Handlungen 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 Minter nicht in der signierten Nachricht (oder prüft nicht die Konsistenz der Informationen in der signierten Nachricht mit dem tatsächlichen Aufrufer), und der Wyvern-Vertrag signiert strukturlose Parameter, sodass die Bedeutung der Handlung geändert werden kann, während die Darstellung (sagen) 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 benötigte Informationen enthalten.
- Fügen Sie alle zu verifizierenden Informationen in die Signatur ein. 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 haben, aber das gleiche Kodierungsergebnis liefern.



