Public-Key-Kryptografie

Public-Key- (asymmetrische) Kryptografie verwendet ein Schlüsselpaar statt eines gemeinsamen Geheimnisses: Was der private Schlüssel signiert, verifiziert der passende öffentliche Schlüssel; was mit einem öffentlichen Schlüssel versiegelt wird, öffnet nur der passende private Schlüssel. Der öffentliche Schlüssel kann an jeden weitergegeben werden, auch an einen Angreifer, ohne dass dadurch etwas geschwächt wird.

 

RSA

 

Die Schlüsselerzeugung sucht nach großen Primzahlen und ist tatsächlich langsam, mehrere Sekunden für einen 2048-Bit-Schlüssel; führen Sie sie niemals in einem UI-Thread aus. Verwenden Sie OAEP für die Verschlüsselung und PSS für Signaturen, beide enthalten ein randomisiertes Element, das PKCS#1 v1.5 fehlt. PKCS#1-v1.5-Sign/Verify ist in sgcCrypto_RSA bereitgestellt, zur Interoperabilität mit Formaten, die es vorschreiben (JWS RS256 ist der übliche Fall); bevorzugen Sie PSS für alles Neue.

 


function sgcRSA_GenerateKey(aBits: Integer): TsgcRSAPrivateKey;
function sgcRSA_PublicKey(const aPrivateKey: TsgcRSAPrivateKey): TsgcRSAPublicKey;
function sgcRSA_OAEP_Encrypt(const aKey: TsgcRSAPublicKey; const aPlain, aLabel: TBytes; aHash: TsgcRSAHash): TBytes;
function sgcRSA_OAEP_Decrypt(const aKey: TsgcRSAPrivateKey; const aCipher, aLabel: TBytes; aHash: TsgcRSAHash; out aPlain: TBytes): Boolean;
function sgcRSA_PSS_Sign(const aKey: TsgcRSAPrivateKey; const aData: TBytes; aHash: TsgcRSAHash; aSaltLen: Integer = -1): TBytes;
function sgcRSA_PSS_Verify(const aKey: TsgcRSAPublicKey; const aData, aSignature: TBytes; aHash: TsgcRSAHash; aSaltLen: Integer = -1): Boolean;

var
  oKey: TsgcRSAPrivateKey;
  oSignature: TBytes;
begin
  oKey := sgcRSA_GenerateKey(2048);
  oSignature := sgcRSA_PSS_Sign(oKey, oData, rhSHA256);
  if sgcRSA_PSS_Verify(sgcRSA_PublicKey(oKey), oData, oSignature, rhSHA256) then
    { valid };
end;

 

PKCS#1-v1.5-Verschlüsselung ist ebenfalls vorhanden, der Modus, den Java RSA/NONE/PKCS1Padding nennt. Sie ist hier ausschließlich zur Interoperabilität mit Formaten da, die sie vorschreiben, und für nichts sonst: Das v1.5-Padding ist genau das, worauf der Bleichenbacher-Angriff abzielt, daher bleibt OAEP für alles Neue die richtige Wahl. Der Klartext ist auf die Modulusgröße minus 11 Byte begrenzt, 245 Byte bei einem 2048-Bit-Schlüssel.

 


function sgcRSA_PKCS1_Encrypt(const aKey: TsgcRSAPublicKey; const aPlain: TBytes): TBytes;
function sgcRSA_PKCS1_Decrypt(const aKey: TsgcRSAPrivateKey; const aCipher: TBytes; out aPlain: TBytes): Boolean;

 

sgcRSA_PKCS1_Decrypt gibt für jede Art von Dekodierungsfehler False zurück und nimmt für jeden von ihnen denselben Pfad, weil genau die Unterscheidung zwischen ihnen das ist, was ein Padding-Oracle benötigt.

 

OAEP mit getrenntem MGF1-Hash

 

