std.pgp — OpenPGP (RFC 4880)
Das std.pgp-Paket implementiert OpenPGP-Format-Parsing und ASCII-Armor nach RFC 4880. Es ist eine reine Parser- und Codec-Bibliothek: kein Schlüssel-Erzeugen, kein Ver- oder Entschlüsseln, kein Signieren, kein Verifizieren — nur das Lesen und Kodieren von PGP-Datenstrukturen.
Typische Einsatzfälle: .asc-Dateien dekodieren, öffentliche Schlüssel aus Keyrings lesen, Key-IDs und Fingerprints extrahieren, Paketströme inspizieren.
Autor: Andreas Röne
Copyright: 2025–2026 Andreas Röne
Quelle: std/pgp/
<WRAP info>
Der frühere Pufferüberlauf in PgpArmorEncode (#1454) ist behoben: Bei zu kleinem Ausgabepuffer kommt jetzt -1 zurück, und der Nachbarspeicher bleibt unberührt (nachgeprüft mit einem Wächterpuffer). Als Faustregel für die Größe weiterhin (dataLen * 4 / 3) + 200 Bytes.
</WRAP>
Units
| Unit | Beschreibung |
|---|---|
std.pgp.core | Konstanten (Tags, Algorithmen, Signaturtypen, Armor-Typen), Paket-Struct-Offsets, PgpCrc24 |
std.pgp.armor | ASCII-Armor kodieren, dekodieren, Typ erkennen |
std.pgp.packet | Paket-Iteration über binäre PGP-Daten |
std.pgp.key | Public-Key-Paket-Analyse: Version, Algorithmus, Zeitstempel, Fingerprint, Key-ID |
Die Units bauen aufeinander auf: key und packet setzen core voraus, key zusätzlich std.crypto.sha1. In der Praxis importiert man alle vier plus std.alloc.
Funktionsreferenz
std.pgp.core
| Signatur | Beschreibung |
|---|---|
PgpCrc24(data: int64, len: int64): int64 | CRC-24 nach RFC 4880 §6.1 (Init 0xB704CE, Polynom 0x1864CFB). Ergebnis sind die unteren 24 Bit |
std.pgp.armor
| Signatur | Beschreibung |
|---|---|
PgpArmorEncode(data, dataLen, tp, out, outMax): int64 | Kodiert Rohbytes als Armor-Block. tp ist eine PGP_ARMOR_*-Konstante. Rückgabe: geschriebene Zeichen ohne NUL, oder -1 wenn outMax nicht reicht |
PgpArmorDecode(arm, armLen, out, outMax): int64 | Dekodiert einen Armor-Block und prüft die CRC-24. Rückgabe: Bytezahl, oder -1 bei CRC-Fehler, ungültigem Block oder zu kleinem out |
PgpArmorType(arm, armLen): int64 | Erkennt den Blocktyp allein an der BEGIN-Zeile, ohne zu dekodieren. Rückgabe: PGP_ARMOR_* |
std.pgp.packet
| Signatur | Beschreibung |
|---|---|
PgpPacketFirst(buf, len, pkt): int64 | Liest das erste Paket ab Offset 0 in den Struct pkt. 1 = Paket gefunden, 0 = leer oder ungültig |
PgpPacketNext(buf, len, pkt): int64 | Liest das Paket am gespeicherten Folge-Offset. 1 = weiteres Paket, 0 = Ende |
std.pgp.key
Alle Funktionen arbeiten auf dem Paket-Body — dem Bereich, den PGP_PKT_OFF_BODY / PGP_PKT_OFF_BLEN beschreiben, ohne Tag-Byte und Längenfeld.
| Signatur | Beschreibung |
|---|---|
PgpKeyVersion(body, blen): int64 | Versionsbyte (3 oder 4), 0 bei zu kurzem Body |
PgpKeyAlgo(body, blen): int64 | Public-Key-Algorithmus (PGP_ALG_*); berücksichtigt die unterschiedliche Feldlage bei v3 und v4 |
PgpKeyTimestamp(body, blen): int64 | Erzeugungszeit als Unix-Sekunden (4 Bytes big-endian ab Offset 1) |
PgpKeyFingerprint(body, blen, out): int64 | 20-Byte-SHA-1-Fingerprint nach §12.2 in out. 1 = Erfolg, 0 bei v3 oder Body > 65535 Bytes |
PgpKeyId(body, blen, out): int64 | 8-Byte-Key-ID (Bytes 12–19 des Fingerprints) in out. 1 = Erfolg |
Konstanten (std.pgp.core)
Paket-Tags (RFC 4880 §4.3)
| Konstante | Wert | Bedeutung |
|---|---|---|
PGP_TAG_PKESK | 1 | Public-Key Encrypted Session Key |
PGP_TAG_SIG | 2 | Signatur |
PGP_TAG_SKESK | 3 | Symmetric-Key Encrypted Session Key |
PGP_TAG_OPS | 4 | One-Pass Signature |
PGP_TAG_SECKEY | 5 | Geheimer Schlüssel |
PGP_TAG_PUBKEY | 6 | Öffentlicher Schlüssel |
PGP_TAG_SECSUBKEY | 7 | Geheimer Unterschlüssel |
PGP_TAG_COMPRESSED | 8 | Komprimierte Daten |
PGP_TAG_SYM_ENC | 9 | Symmetrisch verschlüsselte Daten |
PGP_TAG_MARKER | 10 | Marker-Paket |
PGP_TAG_LITERAL | 11 | Literal-Daten (die eigentlichen Nutzdaten) |
PGP_TAG_TRUST | 12 | Trust-Paket (lokal, nicht exportiert) |
PGP_TAG_UID | 13 | User-ID |
PGP_TAG_PUBSUBKEY | 14 | Öffentlicher Unterschlüssel |
PGP_TAG_UAT | 17 | User-Attribute (z. B. Foto) |
PGP_TAG_SEIPD | 18 | Symmetrisch verschlüsselt, integritätsgeschützt |
PGP_TAG_MDC | 19 | Modification Detection Code |
Public-Key-Algorithmen (RFC 4880 §9.1)
| Konstante | Wert | Algorithmus |
|---|---|---|
PGP_ALG_RSA | 1 | RSA (Encrypt + Sign) |
PGP_ALG_RSA_E | 2 | RSA (nur Encrypt) |
PGP_ALG_RSA_S | 3 | RSA (nur Sign) |
PGP_ALG_ELGAMAL | 16 | ElGamal (nur Encrypt) |
PGP_ALG_DSA | 17 | DSA (nur Sign) |
PGP_ALG_ECDH | 18 | ECDH (nur Encrypt) |
PGP_ALG_ECDSA | 19 | ECDSA (nur Sign) |
PGP_ALG_EDDSA | 22 | EdDSA (nur Sign, z. B. Ed25519) |
Hash-Algorithmen (RFC 4880 §9.4)
| Konstante | Wert | Algorithmus |
|---|---|---|
PGP_HASH_MD5 | 1 | MD5 |
PGP_HASH_SHA1 | 2 | SHA-1 |
PGP_HASH_RIPEMD160 | 3 | RIPEMD-160 |
PGP_HASH_SHA256 | 8 | SHA-256 |
PGP_HASH_SHA384 | 9 | SHA-384 |
PGP_HASH_SHA512 | 10 | SHA-512 |
PGP_HASH_SHA224 | 11 | SHA-224 |
Symmetrische Algorithmen (RFC 4880 §9.2)
| Konstante | Wert | Algorithmus |
|---|---|---|
PGP_SYM_PLAINTEXT | 0 | Klartext (unverschlüsselt) |
PGP_SYM_IDEA | 1 | IDEA |
PGP_SYM_3DES | 2 | Triple-DES |
PGP_SYM_CAST5 | 3 | CAST5 |
PGP_SYM_BLOWFISH | 4 | Blowfish |
PGP_SYM_AES128 | 7 | AES-128 |
PGP_SYM_AES192 | 8 | AES-192 |
PGP_SYM_AES256 | 9 | AES-256 |
PGP_SYM_TWOFISH | 10 | Twofish |
Signatur-Typen (RFC 4880 §5.2.1)
| Konstante | Wert | Bedeutung |
|---|---|---|
PGP_SIG_BINARY | 0x00 | Binärdokument |
PGP_SIG_TEXT | 0x01 | Textdokument (CRLF-normalisiert) |
PGP_SIG_STANDALONE | 0x02 | Eigenständige Signatur |
PGP_SIG_CERT_GENERIC | 0x10 | Generische Zertifizierung |
PGP_SIG_CERT_PERSONA | 0x11 | Zertifizierung ohne Prüfung |
PGP_SIG_CERT_CASUAL | 0x12 | Zertifizierung nach oberflächlicher Prüfung |
PGP_SIG_CERT_POSITIVE | 0x13 | Zertifizierung nach gründlicher Prüfung |
PGP_SIG_SUBKEY_BIND | 0x18 | Unterschlüssel-Bindung |
PGP_SIG_PRIMARY_BIND | 0x19 | Rückbindung an den Hauptschlüssel |
PGP_SIG_DIRECT | 0x1F | Direkte Schlüsselsignatur |
PGP_SIG_REVOKE_KEY | 0x20 | Schlüssel-Widerruf |
PGP_SIG_REVOKE_SUBKEY | 0x28 | Unterschlüssel-Widerruf |
PGP_SIG_REVOKE_CERT | 0x30 | Zertifizierungs-Widerruf |
PGP_SIG_TIMESTAMP | 0x40 | Zeitstempel |
PGP_SIG_THIRD_PARTY | 0x50 | Drittbestätigung |
Armor-Typen
| Konstante | Wert | BEGIN-Header |
|---|---|---|
PGP_ARMOR_UNKNOWN | 0 | Unbekannt / kein Armor |
PGP_ARMOR_MESSAGE | 1 | BEGIN PGP MESSAGE |
PGP_ARMOR_PUBLIC_KEY | 2 | BEGIN PGP PUBLIC KEY BLOCK |
PGP_ARMOR_PRIVATE_KEY | 3 | BEGIN PGP PRIVATE KEY BLOCK |
PGP_ARMOR_SIGNATURE | 4 | BEGIN PGP SIGNATURE |
Paket-Struct-Offsets
Der Aufrufer alloziert einen PGP_PKT_SIZE Bytes großen Puffer und liest die Felder mit peek64:
| Konstante | Offset | Inhalt |
|---|---|---|
PGP_PKT_OFF_TAG | 0 | Paket-Typ (PGP_TAG_*) |
PGP_PKT_OFF_BODY | 8 | Absoluter Zeiger auf die Body-Bytes im Quellpuffer |
PGP_PKT_OFF_BLEN | 16 | Body-Länge in Bytes |
PGP_PKT_OFF_NEXT | 24 | Offset des nächsten Pakets im Quellpuffer |
PGP_PKT_SIZE | 32 | Gesamtgröße des Structs |
Die Iteration kopiert nichts: PGP_PKT_OFF_BODY zeigt direkt in den Quellpuffer. Der muss also gültig bleiben, solange mit den Bodies gearbeitet wird.
Beispiele
Alle Beispiele sind gegen einen mit gpg –quick-generate-key erzeugten Testschlüssel geprüft; die gezeigten Ausgaben stammen aus dem tatsächlichen Lauf.
Schlüsseldatei auslesen
Der vollständige Weg: Datei laden → Armor-Typ prüfen → dekodieren → Pakete durchlaufen → Schlüsseldaten lesen.
import std.pgp.core;
import std.pgp.armor;
import std.pgp.packet;
import std.pgp.key;
import std.alloc;
import std.fs;
fn HexOut(p: int64, n: int64): void {
var hex: pchar := "0123456789ABCDEF";
var i: int64 := 0;
while (i < n) {
var b: int64 := peek8(p + i);
var s: int64 := alloc(3);
poke8(s, StrCharAt(hex, (b >> 4) & 15));
poke8(s + 1, StrCharAt(hex, b & 15));
poke8(s + 2, 0);
Print(s as pchar);
free(s, 3);
i := i + 1;
}
}
fn main(): int64 {
var path: pchar := "key.asc";
var fsize: int64 := FileSize(path);
PrintLn("Armor-Datei: ", IntToStr(fsize), " Bytes");
var arm: int64 := alloc(fsize + 1);
ReadFile(path, arm as pchar, fsize);
PrintLn("PgpArmorType = ", IntToStr(PgpArmorType(arm, fsize)),
" (PGP_ARMOR_PUBLIC_KEY = ", IntToStr(PGP_ARMOR_PUBLIC_KEY), ")");
var raw: int64 := alloc(fsize);
var rawLen: int64 := PgpArmorDecode(arm, fsize, raw, fsize);
PrintLn("PgpArmorDecode -> ", IntToStr(rawLen), " Bytes binaer");
if (rawLen < 0) { return 1; }
var pkt: int64 := alloc(PGP_PKT_SIZE);
var fp: int64 := alloc(20);
var kid: int64 := alloc(8);
var more: int64 := PgpPacketFirst(raw, rawLen, pkt);
while (more != 0) {
var tag: int64 := peek64(pkt + PGP_PKT_OFF_TAG);
var body: int64 := peek64(pkt + PGP_PKT_OFF_BODY);
var blen: int64 := peek64(pkt + PGP_PKT_OFF_BLEN);
PrintLn("Paket: Tag ", IntToStr(tag), ", ", IntToStr(blen), " Bytes Body");
if (tag == PGP_TAG_PUBKEY) {
PrintLn(" Version = ", IntToStr(PgpKeyVersion(body, blen)));
PrintLn(" Algorithmus = ", IntToStr(PgpKeyAlgo(body, blen)));
PrintLn(" Zeitstempel = ", IntToStr(PgpKeyTimestamp(body, blen)));
if (PgpKeyFingerprint(body, blen, fp) != 0) {
Print(" Fingerprint = "); HexOut(fp, 20); PrintLn("");
}
if (PgpKeyId(body, blen, kid) != 0) {
Print(" Key-ID = "); HexOut(kid, 8); PrintLn("");
}
}
more := PgpPacketNext(raw, rawLen, pkt);
}
return 0;
}
Ausgabe:
Armor-Datei: 961 Bytes
PgpArmorType = 2 (PGP_ARMOR_PUBLIC_KEY = 2)
PgpArmorDecode -> 649 Bytes binaer
Paket: Tag 6, 269 Bytes Body
Version = 4
Algorithmus = 1
Zeitstempel = 1786643838
Fingerprint = 85C6D42C76D20716A4DDF55EBBF229737744C1D3
Key-ID = BBF229737744C1D3
Paket: Tag 13, 35 Bytes Body
Paket: Tag 2, 337 Bytes Body
Gegenprobe mit gpg –list-keys –with-colons auf demselben Schlüssel:
fpr:::::::::85C6D42C76D20716A4DDF55EBBF229737744C1D3:
Fingerprint und Key-ID stimmen byteweise überein. Algorithmus 1 ist RSA, Tag 6 der Hauptschlüssel, Tag 13 die User-ID, Tag 2 deren Selbstsignatur.
Paketstruktur eines Schlüsselbunds
Bei einem Schlüssel mit Unterschlüssel kommen zwei weitere Pakete dazu:
import std.pgp.core;
import std.pgp.armor;
import std.pgp.packet;
import std.alloc;
import std.fs;
fn TagName(t: int64): pchar {
if (t == PGP_TAG_PUBKEY) { return "Hauptschluessel"; }
if (t == PGP_TAG_UID) { return "User-ID"; }
if (t == PGP_TAG_SIG) { return "Signatur"; }
if (t == PGP_TAG_PUBSUBKEY) { return "Unterschluessel"; }
return "sonstiges";
}
fn scan(path: pchar): void {
var fsize: int64 := FileSize(path);
var arm: int64 := alloc(fsize + 1);
ReadFile(path, arm as pchar, fsize);
PrintLn("--- ", path, ": Typ ", IntToStr(PgpArmorType(arm, fsize)), ", ", IntToStr(fsize), " Bytes Armor");
var raw: int64 := alloc(fsize);
var rawLen: int64 := PgpArmorDecode(arm, fsize, raw, fsize);
PrintLn(" dekodiert: ", IntToStr(rawLen), " Bytes");
var pkt: int64 := alloc(PGP_PKT_SIZE);
var more: int64 := PgpPacketFirst(raw, rawLen, pkt);
var cnt: int64 := 0;
while (more != 0) {
var tag: int64 := peek64(pkt + PGP_PKT_OFF_TAG);
PrintLn(" Tag ", IntToStr(tag), " (", TagName(tag), "), Body ", IntToStr(peek64(pkt + PGP_PKT_OFF_BLEN)));
cnt := cnt + 1;
more := PgpPacketNext(raw, rawLen, pkt);
}
PrintLn(" Pakete gesamt: ", IntToStr(cnt));
}
fn main(): int64 {
scan("key2.asc");
scan("sig.asc");
return 0;
}
Ausgabe:
--- key2.asc: Typ 2, 1753 Bytes Armor
dekodiert: 1234 Bytes
Tag 6 (Hauptschluessel), Body 269
Tag 13 (User-ID), Body 35
Tag 2 (Signatur), Body 337
Tag 14 (Unterschluessel), Body 269
Tag 2 (Signatur), Body 310
Pakete gesamt: 5
--- sig.asc: Typ 4, 516 Bytes Armor
dekodiert: 332 Bytes
Tag 2 (Signatur), Body 329
Pakete gesamt: 1
Dieselbe Datei durch gpg –list-packets ergibt exakt dieselbe Folge — Hauptschlüssel, User-ID, Signatur, Unterschlüssel, Signatur.
Armor kodieren und wieder dekodieren
import std.pgp.core;
import std.pgp.armor;
import std.alloc;
fn main(): int64 {
var text: pchar := "OpenPGP Rohdaten-Test 1234567890";
var len: int64 := StrLen(text);
var outMax: int64 := (len * 4 / 3) + 200;
var arm: int64 := alloc(outMax);
var n: int64 := PgpArmorEncode(text as int64, len, PGP_ARMOR_MESSAGE, arm, outMax);
PrintLn("PgpArmorEncode -> ", IntToStr(n), " Zeichen");
PrintLn(arm as pchar);
PrintLn("Typ erkannt: ", IntToStr(PgpArmorType(arm, n)), " (PGP_ARMOR_MESSAGE = ", IntToStr(PGP_ARMOR_MESSAGE), ")");
var back: int64 := alloc(len + 16);
var m: int64 := PgpArmorDecode(arm, n, back, len + 16);
PrintLn("PgpArmorDecode -> ", IntToStr(m), " Bytes (Original: ", IntToStr(len), ")");
poke8(back + m, 0);
PrintLn("Inhalt: ", back as pchar);
return 0;
}
Ausgabe:
PgpArmorEncode -> 106 Zeichen
-----BEGIN PGP MESSAGE-----
T3BlblBHUCBSb2hkYXRlbi1UZXN0IDEyMzQ1Njc4OTA=
=WWrf
-----END PGP MESSAGE-----
Typ erkannt: 1 (PGP_ARMOR_MESSAGE = 1)
PgpArmorDecode -> 32 Bytes (Original: 32)
Inhalt: OpenPGP Rohdaten-Test 1234567890
Der Aufbau entspricht RFC 4880 §6.2: BEGIN-Zeile, Leerzeile, Base64-Block, Prüfsummenzeile =WWrf, END-Zeile. Base64-Zeilen sind 76 Zeichen lang (57 Eingangsbytes je Zeile) — RFC 4880 erlaubt bis 76, GnuPG selbst schreibt 64. Beide Formen sind gültig und werden von PgpArmorDecode gelesen.
CRC-Prüfung
PgpArmorDecode verifiziert die Prüfsumme und meldet Manipulation:
// aus dem obigen Programm: ein Zeichen der '='-Zeile kippen
var c: int64 := peek8(arm + crcPos + 1);
if (c == 65) { poke8(arm + crcPos + 1, 66); } else { poke8(arm + crcPos + 1, 65); }
var bad: int64 := PgpArmorDecode(arm, n, back, len + 16);
PrintLn("Decode mit falscher CRC -> ", IntToStr(bad));
Decode mit falscher CRC -> -1
-1 ist der einzige Fehlerwert und deckt drei Fälle ab: CRC-Fehler, fehlender oder unvollständiger BEGIN/END-Rahmen und zu kleiner Ausgabepuffer. Welcher davon vorlag, ist am Rückgabewert nicht erkennbar.
CRC-24 direkt
import std.pgp.core;
fn main(): int64 {
var data: pchar := "Hallo OpenPGP";
PrintLn("CRC-24 = ", IntToStr(PgpCrc24(data as int64, StrLen(data))));
PrintLn("CRC-24 leer = ", IntToStr(PgpCrc24(data as int64, 0)));
return 0;
}
CRC-24 = 6658813
CRC-24 leer = 11994318
Bei Länge 0 kommt der Initialwert 0xB704CE = 11994318 zurück — das ist RFC-konform. PgpArmorEncode ruft PgpCrc24 selbst auf; im normalen Armor-Ablauf braucht man die Funktion nicht direkt.
Fallstricke
Die Puffergröße muss weiterhin selbst berechnet werden
PgpArmorEncode meldet seit dem Fix von #1454 einen zu kleinen Puffer mit -1, statt darüber hinaus zu schreiben:
PgpArmorEncode(200 Bytes, outMax=40) -> -1
überschriebene Wächter-Bytes: 0 von 64
Der Rückgabewert ist damit zu prüfen — sonst bleibt der Puffer stillschweigend leer. Für die Größe gilt (dataLen * 4 / 3) + 200 Bytes; das deckt Base64-Aufblähung, Zeilenumbrüche, Rahmen und Prüfsummenzeile ab. Mit ausreichendem Puffer liefert der Rundlauf Encode → Decode die 200 Ausgangsbytes zurück.
Der Body zeigt in den Quellpuffer
PGP_PKT_OFF_BODY ist ein Zeiger in den dekodierten Puffer, keine Kopie. Zwei Folgen:
- Der Quellpuffer darf nicht freigegeben werden, solange Bodies in Gebrauch sind.
- Ein Body ist nicht nullterminiert. Eine User-ID (Tag 13) direkt als
pcharauszugeben, liest über das Paketende hinaus. Wer sie als String braucht, kopiertblenBytes in einen eigenen Puffer und terminiert dort.
Das früher an dieser Stelle gezeigte poke8(body + blen, 0) überschreibt das erste Byte des Folgepakets und zerstört die weitere Iteration — bitte nicht verwenden.
Fingerprints nur für v4
PgpKeyFingerprint und PgpKeyId liefern 0, wenn das Versionsbyte nicht 4 ist oder der Body größer als 65535 Bytes wäre. v3-Schlüssel geben nur Version, Algorithmus und Zeitstempel her. Der Rückgabewert ist zu prüfen — bei 0 bleibt der Ausgabepuffer uninitialisiert.
Partial-Body-Pakete werden nicht zusammengeführt
Neuformat-Pakete mit Partial-Body-Header (Längenbyte 224–254) behandelt der Iterator als vollständiges Paket der jeweiligen Teillänge. Die Folgestücke erscheinen als eigene, aus Sicht des Formats falsch interpretierte Pakete. Das betrifft in der Praxis große komprimierte oder verschlüsselte Datenpakete, nicht die üblichen Schlüsseldateien.
Einschränkungen
| Was | Einschränkung |
|---|---|
| Schlüsselerzeugung | Nicht vorhanden — kein RSA/DSA/ECDSA/EdDSA-KeyGen |
| Ver- und Entschlüsseln | Nicht vorhanden |
| Signieren und Verifizieren | Nicht vorhanden — Signaturpakete werden erkannt, aber nicht geprüft |
| v3-Fingerprint | Nicht unterstützt |
| Partial-Body-Pakete | Werden nicht zusammengeführt |
| Compressed Data (Tag 8) | Wird als Paket erkannt, aber nicht entpackt |
| Signatur-Subpakete | Werden nicht zerlegt — der Signaturbody bleibt roh |
Für kryptografische Operationen sind die Bausteine in std.crypto.rsa, std.crypto.sha256 und Verwandten vorhanden; die Verbindung zum OpenPGP-Format muss man selbst herstellen.
Quelldateien
| Datei | Inhalt |
|---|---|
std/pgp/core.lyx | Alle Konstanten, Paket-Struct-Offsets, PgpCrc24 |
std/pgp/armor.lyx | PgpArmorEncode, PgpArmorDecode, PgpArmorType samt Base64-Codec |
std/pgp/packet.lyx | PgpPacketFirst, PgpPacketNext; Alt- und Neuformat-Parser |
std/pgp/key.lyx | PgpKeyVersion, PgpKeyAlgo, PgpKeyTimestamp, PgpKeyFingerprint, PgpKeyId |
Verwandte Units
- std.base64 — allgemeine Base64-Kodierung
- std.crypto.sha1 — SHA-1, Basis der v4-Fingerprints
Codebeispiele geprüft: gegen lyxc 1.2.5C übersetzt (Prüflauf 2026-09-08 über die gesamte Doku: 574 Vollprogramme, 0 echte Fehler; zusätzlich 5159 Aufrufe gegen die pub fn-Signaturen in aurum/std gehalten, 0 Abweichungen).
Letzte Aktualisierung: 2026-08-30 (Dateistand)
