moonpgp

    Pure-MoonBit OpenPGP implementation (RFC 4880/9580): email encryption, decryption, signing and verification, interoperable with GnuPG and gopenpgp.

    openpgp
    pgp
    encryption
    signature
    email
    crypto
    imap
    smtp
    mime
    Download zip
    Version
    0.4.0
    License
    Apache-2.0
    Last updated
    5 days ago
    Downloads
    295

    #moonpgp

    An OpenPGP library written in MoonBit: message encryption and decryption, signatures, key management, and PGP/MIME email.

    Module: justinwongcn/moonpgp · Package documentation · CI

    #Install

    You'll need the MoonBit toolchain; moon resolves dependencies for you.

    moon add justinwongcn/moonpgp

    Use 0.2.0 or later: the 0.1.0 release is affected by a known subkey-injection flaw (see SECURITY.md). A fresh moon add resolves the newest release.

    The root package has the main API. Add mime and mail when you need PGP/MIME parsing or RFC 5322 message building:

    import { "justinwongcn/moonpgp", "justinwongcn/moonpgp/mime", "justinwongcn/moonpgp/mail", }

    The root package exposes its @api functions and option types as @moonpgp.*.

    #A taste of the API

    Generate a key, encrypt to it, open the message again. This block is type-checked and run by moon test, so it cannot drift from the code:

    ///|
    test "a tiny round trip" {
    let key = @moonpgp.generate_ed25519_key("alice@example.com")
    let armored = @moonpgp.pgp().encryption().recipient(key.to_public()).finish()(
    b"hi, from the README",
    )
    let open = @moonpgp.pgp().decryption().decryption_key(key).finish()
    match open(armored) {
    Data(data) => assert_eq(data, b"hi, from the README")
    Verified(data) => assert_eq(data, b"hi, from the README")
    SignatureError(_) => fail("unexpected signature error")
    }
    }

    Failures are typed: a wrong passphrase raises WrongPassphrase, unparsable input Malformed, an unimplemented algorithm Unsupported — all reachable from the bare root import:

    ///|
    test "wrong passphrases are a typed error" {
    let key = @moonpgp.generate_ed25519_key("alice@example.com").lock("right")
    try {
    let _ = key.unlock("wrong")
    fail("unlocking must fail")
    } catch {
    @moonpgp.PgpError::WrongPassphrase(_) => () // the expected failure class
    _ => fail("expected WrongPassphrase")
    }
    }

    Sign data and verify detached signatures:

    ///|
    test "detached signatures and verification" {
    let signer = @moonpgp.generate_ed25519_key("signer@example.com")
    let data = b"release tarball bytes or document payload"
    let sig = @moonpgp.sign_detached(data, signer)
    let status = @moonpgp.verify_detached(data, sig, signer.to_public())
    assert_eq(status, @moonpgp.VerifyStatus::Ok)
    }

    For PGP/MIME emails, import "justinwongcn/moonpgp/mime":

    ///|
    test "pgp/mime sign and verify" {
    let key = @moonpgp.generate_ed25519_key("mime@example.com")
    let message = @mime.build_pgp_mime_signed("Hello from MoonBit\n", key)
    let (_, status) = @mime.verify_pgp_mime_signed(message, key.to_public())
    assert_eq(status, @api.Ok)
    }

    There's also a command-line demo, if you clone the repo:

    git clone https://github.com/justinwongcn/moonpgp cd moonpgp moon run cmd/main -- help

    #Features

    • Keys: generate, import, export, lock and unlock with a passphrase. RSA, Ed25519 (legacy EdDSA), and Curve25519/X25519 are covered; new keys are v4, and v6 works too — generate_ed25519_v6_key_with_signing_subkey produces the gpg dual-subkey shape on v6: a cert-only primary, an X25519 encryption subkey and an Ed25519 signing subkey carrying the v6 cross-certification. All bound subkeys are kept, each judged by its own key flags, and helpers check capabilities, expiry, and revocation. Certificates also grow and change in place: revoke a key or a single subkey, issue a standalone .rev certificate (gpg's --gen-revoke certificates apply too), add fresh encryption or signing subkeys to an existing key, set or extend expiry at generation time (KeyGenBuilder::expires) or later (set_expiry, set_subkey_expiry), and manage identities with add_user_id, revoke_user_id and set_primary_user_id.
    • Messages: public-key or passphrase encryption, detached/inline/cleartext signatures, session-key operations, ZIP/ZLIB compression on output. On input, BZip2-compressed messages decrypt as well — the format gpg emits under --compress-algo bzip2 — through a pure-MoonBit codec that verifies every block and stream CRC. Document signatures come from the primary key when it may sign, otherwise from a signing subkey (its binding carries the cross-certification gpg requires). Anything this library encrypts uses AES; CAST5 and 3DES exist for decryption only.
    • v6 messages: v6 PKESK/SKESK and v2 SEIPD with OCB, EAX, or GCM AEAD, end to end. In the encryption builder, .password_v6 adds a v6 passphrase recipient; v4 and v6 shapes don't mix in one message. Regular v4 recipient and passphrase flows default to v1 SEIPD.
    • Passphrase protection: iterated-and-salted S2K and Argon2, including the AEAD-protected secret-key formats.
    • Email: mime parses and verifies PGP/MIME; mail builds RFC 5322 messages, signed and encrypted PGP/MIME included. The SMTP/IMAP clients under example/ are examples rather than library API, and they only run on native.

    #Interop and local checks

    Tests include some interop with GnuPG, OpenSSL, and go-crypto, plus RFC test vectors. The GnuPG checks live in scripts/interop_check.sh and run on a nightly CI schedule (plus frozen GnuPG-produced vectors in the per-push suite).

    Locally:

    moon check --target all --deny-warn moon build --target all moon test --target native moon fmt --check

    CI covers native, wasm, and js; for day-to-day work, use native. Key generation and anything else that depends on randomness needs a platform entropy source, which not every runtime provides.

    #Security and limitations

    • Import verifies the signatures it understands (User ID certifications, subkey bindings, v6 direct-key signatures), but that's signature checking, not trust management: whether a key really belongs to someone is for your application to decide.
    • RSA, Ed25519, and X25519 arithmetic runs on @bigint and is not constant-time, and a GC runtime makes no promise about wiping private keys from memory.
    • Decrypting untrusted Argon2-protected data is capped at 64 MiB of memory by default; over-limit requests are rejected before anything is allocated. Some APIs let you set a different limit.
    • The encryption APIs don't pad v2 SEIPD messages, so ciphertext length gives away roughly how long the plaintext is.
    • On targets without a platform clock, expiration checks are off and revocation dates can't be evaluated; revocation signatures are still checked cryptographically.

    Dependency and attribution details are in THIRD-PARTY-NOTICES.md.

    #License

    Apache-2.0. See LICENSE, NOTICE, and THIRD-PARTY-NOTICES.md.

    AeadMode

    Option types that appear in @api signatures, re-exported so the bare root import covers the whole main workflow (an AeadMode for encrypt_with_password_v6, a CompressedAlgo for the encryption builders, a CipherAlgo for session keys, a HashId for sign_cleartext_with).

    CipherAlgo

    Symmetric cipher algorithms, RFC 9580 §9.3 numbers. TripleDES (2) and CAST5 (3) exist for the read side only — RFC 4880 §9.2 makes 3DES a MUST-implement and CAST5 was GnuPG's pre-AES default; the generation paths always pick AES.

    CompressedAlgo

    Compression algorithms for the Compressed Data packet (RFC 9580 §9.4).

    DecryptResult

    Root facade of justinwongcn/moonpgp: a single import "justinwongcn/moonpgp" gives the complete high-level OpenPGP workflow (@moonpgp.generate_key, @moonpgp.encrypt_with_password, sign/verify, builders, key management). Every item is re-exported from its owning public package, so methods resolve through the usual owner loading and lower layers (@packet, @pki, @armor, ...) remain directly importable for advanced use.

    DecryptionBuilder

    Root facade of justinwongcn/moonpgp: a single import "justinwongcn/moonpgp" gives the complete high-level OpenPGP workflow (@moonpgp.generate_key, @moonpgp.encrypt_with_password, sign/verify, builders, key management). Every item is re-exported from its owning public package, so methods resolve through the usual owner loading and lower layers (@packet, @pki, @armor, ...) remain directly importable for advanced use.

    EncryptionBuilder

    Root facade of justinwongcn/moonpgp: a single import "justinwongcn/moonpgp" gives the complete high-level OpenPGP workflow (@moonpgp.generate_key, @moonpgp.encrypt_with_password, sign/verify, builders, key management). Every item is re-exported from its owning public package, so methods resolve through the usual owner loading and lower layers (@packet, @pki, @armor, ...) remain directly importable for advanced use.

    HashId

    Hash algorithms used by OpenPGP, identified by their RFC 9580 §9.5 algorithm numbers. This is the PGP-level identity; concrete digest computation is delegated to moonbitlang/x/crypto inside this package only, so the hash backend can be swapped without touching callers.

    KeyGenBuilder

    Root facade of justinwongcn/moonpgp: a single import "justinwongcn/moonpgp" gives the complete high-level OpenPGP workflow (@moonpgp.generate_key, @moonpgp.encrypt_with_password, sign/verify, builders, key management). Every item is re-exported from its owning public package, so methods resolve through the usual owner loading and lower layers (@packet, @pki, @armor, ...) remain directly importable for advanced use.

    KeyRing

    Root facade of justinwongcn/moonpgp: a single import "justinwongcn/moonpgp" gives the complete high-level OpenPGP workflow (@moonpgp.generate_key, @moonpgp.encrypt_with_password, sign/verify, builders, key management). Every item is re-exported from its owning public package, so methods resolve through the usual owner loading and lower layers (@packet, @pki, @armor, ...) remain directly importable for advanced use.

    Pgp

    Root facade of justinwongcn/moonpgp: a single import "justinwongcn/moonpgp" gives the complete high-level OpenPGP workflow (@moonpgp.generate_key, @moonpgp.encrypt_with_password, sign/verify, builders, key management). Every item is re-exported from its owning public package, so methods resolve through the usual owner loading and lower layers (@packet, @pki, @armor, ...) remain directly importable for advanced use.

    PgpError

    The error type raised throughout the module, re-exported so a bare root import is enough to catch and branch on the failure classes (Malformed, Unsupported, WrongPassphrase).

    PrivateKey

    Root facade of justinwongcn/moonpgp: a single import "justinwongcn/moonpgp" gives the complete high-level OpenPGP workflow (@moonpgp.generate_key, @moonpgp.encrypt_with_password, sign/verify, builders, key management). Every item is re-exported from its owning public package, so methods resolve through the usual owner loading and lower layers (@packet, @pki, @armor, ...) remain directly importable for advanced use.

    PublicKey

    Root facade of justinwongcn/moonpgp: a single import "justinwongcn/moonpgp" gives the complete high-level OpenPGP workflow (@moonpgp.generate_key, @moonpgp.encrypt_with_password, sign/verify, builders, key management). Every item is re-exported from its owning public package, so methods resolve through the usual owner loading and lower layers (@packet, @pki, @armor, ...) remain directly importable for advanced use.

    RevocationReason

    Root facade of justinwongcn/moonpgp: a single import "justinwongcn/moonpgp" gives the complete high-level OpenPGP workflow (@moonpgp.generate_key, @moonpgp.encrypt_with_password, sign/verify, builders, key management). Every item is re-exported from its owning public package, so methods resolve through the usual owner loading and lower layers (@packet, @pki, @armor, ...) remain directly importable for advanced use.

    SecretSubkeyCert

    Root facade of justinwongcn/moonpgp: a single import "justinwongcn/moonpgp" gives the complete high-level OpenPGP workflow (@moonpgp.generate_key, @moonpgp.encrypt_with_password, sign/verify, builders, key management). Every item is re-exported from its owning public package, so methods resolve through the usual owner loading and lower layers (@packet, @pki, @armor, ...) remain directly importable for advanced use.

    SessionKey

    Root facade of justinwongcn/moonpgp: a single import "justinwongcn/moonpgp" gives the complete high-level OpenPGP workflow (@moonpgp.generate_key, @moonpgp.encrypt_with_password, sign/verify, builders, key management). Every item is re-exported from its owning public package, so methods resolve through the usual owner loading and lower layers (@packet, @pki, @armor, ...) remain directly importable for advanced use.

    SubkeyCert

    Root facade of justinwongcn/moonpgp: a single import "justinwongcn/moonpgp" gives the complete high-level OpenPGP workflow (@moonpgp.generate_key, @moonpgp.encrypt_with_password, sign/verify, builders, key management). Every item is re-exported from its owning public package, so methods resolve through the usual owner loading and lower layers (@packet, @pki, @armor, ...) remain directly importable for advanced use.

    UserIdBinding

    Root facade of justinwongcn/moonpgp: a single import "justinwongcn/moonpgp" gives the complete high-level OpenPGP workflow (@moonpgp.generate_key, @moonpgp.encrypt_with_password, sign/verify, builders, key management). Every item is re-exported from its owning public package, so methods resolve through the usual owner loading and lower layers (@packet, @pki, @armor, ...) remain directly importable for advanced use.

    VerifyStatus

    Root facade of justinwongcn/moonpgp: a single import "justinwongcn/moonpgp" gives the complete high-level OpenPGP workflow (@moonpgp.generate_key, @moonpgp.encrypt_with_password, sign/verify, builders, key management). Every item is re-exported from its owning public package, so methods resolve through the usual owner loading and lower layers (@packet, @pki, @armor, ...) remain directly importable for advanced use.

    DEFAULT_MAX_DECOMPRESSED_BYTES

    let DEFAULT_MAX_DECOMPRESSED_BYTES : Int

    Default ceiling on decompressed payload size (M4): a 512 MiB bound on what an untrusted compressed packet may expand to. Mirrors the Argon2 pre-allocation cap policy; override per call with max_decompressed~.

    build_pgp_mime_signed

    fn build_pgp_mime_signed(text : String, key :
    PrivateKey
    ) -> String raise

    Build a multipart/signed PGP/MIME message over UTF-8 text with the SHA-256 signature of key (RFC 3156 §5). The first part is signed over its exact transmitted bytes (CRLF line endings).

    decrypt_and_verify

    fn decrypt_and_verify(message : String, decryption_keys : Array[
    PrivateKey
    ], verifier :
    PublicKey
    ?, max_decompressed? : Int) -> (Bytes,
    VerifyStatus
    ) raise

    Decrypt an armored message (password OR private keys) and verify an inline signature against verifier. Returns (data, status).

    The plaintext is returned even when the signature does not verify — Failed, KeyRevoked and Expired all arrive with data. Callers that must not act on unverified content check the status first (the DecryptionBuilder wraps this contract as DecryptResult, surfacing the broken-signature case as SignatureError(data)).

    decrypt_binary_with_password

    fn decrypt_binary_with_password(data : BytesView, passphrase : String, argon2_max_memory_kib? : Int, max_decompressed? : Int) -> Bytes raise

    Same as decrypt_with_password but takes binary packet data.

    Every SKESK in the stream is tried until one both unwraps AND opens the message payload (N4): a v4 SKESK has no integrity check of its own, so a wrong passphrase can unwrap to structurally plausible garbage whose wrongness only surfaces at the SEIPD MDC — the retry must therefore span the payload decryption, not just the unwrap.

    decrypt_binary_with_private_key

    fn decrypt_binary_with_private_key(data : BytesView, key :
    PrivateKey
    , max_decompressed? : Int) -> Bytes raise

    Decrypt a binary message with an unlocked private key. Matches PKESKs against the key's ids (RSA or ECDH), recovers the session key, checks the session-key checksum, and unwraps the SEIPD payload.

    decrypt_to_session_key

    fn decrypt_to_session_key(message : String, sk :
    SessionKey
    ) -> Bytes raise

    Decrypt a message with a caller-held session key. The message may be a bare SEIPD container (from encrypt_to_session_key) or a normal PKESK/SKESK-protected message whose session key the caller knows.

    decrypt_with_password

    fn decrypt_with_password(message : String, passphrase : String, argon2_max_memory_kib? : Int, max_decompressed? : Int) -> Bytes raise

    Decrypt an (optionally armored) message using a passphrase. Returns the literal data. Compressed packets are transparently decompressed. A wrong passphrase raises the typed WrongPassphrase error. See encrypt_with_password for a round-trip example.

    decrypt_with_private_key

    fn decrypt_with_private_key(message : String, key :
    PrivateKey
    , max_decompressed? : Int) -> Bytes raise

    Decrypt an armored message with an unlocked private key.

    encrypt_to_recipients

    fn encrypt_to_recipients(plaintext : BytesView, recipients : Array[
    PublicKey
    ], file_name? : String, compress? :
    CompressedAlgo
    ) -> String raise

    Encrypt plaintext to one or more recipients' public keys. Each recipient gets its own fresh PKESK encoding of the session key (RFC 9580 §5.1.3 requires re-encoding per key). Result: armored PGP MESSAGE of PKESK(s) + SEIPD, v2 for v6 recipients and v1 otherwise.

    encrypt_to_session_key

    fn encrypt_to_session_key(plaintext : BytesView, sk :
    SessionKey
    , file_name? : String, compress? :
    CompressedAlgo
    ) -> String raise

    Encrypt plaintext under a caller-held session key: a bare SEIPD v1 container with no PKESK/SKESK — only whoever holds sk out of band can read it (the gopenpgp "raw session key" backend). Everything this library encrypts uses AES: CAST5/3DES session keys stay decryption-only.

    encrypt_with_password

    fn encrypt_with_password(plaintext : BytesView, passphrase : String, file_name? : String, aead? : Bool, argon2? : Bool, argon2_memory_exp? : Int, compress? :
    CompressedAlgo
    ) -> String raise

    Encrypt plaintext to a passphrase. Produces an armored PGP MESSAGE with a v4 SKESK (iterated+salted S2K, AES-256 session key wrapped in CFB) followed by a v1 SEIPD packet holding the literal data.

    For new code prefer the builder (pgp().encryption().password(...), which can also mix recipients and a signer) or encrypt_with_password_v6 for the RFC 9580 v6 shape; this entry stays for the classic v4 message.

    Example

    test "password encryption round trip" {
    let armored = @api.encrypt_with_password(b"Hello, world!", "hunter2")
    inspect(armored.has_prefix("-----BEGIN PGP MESSAGE-----"), content="true")
    assert_eq(@api.decrypt_with_password(armored, "hunter2"), b"Hello, world!")
    }

    encrypt_with_password_argon2

    fn encrypt_with_password_argon2(plaintext : BytesView, passphrase : String, file_name? : String, passes? : Int, parallelism? : Int, memory_exp? : Int, compress? :
    CompressedAlgo
    ) -> String raise

    Encrypt plaintext to a passphrase using Argon2id as the S2K specifier (type 4, RFC 9580 §3.7.1.4) — the RFC's recommendation, and the shape of Appendix A.12. The message keeps the v4 SKESK form (CFB with an all-zero IV) plus a v1 SEIPD packet, so every reader that understands Argon2 can consume it.

    memory_exp is the exponent of the Argon2 memory size in KiB (default 16 → 64 MiB, go-crypto's default); passes and parallelism default to t=3, p=4.

    In new code prefer encrypt_with_password_v6 (Argon2 by default) or the builder's .password_v6; this entry produces the v4 message shape with an Argon2 specifier.

    encrypt_with_password_v6

    fn encrypt_with_password_v6(plaintext : BytesView, passphrase : String, file_name? : String, aead? :
    AeadMode
    , argon2? : Bool, passes? : Int, parallelism? : Int, memory_exp? : Int, compress? :
    CompressedAlgo
    ) -> String raise

    Encrypt plaintext to a passphrase in the RFC 9580 v6 shape: a v6 SKESK (AEAD + HKDF-SHA256 key separation, §5.3.2) followed by a v2 SEIPD packet (§5.13.2). argon2 (default true) selects the Argon2id S2K the RFC recommends for v6; set it to false for the much cheaper iterated+salted SHA-256. This is the recommended passphrase entry point for new code; to mix in public-key recipients or a signer, use the builder's .password_v6 instead.

    generate_ed25519_key

    fn generate_ed25519_key(user_id : String) ->
    PrivateKey
    raise

    Generate a transferable key with an Ed25519 primary (EdDSA legacy, signing/certification) and a Curve25519Legacy ECDH encryption subkey — the gopenpgp "Default" profile shape. EdDSA signatures require the digest itself as the Ed25519 message (RFC 9580 §5.2.3).

    Example

    test "generate an ed25519 key" {
    let key = @api.generate_ed25519_key("A <a@example.com>")
    assert_eq(key.user_id(), "A <a@example.com>")
    inspect(key.fingerprint().length(), content="20")
    }

    generate_ed25519_v6_key

    fn generate_ed25519_v6_key(user_id : String) ->
    PrivateKey
    raise

    Generate a v6 Ed25519 signing key with an X25519 encryption subkey (RFC 9580 §5.5.5.7/§5.5.5.9) — the shape of the Appendix A.3/A.4 sample certificate: a v6 primary key with a User ID self-certification, the Direct Key signature the RFC requires before a v6 key may be used, and a v6 X25519 subkey with a v6 binding signature.

    generate_ed25519_v6_key_with_signing_subkey

    fn generate_ed25519_v6_key_with_signing_subkey(user_id : String) ->
    PrivateKey
    raise

    Generate a v6 certificate in the gpg dual-subkey shape: a CERT-ONLY Ed25519 primary (key flags 0x01 — the self-signature withholds the sign flag), an X25519 encryption subkey and an Ed25519 signing subkey whose v6 binding embeds a v6 Primary Key Binding (cross-certification, RFC 9580 §5.2.3.26 — a v6 key's self-signatures MUST be v6, so the embedded signature is v6-shaped). Document signatures then flow through the signing subkey (signing_packet), exactly like the gpg dual-subkey interop fixtures, exercising the v6 cross-certification path end to end.

    generate_key

    fn generate_key(user_id : String, bits? : Int, expiry? : Int64) ->
    PrivateKey
    raise

    Generate a fresh transferable key: RSA primary (certify+sign) with a User ID self-certification plus an RSA encryption subkey. expiry sets a Key Expiration Time (seconds from the key's creation; 0, the default, never expires) on both self-certification and binding.

    generate_revocation_certificate

    fn generate_revocation_certificate(key :
    PrivateKey
    , reason? :
    RevocationReason
    , message? : String, created? : Int) -> String raise

    Generate, but do not apply, a standalone revocation certificate for the primary key: a single key-revocation signature packet in PUBLIC KEY BLOCK armor — the .rev shape gpg --gen-revoke emits (gpg refuses to --import a bare Signature armor block, verified against 2.4.5). The owner distributes it out of band; anyone can apply it to their copy of the public key.

    Example

    test "a revocation certificate is armored" {
    let key = @api.generate_ed25519_key("A <a@example.com>")
    let cert = @api.generate_revocation_certificate(key)
    inspect(
    cert.has_prefix("-----BEGIN PGP PUBLIC KEY BLOCK-----"),
    content="true",
    )
    }

    generate_session_key

    Generate a fresh random session key (platform entropy; AES-256 unless another cipher is requested).

    pgp

    gopenpgp-style entry point: pgp().encryption()....

    recover_session_key_with_key

    fn recover_session_key_with_key(message : String, key :
    PrivateKey
    ) ->
    SessionKey
    raise

    Recover the session key of a message encrypted to key's public key: unwrap the matching PKESK without decrypting the payload. The algorithm id comes from the PKESK payload for the v4 shape and from the SEIPD v2 header for v6.

    recover_session_key_with_password

    fn recover_session_key_with_password(message : String, passphrase : String, argon2_max_memory_kib? : Int) ->
    SessionKey
    raise

    Recover the session key of a password-encrypted message (the gopenpgp RetrieveSessionKey flow): unwrap the SKESK without touching the payload.

    revoke_key

    Return a copy of key with a fresh primary-key revocation appended; the certificate round-trips with the revocation intact and every lifecycle gate (is_revoked, can_encrypt, can_verify, signature verification) sees the key as revoked.

    revoke_subkey

    fn revoke_subkey(key :
    PrivateKey
    , subkey_id : BytesView, reason? :
    RevocationReason
    , message? : String, created? : Int) ->
    PrivateKey
    raise

    Return a copy of key with a revocation signature on the subkey whose key id matches subkey_id. A revoked subkey drops out of encryption selection immediately; decryption with it stays possible (mail that arrived before the revocation must keep decrypting, mirroring KeyRing::find_decryption_key).

    sign_and_encrypt

    fn sign_and_encrypt(data : BytesView, signer :
    PrivateKey
    , recipients : Array[
    PublicKey
    ], text_mode? : Bool, compress? :
    CompressedAlgo
    , created? : Int, file_name? : String) -> String raise

    Sign and encrypt in one step (inline signature inside SEIPD).

    created is the signature creation time in seconds since 1970; without it the current time is used (see sign_detached). file_name names the literal packet (N9; default "message.txt").

    sign_cleartext

    fn sign_cleartext(text : String, key :
    PrivateKey
    , created? : Int) -> String raise

    Create a cleartext signed message (RFC 9580 §7) over UTF-8 text with the default SHA-256 signature of the primary key.

    created is the signature creation time in seconds since 1970; without it the current time is used (see sign_detached).

    sign_cleartext_with

    fn sign_cleartext_with(text : String, key :
    PrivateKey
    , hash :
    HashId
    , created? : Int) -> String raise

    Create a cleartext signed message with an explicit hash algorithm.

    sign_detached

    fn sign_detached(data : BytesView, key :
    PrivateKey
    , text_mode? : Bool, created? : Int) -> String raise

    Create a detached ASCII-armored signature over data.

    created is the signature creation time in seconds since 1970; without it the current time is used. Passing the time makes the signature reproducible for RSA PKCS#1 v1.5 and Ed25519 keys, which sign deterministically (for example for reproducible builds).

    Example

    test "detached signature round trip" {
    let key = @api.generate_ed25519_key("A <a@example.com>")
    let sig = @api.sign_detached(b"payload", key)
    inspect(sig.has_prefix("-----BEGIN PGP SIGNATURE-----"), content="true")
    assert_eq(
    @api.verify_detached(b"payload", sig, key.to_public()),
    @api.VerifyStatus::Ok,
    )
    assert_true(@api.verify_detached(b"tampered", sig, key.to_public()) is Failed)
    }

    sign_inline

    fn sign_inline(data : BytesView, key :
    PrivateKey
    , text_mode? : Bool, created? : Int, file_name? : String) -> String raise

    Create an inline-signed, armored message: One-Pass-Signature || Literal || Signature.

    created is the signature creation time in seconds since 1970; without it the current time is used (see sign_detached — deterministic for RSA/Ed25519 keys, so the message becomes reproducible). file_name names the literal packet (N9; default "message.txt").

    verify_cleartext

    fn verify_cleartext(message : String, verifier :
    PublicKey
    ) -> (String,
    VerifyStatus
    ) raise

    Verify a cleartext signed message. Returns the de-escaped, canonical cleartext and the verification status against verifier.

    verify_detached

    fn verify_detached(data : BytesView, armored_signature : String, verifier :
    PublicKey
    ) ->
    VerifyStatus
    raise

    Verify a detached armored signature over data using verifier.

    verify_inline

    fn verify_inline(message : String, verifier :
    PublicKey
    , max_decompressed? : Int) -> (Bytes,
    VerifyStatus
    ) raise

    Verify an inline-signed armored message; returns the literal data and the verification status against verifier.

    verify_key_signature

    Verify a self-certification or binding signature over key material. Deliberately public (F14 review decision): it is the key-management entry point for validating third-party keys, exercised from outside the package by the gopenpgp self-certification cross-validation tests.

    verify_pgp_mime_signed

    fn verify_pgp_mime_signed(mime_text : String, verifier :
    PublicKey
    ) -> (String,
    VerifyStatus
    ) raise

    Convenience: verify a received multipart/signed message against verifier; returns the signed text and the status.

    Source Files