OAEP verwendet einen Hash an zwei Stellen, über dem Label und innerhalb der MGF1-Maskenerzeugungsfunktion, und beide müssen nicht identisch sein. Genau hier beißt Java zu. SunJCE liest OAEPWithSHA256AndMGF1Padding als SHA-256 für den Digest, während MGF1 weiterhin auf SHA-1 bleibt, wohingegen Bouncy Castle dieselbe Zeichenkette als SHA-256 für beide liest, sodass ein von der einen Seite erzeugter Chiffretext von der anderen nicht entschlüsselt werden kann. Die fünf-Parameter-Überladungen legen die beiden Hashes unabhängig voneinander fest, was für die Kommunikation mit einem Java-Peer erforderlich sein kann. Die vier-Parameter-Form ist unverändert und übergibt aHash für beide.

 


function sgcRSA_OAEP_Encrypt(const aKey: TsgcRSAPublicKey; const aPlain, aLabel: TBytes; aHash, aMGFHash: TsgcRSAHash): TBytes; overload;
function sgcRSA_OAEP_Decrypt(const aKey: TsgcRSAPrivateKey; const aCipher, aLabel: TBytes; aHash, aMGFHash: TsgcRSAHash; out aPlain: TBytes): Boolean; overload;

var
  oKey: TsgcRSAPrivateKey;
  oCipher: TBytes;
begin
  { SunJCE OAEPWithSHA256AndMGF1Padding: SHA-256 label hash, MGF1 on SHA-1. }
  oCipher := sgcRSA_OAEP_Encrypt(sgcRSA_PublicKey(oKey), oPlain, nil, rhSHA256, rhSHA1);
end;

 

Ein Hash-Fehlabgleich ist nicht von einem beschädigten Chiffretext zu unterscheiden, daher liefert ein falscher aMGFHash lediglich ein einfaches False zurück, ohne Hinweis darauf, welcher der beiden falsch ist. Beide Seiten müssen sich vorab darauf einigen.

 

Schlüssel exportieren nach PKCS#1 und SubjectPublicKeyInfo DER, sowie nach PEM:

 


function sgcRSA_ExportPrivateKeyPEM(const aKey: TsgcRSAPrivateKey): string;
function sgcRSA_ExportPublicKeyPEM(const aKey: TsgcRSAPublicKey): string;

 

PKCS#8 ist der andere Private-Key-Container, derjenige, der als BEGIN PRIVATE KEY geschrieben wird, und es ist das, was Java und .NET erwarten; das obige Funktionspaar gibt stattdessen das ältere PKCS#1 BEGIN RSA PRIVATE KEY aus. Keiner der beiden Container ist verschlüsselt, daher ist das Ergebnis blankes Schlüsselmaterial, das an keiner Stelle landen darf, die ein Log oder ein Backup erreichen kann.

 


function sgcRSA_ExportPrivateKeyPKCS8DER(const aKey: TsgcRSAPrivateKey): TBytes;
function sgcRSA_ExportPrivateKeyPKCS8PEM(const aKey: TsgcRSAPrivateKey): string;

 

Einen RSA-Schlüssel importieren

 

Bevor es diese gab, konnte ein TsgcRSAPrivateKey nur aus sgcRSA_GenerateKey stammen, was bedeutete, dass ein auf der Festplatte gespeicherter Schlüssel überhaupt nicht verwendet werden konnte. Die Importroutinen lesen beide Private-Key-Container, PKCS#1 RSAPrivateKey und PKCS#8 PrivateKeyInfo, sowie beide Public-Key-Kodierungen, PKCS#1 RSAPublicKey und X.509 SubjectPublicKeyInfo, als DER oder als PEM. Ein verschlüsseltes PKCS#8, die Form BEGIN ENCRYPTED PRIVATE KEY, wird nicht unterstützt und liefert False zurück. Die CRT-Parameter werden neu berechnet, wenn die Datei sie auslässt, sodass ein importierter Schlüssel weiterhin mit voller Geschwindigkeit läuft.

 


