use crate::types::{KeyImage, Nonce, OwnershipProof, PublicKey, SecretKey, Signature};
use extism_pdk::{FnResult, Json, plugin_fn};
use serde::{Deserialize, Serialize};
use zeroize::Zeroizing;
#[derive(Serialize)]
pub struct IdentityOut {
pub secret: String,
pub public: String,
}
#[plugin_fn]
pub fn generate_identity() -> FnResult<Json<IdentityOut>> {
let id = crate::generate_identity();
Ok(Json(IdentityOut {
secret: id.secret_key.to_prefixed(),
public: id.public_key.to_prefixed(),
}))
}
#[derive(Deserialize)]
pub struct DerivePublicIn {
pub secret: String,
}
#[derive(Serialize)]
pub struct DerivePublicOut {
pub public: String,
}
#[plugin_fn]
pub fn derive_public_key(Json(input): Json<DerivePublicIn>) -> FnResult<Json<DerivePublicOut>> {
let secret = Zeroizing::new(input.secret);
let sk = SecretKey::from_prefixed(&secret)?;
Ok(Json(DerivePublicOut {
public: sk.public_key().to_prefixed(),
}))
}
#[derive(Deserialize)]
pub struct SignIn {
pub secret: String,
pub vote: String,
pub election_id: String,
pub ring: Vec<String>,
}
#[derive(Serialize)]
pub struct SignOut {
pub signature: String,
pub key_image: String,
}
#[plugin_fn]
pub fn sign_vote_str(Json(input): Json<SignIn>) -> FnResult<Json<SignOut>> {
let vote_bytes = input.vote.into_bytes();
sign_with_bytes(input.secret, &vote_bytes, &input.election_id, &input.ring)
}
#[plugin_fn]
pub fn sign_vote_hex(Json(input): Json<SignIn>) -> FnResult<Json<SignOut>> {
let vote_bytes = hex::decode(&input.vote)?;
sign_with_bytes(input.secret, &vote_bytes, &input.election_id, &input.ring)
}
fn sign_with_bytes(
secret: String,
vote: &[u8],
election_id: &str,
ring: &[String],
) -> FnResult<Json<SignOut>> {
let secret = Zeroizing::new(secret);
let sk = SecretKey::from_prefixed(&secret)?;
let ring: Vec<PublicKey> = ring
.iter()
.map(|h| PublicKey::from_prefixed(h))
.collect::<Result<_, _>>()?;
let proof = crate::sign_vote(&sk, vote, election_id, &ring)?;
Ok(Json(SignOut {
signature: proof.signature.to_prefixed(),
key_image: proof.key_image.to_prefixed(),
}))
}
#[derive(Deserialize)]
pub struct VerifyIn {
pub vote: String,
pub election_id: String,
pub signature: String,
pub key_image: String,
pub ring: Vec<String>,
}
#[derive(Serialize)]
pub struct VerifyOut {
pub valid: bool,
}
#[plugin_fn]
pub fn verify_vote_str(Json(input): Json<VerifyIn>) -> FnResult<Json<VerifyOut>> {
let valid = verify_with_bytes(input.vote.as_bytes(), &input).unwrap_or(false);
Ok(Json(VerifyOut { valid }))
}
#[plugin_fn]
pub fn verify_vote_hex(Json(input): Json<VerifyIn>) -> FnResult<Json<VerifyOut>> {
let valid = hex::decode(&input.vote)
.ok()
.and_then(|vote| verify_with_bytes(&vote, &input))
.unwrap_or(false);
Ok(Json(VerifyOut { valid }))
}
#[derive(Deserialize)]
pub struct IsValidSecretIn {
pub secret: String,
}
#[derive(Serialize)]
pub struct IsValidSecretOut {
pub valid: bool,
}
#[plugin_fn]
pub fn is_valid_secret_key(Json(input): Json<IsValidSecretIn>) -> FnResult<Json<IsValidSecretOut>> {
let secret = Zeroizing::new(input.secret);
Ok(Json(IsValidSecretOut {
valid: SecretKey::is_valid_prefixed(&secret),
}))
}
#[derive(Serialize)]
pub struct GenerateNonceOut {
pub nonce: String,
}
#[plugin_fn]
pub fn generate_nonce() -> FnResult<Json<GenerateNonceOut>> {
Ok(Json(GenerateNonceOut {
nonce: crate::generate_nonce().to_prefixed(),
}))
}
#[derive(Deserialize)]
pub struct ProveOwnershipIn {
pub secret: String,
pub election_id: String,
pub nonce: String,
}
#[derive(Serialize)]
pub struct ProveOwnershipOut {
pub proof: String,
}
#[plugin_fn]
pub fn prove_ownership(Json(input): Json<ProveOwnershipIn>) -> FnResult<Json<ProveOwnershipOut>> {
let secret = Zeroizing::new(input.secret);
let sk = SecretKey::from_prefixed(&secret)?;
let nonce = Nonce::from_prefixed(&input.nonce)?;
let proof = crate::prove_ownership(&sk, &input.election_id, nonce.as_bytes());
Ok(Json(ProveOwnershipOut {
proof: proof.to_prefixed(),
}))
}
#[derive(Deserialize)]
pub struct VerifyOwnershipIn {
pub public: String,
pub key_image: String,
pub election_id: String,
pub nonce: String,
pub proof: String,
}
#[derive(Serialize)]
pub struct VerifyOwnershipOut {
pub valid: bool,
}
#[plugin_fn]
pub fn verify_ownership(
Json(input): Json<VerifyOwnershipIn>,
) -> FnResult<Json<VerifyOwnershipOut>> {
let valid = verify_ownership_inner(&input).unwrap_or(false);
Ok(Json(VerifyOwnershipOut { valid }))
}
fn verify_ownership_inner(input: &VerifyOwnershipIn) -> Option<bool> {
let public = PublicKey::from_prefixed(&input.public).ok()?;
let key_image = KeyImage::from_prefixed(&input.key_image).ok()?;
let nonce = Nonce::from_prefixed(&input.nonce).ok()?;
let proof = OwnershipProof::from_prefixed(&input.proof).ok()?;
Some(crate::verify_ownership(
&public,
&key_image,
&input.election_id,
nonce.as_bytes(),
&proof,
))
}
fn verify_with_bytes(vote: &[u8], input: &VerifyIn) -> Option<bool> {
let ring: Vec<PublicKey> = input
.ring
.iter()
.map(|h| PublicKey::from_prefixed(h).ok())
.collect::<Option<_>>()?;
let signature = Signature::from_prefixed(&input.signature, ring.len()).ok()?;
let key_image = KeyImage::from_prefixed(&input.key_image).ok()?;
Some(crate::verify_vote(
vote,
&input.election_id,
&signature,
&key_image,
&ring,
))
}