ve-core

verifiable encryption for secp256k1 secrets.

seal a secp256k1 scalar to a recipient key. anyone can verify it maps to a given public key, without seeing it or decrypting it.

what it is

verifiable encryption is encryption with a public check attached. the recipient can open the ciphertext; anyone else can verify, from public data, what it decrypts to, without decrypting it.

ve-core does this for one plaintext: a secp256k1 scalar. a private key, a FROST signing share, a share of an MPC-distributed key. seal it to a recipient key; verify rejects it unless the scalar's public key is the target you name, c = m·g must equal t. a holder of only the public key learns exactly which scalar is sealed, and never sees it.

useful wherever a secp256k1 secret crosses a trust boundary: escrow, custody handoff, auditable backup, wallet recovery, an MPC setup. it is the difference between a spare key you assume fits and one you have already tried in the lock.

one key, or a package

one scalar, one recipient: you know the target t = m·g, seal the scalar to the recipient key, and anyone with the ciphertext, target, and context can verify it. only the recipient's secret key opens it.

or split the scalar into shares (a FROST or MPC sharing), so no one holds it whole. seal each share to its own recipient with its own proof. that bundle is a package; it checks out only when the shares' public keys add up to the target your protocol expects. one swapped or junk share fails up front.

single or split, the same narrow job: verifiable encryption of secp256k1 scalars.

two backends, one interface

ve-core is the seam: the plaintext, the binding context, and the backend trait family. pick a backend and the rest is the same.

what makes it hold up

it cannot lie about what is inside. the proof is knowledge-sound, so a passing check pins the ciphertext to one scalar: the private key for the public key you named. no sealing junk or a wrong key that only surfaces when someone finally opens it. checked when the ciphertext is made, recipient absent.

it does not reveal the secret. to anyone without the recipient key, the ciphertext leaks nothing beyond its commitment, which was public anyway.

you cannot forget to check. verify hands back the commitment it proved, never a bare ok; you accept only if it equals the one you expected. the check is the return value, not a step you can skip.

using it

let (pk, sk) = keygen()?;
let ct = pk.seal(&secret, &ctx)?;         // ciphertext + correctness proof
pk.verify(&ct, &target, &ctx)?;           // proof holds AND the scalar maps to `target`
let secret = sk.open(&ct, &ctx)?;         // verify, then decrypt

pure rust. variable-time, for software adversaries observing messages and stored ciphertexts, not local side-channel attackers. heavily audited. not published yet: no crates.io, no public source.

reading

cl-hsmq

ec-segve