function sgcRSA_ImportPrivateKeyDER(const aDER: TBytes; out aKey: TsgcRSAPrivateKey): Boolean;
function sgcRSA_ImportPrivateKeyPEM(const aPEM: string; out aKey: TsgcRSAPrivateKey): Boolean;
function sgcRSA_ImportPublicKeyDER(const aDER: TBytes; out aKey: TsgcRSAPublicKey): Boolean;
function sgcRSA_ImportPublicKeyPEM(const aPEM: string; out aKey: TsgcRSAPublicKey): Boolean;

var
  oKey: TsgcRSAPrivateKey;
  oSignature: TBytes;
begin
  if sgcRSA_ImportPrivateKeyPEM(Memo1.Text, oKey) then
    oSignature := sgcRSA_PSS_Sign(oKey, oData, rhSHA256);
end;

 

Ein Zertifikat ist kein öffentlicher Schlüssel, übergeben Sie sgcRSA_ImportPublicKeyDER daher das aus dem Zertifikat extrahierte SubjectPublicKeyInfo, nicht das Zertifikat selbst. sgcRSA_CheckPrivateKey verifiziert die interne Konsistenz von allem, was von anderswo stammt: dass n = p*q ist, dass e*d kongruent zu 1 ist, und dass die CRT-Parameter übereinstimmen.

 

Ed25519 und Ed448 (Signaturen)

 

EdDSA-Nonces werden deterministisch aus dem privaten Schlüssel und der Nachricht abgeleitet, sodass beim Signieren kein Zufallszahlengenerator im Spiel ist und keine Möglichkeit besteht, dass eine wiederholte Nonce den Schlüssel preisgibt, im Gegensatz zu ECDSA (siehe unten). sgcCrypto_Ed25519 ist die Verify/Sign-Primitive, die vom WebAuthn-EdDSA-Credential-Typ an anderer Stelle in sgcWebSockets verwendet wird; sie erwartet, dass der Aufrufer den 32-Byte-öffentlichen-Schlüssel bereits kennt (WebAuthn übergibt ihn Ihnen aus dem Credential) und stellt keinen separaten Aufruf zur Schlüsselerzeugung oder zur Ableitung des öffentlichen Schlüssels bereit. sgcCrypto_Ed448 ist der universelle Signierer, mit etwa 224 Bit Sicherheit gegenüber 128 bei Ed25519, auf Kosten eines größeren Schlüssels und einer größeren Signatur sowie langsameren Signierens.

 


{ Ed25519: caller supplies the 32-byte seed and, to verify, the 32-byte public key. }
function sgcEd25519_Sign(const aPrivateSeed, aMessage: TBytes): TBytes;
function sgcEd25519_Verify(const aPublicKey, aMessage, aSignature: TBytes): Boolean;

{ Ed448: 57-byte keys, 114-byte signatures, optional context string. }
procedure sgcEd448_GenerateKeyPair(out aPrivateKey, aPublicKey: TBytes);
function sgcEd448_Sign(const aPrivateKey, aMessage, aContext: TBytes): TBytes;
function sgcEd448_Verify(const aPublicKey, aMessage, aSignature, aContext: TBytes): Boolean;

 

X25519 und X448 (Key Agreement)

 

Diffie-Hellman auf Montgomery-Kurven: Beide Seiten erzeugen ein Schlüsselpaar, tauschen öffentliche Schlüssel aus, und jede berechnet dasselbe gemeinsame Geheimnis aus ihrem eigenen privaten Schlüssel und dem öffentlichen Schlüssel der Gegenseite. Führen Sie die rohe Ausgabe immer durch eine KDF (siehe Passwort-Hashing & Schlüsselableitung, HKDF), bevor Sie sie zum Verschlüsseln von irgendetwas verwenden; die rohe ECDH-Ausgabe ist selbst kein geeigneter Schlüssel. X448 bietet etwa 224 Bit Sicherheit gegenüber 128 bei X25519, auf Kosten eines größeren Schlüssels und etwa der dreifachen Rechenzeit.

 


