std.crypto.ec — Elliptische Kurven
P-256 und secp256k1 in Lyx gerechnet: konstantzeit Montgomery-Leiter, vollständige RCB-Additionsformeln, deterministisches ECDSA nach RFC 6979, ECDH, SEC1- und DER-Kodierung.
→ Mathematik-Units · Kryptografie · std.bignum
import std.crypto.ec;
Schicht 0. Hängt an std.math, std.alloc und vier Krypto-Units des Bestands: std.crypto.sha256, std.crypto.hmac, std.crypto.rand, std.crypto.ct. 17 Funktionen. Alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt.
Verhältnis zu ''std.crypto.ecc''
<WRAP info>
std.crypto.ec tritt neben das vorhandene std.crypto.ecc, ohne es anzutasten. Über eine Ablösung ist damit nichts entschieden. Der Unterschied: ec rechnet vollständig in Lyx, arbeitet in konstanter Zeit und bringt RFC-6979-Determinismus mit.
</WRAP>
Darstellung
Alle Skalare und Koordinaten sind 32-Byte-Puffer (alloc(32)), big-endian. Kurven wählt man über eine Konstante:
| Konstante | Kurve |
|---|---|
EC_P256 | NIST P-256 (secp256r1) |
EC_SECP256K1 | secp256k1 (Bitcoin, Ethereum) |
EcIsValidCurve, EcCurveOrder(curve, out32) und EcCurvePrime(curve, out32) geben Auskunft über die gewählte Kurve.
Intern liegen die Zahlen in 10 Gliedern zu 28 Bit — eine Darstellung, die Überträge sammelt und erst spät normalisiert. Das ist der übliche Weg, um Multiplikationen ohne Verzweigung über die Zahlengröße zu rechnen.
Schlüssel, Signatur, Prüfung
unit main;
import std.io;
import std.string;
import std.alloc;
import std.crypto.ec;
fn ZI(t: pchar, v: int64): void { Print(t); PrintLn(IntToStr(v)); }
fn main(): int64 {
var curve: int64 := EC_P256;
ZI("IsValidCurve(P256) : ", EcIsValidCurve(curve) as int64);
var priv: int64 := alloc(32);
var px: int64 := alloc(32);
var py: int64 := alloc(32);
ZI("GenerateKey : ", EcGenerateKey(curve, priv, px, py) as int64);
ZI("IsOnCurve(pub) : ", EcIsOnCurve(curve, px, py) as int64);
ZI("ValidatePublicKey : ", EcValidatePublicKey(curve, px, py) as int64);
var h: int64 := alloc(32);
var i: int64 := 0;
while (i < 32) { poke8(h + i, (i * 7 + 3) % 256); i := i + 1; }
var r: int64 := alloc(32);
var s: int64 := alloc(32);
ZI("EcdsaSign : ", EcdsaSign(curve, h, priv, r, s) as int64);
ZI("EcdsaVerify (echt) : ", EcdsaVerify(curve, h, px, py, r, s) as int64);
// ein einziges Byte des Hashes kippen
poke8(h, peek8(h) + 1);
ZI("EcdsaVerify (falsch) : ", EcdsaVerify(curve, h, px, py, r, s) as int64);
poke8(h, peek8(h) - 1);
// RFC 6979: dasselbe zweimal signiert ergibt dieselbe Signatur
var r2: int64 := alloc(32);
var s2: int64 := alloc(32);
EcdsaSign(curve, h, priv, r2, s2);
var gleich: int64 := 1;
i := 0;
while (i < 32) {
if (peek8(r + i) != peek8(r2 + i)) { gleich := 0; }
if (peek8(s + i) != peek8(s2 + i)) { gleich := 0; }
i := i + 1;
}
ZI("Signatur determinist.: ", gleich);
var der: int64 := alloc(80);
ZI("DER-Laenge : ", EcdsaSigToDer(r, s, der));
var enc: int64 := alloc(80);
ZI("SEC1 komprimiert : ", EcPointEncode(curve, px, py, true, enc));
ZI("SEC1 unkomprimiert : ", EcPointEncode(curve, px, py, false, enc));
return 0;
}
IsValidCurve(P256) : 1
GenerateKey : 1
IsOnCurve(pub) : 1
ValidatePublicKey : 1
EcdsaSign : 1
EcdsaVerify (echt) : 1
EcdsaVerify (falsch) : 0
Signatur determinist.: 1
DER-Laenge : 71
SEC1 komprimiert : 33
SEC1 unkomprimiert : 65
Vier Aussagen stecken darin, und die dritte ist die wichtigste:
- Schlüsselerzeugung liefert einen Punkt, der auf der Kurve liegt und die Validierung besteht.
- Signieren und Prüfen passen zusammen.
- Ein einziges gekipptes Byte im Hash lässt die Prüfung fehlschlagen. Ohne diese Gegenprobe belegt ein erfolgreiches
Verifynichts — eine Funktion, die immertruezurückgibt, bestünde den ersten Test genauso. - Zweimal signiert ergibt dieselbe Signatur. Das ist RFC 6979 und kein Zufall.
Die Längen stimmen mit den Standards überein: SEC1 komprimiert 33 Byte (Präfix + x), unkomprimiert 65 (Präfix + x + y), DER hier 71.
Warum deterministisches ECDSA
<WRAP alert>
Ein wiederverwendeter Zufallswert k gibt den privaten Schlüssel preis. Wer zwei verschiedene Nachrichten mit demselben k signiert, ermöglicht es, den Schlüssel aus den beiden Signaturen direkt auszurechnen — das ist kein theoretischer Angriff, sondern hat mehrfach reale Systeme gekostet.
RFC 6979 leitet k deterministisch aus Nachricht und privatem Schlüssel über HMAC ab. Damit hängt die Sicherheit nicht mehr an der Qualität des Zufallszahlengenerators zum Signaturzeitpunkt. EcRfc6979Nonce ist einzeln aufrufbar; EcdsaSign nutzt es von sich aus.
</WRAP>
Die Signatur ist dadurch reproduzierbar — dieselbe Nachricht und derselbe Schlüssel ergeben immer dieselben Bytes. Das ist beim Testen ein Vorteil und in Protokollen, die Signaturen vergleichen, eine Eigenschaft, auf die man sich verlassen kann.
Konstante Laufzeit
Die Skalarmultiplikation läuft als Montgomery-Leiter: in jedem Schritt werden dieselben Operationen ausgeführt, unabhängig davon, ob das Schlüsselbit 0 oder 1 ist. Die Punktaddition nutzt die vollständigen RCB-Formeln (Renes–Costello–Batina), die ohne Sonderfall für „gleicher Punkt„ oder „Punkt im Unendlichen“ auskommen.
Beides zielt auf dasselbe: aus Laufzeit oder Stromverbrauch soll sich nichts über den Schlüssel ableiten lassen. Verzweigungen und Tabellenzugriffe, die vom Schlüssel abhängen, sind genau die Stellen, an denen Seitenkanalangriffe ansetzen.
Konstante Laufzeit im Code ist nicht konstante Laufzeit auf der Maschine. Compileroptimierungen, Cache-Verhalten und Sprungvorhersage können Unterschiede wieder einführen. Die Unit tut, was auf Quellebene möglich ist; ein Nachweis auf einer bestimmten Zielplattform ist damit nicht erbracht.
Funktionsübersicht
| Gruppe | Funktionen |
|---|---|
| Kurve | EcIsValidCurve · EcCurveOrder · EcCurvePrime |
| Punkte | EcIsOnCurve · EcValidatePublicKey · EcScalarMul |
| Schlüssel | EcGenerateKey · EcPublicFromPrivate |
| Kodierung | EcPointEncode · EcPointDecode (SEC1, komprimiert und offen) |
| ECDSA | EcRfc6979Nonce · EcdsaSign · EcdsaVerify |
| DER | EcdsaSigToDer · EcdsaSigFromDer |
| ECDH | EcdhSharedSecret · EcdhSharedSecretHashed |
EcValidatePublicKeygehört vor jede Verwendung eines fremden Punktes. Ein Gegenüber, das einen Punkt außerhalb der Kurve oder in einer kleinen Untergruppe schickt, kann aus den Antworten Rückschlüsse auf den eigenen privaten Schlüssel ziehen.EcIsOnCurveprüft nur die Kurvengleichung,EcValidatePublicKeyzusätzlich die Ordnung.
Für ECDH istEcdhSharedSecretHasheddie richtige Wahl. Die rohe x-Koordinate ausEcdhSharedSecretist kein gleichverteiltes Schlüsselmaterial und gehört nicht direkt in einen Chiffrierschlüssel.
Letzte Aktualisierung: 2026-08-16 · alle Beispiele mit lyxc 1.0.21A übersetzt und ausgeführt; Signaturprüfung mit Negativkontrolle (gekipptes Hash-Byte) und RFC-6979-Determinismus einzeln nachgemessen, Kodierungslängen gegen die Standards abgeglichen.
