Dies ist eine informative Übersetzung. Maßgeblich ist die englische Fassung; sie hat im Zweifel Vorrang. Zur englischen Fassung

Signaturen

Das optionale `sigs`-Array auf Datensatzebene, ein abgetrenntes COSE_Sign1 über den gesamten Datensatzkörper, sein domänengetrennter signierter Inhalt, die beiden Wege zur Übermittlung des Signaturschlüssels und die strenge Ed25519-Verifizierung.

Ein Label 309-Datensatz KANN eine oder mehrere Signaturen zur Urheberschaft in einem optionalen sigs-Array auf oberster Ebene führen. Jeder Eintrag ist ein abgetrenntes COSE_Sign1 (RFC 9052) über den Datensatzkörper und belegt, dass ein bestimmter Schlüssel für den Datensatz einsteht. Die Urheberschaft ist stets optional: Der Standard verlangt nie eine Signatur, und ein Datensatz ohne sigs-Feld ist ein vollständiger, uneingeschränkt verifizierbarer Existenznachweis (engl. Proof of Existence, PoE).

Eine Signatur kommt hinzu, sie ersetzt nichts: Sie beantwortet zusätzlich zur Zeitstempelaussage die Frage „und dieser Schlüssel steht dafür ein", niemals an deren Stelle. Der Inhalts-Hash ist der primäre Anspruch; eine Signatur ist ein Metadatum darüber, wer hinter diesem Anspruch steht. Entscheidend dabei ist, dass eine Signatur, die die prüfende Stelle nicht überprüfen kann, etwa wegen eines nicht unterstützten Algorithmus oder eines nicht auflösbaren Schlüssels, den Inhalts- oder Zeitstempelanspruch niemals ungültig macht. Signaturen scheitern sanft; die Existenz nicht.

Diese Seite legt fest, was eine Signatur abdeckt, welche Bytes genau signiert werden, auf welchen beiden Wegen der öffentliche Schlüssel (Public Key) des Signierenden übermittelt wird und welche strenge Verifizierung ein öffentlicher Verifizierer durchführt. Der Ed25519-Schlüssel selbst ist auf Schlüssel definiert; das sigs-Feld im Übertragungsformat – in dem cose_sign1 und cose_key jeweils ein einzelner CBOR-Byte-String sind – ist auf Der Datensatz beschrieben.

Was eine Signatur abdeckt

Ein einzelner sigs[i]-Eintrag bezeugt den gesamten Datensatzkörper, einheitlich. Es gibt keine Signaturgranularität auf Ebene einzelner Items, URIs oder Felder: Eine Signatur bindet sich an jedes Item, jede Speicher-URI, jeden Verschlüsselungsumschlag, den supersedes-Verweis, sofern vorhanden, sowie jeden Erweiterungsschlüssel, den der Datensatz trägt. Ein Relay kann nach der Signierung keines dieser Felder hinzufügen, entfernen oder umschreiben, ohne die Signatur zu zerstören.

Der signierte Körper ist die Datensatz-Map mit entferntem sigs-Feld, also remove_keys(record_map, ["sigs"]), hier als record_body bezeichnet. Das sigs-Array ist ausgeschlossen, weil eine Signatur sich nicht selbst abdecken kann und weil sich jeder Signierende nur an den Anspruch bindet, nicht an die Liste der Mitunterzeichner. Konkret signiert jeder Eintrag {v, items?, merkle?, supersedes?, crit?, <extensions?>}, also dieselben record_body-Bytes für jeden Eintrag, doch keiner signiert die anderen Einträge in sigs. Ein Signierender bestätigt damit, dass der von ihm signierte Körper derselbe ist, an den alle anderen Einträge gebunden sind; kein Signierender bestätigt, welche anderen Signierenden mitunterzeichnet haben.

Der Umfang der Signatur ist der Datensatzkörper, nicht die Transaktion

