zei 0.0.10

Zei: Confidential Assets
Documentation
// Copyright 2019 Stichting Organism
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Elgamal encryption

use mohan::{
    ser,
    dalek::{
        scalar::Scalar,
        constants::RISTRETTO_BASEPOINT_TABLE,
        ristretto::CompressedRistretto,
        traits::Identity
    },
    mohan_rand
};
use bacteria::Transcript;
use schnorr::SecretKey;
use schnorr::PublicKey;
use crate::ZeiError;
use serde::{ Serialize, Deserialize };


#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
/// On Chain Magic Box
pub struct Lockbox {
    /// our blinded randomned g^R
    pub rand: CompressedRistretto,
    /// Encrypted data
    pub data: Vec<u8>,
}

impl Default for Lockbox {
    fn default() -> Lockbox {
        Lockbox {
            rand: CompressedRistretto::identity(),
            data: Vec::new()
        }
    }
}

//locks a given amount with reciever PK and returns cipher text and rand
pub fn lock(randomness: &Scalar, publickey: &PublicKey, message: &[u8]) -> Lockbox {
        //New strobe context
        let mut ctx = Transcript::new(b"Zei.Lockbox");
        //commit pk
        ctx.append_message(b"PK", publickey.as_bytes());
        
        //get our enc key and blinded randomness
        //g^(x*r), g^r
        
        //pk^R
        let shared_key = randomness * publickey.as_point();

        //g^R where R = randomness used to derive shared key
        let blind_rand = randomness * &RISTRETTO_BASEPOINT_TABLE;
        
        //key strobe with shared key
        ctx.strobe.key(shared_key.compress().as_bytes(), false);

        let mut buf = message.to_vec();
        ctx.strobe.send_enc(buf.as_mut_slice(), false);
        
        return Lockbox {
            data: buf,
            rand: blind_rand.compress()
        };
}


/// Open the box with the corresponding secret key & randomness
pub fn unlock(boxy: &mut Lockbox, secretkey: &SecretKey, publickey: &PublicKey) -> Result<(), ZeiError> {
    //g^x = pk. x is secret key, g^R == rand 
    //New strobe context
    let mut ctx = Transcript::new(b"Zei.Lockbox");

    //commit pk
    ctx.append_message(b"PK", publickey.as_bytes());

    //REDERIVED_KEY == (g^R)^x
    let dec_key = boxy.rand.decompress().unwrap() * secretkey.as_scalar();
    
    //feed our secret
    ctx.strobe.key(dec_key.compress().as_bytes(), false);
    //open sesame
    ctx.strobe.recv_enc(&mut boxy.data, false);

    Ok(())
    
}

impl ser::Writeable for Lockbox {
	fn write<W: ser::Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
		self.rand.write(writer)?;
        //Data length
        writer.write_u64(self.data.len() as u64)?;
        //Raw Data
		self.data.write(writer)?;

        Ok(())
	}
}

impl ser::Readable for Lockbox {
	fn read(reader: &mut dyn ser::Reader) -> Result<Lockbox, ser::Error> {
		let rand = CompressedRistretto::read(reader)?;
        let data_len = reader.read_u64()?;
        let raw_data = reader.read_fixed_bytes(data_len as usize)?;
      
		Ok(Lockbox{
            rand: rand,
            data: raw_data
        })
	}
}


#[cfg(test)]
mod test {
    use super::*;
    use mohan::mohan_rand;
    use schnorr::Keypair;
    use mohan::byteorder::{ByteOrder, BigEndian};


    #[test]
    fn test_lock_unlock() { 
        //Sample Fresh Keypair
        let mut csprng = mohan_rand();
        let keypair: Keypair = Keypair::generate(&mut csprng);
        
        //1. Sample Fresh blinding factor [blind], its a scalar
        let blinding_t = Scalar::random(&mut csprng);
        // coins to send
        let amount: u32 = 101;

        //7. Encrypt to receiver pubkey both the amount transferred and the blinding factor [blind] 
        let mut to_encrypt = Vec::new();
        //first add amount which is fixed 4 bytes in big endian
        let mut encoded_amount = [0; 4];
        BigEndian::write_u32(&mut encoded_amount, amount);
       
        //println!("encoded_amount: {:?}", encoded_amount);
        to_encrypt.extend_from_slice(&encoded_amount);
        //next add the blind
        to_encrypt.extend_from_slice(&blinding_t.to_bytes());

        //println!("to_encrypt: {:?}", to_encrypt);
        
        //lock em up
        let rand_t = Scalar::random(&mut csprng);
        let mut lbox = lock(&rand_t, &keypair.public, &to_encrypt);

        //Now we unbox to check if we get same results
        
        //unlock encrypted box
        let unlocked = unlock(&mut lbox, &keypair.secret, &keypair.public);
        assert!(unlocked.is_ok());
        //extract balance value & blind value
        let (raw_amount, _raw_blind) = lbox.data.split_at(5);
        //println!("unlocked value: {:?}", unlocked);
        //convert to u32
        let p_amount = BigEndian::read_u32(&raw_amount);
        
        //println!("amount open: {:?}", p_amount);
        //check if amounts are the same
        assert_eq!(p_amount, amount);
    }

     #[test]
    fn test_box_roundtrip() { 
        //Sample Fresh Keypair
         let mut csprng = mohan_rand();
        let keypair: Keypair = Keypair::generate(&mut csprng);
        
        //1. Sample Fresh blinding factor [blind], its a scalar
        let blinding_t = Scalar::random(&mut csprng);
        // coins to send
        let amount: u32 = 101;

        //7. Encrypt to receiver pubkey both the amount transferred and the blinding factor [blind] 
        let mut to_encrypt = Vec::new();
        //first add amount which is fixed 4 bytes in big endian
        let mut encoded_amount = [0; 4];
        BigEndian::write_u32(&mut encoded_amount, amount);
       
        //println!("encoded_amount: {:?}", encoded_amount);
        to_encrypt.extend_from_slice(&encoded_amount);
        //next add the blind
        to_encrypt.extend_from_slice(&blinding_t.to_bytes());

        //println!("to_encrypt: {:?}", to_encrypt);
        
        //lock em up
        let rand_t = Scalar::random(&mut csprng);
        let lbox = lock(&rand_t, &keypair.public, &to_encrypt);

        //Now we ser/der
        use std::io::{self, Write, Read};

        // Write
        let mut tmpfile = tempfile::NamedTempFile::new().unwrap();
        // Re-open it.
        let mut read_handle = tmpfile.reopen().unwrap();

        let _x = mohan::ser::serialize_default(&mut tmpfile, &lbox).expect("serialization failed");
        let mut lbox: Lockbox = mohan::ser::deserialize_default(&mut read_handle).unwrap();
            
        //unlock encrypted box
        let unlocked = unlock(&mut lbox, &keypair.secret, &keypair.public);
        assert!(unlocked.is_ok());
        //extract balance value & blind value
        let (raw_amount, _raw_blind) = lbox.data.split_at(5);
        //println!("unlocked value: {:?}", unlocked);
        //convert to u32
        let p_amount = BigEndian::read_u32(&raw_amount);
        
        //println!("amount open: {:?}", p_amount);
        //check if amounts are the same
        assert_eq!(p_amount, amount);
    }

}