fift 0.2.2

Rust implementation of the Fift esoteric language
Documentation
use anyhow::{Context as _, Result};
use tycho_crypto::ed25519;

use crate::core::*;
use crate::util::{CRC_16, CRC_32, CRC_32_C};

pub struct Crypto;

#[fift_module]
impl Crypto {
    #[cmd(name = "newkeypair", stack)]
    fn interpret_newkeypair(stack: &mut Stack) -> Result<()> {
        let secret = rand::random::<ed25519::SecretKey>();
        let public = ed25519::PublicKey::from(&secret);
        stack.push(secret.as_bytes().to_vec())?;
        stack.push(public.as_bytes().to_vec())
    }

    #[cmd(name = "priv>pub", stack)]
    fn interpret_priv_key_to_pub(stack: &mut Stack) -> Result<()> {
        let secret = pop_secret_key(stack)?;
        stack.push(ed25519::PublicKey::from(&secret).as_bytes().to_vec())
    }

    #[cmd(name = "ed25519_sign", stack)]
    fn interpret_ed25519_sign(stack: &mut Stack) -> Result<()> {
        let secret = pop_secret_key(stack)?;
        let public = ed25519::PublicKey::from(&secret);
        let data = stack.pop_bytes()?;
        let signature = secret.expand().sign_raw(&data, &public);
        stack.push(signature.to_vec())
    }

    #[cmd(name = "ed25519_chksign", stack)]
    fn interpret_ed25519_chksign(stack: &mut Stack) -> Result<()> {
        let public = pop_public_key(stack)?;
        let signature = pop_signature(stack)?;
        let data = stack.pop_bytes()?;
        stack.push_bool(public.verify_raw(&data, &signature))
    }

    #[cmd(name = "ed25519_sign_uint", stack)]
    fn interpret_ed25519_sign_uint(stack: &mut Stack) -> Result<()> {
        let secret = pop_secret_key(stack)?;
        let public = ed25519::PublicKey::from(&secret);
        let int = stack.pop_int()?;
        anyhow::ensure!(
            int.sign() != num_bigint::Sign::Minus,
            "Expected a positive number"
        );
        anyhow::ensure!(
            int.bits() <= 256,
            "Ed25519 data to be signed must fit into 256 bits"
        );
        let (_, mut data) = int.to_bytes_le();
        data.resize(32, 0);
        data.reverse();

        let signature = secret.expand().sign_raw(&data, &public);
        stack.push(signature.to_vec())
    }

    #[cmd(name = "crc16", stack)]
    fn interpret_crc16(stack: &mut Stack) -> Result<()> {
        let bytes = stack.pop_bytes()?;
        let mut res = CRC_16.digest();
        res.update(bytes.as_slice());
        stack.push_int(res.finalize())
    }

    #[cmd(name = "crc32", stack)]
    fn interpret_crc32(stack: &mut Stack) -> Result<()> {
        let bytes = stack.pop_bytes()?;
        let mut res = CRC_32.digest();
        res.update(bytes.as_slice());
        stack.push_int(res.finalize())
    }

    #[cmd(name = "crc32c", stack)]
    fn interpret_crc32c(stack: &mut Stack) -> Result<()> {
        let bytes = stack.pop_bytes()?;
        let mut res = CRC_32_C.digest();
        res.update(bytes.as_slice());
        stack.push_int(res.finalize())
    }
}

fn pop_secret_key(stack: &mut Stack) -> Result<ed25519::SecretKey> {
    let b = stack.pop_bytes()?;
    Ok(ed25519::SecretKey::from_bytes(
        b.as_slice().try_into().ok().context("Invalid secret key")?,
    ))
}

fn pop_public_key(stack: &mut Stack) -> Result<ed25519::PublicKey> {
    let b = stack.pop_bytes()?;
    if let Ok(b) = b.as_slice().try_into()
        && let Some(key) = ed25519::PublicKey::from_bytes(b)
    {
        return Ok(key);
    }
    anyhow::bail!("Invalid public key")
}

fn pop_signature(stack: &mut Stack) -> Result<[u8; 64]> {
    let b = stack.pop_bytes()?;
    b.as_slice().try_into().ok().context("Invalid signature")
}