Eine verifizierte Signatur beweist, dass ein Schlüssel eine Signatur über den Datensatzkörper erstellt hat. Sie beweist nicht, dass derselbe Schlüssel die tragende Transaktion eingereicht, ihre Gebühr bezahlt oder ihre Blockzeit gewählt hat. Derselbe Datensatzkörper KANN von jeder Partei in einer späteren Transaktion erneut veröffentlicht werden, das ist die beabsichtigte Portabilität von Datensätzen. Eine verifizierte Signatur ist als „signiert von <Schlüssel>" darzustellen, niemals als „<Schlüssel> hat dies eingereicht" oder „veröffentlicht von <Schlüssel> zum Zeitpunkt <Zeit>".

Der signierte Inhalt

Jeder Eintrag trägt ein abgetrenntes COSE_Sign1, sodass das COSE-Payload-Feld leer ist und die tatsächlich signierten Bytes vom Verifizierer aus dem on-chain (in der Blockchain) liegenden Datensatz rekonstruiert werden. Der Signierende berechnet:

record_body       = remove_keys(record_map, ["sigs"])
record_body_bytes = canonical_cbor(record_body)
SIG_DOMAIN_RECORD = utf8("cardano-poe-record-sig-v1")        ; 25 bytes
to_sign           = SIG_DOMAIN_RECORD || record_body_bytes   ; concatenation
Sig_structure     = [ "Signature1", protected, h'', to_sign ]
signature         = Sign(canonical_cbor(Sig_structure), signer_key)

record_body wird als kanonisches CBOR gemäß RFC 8949 §4.2.1 serialisiert, mit derselben deterministischen Kodierung, die der gesamte Datensatz verwendet. Erst der Determinismus macht eine Signatur interoperabel: Zwei Implementierungen, die denselben logischen Körper kodieren, erzeugen byteidentische record_body_bytes, sodass eine Signatur, die von einer Implementierung erstellt wurde, unter einer anderen verifiziert werden kann.

Das Präfix zur Domänentrennung

to_sign ist der 25 Byte lange UTF-8-String cardano-poe-record-sig-v1, dem record_body_bytes vorangestellt wird. Das Präfix bindet die Signatur an ihre Label 309-Rolle und verhindert protokollübergreifende Replay-Angriffe. Ein künftiges Cardano-Metadatenschema, das zufällig dieselbe CBOR-Form des Körpers aufwiese (gleiche Schlüssel, gleiche Typen), könnte eine Label 309-Signatur nicht gegen sich selbst wiederverwenden: Sein to_sign trüge ein anderes Präfix oder gar keines, sodass die signierte Bytefolge abwiche und die Signatur scheiterte. Implementierungen MÜSSEN diese exakte Bytefolge als führende Bytes von to_sign einbetten; das bloße kanonische CBOR ohne Präfix zu signieren, ist nicht standardkonform.

Warum external_aad leer ist

Label 309 platziert den Domänentrenner innerhalb von to_sign, nicht in COSE external_aad. Der external_aad-Slot (Sig_structure[2]) ist stets der leere Byte-String h''. Dies ist eine bewusste Abweichung vom üblichen COSE-Muster, einen Domänen-String in external_aad zu legen, und der Grund dafür ist die Interoperabilität mit Wallets: CIP-30 signData, der Standardweg für die Wallet-Signatur auf Cardano, legt fest, dass kein external_aad verwendet wird, und gibt einer dApp keine Möglichkeit, eines beizusteuern. Ein nicht leeres external_aad würde jede von einer Wallet erzeugte Signatur scheitern lassen. Das Einbetten des Präfix in die Nutzdaten bewahrt dieselbe Replay-Schutzeigenschaft und hält dabei die von der Wallet erzeugten und die von der prüfenden Stelle neu berechneten Bytes Byte für Byte gleich.

Die Sig_structure

Sig_structure ist das 4-elementige COSE_Sign1-Signier-Array aus RFC 9052 §4.4:

SlotWertHinweise
[0]"Signature1"Fester COSE-Kontextbezeichner, als vollständiger CBOR-Textstring (11 Bytes) ausgegeben, nie als bloßes UTF-8.
[1]protectedDie bstr-verpackten, kanonisch CBOR-kodierten Protected-Header-Bytes des Signierenden, unverändert verwendet, vom Verifizierer nie neu kanonisiert.
[2]external_aadStets h'' (Länge null, bstr).
[3]to_signDas 25 Byte lange Präfix, verkettet mit record_body_bytes.