function sgcX25519_PublicKey(const aPrivateKey: TBytes): TBytes;
function sgcX25519_SharedSecret(const aPrivateKey, aPeerPublicKey: TBytes; out aSecret: TBytes): Boolean;
function sgcX448_PublicKey(const aPrivateKey: TBytes): TBytes;
function sgcX448_SharedSecret(const aPrivateKey, aPeerPublicKey: TBytes; out aSecret: TBytes): Boolean;

var
  oAlicePriv, oBobPriv, oAlicePub, oBobPub, oSecretA, oSecretB: TBytes;
begin
  oAlicePriv := sgcRandomBytes(32);
  oBobPriv := sgcRandomBytes(32);
  oAlicePub := sgcX25519_PublicKey(oAlicePriv);
  oBobPub := sgcX25519_PublicKey(oBobPriv);
  sgcX25519_SharedSecret(oAlicePriv, oBobPub, oSecretA);
  sgcX25519_SharedSecret(oBobPriv, oAlicePub, oSecretB);
  { oSecretA = oSecretB }
end;

 

SharedSecret gibt False für einen Low-Order-Peer-Schlüssel zurück, die kleine Menge öffentlicher Schlüssel, die das gemeinsame Geheimnis unabhängig vom eigenen privaten Schlüssel auf lauter Nullen zwingt. Prüfen Sie immer das Ergebnis, statt der Ausgabe bedingungslos zu vertrauen.

 

ECIES (Sealed-Box-Verschlüsselung)

 

Kombiniert X25519, HKDF-SHA256 und XChaCha20-Poly1305 zu einem einzigen Aufruf, der eine Nachricht an den öffentlichen Schlüssel eines Empfängers verschlüsselt, ohne dass vorab ein gemeinsames Geheimnis nötig ist. aContext bindet die versiegelte Nachricht an einen Zweck oder Protokollschritt, das Öffnen unter einem anderen Kontext schlägt fehl, was verhindert, dass eine versiegelte Nachricht aus einem Austausch in einen anderen hineinrepliziert wird.

 


procedure sgcECIES_GenerateKeyPair(out aPrivateKey, aPublicKey: TBytes);
function sgcECIES_Seal(const aRecipientPublicKey, aPlain, aAAD, aContext: TBytes): TBytes;
function sgcECIES_Open(const aPrivateKey, aSealed, aAAD, aContext: TBytes; out aPlain: TBytes): Boolean;

 

ECDSA / ECDH auf benannten Kurven

 

Zwei separate Units decken die kurzen Weierstraß-Kurven ab, aufgeteilt danach, welche Kurvenfamilie sie implementieren:

 

sgcCrypto_EC deckt die NIST-Kurven (P-256, P-384, P-521) ab und ist auf PEM-Schlüssel und JWS-Signaturen ausgerichtet, die Form, die JWT-Bibliotheken erwarten (ES256 und ähnliche).

 

sgcCrypto_ECCurves deckt secp256k1 (die Bitcoin- und Ethereum-Kurve) sowie die brainpoolP256r1- / P384r1- / P512r1-Kurven aus RFC 5639 ab und arbeitet direkt mit rohen Schlüssel- und Signatur-Bytes statt mit PEM.

 

TsgcECCurve in sgcCrypto_ECCurves benennt jetzt sieben statt vier Kurven: eccSecp256k1, die drei Brainpool-Kurven, sowie eccP256, eccP384 und eccP521 für NIST P-256, P-384 und P-521. Die drei NIST-Kurven waren bisher nur über die PEM- und JWS-API von sgcCrypto_EC erreichbar, sodass eine Anwendung, die einen P-256-Schlüssel als rohe Bytes hielt, zunächst PEM schreiben musste. Die untenstehenden Aufrufe zur rohen Schlüsselerzeugung, zum Signieren, Verifizieren, für ECDH und zur Kompression decken jetzt alle sieben ab. Greifen Sie zu sgcCrypto_EC, wenn die Aufgabe JWS ist, und zu sgcCrypto_ECCurves, wenn das Schlüsselmaterial bereits als Bytes vorliegt.

 

