Pure-MoonBit OpenPGP implementation (RFC 4880/9580): email encryption, decryption, signing and verification, interoperable with GnuPG and gopenpgp.
moon add justinwongcn/moonpgpimport {
"justinwongcn/moonpgp",
"justinwongcn/moonpgp/mime",
"justinwongcn/moonpgp/mail",
}///|
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")
}
}///|
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")
}
}///|
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)
}///|
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)
}git clone https://github.com/justinwongcn/moonpgp
cd moonpgp
moon run cmd/main -- helpmoon check --target all --deny-warn
moon build --target all
moon test --target native
moon fmt --checklet DEFAULT_MAX_DECOMPRESSED_BYTES : Intfn decrypt_and_verify(message : String, decryption_keys : Array[PrivateKey], verifier : PublicKey?, max_decompressed? : Int) -> (Bytes, VerifyStatus) raisefn decrypt_binary_with_password(data : BytesView, passphrase : String, argon2_max_memory_kib? : Int, max_decompressed? : Int) -> Bytes raisefn decrypt_binary_with_private_key(data : BytesView, key : PrivateKey, max_decompressed? : Int) -> Bytes raisefn decrypt_with_password(message : String, passphrase : String, argon2_max_memory_kib? : Int, max_decompressed? : Int) -> Bytes raisefn decrypt_with_private_key(message : String, key : PrivateKey, max_decompressed? : Int) -> Bytes raisefn encrypt_to_recipients(plaintext : BytesView, recipients : Array[PublicKey], file_name? : String, compress? : CompressedAlgo) -> String raisefn encrypt_to_session_key(plaintext : BytesView, sk : SessionKey, file_name? : String, compress? : CompressedAlgo) -> String raisefn encrypt_with_password(plaintext : BytesView, passphrase : String, file_name? : String, aead? : Bool, argon2? : Bool, argon2_memory_exp? : Int, compress? : CompressedAlgo) -> String raisetest "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!")
}fn encrypt_with_password_argon2(plaintext : BytesView, passphrase : String, file_name? : String, passes? : Int, parallelism? : Int, memory_exp? : Int, compress? : CompressedAlgo) -> String raisefn 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 raisetest "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")
}fn generate_revocation_certificate(key : PrivateKey, reason? : RevocationReason, message? : String, created? : Int) -> String raisetest "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",
)
}fn recover_session_key_with_password(message : String, passphrase : String, argon2_max_memory_kib? : Int) -> SessionKey raisefn revoke_key(key : PrivateKey, reason? : RevocationReason, message? : String, created? : Int) -> PrivateKey raisefn revoke_subkey(key : PrivateKey, subkey_id : BytesView, reason? : RevocationReason, message? : String, created? : Int) -> PrivateKey raisefn sign_and_encrypt(data : BytesView, signer : PrivateKey, recipients : Array[PublicKey], text_mode? : Bool, compress? : CompressedAlgo, created? : Int, file_name? : String) -> String raisefn sign_cleartext_with(text : String, key : PrivateKey, hash : HashId, created? : Int) -> String raisefn sign_detached(data : BytesView, key : PrivateKey, text_mode? : Bool, created? : Int) -> String raisetest "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)
}fn sign_inline(data : BytesView, key : PrivateKey, text_mode? : Bool, created? : Int, file_name? : String) -> String raisefn verify_detached(data : BytesView, armored_signature : String, verifier : PublicKey) -> VerifyStatus raisefn verify_inline(message : String, verifier : PublicKey, max_decompressed? : Int) -> (Bytes, VerifyStatus) raisefn verify_key_signature(sig : SignaturePacket, signer_pub : PublicKeyPacket, certified_pub : PublicKeyPacket, user_id? : String, direct? : Bool) -> Bool raiseInstall
Download zipPure-MoonBit OpenPGP implementation (RFC 4880/9580): email encryption, decryption, signing and verification, interoperable with GnuPG and gopenpgp.