Der veröffentlichte COSE_Sign1 trägt sein Payload-Feld (COSE_Sign1[2]) als CBOR null (0xF6), also in der abgetrennten Form. Ein angehängtes Payload, einschließlich eines null Byte langen Byte-Strings, wird abgelehnt. Das Abtrennen der Nutzdaten bindet die signierten Bytes an den Datensatzkörper, den der Verifizierer unabhängig rekonstruiert; eine angehängte Form ließe es einem Produzenten zu, geliehene Bytes zu signieren, die keinen Bezug zu den on-chain liegenden Aussagen haben.

Gehashter Modus für Hardware-Wallets

CIP-30 / CIP-8 definieren ein optionales "hashed": true-Flag im Unprotected Header, das ein in seinen Ressourcen eingeschränkter Hardware-Mitunterzeichner setzen kann. Ist es vorhanden und true, ist Sig_structure[3] der 28 Byte lange Digest Blake2b-224(to_sign) anstelle von to_sign selbst; die anderen drei Slots bleiben unverändert. Ein Verifizierer MUSS den Unprotected Header prüfen und diese Ersetzung vor der strengen Ed25519-Verifizierung vornehmen. Software- und SDK- Produzenten SOLLTEN es nicht setzen, es spart keine Bytes im Übertragungsformat und verkompliziert die Verifizierer-Codepfade.

Signaturalgorithmus

Der einzige Signaturalgorithmus in v1 ist EdDSA über Ed25519 (RFC 8032), identifiziert durch COSE alg = -8 (RFC 9053 §2.2), der im Protected Header des COSE_Sign1 steht. Die verpflichtende Mindestunterstützung eines v1-Verifizierers ist {-8}; er KANN zusätzlich -19 (Ed25519, vollständig spezifiziert) akzeptieren und beide Codepunkte unter demselben Ed25519-Primitiv verifizieren. Die Registry ist erweiterbar: Künftige Revisionen fügen Post-Quanten-Signaturen additiv hinzu, niemals als Bruch der Abwärtskompatibilität.

Auflösung des Signaturschlüssels

Ein öffentlicher Verifizierer muss den öffentlichen Schlüssel des Signierenden ohne Kontaktaufnahme mit einem Dienst auflösen, daher trägt jede Signatur ihren Schlüssel oder einen eindeutigen, in der Signatur liegenden Verweis darauf on-chain. Es gibt in v1 genau zwei Übertragungsformen, und sie schließen sich innerhalb eines einzelnen Eintrags gegenseitig aus: Ein Eintrag, der beide verwendet, ist ein struktureller Fehler.

Pfad 1: Identitätssignatur (kid in der Signatur)

Der 32 Byte lange rohe öffentliche Ed25519-Schlüssel wird am COSE-Header-Label 4 (kid, RFC 9052 §3.1) innerhalb des Protected Headers des COSE_Sign1 platziert. Der Eintrag trägt kein cose_key-Feld. Gemäß der Label 309-Konvention ist ein Protected-Header-kid von genau 32 Bytes der öffentliche Schlüssel selbst, kein undurchsichtiger Verweis auf einen außerhalb des Bandes nachgeschlagenen Schlüssel. Die Länge von 32 Bytes ist ein eindeutiges Unterscheidungsmerkmal: Öffentliche Ed25519-Schlüssel sind immer 32 Bytes lang. Die Platzierung des Schlüssels im Protected Header (nicht im Unprotected Header) bindet ihn an die Signatur; wer ihn umschriebe, zerstörte damit die Verifizierung.

Diese Konvention ist eine bewusste, dokumentierte Abweichung von der Lesart des kid als undurchsichtiger Bezeichner in RFC 9052; sie macht den Identitätspfad dienstunabhängig, ohne dass ein Schlüsselverzeichnis erforderlich ist. Das Schlüsselmodell ist auf Schlüssel definiert.