Beide Units deklarieren einen Typ namens TsgcECCurve und eine Routine namens sgcEC_GenerateKeyPair, mit unterschiedlicher Bedeutung in jeder Unit. Fügen Sie nicht beide Units in dieselbe uses-Klausel ein und referenzieren Sie einen der beiden Namen unqualifiziert; qualifizieren Sie ihn mit dem Unit-Namen (sgcCrypto_ECCurves.sgcEC_GenerateKeyPair), wenn eine einzelne Unit tatsächlich beide benötigt.

 


{ sgcCrypto_ECCurves: secp256k1 and Brainpool, RFC 6979 deterministic nonces,
  low-S normalized signatures (BIP 62). }
procedure sgcEC_GenerateKeyPair(aCurve: TsgcECCurve; out aPrivateKey, aPublicKey: TBytes);
function sgcECDSA_SignHash(aCurve: TsgcECCurve; const aPrivateKey, aHash: TBytes; aHashKind: TsgcECHash = echSHA256): TBytes;
function sgcECDSA_VerifyHash(aCurve: TsgcECCurve; const aPublicKey, aHash, aSignature: TBytes): Boolean;
function sgcECDH_SharedSecret(aCurve: TsgcECCurve; const aPrivateKey, aPeerPublicKey: TBytes; out aSecret: TBytes): Boolean;
function sgcEC_ValidatePublicKey(aCurve: TsgcECCurve; const aPublicKey: TBytes): Boolean;
function sgcEC_Compress(aCurve: TsgcECCurve; const aPublicKey: TBytes): TBytes;
function sgcEC_Decompress(aCurve: TsgcECCurve; const aCompressed: TBytes): TBytes;

 

ECDSA-Signaturen verwenden hier deterministische Nonces gemäß RFC 6979: Die Nonce wird aus dem privaten Schlüssel und der Nachricht abgeleitet, statt einem Zufallszahlengenerator entnommen zu werden, was den einzigen häufigsten Weg beseitigt, auf dem reale Deployments einen privaten ECDSA-Schlüssel verloren haben, eine wiederholte oder teilweise vorhersagbare Nonce. Signaturen tragen die Low-S-Normalisierung, die Bitcoin (BIP 62) und Ethereum verlangen; die Verifikation akzeptiert beide Formen.

 

DER-kodierte ECDSA-Signaturen

 

Eine Signatur, zwei Kodierungen, und die falsche zu wählen gehört zu den häufigsten Integrationsfehlern in diesem Bereich. Das rohe Paar R || S, jede Hälfte in der Feldgröße der Kurve, ist das, was JOSE und WebAuthn tragen. Eine DER-SEQUENCE { r, s } ist das, was X.509, CMS und TLS tragen. sgcECDSA_SignHash oben erzeugt die rohe Form, die untenstehenden Aufrufe erzeugen und verarbeiten die DER-Form, und die beiden Konverter übersetzen zwischen ihnen, ohne irgendetwas neu zu signieren.

 


{ sgcCrypto_ECCurves: raw keys, a digest the caller has already computed. }
function sgcECDSA_SignDER(aCurve: TsgcECCurve; const aPrivateKey, aHash: TBytes; aHashKind: TsgcECHash): TBytes; overload;
function sgcECDSA_VerifyDER(aCurve: TsgcECCurve; const aPublicKey, aHash, aDERSignature: TBytes): Boolean; overload;
function sgcECDSA_RawToDER(aCurve: TsgcECCurve; const aRaw: TBytes): TBytes;
function sgcECDSA_DERToRaw(aCurve: TsgcECCurve; const aDER: TBytes; out aRaw: TBytes): Boolean;

