1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
// 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
    }
};
use bacteria::Transcript;
use schnorr::SecretKey;
use schnorr::PublicKey;
use crate::ZeiError;


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


//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 rand::OsRng;
    use schnorr::Keypair;
    use mohan::byteorder::{ByteOrder, BigEndian};


    #[test]
    fn test_lock_unlock() { 
        //Sample Fresh Keypair
        let mut csprng: OsRng = OsRng::new().unwrap();
        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: OsRng = OsRng::new().unwrap();
        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);
    }

}