diff --git a/Cargo.lock b/Cargo.lock index ff89f23d..997f2f72 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1444,6 +1444,7 @@ version = "4.0.0-alpha.1" dependencies = [ "chrono", "easy-jsonrpc-mw", + "ed25519-dalek", "failure", "failure_derive", "futures 0.3.5", @@ -1465,7 +1466,6 @@ dependencies = [ "tokio", "url 1.7.2", "uuid", - "x25519-dalek", ] [[package]] @@ -1509,6 +1509,7 @@ version = "4.0.0-alpha.1" dependencies = [ "age", "base64 0.9.3", + "bech32", "blake2-rfc", "bs58", "byteorder", diff --git a/controller/Cargo.toml b/controller/Cargo.toml index cb5624ca..d9d96252 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -38,4 +38,4 @@ grin_wallet_libwallet = { path = "../libwallet", version = "4.0.0-alpha.1" } grin_wallet_config = { path = "../config", version = "4.0.0-alpha.1" } [dev-dependencies] -x25519-dalek = "0.6" +ed25519-dalek = "1.0.0-pre.1" diff --git a/controller/tests/slatepack.rs b/controller/tests/slatepack.rs index 5447d439..ccd9d001 100644 --- a/controller/tests/slatepack.rs +++ b/controller/tests/slatepack.rs @@ -22,17 +22,18 @@ use grin_wallet_util::grin_core as core; use grin_wallet_util::OnionV3Address; use impls::test_framework::{self, LocalWalletClient}; -use impls::{ - PathToSlatepack, PathToSlatepackArmored, SlateGetter as _, SlatePutter as _, SlatepackArgs, -}; +use impls::{PathToSlatepack, SlatePutter as _}; use std::sync::atomic::Ordering; use std::thread; use std::time::Duration; -use grin_wallet_libwallet::{InitTxArgs, IssueInvoiceTxArgs, Slate, Slatepack}; +use grin_wallet_libwallet::{ + InitTxArgs, IssueInvoiceTxArgs, Slate, Slatepack, SlatepackAddress, Slatepacker, + SlatepackerArgs, +}; -use x25519_dalek::PublicKey as xDalekPublicKey; -use x25519_dalek::StaticSecret; +use ed25519_dalek::PublicKey as edDalekPublicKey; +use ed25519_dalek::SecretKey as edDalekSecretKey; #[macro_use] mod common; @@ -43,54 +44,37 @@ fn output_slatepack( file: &str, armored: bool, use_bin: bool, - sender: Option, - recipients: Vec, + sender: Option, + recipients: Vec, ) -> Result<(), libwallet::Error> { + let packer = Slatepacker::new(SlatepackerArgs { + sender, + recipients, + dec_key: None, + }); + let mut file = file.into(); if armored { - let file = format!("{}.armored", file); - let args = SlatepackArgs { - pathbuf: file.into(), - sender, - recipients, - dec_key: None, - }; - PathToSlatepackArmored::new(args).put_tx(&slate, use_bin) - } else { - let args = SlatepackArgs { - pathbuf: file.into(), - sender, - recipients, - dec_key: None, - }; - PathToSlatepack::new(args).put_tx(&slate, use_bin) + file = format!("{}.armored", file); } + PathToSlatepack::new(file.into(), &packer, armored).put_tx(&slate, use_bin) } fn slate_from_packed( file: &str, armored: bool, - dec_key: Option<&StaticSecret>, + dec_key: Option<&edDalekSecretKey>, ) -> Result<(Slatepack, Slate), libwallet::Error> { + let packer = Slatepacker::new(SlatepackerArgs { + sender: None, + recipients: vec![], + dec_key, + }); + let mut file = file.into(); if armored { - let file = format!("{}.armored", file); - let args = SlatepackArgs { - pathbuf: file.into(), - sender: None, - recipients: vec![], - dec_key, - }; - let pts = PathToSlatepackArmored::new(args); - Ok((pts.get_slatepack()?, pts.get_tx()?.0)) - } else { - let args = SlatepackArgs { - pathbuf: file.into(), - sender: None, - recipients: vec![], - dec_key, - }; - let pts = PathToSlatepack::new(args); - Ok((pts.get_slatepack()?, pts.get_tx()?.0)) + file = format!("{}.armored", file); } + let slatepack = PathToSlatepack::new(file.into(), &packer, armored).get_slatepack()?; + Ok((slatepack.clone(), packer.get_slate(&slatepack)?)) } /// self send impl @@ -165,34 +149,38 @@ fn slatepack_exchange_test_impl( let (recipients_1, dec_key_1, sender_1) = match use_encryption { true => { - let mut rec_address = xDalekPublicKey::from([0u8; 32]); - let mut sec_key = StaticSecret::from([0u8; 32]); + let mut rec_address = SlatepackAddress::random(); + let mut sec_key = edDalekSecretKey::from_bytes(&[0u8; 32]).unwrap(); wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { - let ed25519_sec_key = api.get_secret_key(m, 0)?; - let mut b = [0u8; 32]; - b.copy_from_slice(&ed25519_sec_key.as_ref()[0..32]); - sec_key = StaticSecret::from(b); - rec_address = xDalekPublicKey::from(&sec_key); + sec_key = api.get_secret_key(m, 0)?; + let pub_key = edDalekPublicKey::from(&sec_key); + rec_address = SlatepackAddress::new(&pub_key); Ok(()) })?; - (vec![rec_address], Some(sec_key), Some(rec_address.clone())) + ( + vec![rec_address.clone()], + Some(sec_key), + Some(rec_address.clone()), + ) } false => (vec![], None, None), }; let (recipients_2, dec_key_2, sender_2) = match use_encryption { true => { - let mut rec_address = xDalekPublicKey::from([0u8; 32]); - let mut sec_key = StaticSecret::from([0u8; 32]); + let mut rec_address = SlatepackAddress::random(); + let mut sec_key = edDalekSecretKey::from_bytes(&[0u8; 32]).unwrap(); wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { - let ed25519_sec_key = api.get_secret_key(m, 0)?; - let mut b = [0u8; 32]; - b.copy_from_slice(&ed25519_sec_key.as_ref()[0..32]); - sec_key = StaticSecret::from(b); - rec_address = xDalekPublicKey::from(&sec_key); + sec_key = api.get_secret_key(m, 0)?; + let pub_key = edDalekPublicKey::from(&sec_key); + rec_address = SlatepackAddress::new(&pub_key); Ok(()) })?; - (vec![rec_address], Some(sec_key), Some(rec_address.clone())) + ( + vec![rec_address.clone()], + Some(sec_key), + Some(rec_address.clone()), + ) } false => (vec![], None, None), }; @@ -232,7 +220,7 @@ fn slatepack_exchange_test_impl( &send_file, use_armored, use_bin, - sender_1, + sender_1.clone(), recipients_2.clone(), )?; api.tx_lock_outputs(m, &slate)?; @@ -257,8 +245,8 @@ fn slatepack_exchange_test_impl( use_armored, use_bin, // re-encrypt for sender! - sender_2, - match slatepack.sender { + sender_2.clone(), + match slatepack.sender.clone() { Some(s) => vec![s.clone()], None => vec![], }, @@ -326,7 +314,7 @@ fn slatepack_exchange_test_impl( &send_file, use_armored, use_bin, - sender_2, + sender_2.clone(), recipients_1.clone(), )?; Ok(()) @@ -352,8 +340,8 @@ fn slatepack_exchange_test_impl( &receive_file, use_armored, use_bin, - sender_1, - match slatepack.sender { + sender_1.clone(), + match slatepack.sender.clone() { Some(s) => vec![s.clone()], None => vec![], }, diff --git a/impls/src/adapters/mod.rs b/impls/src/adapters/mod.rs index 4ca603dd..f1102955 100644 --- a/impls/src/adapters/mod.rs +++ b/impls/src/adapters/mod.rs @@ -20,7 +20,7 @@ mod slatepack; pub use self::file::PathToSlate; pub use self::http::{HttpSlateSender, SchemeNotHttp}; pub use self::keybase::{KeybaseAllChannels, KeybaseChannel}; -pub use self::slatepack::{PathToSlatepack, PathToSlatepackArmored, SlatepackArgs}; +pub use self::slatepack::PathToSlatepack; use crate::config::{TorConfig, WalletConfig}; use crate::libwallet::{Error, ErrorKind, Slate}; diff --git a/impls/src/adapters/slatepack.rs b/impls/src/adapters/slatepack.rs index dcff4370..acd5bfcb 100644 --- a/impls/src/adapters/slatepack.rs +++ b/impls/src/adapters/slatepack.rs @@ -12,90 +12,59 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::convert::TryFrom; /// Slatepack Output 'plugin' implementation use std::fs::File; use std::io::{Read, Write}; use std::path::PathBuf; -use x25519_dalek::PublicKey as xDalekPublicKey; -use x25519_dalek::StaticSecret; - -use crate::libwallet::{ - Error, ErrorKind, Slate, SlateVersion, Slatepack, SlatepackArmor, SlatepackBin, - VersionedBinSlate, VersionedSlate, -}; +use crate::libwallet::{Error, ErrorKind, Slate, Slatepack, SlatepackBin, Slatepacker}; use crate::{SlateGetter, SlatePutter}; use grin_wallet_util::byte_ser; -#[derive(Clone)] -pub struct SlatepackArgs<'a> { +// And Slate putter impls to output to files +pub struct PathToSlatepack<'a> { pub pathbuf: PathBuf, - pub sender: Option, - pub recipients: Vec, - pub dec_key: Option<&'a StaticSecret>, + pub packer: &'a Slatepacker<'a>, + pub armor_output: bool, } -pub struct PathToSlatepack<'a>(SlatepackArgs<'a>); - impl<'a> PathToSlatepack<'a> { /// Create with pathbuf and recipients - pub fn new(args: SlatepackArgs<'a>) -> Self { - Self(args) + pub fn new(pathbuf: PathBuf, packer: &'a Slatepacker<'a>, armor_output: bool) -> Self { + Self { + pathbuf, + packer, + armor_output, + } } pub fn get_slatepack_file_contents(&self) -> Result, Error> { - let mut pub_tx_f = File::open(&self.0.pathbuf)?; + let mut pub_tx_f = File::open(&self.pathbuf)?; let mut data = Vec::new(); pub_tx_f.read_to_end(&mut data)?; Ok(data) } - // return slatepack itself - pub fn deser_slatepack(&self, data: Vec) -> Result { - // try as bin first, then as json - let bin_res = byte_ser::from_bytes::(&data); - match bin_res { - Err(e) => debug!("Not a valid binary slatepack: {} - Will try JSON", e), - Ok(s) => return Ok(s.0), - } - // Otherwise try json - let content = String::from_utf8(data).map_err(|_| ErrorKind::SlatepackDeser)?; - let slatepack: Slatepack = serde_json::from_str(&content).map_err(|e| { - error!("Error reading JSON Slatepack: {}", e); - ErrorKind::SlatepackDeser - })?; - slatepack.ver_check_warn(); - Ok(slatepack) - } - pub fn get_slatepack(&self) -> Result { let data = self.get_slatepack_file_contents()?; - self.deser_slatepack(data) - } - - // Create slatepack from slate and args - pub fn create_slatepack(&self, slate: &Slate) -> Result { - let out_slate = VersionedSlate::into_version(slate.clone(), SlateVersion::V4)?; - let bin_slate = - VersionedBinSlate::try_from(out_slate).map_err(|_| ErrorKind::SlatepackSer)?; - let mut slatepack = Slatepack::default(); - slatepack.payload = byte_ser::to_bytes(&bin_slate).map_err(|_| ErrorKind::SlatepackSer)?; - slatepack.sender = self.0.sender; - slatepack.try_encrypt_payload(self.0.recipients.clone())?; - Ok(slatepack) + self.packer.deser_slatepack(data) } } impl<'a> SlatePutter for PathToSlatepack<'a> { fn put_tx(&self, slate: &Slate, as_bin: bool) -> Result<(), Error> { - let slatepack = self.create_slatepack(slate)?; - let mut pub_tx = File::create(&self.0.pathbuf)?; + let slatepack = self.packer.create_slatepack(slate)?; + let mut pub_tx = File::create(&self.pathbuf)?; if as_bin { - pub_tx.write_all( - &byte_ser::to_bytes(&SlatepackBin(slatepack)) - .map_err(|_| ErrorKind::SlatepackSer)?, - )?; + if self.armor_output { + let armored = self.packer.armor_slatepack(&slatepack)?; + pub_tx.write_all(armored.as_bytes())?; + } else { + pub_tx.write_all( + &byte_ser::to_bytes(&SlatepackBin(slatepack)) + .map_err(|_| ErrorKind::SlatepackSer)?, + )?; + } } else { pub_tx.write_all( serde_json::to_string_pretty(&slatepack) @@ -111,59 +80,7 @@ impl<'a> SlatePutter for PathToSlatepack<'a> { impl<'a> SlateGetter for PathToSlatepack<'a> { fn get_tx(&self) -> Result<(Slate, bool), Error> { let data = self.get_slatepack_file_contents()?; - let mut slatepack = self.deser_slatepack(data)?; - slatepack.try_decrypt_payload(self.0.dec_key)?; - let slate = byte_ser::from_bytes::(&slatepack.payload) - .map_err(|_| ErrorKind::SlatepackSer)?; - Ok((Slate::upgrade(slate.into())?, true)) - } -} - -pub struct PathToSlatepackArmored<'a>(SlatepackArgs<'a>); - -impl<'a> PathToSlatepackArmored<'a> { - /// Create with pathbuf and recipients - pub fn new(args: SlatepackArgs<'a>) -> Self { - Self(args) - } - - /// decode armor - pub fn decode_armored_file(&self) -> Result, Error> { - let mut pub_sp_armored = File::open(&self.0.pathbuf)?; - let mut data = Vec::new(); - pub_sp_armored.read_to_end(&mut data)?; - SlatepackArmor::decode(&String::from_utf8(data).unwrap()) - } - - // return slatepack - pub fn get_slatepack(&self) -> Result { - let data = self.decode_armored_file()?; - let pts = PathToSlatepack::new(self.0.clone()); - pts.deser_slatepack(data) - } -} - -impl<'a> SlatePutter for PathToSlatepackArmored<'a> { - fn put_tx(&self, slate: &Slate, _as_bin: bool) -> Result<(), Error> { - let pts = PathToSlatepack::new(self.0.clone()); - let slatepack = pts.create_slatepack(slate)?; - let armored = SlatepackArmor::encode(&slatepack, 3)?; - let mut pub_tx = File::create(&self.0.pathbuf)?; - pub_tx.write_all(armored.as_bytes())?; - pub_tx.sync_all()?; - Ok(()) - } -} - -impl<'a> SlateGetter for PathToSlatepackArmored<'a> { - fn get_tx(&self) -> Result<(Slate, bool), Error> { - let mut slatepack = self.get_slatepack()?; - slatepack.try_decrypt_payload(self.0.dec_key)?; - let slate_bin = - byte_ser::from_bytes::(&slatepack.payload).map_err(|e| { - error!("Error reading slate from armored slatepack: {}", e); - ErrorKind::SlatepackDeser - })?; - Ok((Slate::upgrade(slate_bin.into())?, true)) + let slatepack = self.packer.deser_slatepack(data)?; + Ok((self.packer.get_slate(&slatepack)?, true)) } } diff --git a/impls/src/lib.rs b/impls/src/lib.rs index 7b629350..39eca1e6 100644 --- a/impls/src/lib.rs +++ b/impls/src/lib.rs @@ -45,8 +45,7 @@ pub mod tor; pub use crate::adapters::{ create_sender, HttpSlateSender, KeybaseAllChannels, KeybaseChannel, PathToSlate, - PathToSlatepack, PathToSlatepackArmored, SlateGetter, SlatePutter, SlateReceiver, SlateSender, - SlatepackArgs, + PathToSlatepack, SlateGetter, SlatePutter, SlateReceiver, SlateSender, }; pub use crate::backends::{wallet_db_exists, LMDBBackend}; pub use crate::error::{Error, ErrorKind}; diff --git a/libwallet/Cargo.toml b/libwallet/Cargo.toml index 6aa746c6..3d7231ab 100644 --- a/libwallet/Cargo.toml +++ b/libwallet/Cargo.toml @@ -34,6 +34,7 @@ bs58 = "0.3" age = "0.4" curve25519-dalek = "2.0.0" secrecy = "0.6" +bech32 = "0.7" grin_wallet_util = { path = "../util", version = "4.0.0-alpha.1" } grin_wallet_config = { path = "../config", version = "4.0.0-alpha.1" } diff --git a/libwallet/src/error.rs b/libwallet/src/error.rs index 6fdce3a0..eb08dfe7 100644 --- a/libwallet/src/error.rs +++ b/libwallet/src/error.rs @@ -207,8 +207,8 @@ pub enum ErrorKind { SlatepackSer, /// Can't deserialize slate - #[fail(display = "Can't Deserialize slatepack")] - SlatepackDeser, + #[fail(display = "Can't Deserialize slatepack: {}", _0)] + SlatepackDeser(String), /// Unknown slate version #[fail(display = "Unknown Slate Version: {}", _0)] @@ -290,6 +290,10 @@ pub enum ErrorKind { #[fail(display = "Age error: {}", _0)] Age(String), + /// Slatepack address parsing error + #[fail(display = "SlatepackAddress error: {}", _0)] + SlatepackAddress(String), + /// Other #[fail(display = "Generic error: {}", _0)] GenericError(String), @@ -426,3 +430,11 @@ impl From for Error { } } } + +impl From for Error { + fn from(error: bech32::Error) -> Error { + Error { + inner: Context::new(ErrorKind::SlatepackAddress(format!("{}", error))), + } + } +} diff --git a/libwallet/src/lib.rs b/libwallet/src/lib.rs index 5e2bc649..506f96a8 100644 --- a/libwallet/src/lib.rs +++ b/libwallet/src/lib.rs @@ -61,7 +61,9 @@ pub use crate::slate_versions::{ SlateVersion, VersionedBinSlate, VersionedCoinbase, VersionedSlate, CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION, }; -pub use crate::slatepack::{Slatepack, SlatepackArmor, SlatepackBin}; +pub use crate::slatepack::{ + Slatepack, SlatepackAddress, SlatepackArmor, SlatepackBin, Slatepacker, SlatepackerArgs, +}; pub use api_impl::owner_updater::StatusMessage; pub use api_impl::types::{ BlockFees, InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs, NodeHeightResult, diff --git a/libwallet/src/slatepack/address.rs b/libwallet/src/slatepack/address.rs new file mode 100644 index 00000000..fc381335 --- /dev/null +++ b/libwallet/src/slatepack/address.rs @@ -0,0 +1,248 @@ +// Copyright 2020 The Grin Developers +// +// 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. + +use bech32::{self, FromBase32, ToBase32}; +/// Slatepack Address definition +use ed25519_dalek::PublicKey as edDalekPublicKey; +use ed25519_dalek::SecretKey as edDalekSecretKey; +use rand::{thread_rng, Rng}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use x25519_dalek::PublicKey as xDalekPublicKey; + +use crate::grin_core::ser::{self, Readable, Reader, Writeable, Writer}; +use crate::util::OnionV3Address; +use crate::{Error, ErrorKind}; + +use std::convert::TryFrom; +use std::fmt::{self, Display}; + +/// Definition of a Slatepack address +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct SlatepackAddress { + /// Human-readable prefix + pub hrp: String, + /// ed25519 Public key, to be bech32 encoded, + /// interpreted as tor address or converted + /// to an X25519 public key for encrypting + /// slatepacks + pub pub_key: edDalekPublicKey, +} + +impl SlatepackAddress { + /// new with default hrp + pub fn new(pub_key: &edDalekPublicKey) -> Self { + Self { + hrp: String::from("slatepack"), + pub_key: pub_key.clone(), + } + } + + /// new with a random key + pub fn random() -> Self { + let bytes: [u8; 32] = thread_rng().gen(); + let pub_key = edDalekPublicKey::from(&edDalekSecretKey::from_bytes(&bytes).unwrap()); + SlatepackAddress::new(&pub_key) + } + + /// calculate encoded length + pub fn encoded_len(&self) -> Result { + let encoded = String::try_from(self)?; + // add length byte + Ok(encoded.as_bytes().len() + 1) + } +} + +impl Display for SlatepackAddress { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str(&String::try_from(self).unwrap()) + } +} + +impl TryFrom<&str> for SlatepackAddress { + type Error = Error; + fn try_from(encoded: &str) -> Result { + let (hrp, data) = bech32::decode(&encoded)?; + let bytes = Vec::::from_base32(&data)?; + let pub_key = match edDalekPublicKey::from_bytes(&bytes) { + Ok(k) => k, + Err(e) => { + return Err(ErrorKind::ED25519Key(format!("{}", e)).into()); + } + }; + Ok(SlatepackAddress { hrp, pub_key }) + } +} + +impl TryFrom<&SlatepackAddress> for String { + type Error = Error; + fn try_from(addr: &SlatepackAddress) -> Result { + let encoded = bech32::encode(&addr.hrp, addr.pub_key.to_bytes().to_base32())?; + Ok(encoded.to_string()) + } +} + +impl From<&SlatepackAddress> for OnionV3Address { + fn from(addr: &SlatepackAddress) -> Self { + OnionV3Address::from_bytes(addr.pub_key.to_bytes()) + } +} + +impl TryFrom<&SlatepackAddress> for xDalekPublicKey { + type Error = Error; + fn try_from(addr: &SlatepackAddress) -> Result { + let cep = + curve25519_dalek::edwards::CompressedEdwardsY::from_slice(addr.pub_key.as_bytes()); + let ep = match cep.decompress() { + Some(p) => p, + None => { + return Err( + ErrorKind::ED25519Key("Can't decompress ed25519 Edwards Point".into()).into(), + ); + } + }; + let res = xDalekPublicKey::from(ep.to_montgomery().to_bytes()); + Ok(res) + } +} + +/// Serializes a SlatepackAddress to a bech32 string +impl Serialize for SlatepackAddress { + /// + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str( + &String::try_from(self).map_err(|err| serde::ser::Error::custom(err.to_string()))?, + ) + } +} + +/// Deserialize from a bech32 string +impl<'de> Deserialize<'de> for SlatepackAddress { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct SlatepackAddressVisitor; + + impl<'de> serde::de::Visitor<'de> for SlatepackAddressVisitor { + type Value = SlatepackAddress; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "a SlatepackAddress") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + let s = SlatepackAddress::try_from(value) + .map_err(|err| serde::de::Error::custom(err.to_string()))?; + Ok(s) + } + } + + deserializer.deserialize_str(SlatepackAddressVisitor) + } +} + +/// write binary trait +impl Writeable for SlatepackAddress { + fn write(&self, writer: &mut W) -> Result<(), ser::Error> { + // We're actually going to encode the bech32 address as opposed to + // the binary serialization + let encoded = match String::try_from(self) { + Ok(e) => e, + Err(e) => { + error!("Cannot parse Slatepack address: {:?}, {}", self, e); + return Err(ser::Error::CorruptedData); + } + }; + // write length, max 255 + let bytes = encoded.as_bytes(); + if bytes.len() > 255 { + error!( + "Cannot encode Slatepackaddress: {:?}, Too Long (Max 255)", + self + ); + return Err(ser::Error::CorruptedData); + } + writer.write_u8(bytes.len() as u8)?; + writer.write_fixed_bytes(&bytes) + } +} + +impl Readable for SlatepackAddress { + fn read(reader: &mut R) -> Result { + // read length as u8 + let len = reader.read_u8()?; + // and bech32 string + let encoded = match String::from_utf8(reader.read_fixed_bytes(len as usize)?) { + Ok(a) => a, + Err(e) => { + error!("Cannot parse Slatepack address from utf8: {}", e); + return Err(ser::Error::CorruptedData); + } + }; + let parsed_addr = match SlatepackAddress::try_from(encoded.as_str()) { + Ok(a) => a, + Err(e) => { + error!("Cannot parse Slatepack address: {}, {}", encoded, e); + return Err(ser::Error::CorruptedData); + } + }; + Ok(parsed_addr) + } +} + +#[test] +fn slatepack_address() -> Result<(), Error> { + use rand::{thread_rng, Rng}; + let sec_key_bytes: [u8; 32] = thread_rng().gen(); + + let ed_sec_key = edDalekSecretKey::from_bytes(&sec_key_bytes).unwrap(); + let ed_pub_key = edDalekPublicKey::from(&ed_sec_key); + let addr = SlatepackAddress::new(&ed_pub_key); + let x_pub_key = xDalekPublicKey::try_from(&addr)?; + + let x_dec_secret = x25519_dalek::StaticSecret::from(sec_key_bytes); + let x_pub_key_direct = xDalekPublicKey::from(&x_dec_secret); + + println!("ed sec key: {:?}", ed_sec_key); + println!("ed pub key: {:?}", ed_pub_key); + println!("x pub key from addr: {:?}", x_pub_key); + println!("x pub key direct: {:?}", x_pub_key_direct); + + let encoded = String::try_from(&addr).unwrap(); + println!("Encoded bech32: {}", encoded); + let parsed_addr = SlatepackAddress::try_from(encoded.as_str()).unwrap(); + assert_eq!(addr, parsed_addr); + + // ensure ed25519 pub keys and x25519 pubkeys are equivalent on decryption + let mut slatepack = super::Slatepack::default(); + let mut payload: Vec = Vec::with_capacity(243); + for _ in 0..payload.capacity() { + payload.push(rand::random()); + } + slatepack.payload = payload; + let orig_sp = slatepack.clone(); + + slatepack.try_encrypt_payload(vec![addr.clone()])?; + slatepack.try_decrypt_payload(Some(&ed_sec_key))?; + + assert_eq!(orig_sp.payload, slatepack.payload); + + Ok(()) +} diff --git a/libwallet/src/slatepack/armor.rs b/libwallet/src/slatepack/armor.rs index 15c65244..30a7bb6f 100644 --- a/libwallet/src/slatepack/armor.rs +++ b/libwallet/src/slatepack/armor.rs @@ -29,7 +29,7 @@ use std::str; use super::types::{Slatepack, SlatepackBin}; // Framing and formatting for slate armor -static HEADER: &str = "BEGINSLATEPACK. "; +pub static HEADER: &str = "BEGINSLATEPACK. "; static FOOTER: &str = ". ENDSLATEPACK."; const WORD_LENGTH: usize = 15; diff --git a/libwallet/src/slatepack/mod.rs b/libwallet/src/slatepack/mod.rs index 640af4b0..ab72b94b 100644 --- a/libwallet/src/slatepack/mod.rs +++ b/libwallet/src/slatepack/mod.rs @@ -12,8 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod address; mod armor; +mod packer; mod types; +pub use self::address::SlatepackAddress; pub use self::armor::SlatepackArmor; +pub use self::packer::{Slatepacker, SlatepackerArgs}; pub use self::types::{Slatepack, SlatepackBin}; diff --git a/libwallet/src/slatepack/packer.rs b/libwallet/src/slatepack/packer.rs new file mode 100644 index 00000000..df59a4f8 --- /dev/null +++ b/libwallet/src/slatepack/packer.rs @@ -0,0 +1,120 @@ +// Copyright 2020 The Grin Developers +// +// 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. + +use std::convert::TryFrom; +use std::iter::FromIterator; + +use crate::{Error, ErrorKind}; +use crate::{ + Slate, SlateVersion, Slatepack, SlatepackAddress, SlatepackArmor, SlatepackBin, + VersionedBinSlate, VersionedSlate, +}; + +use grin_wallet_util::byte_ser; + +use ed25519_dalek::SecretKey as edSecretKey; + +#[derive(Clone)] +/// Arguments, mostly for encrypting decrypting a slatepack +pub struct SlatepackerArgs<'a> { + /// Optional sender to include in slatepack + pub sender: Option, + /// Optional list of recipients, for encryption + pub recipients: Vec, + /// Optional decryption key + pub dec_key: Option<&'a edSecretKey>, +} + +/// Helper struct to pack and unpack slatepacks +#[derive(Clone)] +pub struct Slatepacker<'a>(SlatepackerArgs<'a>); + +impl<'a> Slatepacker<'a> { + /// Create with pathbuf and recipients + pub fn new(args: SlatepackerArgs<'a>) -> Self { + Self(args) + } + + /// return slatepack + pub fn deser_slatepack(&self, data: Vec) -> Result { + // check if data is armored, if so, remove and continue + let test_header = Vec::from_iter(data[0..super::armor::HEADER.len()].iter().cloned()); + let data = match String::from_utf8(test_header) { + Ok(s) => { + if s.as_str() == super::armor::HEADER { + SlatepackArmor::decode( + String::from_utf8(data) + .map_err(|e| { + let msg = format!("{}", e); + error!("Error decoding slatepack armor: {}", msg); + ErrorKind::SlatepackDeser(msg) + })? + .as_str(), + )? + } else { + data + } + } + Err(_) => data, + }; + + // try as bin first, then as json + let mut slatepack = match byte_ser::from_bytes::(&data) { + Ok(s) => s.0, + Err(e) => { + debug!("Not a valid binary slatepack: {} - Will try JSON", e); + let content = String::from_utf8(data).map_err(|e| { + let msg = format!("{}", e); + ErrorKind::SlatepackDeser(msg) + })?; + serde_json::from_str(&content).map_err(|e| { + let msg = format!("Error reading JSON slatepack: {}", e); + ErrorKind::SlatepackDeser(msg) + })? + } + }; + + slatepack.ver_check_warn(); + slatepack.try_decrypt_payload(self.0.dec_key)?; + Ok(slatepack) + } + + /// Create slatepack from slate and args + pub fn create_slatepack(&self, slate: &Slate) -> Result { + let out_slate = VersionedSlate::into_version(slate.clone(), SlateVersion::V4)?; + let bin_slate = + VersionedBinSlate::try_from(out_slate).map_err(|_| ErrorKind::SlatepackSer)?; + let mut slatepack = Slatepack::default(); + slatepack.payload = byte_ser::to_bytes(&bin_slate).map_err(|_| ErrorKind::SlatepackSer)?; + slatepack.sender = self.0.sender.clone(); + slatepack.try_encrypt_payload(self.0.recipients.clone())?; + Ok(slatepack) + } + + /// Armor a slatepack + pub fn armor_slatepack(&self, slatepack: &Slatepack) -> Result { + SlatepackArmor::encode(&slatepack, 3) + } + + /// Return/upgrade slate from slatepack + pub fn get_slate(&self, slatepack: &Slatepack) -> Result { + let slate_bin = + byte_ser::from_bytes::(&slatepack.payload).map_err(|e| { + error!("Error reading slate from armored slatepack: {}", e); + let msg = format!("{}", e); + ErrorKind::SlatepackDeser(msg) + })?; + Ok(Slate::upgrade(slate_bin.into())?) + } +} diff --git a/libwallet/src/slatepack/types.rs b/libwallet/src/slatepack/types.rs index e6536bd1..8e05d99b 100644 --- a/libwallet/src/slatepack/types.rs +++ b/libwallet/src/slatepack/types.rs @@ -13,20 +13,24 @@ // limitations under the License. /// Slatepack Types + Serialization implementation -use x25519_dalek::PublicKey as xDalekPublicKey; +use ed25519_dalek::SecretKey as edSecretKey; +use sha2::{Digest, Sha512}; use x25519_dalek::StaticSecret; use crate::dalek_ser; use crate::grin_core::ser::{self, Readable, Reader, Writeable, Writer}; use crate::Error; +use super::SlatepackAddress; + +use std::convert::TryInto; use std::io::{Read, Write}; pub const SLATEPACK_MAJOR_VERSION: u8 = 1; pub const SLATEPACK_MINOR_VERSION: u8 = 0; /// Basic Slatepack definition -#[derive(Serialize, Deserialize, Debug, Clone)] +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)] pub struct Slatepack { // Required Fields /// Versioning info @@ -38,9 +42,8 @@ pub struct Slatepack { // Optional Fields /// Optional Sender address #[serde(default = "default_sender_none")] - #[serde(with = "dalek_ser::option_xdalek_pubkey_serde")] #[serde(skip_serializing_if = "Option::is_none")] - pub sender: Option, + pub sender: Option, // Payload /// Binary payload, can be encrypted or plaintext @@ -51,7 +54,7 @@ pub struct Slatepack { pub payload: Vec, } -fn default_sender_none() -> Option { +fn default_sender_none() -> Option { None } @@ -73,21 +76,21 @@ impl Slatepack { /// return length of optional fields pub fn opt_fields_len(&self) -> Result { let mut retval = 0; - if self.sender.is_some() { - retval += 32; + if let Some(s) = self.sender.as_ref() { + retval += s.encoded_len().unwrap(); } Ok(retval) } /// age encrypt the payload with the given public key - pub fn try_encrypt_payload(&mut self, recipients: Vec) -> Result<(), Error> { + pub fn try_encrypt_payload(&mut self, recipients: Vec) -> Result<(), Error> { if recipients.is_empty() { return Ok(()); } let rec_keys: Result, _> = recipients .into_iter() - .map(|pk| { - let key = age::keys::RecipientKey::X25519(pk); + .map(|addr| { + let key = age::keys::RecipientKey::X25519((&addr).try_into()?); Ok(key) }) .collect(); @@ -108,7 +111,7 @@ impl Slatepack { } /// As above, decrypt if needed - pub fn try_decrypt_payload(&mut self, dec_key: Option<&StaticSecret>) -> Result<(), Error> { + pub fn try_decrypt_payload(&mut self, dec_key: Option<&edSecretKey>) -> Result<(), Error> { if self.mode == 0 { return Ok(()); } @@ -116,7 +119,16 @@ impl Slatepack { Some(k) => k, None => return Ok(()), }; - let key = age::keys::SecretKey::X25519(dec_key.clone()); + let mut b = [0u8; 32]; + b.copy_from_slice(&dec_key.as_bytes()[0..32]); + let mut hasher = Sha512::new(); + hasher.input(b); + let result = hasher.result(); + b.copy_from_slice(&result[0..32]); + + let x_dec_secret = StaticSecret::from(b); + let key = age::keys::SecretKey::X25519(x_dec_secret); + let decryptor = match age::Decryptor::new(&self.payload[..])? { age::Decryptor::Recipients(d) => d, _ => unreachable!(), @@ -213,7 +225,7 @@ impl Writeable for SlatepackBin { // write optional fields if let Some(s) = sp.sender { - writer.write_fixed_bytes(s.as_bytes())?; + s.write(writer)?; }; // Now write payload (length prefixed) @@ -240,11 +252,16 @@ impl Readable for SlatepackBin { let mut bytes_to_payload = reader.read_u32()?; let sender = if opt_flags & 0x01 > 0 { - bytes_to_payload -= 32; - let bytes = reader.read_fixed_bytes(32)?; - let mut b = [0u8; 32]; - b.copy_from_slice(&bytes[0..32]); - Some(xDalekPublicKey::from(b)) + let addr = SlatepackAddress::read(reader)?; + let len = match addr.encoded_len() { + Ok(e) => e as u32, + Err(e) => { + error!("Cannot parse Slatepack address: {}", e); + return Err(ser::Error::CorruptedData); + } + }; + bytes_to_payload -= len; + Some(addr) } else { None }; @@ -328,30 +345,6 @@ pub mod slatepack_version { } } -/// Header struct definition -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct RecipientListEntry { - #[serde(with = "dalek_ser::dalek_xpubkey_serde")] - /// Public Address - pub pub_address: xDalekPublicKey, -} - -impl Writeable for RecipientListEntry { - fn write(&self, writer: &mut W) -> Result<(), ser::Error> { - writer.write_fixed_bytes(self.pub_address.as_bytes()) - } -} - -impl Readable for RecipientListEntry { - fn read(reader: &mut R) -> Result { - let bytes = reader.read_fixed_bytes(32)?; - let mut b = [0u8; 32]; - b.copy_from_slice(&bytes[0..32]); - let pub_address = xDalekPublicKey::from(b); - Ok(RecipientListEntry { pub_address }) - } -} - #[test] fn slatepack_bin_basic_ser() -> Result<(), grin_wallet_util::byte_ser::Error> { use grin_wallet_util::byte_ser; @@ -377,7 +370,6 @@ fn slatepack_bin_basic_ser() -> Result<(), grin_wallet_util::byte_ser::Error> { #[test] fn slatepack_bin_opt_fields_ser() -> Result<(), grin_wallet_util::byte_ser::Error> { use grin_wallet_util::byte_ser; - use rand::{thread_rng, Rng}; let slatepack = SlatepackVersion { major: 1, minor: 0 }; let mut payload: Vec = Vec::with_capacity(243); for _ in 0..payload.capacity() { @@ -385,9 +377,7 @@ fn slatepack_bin_opt_fields_ser() -> Result<(), grin_wallet_util::byte_ser::Erro } // includes optional fields - let bytes: [u8; 32] = thread_rng().gen(); - let sender_secret = StaticSecret::from(bytes); - let sender = Some(xDalekPublicKey::from(&sender_secret)); + let sender = Some(SlatepackAddress::random()); let sp = Slatepack { slatepack, mode: 1, @@ -396,12 +386,7 @@ fn slatepack_bin_opt_fields_ser() -> Result<(), grin_wallet_util::byte_ser::Erro }; let ser = byte_ser::to_bytes(&SlatepackBin(sp.clone()))?; let deser = byte_ser::from_bytes::(&ser)?.0; - assert_eq!(sp.slatepack, deser.slatepack); - assert_eq!(sp.mode, deser.mode); - assert_eq!( - sp.sender.unwrap().as_bytes(), - deser.sender.unwrap().as_bytes() - ); + assert_eq!(sp, deser); Ok(()) } @@ -413,19 +398,18 @@ fn slatepack_bin_future() -> Result<(), grin_wallet_util::byte_ser::Error> { use grin_wallet_util::byte_ser; use rand::{thread_rng, Rng}; use std::io::Cursor; + let slatepack = SlatepackVersion { major: 1, minor: 0 }; let payload_size = 1234; let mut payload: Vec = Vec::with_capacity(payload_size); for _ in 0..payload.capacity() { payload.push(rand::random()); } + let sender = Some(SlatepackAddress::random()); - let bytes: [u8; 32] = thread_rng().gen(); - let sender_secret = StaticSecret::from(bytes); - let sender = Some(xDalekPublicKey::from(&sender_secret)); println!( - "sender key len: {}", - sender.as_ref().unwrap().as_bytes().len() + "sender len: {}", + sender.as_ref().unwrap().encoded_len().unwrap() ); let sp = Slatepack { @@ -445,14 +429,14 @@ fn slatepack_bin_future() -> Result<(), grin_wallet_util::byte_ser::Error> { // opt fields len (bytes to payload) 4 // bytes 5-8 are opt fields len - // sender 32 + // sender 68 let mut opt_fields_len_bytes = [0u8; 4]; opt_fields_len_bytes.copy_from_slice(&ser[5..9]); let mut rdr = Cursor::new(opt_fields_len_bytes.to_vec()); let opt_fields_len = rdr.read_u32::().unwrap(); // check this matches what we expect below - assert_eq!(opt_fields_len, 32); + assert_eq!(opt_fields_len, 69); let end_head_pos = opt_fields_len as usize + 8 + 1; @@ -481,11 +465,6 @@ fn slatepack_bin_future() -> Result<(), grin_wallet_util::byte_ser::Error> { } let deser = byte_ser::from_bytes::(&new_bytes)?.0; - assert_eq!(sp.slatepack, deser.slatepack); - assert_eq!(sp.mode, deser.mode); - assert_eq!( - sp.sender.unwrap().as_bytes(), - deser.sender.unwrap().as_bytes() - ); + assert_eq!(sp, deser); Ok(()) }