{ sgcCrypto_EC: PEM keys, hashes aData itself with the curve SHA. }
function sgcECDSA_SignDER(const aPrivateKeyPEM: string; const aData: TBytes; aCurveBits: Integer): TBytes; overload;
function sgcECDSA_VerifyDER(const aPublicPoint, aData, aDERSignature: TBytes; aCurveBits: Integer): Boolean; overload;

var
  oRaw, oDER: TBytes;
begin
  oRaw := sgcECDSA_SignHash(eccP256, oPrivateKey, oDigest, echSHA256);
  oDER := sgcECDSA_RawToDER(eccP256, oRaw);
end;

 

aHashKind in der rohen Schlüsselform wählt nur den HMAC hinter der RFC-6979-Nonce, es hasht für Sie nichts, übergeben Sie daher sgcEC_DefaultHash(aCurve), wenn es keinen Grund gibt, anders zu wählen. sgcECDSA_RawToDER gibt ein leeres Array zurück, wenn das Paar nicht exakt aus zwei Hälften in Feldgröße besteht, und sgcECDSA_DERToRaw gibt False bei fehlerhaftem DER oder bei einem Wert zurück, der nicht zur Kurve passt, sodass beide sicher auf nicht vertrauenswürdige Eingaben angewendet werden können.

 

EC-Schlüssel exportieren und importieren

 

Drei Container, und das PEM-Label ist es, was sie unterscheidet: Ein SEC1 ECPrivateKey ist BEGIN EC PRIVATE KEY, ein PKCS#8 PrivateKeyInfo ist BEGIN PRIVATE KEY, und ein X.509 SubjectPublicKeyInfo ist BEGIN PUBLIC KEY. Java und .NET wollen PKCS#8, OpenSSL und die meisten Kommandozeilenwerkzeuge lesen beide. Keine der beiden privaten Formen ist verschlüsselt, daher ist das Ergebnis genauso geheim wie der Skalar selbst.

 


function sgcEC_ExportPrivateKeySEC1DER(aCurve: TsgcECCurve; const aPrivateKey, aPublicKey: TBytes): TBytes;
function sgcEC_ExportPrivateKeyPKCS8DER(aCurve: TsgcECCurve; const aPrivateKey, aPublicKey: TBytes): TBytes;
function sgcEC_ExportSubjectPublicKeyInfo(aCurve: TsgcECCurve; const aPublicKey: TBytes): TBytes;
function sgcEC_ExportPrivateKeySEC1PEM(aCurve: TsgcECCurve; const aPrivateKey, aPublicKey: TBytes): string;
function sgcEC_ExportPrivateKeyPKCS8PEM(aCurve: TsgcECCurve; const aPrivateKey, aPublicKey: TBytes): string;
function sgcEC_ExportPublicKeyPEM(aCurve: TsgcECCurve; const aPublicKey: TBytes): string;

function sgcEC_ImportPrivateKeyDER(const aDER: TBytes; out aCurve: TsgcECCurve; out aPrivateKey, aPublicKey: TBytes): Boolean;
function sgcEC_ImportPrivateKeyPEM(const aPEM: string; out aCurve: TsgcECCurve; out aPrivateKey, aPublicKey: TBytes): Boolean;
function sgcEC_ImportPublicKeyDER(const aDER: TBytes; out aCurve: TsgcECCurve; out aPublicKey: TBytes): Boolean;
function sgcEC_ImportPublicKeyPEM(const aPEM: string; out aCurve: TsgcECCurve; out aPublicKey: TBytes): Boolean;

var
  oCurve: TsgcECCurve;
  oPrivateKey, oPublicKey: TBytes;
begin
  if sgcEC_ImportPrivateKeyPEM(Memo1.Text, oCurve, oPrivateKey, oPublicKey) then
    Memo2.Text := sgcEC_ExportPrivateKeyPKCS8PEM(oCurve, oPrivateKey, oPublicKey);
end;

 

