ProvenPin Install

PyNaCl SecretBox encryption + Ed25519 signing: working versions (tested 2026-10-18)

Authenticated symmetric encryption with SecretBox and Ed25519 signing with SigningKey/VerifyKey, showing tamper detection, nonce rules and Base64/Hex key encoding round-trips.

passed6 passed · 0 failed2026-10-18 03:57 UTC
Status
passed
Last passed
2026-10-18 03:57 UTC
Tests
6 / 6
Language
Python
Runtime
Python 3.13.16
pinned versions2
pynacl@1.6.2pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 39095370682

The code

Exactly the files that passed. Your agent can pull them with get_recipe("pynacl-secretbox-signing") (install), or see them on GitHub.

requirements.txt
pynacl==1.6.2
pytest==9.1.1
crypto.py
from nacl import encoding
from nacl.secret import SecretBox
from nacl.signing import SigningKey, VerifyKey
from nacl.utils import random


def new_secret_key() -> bytes:
    return random(SecretBox.KEY_SIZE)


def encrypt(secret_key: bytes, plaintext: bytes) -> bytes:
    return bytes(SecretBox(secret_key).encrypt(plaintext))


def decrypt(secret_key: bytes, blob: bytes) -> bytes:
    return SecretBox(secret_key).decrypt(blob)


def new_signing_key() -> SigningKey:
    return SigningKey.generate()


def sign(signing_key: SigningKey, message: bytes) -> bytes:
    return bytes(signing_key.sign(message))


def verify(verify_key: VerifyKey, signed: bytes) -> bytes:
    return verify_key.verify(signed)


def encode_key(key, encoder: encoding.Encoder = encoding.Base64Encoder) -> bytes:
    return key.encode(encoder=encoder)


def decode_signing_key(
    encoded: bytes, encoder: encoding.Encoder = encoding.Base64Encoder
) -> SigningKey:
    return SigningKey(encoded, encoder=encoder)


def decode_verify_key(
    encoded: bytes, encoder: encoding.Encoder = encoding.Base64Encoder
) -> VerifyKey:
    return VerifyKey(encoded, encoder=encoder)
tests/test_crypto.py
import pytest
from nacl import encoding
from nacl.exceptions import BadSignatureError, CryptoError
from nacl.secret import SecretBox
from nacl.utils import random

from crypto import (
    decode_signing_key,
    decode_verify_key,
    decrypt,
    encode_key,
    encrypt,
    new_secret_key,
    new_signing_key,
    sign,
    verify,
)


def test_secretbox_roundtrip_and_blob_layout():
    key = new_secret_key()
    blob = encrypt(key, b"attack at dawn")
    assert decrypt(key, blob) == b"attack at dawn"

    box = SecretBox(key)
    enc = box.encrypt(b"attack at dawn", random(SecretBox.NONCE_SIZE))
    assert bytes(enc) == enc.nonce + enc.ciphertext
    with pytest.raises(CryptoError):
        box.decrypt(bytes(enc), enc.nonce)
    assert box.decrypt(enc.ciphertext, enc.nonce) == b"attack at dawn"


def test_secretbox_tamper_detected():
    key = new_secret_key()
    blob = bytearray(encrypt(key, b"transfer $10"))
    blob[-1] ^= 0x01
    with pytest.raises(CryptoError):
        decrypt(key, bytes(blob))
    with pytest.raises(CryptoError):
        decrypt(new_secret_key(), encrypt(key, b"transfer $10"))


def test_secretbox_nonce_rules():
    key = new_secret_key()
    box = SecretBox(key)
    nonce = random(SecretBox.NONCE_SIZE)
    assert bytes(box.encrypt(b"same", nonce)) == bytes(box.encrypt(b"same", nonce))
    assert bytes(box.encrypt(b"same")) != bytes(box.encrypt(b"same", nonce))
    with pytest.raises(ValueError):
        box.encrypt(b"data", b"too-short")


def test_ed25519_sign_verify_and_message_layout():
    sk = new_signing_key()
    sm = sk.sign(b"release v1")
    assert bytes(sm) == sm.signature + sm.message
    assert len(sm.signature) == 64
    assert verify(sk.verify_key, bytes(sm)) == b"release v1"
    assert sk.verify_key.verify(sm.message, sm.signature) == b"release v1"


def test_ed25519_tamper_detected():
    sk = new_signing_key()
    signed = bytearray(sign(sk, b"pay bob $5"))
    signed[-1] ^= 0x01
    with pytest.raises(BadSignatureError):
        verify(sk.verify_key, bytes(signed))
    with pytest.raises(BadSignatureError):
        verify(new_signing_key().verify_key, sign(sk, b"pay bob $5"))


def test_key_encoding_roundtrip():
    sk = new_signing_key()
    restored = decode_signing_key(encode_key(sk))
    assert restored.verify_key.encode() == sk.verify_key.encode()
    assert sign(restored, b"hi") == sign(sk, b"hi")

    vk = sk.verify_key
    hex_vk = encode_key(vk, encoding.HexEncoder)
    assert decode_verify_key(hex_vk, encoding.HexEncoder).encode() == vk.encode()
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.