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, an MPC key share. 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.

useful wherever a secp256k1 secret crosses a trust boundary.

what it's for

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, when a key is already split across participants (a FROST or MPC sharing), each participant seals their share to the same recipient, with its own proof. together the sealed shares are a package; it checks out only when their public keys add up to the target the group certified. one wrong share fails the whole package up front.

two backends, one interface

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

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. nobody can seal junk that checks out now and fails when it is opened. 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.

the check is built in. verify returns the commitment it proved, not a bare ok; you accept only if it equals your target. there is no accept path that skips the comparison.

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