std.crypto.ecc — secp256k1 ECDSA

import std.crypto.ecc;

ECDSA (Elliptic Curve Digital Signature Algorithm) auf der Kurve secp256k1 — dieselbe Kurve wie Bitcoin/Ethereum. Alle 256-Bit-Werte als 32-Byte Little-Endian-Puffer (Byte 0 = LSB). Jacobian-Koordinaten intern, Ausgabe in affiner Form.

Abhängigkeit: std.crypto.rand (für Schlüsselgenerierung und Signing).

std.crypto Paket · std.crypto.rand · std.crypto.sha256


Datenformat

Typ Größe Format
Privater Schlüssel 32 Bytes Little-Endian, Byte 0 = LSB
Öffentlicher Schlüssel X 32 Bytes Little-Endian
Öffentlicher Schlüssel Y 32 Bytes Little-Endian
Signatur r 32 Bytes Little-Endian
Signatur s 32 Bytes Little-Endian
Hash-Eingabe 32 Bytes Beliebig (typisch SHA-256-Digest)
Little-Endian-Besonderheit: Die Standard-Darstellung für secp256k1 in anderen Bibliotheken (OpenSSL, libsecp256k1) ist Big-Endian. Beim Austausch mit externen Systemen muss byte-reversed werden.

Funktionen

Funktion Signatur Beschreibung
ECCGenKey (privk: int64, pubx: int64, puby: int64): void Generiert zufälliges Schlüsselpaar; schreibt privaten Schlüssel und öffentlichen Schlüssel (X/Y)
ECCPubFromPriv (privk: int64, pubx: int64, puby: int64): void Berechnet öffentlichen Schlüssel aus privatem; pubkey = privk × G
ECDSASign (hash: int64, privk: int64, r_out: int64, s_out: int64): int64 Signiert 32-Byte-Hash mit privatem Schlüssel; gibt immer 1 zurück
ECDSAVerify (hash: int64, pubx: int64, puby: int64, r: int64, s: int64): int64 Verifiziert Signatur; gibt 1 wenn gültig, 0 wenn ungültig

Verwendung

Schlüsselpaar generieren

import std.crypto.ecc;
import std.alloc;

fn main(): int64 {
    var privk: int64 := alloc(32);
    var pubx:  int64 := alloc(32);
    var puby:  int64 := alloc(32);

    ECCGenKey(privk, pubx, puby);
    // privk: 32-Byte privater Schlüssel (geheim halten!)
    // pubx/puby: öffentlicher Schlüssel (teilbar)

    free(privk, 32);
    free(pubx,  32);
    free(puby,  32);
    return 0;
}

Nachricht signieren und verifizieren

import std.crypto.ecc;
import std.crypto.sha256;
import std.alloc;

fn SignAndVerify(msg: pchar, msgLen: int64): int64 {
    var privk:  int64 := alloc(32);
    var pubx:   int64 := alloc(32);
    var puby:   int64 := alloc(32);
    var hash:   int64 := alloc(32);
    var sig_r:  int64 := alloc(32);
    var sig_s:  int64 := alloc(32);

    // 1. Schlüsselpaar erzeugen
    ECCGenKey(privk, pubx, puby);

    // 2. Nachricht hashen
    SHA256(msg as int64, msgLen, hash);

    // 3. Signieren
    ECDSASign(hash, privk, sig_r, sig_s);

    // 4. Verifizieren
    var ok: int64 := ECDSAVerify(hash, pubx, puby, sig_r, sig_s);

    free(privk, 32); free(pubx, 32); free(puby, 32);
    free(hash, 32);  free(sig_r, 32); free(sig_s, 32);
    return ok;  // 1 = gültig
}

Öffentlichen Schlüssel aus bekanntem privaten Schlüssel

import std.crypto.ecc;
import std.alloc;

fn RestorePubKey(privk: int64, pubx: int64, puby: int64): void {
    // Nützlich wenn nur der private Schlüssel gespeichert wurde
    ECCPubFromPriv(privk, pubx, puby);
}


Hinweise

  • Nonce-Sicherheit: ECDSASign generiert den Nonce k intern zufällig via std.crypto.rand. Ein wiederverwendeter Nonce legt den privaten Schlüssel vollständig offen — das ist durch die interne Implementierung verhindert, aber bei eigenem ECDSA-Code kritisch.
  • Little-Endian: Alle 32-Byte-Werte sind Little-Endian (LSB zuerst). Für Interoperabilität mit OpenSSL oder RFC-Formaten muss byte-reversed werden.
  • secp256k1 (nicht secp256r1): Dies ist die Bitcoin/Ethereum-Kurve, nicht die häufiger in TLS verwendete NIST-Kurve P-256 (secp256r1).
  • Keine ASN.1-Kodierung: ECDSASign gibt r und s als rohe 32-Byte-Werte zurück, kein DER-Format. Für TLS/X.509 muss DER-Encoding selbst implementiert werden.
  • Performance: Schulmethode (schoolbook multiplication). Für produktiven Hochdurchsatz-Einsatz sollte eine optimierte ECDSA-Bibliothek per @extern eingebunden werden.

Letzte Aktualisierung: 2026-06-05