Pfad 2: Wallet-Signatur (Inline-cose_key)

Eine CIP-30-signData-Signatur gibt den öffentlichen Schlüssel des Signierenden als separaten cbor<COSE_Key>-Blob zurück, nicht innerhalb des COSE_Sign1. Ein Produzent, der eine solche Signatur in einen Datensatz einbindet, MUSS diesen COSE_Key in denselben sigs[i]-Eintrag unter dem Schlüssel cose_key legen, als einzelnen CBOR-Byte-String. Der Verifizierer dekodiert ihn als COSE_Key und liest den öffentlichen Ed25519-Schlüssel aus Label -2. Der COSE_Key MUSS ausschließlich die öffentliche Hälfte beschreiben, also kty = OKP (1), crv = Ed25519 (6) und den 32 Byte langen Wert x an Label -2, und DARF KEIN privates Schlüsselmaterial enthalten (Label -4 und Ähnliches); einen privaten Skalar auf einem permanenten Ledger zu veröffentlichen, ist ein unwiderrufliches Durchsickern eines Schlüssels.

Gegenseitiger Ausschluss

Die beiden Pfade schließen sich auf Übertragungsebene aus. Ein Eintrag trägt entweder einen 32 Byte langen Protected-Header-kid und kein cose_key (Pfad 1), oder ein cose_key-Feld und keinen 32 Byte langen Protected-Header-kid (Pfad 2), niemals beides. Ein Eintrag, der beides trägt, wird abgelehnt; ein Verifizierer muss bei der Verifizierung nie disambiguieren. Die Auflösung ist daher eine Unterscheidung auf Übertragungsebene, keine priorisierte Rangfolge:

PfadBedingungSignaturschlüssel
132 Byte langer Protected-kid, kein cose_keyDer 32 Byte lange kid-Wert, direkt verwendet.
2cose_key vorhanden, kein 32 Byte langer kidDer Ed25519-Schlüssel an COSE_Key-Label -2.

Ein kid, der nur im Unprotected Header geführt wird, ist kein zulässiger Auflösungspfad: Er liegt außerhalb des signierten Umschlags, sodass ein Relay ihn umschreiben könnte, ohne die Signatur zu zerstören. Ein Verifizierer MUSS kid-Werte aus dem Unprotected Header für die Auflösung ignorieren. Ergibt kein zulässiger Pfad einen 32 Byte langen Ed25519-Schlüssel, wird der Eintrag als nicht aufgelöst gemeldet und trägt keinen Urheberschaftsnachweis bei.

Verifizierung

Ein öffentlicher Verifizierer prüft jeden sigs[i]-Eintrag unabhängig, in dieser Reihenfolge:

  1. Dekodieren. Den Byte-String sigs[i].cose_sign1 als COSE_Sign1 parsen. Das Payload-Feld MUSS null sein (abgetrennt); jedes nicht-null- oder nicht-leere Payload ist fehlerhaft.
  2. Algorithmus. Den Protected-Header-alg lesen. Liegt er außerhalb des vom Verifizierer unterstützten Satzes, ist der Eintrag nicht unterstützt (siehe unten), kein Fehler am Datensatz.
  3. Schlüssel auflösen. Die obige Unterscheidung zwischen Pfad 1 und Pfad 2 anwenden, um den 32 Byte langen öffentlichen Ed25519-Schlüssel zu ermitteln. Ergibt kein Pfad einen solchen, ist der Eintrag nicht aufgelöst.
  4. Rekonstruieren und verifizieren. to_sign und Sig_structure = ["Signature1", protected, h'', to_sign] neu aufbauen, kanonisch CBOR-kodieren und die Signatur mit strengem Ed25519 verifizieren. (Dabei zuvor Blake2b-224(to_sign) für to_sign einsetzen, wenn der Unprotected Header "hashed": true trägt.)
  5. Wallet-Bindung (nur Pfad 2). Die Stake-Adresse aus dem aufgelösten Schlüssel neu berechnen und Byte für Byte gegen die Protected-Header-address vergleichen; eine Abweichung lässt die Bindung scheitern, auch wenn die Ed25519-Signatur selbst verifiziert wurde. Erst diese nur in Pfad 2 vorkommende Prüfung ermöglicht es einer Oberfläche, einen Datensatz als wallet-gebunden darzustellen; Pfad-1-Einträge überspringen sie.