Die Importroutinen lesen die Kurve aus der Datei, weshalb aCurve als out-Parameter zurückkommt, statt übergeben zu werden. Eine Kurve, die die Unit nicht implementiert, ist ein Verhandlungsfehler und kein Bug, daher geben sie False zurück und lösen niemals eine Exception aus, was sie sicher für nicht vertrauenswürdige Eingaben macht. Der öffentliche Punkt wird aus dem Skalar neu berechnet, wenn die Datei ihn auslässt, und gegen die Kurve validiert, bevor er zurückgegeben wird. Die Exportroutinen lösen stattdessen eine Exception aus, weil Schlüsselmaterial, das nicht zur Kurve passt, ein Fehler des Aufrufers und keine Eingabe ist.

 

Die gepunktete benannte-Kurven-OID, die in jeden dieser Container eingeht, ist direkt verfügbar, und ist das, was die Export- und Importroutinen intern verwenden:

 


function sgcEC_CurveOID(aCurve: TsgcECCurve): string;
function sgcEC_CurveFromOID(const aDotted: string; out aCurve: TsgcECCurve): Boolean;

 

Validieren Sie immer den öffentlichen Schlüssel einer Gegenseite

 

Bevor Sie einen öffentlichen Schlüssel verwenden, den Sie nicht selbst erzeugt haben, sei es für ECDH, ECDSA-Verifikation oder ECIES, validieren Sie mit sgcEC_ValidatePublicKey, dass er tatsächlich ein Punkt auf der Kurve ist. Ein Punkt, der nicht auf der Kurve liegt, kann über die zu seiner Verarbeitung verwendete Arithmetik Bits Ihres eigenen privaten Schlüssels preisgeben.

 

Schnorr-Signaturen (BIP-340)

 

Nur secp256k1, und das ist es, was Taproot, Nostr und Lightning verwenden. Es ist kein direkter Ersatz für das obige ECDSA, weil sich zwei Dinge unterscheiden. Öffentliche Schlüssel sind x-only, 32 Byte, wobei die y-Koordinate implizit als gerade angenommen wird, sodass ein BIP-340-Schlüssel nicht mit den 33-Byte-komprimierten oder den 65-Byte-unkomprimierten Formen austauschbar ist, die der Rest dieser Seite erzeugt. Und die Challenge hasht den öffentlichen Schlüssel zusammen mit der Nonce und der Nachricht, was die Signatur auf eine Weise an den Schlüssel bindet, wie es eine ECDSA-Signatur nicht tut.

 


function sgcSchnorr_PublicKey(const aPrivateKey: TBytes): TBytes;
function sgcSchnorr_Sign(const aPrivateKey, aMessage, aAuxRand: TBytes): TBytes;
function sgcSchnorr_Verify(const aPublicKey, aMessage, aSignature: TBytes): Boolean;
function sgcSchnorr_TaggedHash(const aTag: string; const aData: TBytes): TBytes;

var
  oPrivateKey, oPublicKey, oSignature: TBytes;
begin
  oPrivateKey := sgcRandomBytes(32);
  oPublicKey := sgcSchnorr_PublicKey(oPrivateKey);   { 32 bytes, x-only }
  oSignature := sgcSchnorr_Sign(oPrivateKey, oMessage, sgcRandomBytes(32));
  if sgcSchnorr_Verify(oPublicKey, oMessage, oSignature) then
    { valid };
end;

 

aAuxRand sind 32 Byte zusätzlicher Zufälligkeit, die in die Nonce-Ableitung eingemischt werden. Es handelt sich um Defense in Depth gegen Fault-Angriffe, nicht um die Nonce selbst: Das Verfahren bleibt sicher, auch wenn es lauter Nullen sind, und ein leeres Array wird als 32 Nullbytes behandelt, damit sich die BIP-340-Testvektoren exakt reproduzieren lassen. Beim tatsächlichen Einsatz sollte frische Zufälligkeit übergeben werden. sgcSchnorr_TaggedHash ist freigelegt, weil BIP-341 und BIP-342 weitere Tags auf derselben Konstruktion aufbauen.