Strenges Ed25519

Die Verifizierung folgt den strengen Regeln aus RFC 8032 §5.1.7: Für ein gegebenes Schlüssel-, Nachrichten- und Signatur-Tripel gibt es genau eine zulässige Antwort:

  • Nicht-kanonische Kodierungen von R oder dem Signatur-Skalar S (insbesondere jedes S ≥ ℓ, die Gruppenordnung) MÜSSEN abgelehnt werden.
  • Öffentliche Schlüssel und R-Werte mit kleiner Ordnung, in einer kleinen Untergruppe oder mit Torsionskomponente MÜSSEN abgelehnt werden.
  • Die kofaktorisierte Verifikationsgleichung (die ZIP-215-/Batch-freundliche Form) DARF NICHT an die Stelle der strengen Gleichung treten.

Erst die Strenge macht das Ergebnis über Implementierungen hinweg reproduzierbar: Ein kofaktorisierter Verifizierer akzeptierte Signaturen, die ein strenger ablehnt, sodass zwei konforme Verifizierer uneins wären. Implementierungen müssen eine Bibliothek, oder einen Bibliotheksmodus, wählen, die bzw. der eine strenge, nicht kofaktorisierte Verifizierung durchführt.

Semantik der Ergebnisse

Signaturen kommen hinzu, sodass eine nicht verifizierbare Signatur am Eintrag gemeldet und nicht zu einem Fehler auf Datensatzebene erhoben wird. Jedes sigs[i] führt zu einem dieser typisierten Ergebnisse pro Eintrag; der vollständige Fehlerkatalog und die Regeln für das Ergebnis auf Datensatzebene sind auf Verifizierung beschrieben:

ErgebnisBedeutung
verifiedStrenges Ed25519 (und, bei Pfad 2, die Adressbindung) hat bestanden.
signature unsupportedDer Protected-Header-alg liegt außerhalb des Verifizierer-Satzes. Info, kein Fehler.
signer key unresolvedKein zulässiger Pfad ergibt einen 32 Byte langen öffentlichen Ed25519-Schlüssel.
signature invalidStrenges Ed25519 hat false über die rekonstruierte Sig_structure zurückgegeben.
wallet address mismatchPfad 2: Die Signatur wurde verifiziert, aber die neu berechnete Stake-Adresse ≠ die angegebene.

Eine nicht unterstützte Signatur macht den Nachweis niemals ungültig

Ein unbekannter oder nicht unterstützter Signaturalgorithmus ergibt ein typisiertes signature-unsupported-Ergebnis mit der Schwere „Info". Der Inhalts- und Zeitstempelanspruch, also die on-chain liegende hashes-Bindung, ist unabhängig davon strukturell gültig, welche Signaturalgorithmen ein Verifizierer implementiert. Ein Datensatz, der ausschließlich Signaturen künftiger Algorithmen trägt, wird dennoch als gültiger Existenznachweis ausgewiesen, wobei jeder solche Eintrag als nicht unterstützt gekennzeichnet ist. Signaturen kommen hinzu; die Existenz hängt nicht von ihnen ab.

Verwandte Seiten

  • Schlüssel: der Ed25519-Signierschlüssel, seine Ableitung und der 32 Byte lange öffentliche Schlüssel im kid von Pfad 1.
  • Der Datensatz: das sigs-Feld auf oberster Ebene, die geschlossene sig-entry-Map (cose_sign1 / cose_key jeweils ein einzelner Byte-String) und der Transport über den gesamten Körper.
  • Verifizierung: die Ergebniscodes pro Eintrag, die Regeln für das Ergebnis auf Datensatzebene und die vollständige Validierungspipeline.