Slatepack Pt. 3 - The Packening (SlatepackAddress implementation) (#413)

* addition of SlatepackAddress type

* complete conversion of ed25519 keys to x25519 and isolate conversion within libwallet

* refactor packing/unpacking of slates logic into libwallet and out of slate adapters

* println
This commit is contained in:
Yeastplume 2020-05-26 09:51:05 +01:00 committed by GitHub
parent 038dafcc25
commit 03cb1097e0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 519 additions and 248 deletions

3
Cargo.lock generated
View file

@ -1444,6 +1444,7 @@ version = "4.0.0-alpha.1"
dependencies = [ dependencies = [
"chrono", "chrono",
"easy-jsonrpc-mw", "easy-jsonrpc-mw",
"ed25519-dalek",
"failure", "failure",
"failure_derive", "failure_derive",
"futures 0.3.5", "futures 0.3.5",
@ -1465,7 +1466,6 @@ dependencies = [
"tokio", "tokio",
"url 1.7.2", "url 1.7.2",
"uuid", "uuid",
"x25519-dalek",
] ]
[[package]] [[package]]
@ -1509,6 +1509,7 @@ version = "4.0.0-alpha.1"
dependencies = [ dependencies = [
"age", "age",
"base64 0.9.3", "base64 0.9.3",
"bech32",
"blake2-rfc", "blake2-rfc",
"bs58", "bs58",
"byteorder", "byteorder",

View file

@ -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" } grin_wallet_config = { path = "../config", version = "4.0.0-alpha.1" }
[dev-dependencies] [dev-dependencies]
x25519-dalek = "0.6" ed25519-dalek = "1.0.0-pre.1"

View file

@ -22,17 +22,18 @@ use grin_wallet_util::grin_core as core;
use grin_wallet_util::OnionV3Address; use grin_wallet_util::OnionV3Address;
use impls::test_framework::{self, LocalWalletClient}; use impls::test_framework::{self, LocalWalletClient};
use impls::{ use impls::{PathToSlatepack, SlatePutter as _};
PathToSlatepack, PathToSlatepackArmored, SlateGetter as _, SlatePutter as _, SlatepackArgs,
};
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::thread; use std::thread;
use std::time::Duration; 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 ed25519_dalek::PublicKey as edDalekPublicKey;
use x25519_dalek::StaticSecret; use ed25519_dalek::SecretKey as edDalekSecretKey;
#[macro_use] #[macro_use]
mod common; mod common;
@ -43,54 +44,37 @@ fn output_slatepack(
file: &str, file: &str,
armored: bool, armored: bool,
use_bin: bool, use_bin: bool,
sender: Option<xDalekPublicKey>, sender: Option<SlatepackAddress>,
recipients: Vec<xDalekPublicKey>, recipients: Vec<SlatepackAddress>,
) -> Result<(), libwallet::Error> { ) -> Result<(), libwallet::Error> {
let packer = Slatepacker::new(SlatepackerArgs {
sender,
recipients,
dec_key: None,
});
let mut file = file.into();
if armored { if armored {
let file = format!("{}.armored", file); 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)
} }
PathToSlatepack::new(file.into(), &packer, armored).put_tx(&slate, use_bin)
} }
fn slate_from_packed( fn slate_from_packed(
file: &str, file: &str,
armored: bool, armored: bool,
dec_key: Option<&StaticSecret>, dec_key: Option<&edDalekSecretKey>,
) -> Result<(Slatepack, Slate), libwallet::Error> { ) -> Result<(Slatepack, Slate), libwallet::Error> {
let packer = Slatepacker::new(SlatepackerArgs {
sender: None,
recipients: vec![],
dec_key,
});
let mut file = file.into();
if armored { if armored {
let file = format!("{}.armored", file); 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))
} }
let slatepack = PathToSlatepack::new(file.into(), &packer, armored).get_slatepack()?;
Ok((slatepack.clone(), packer.get_slate(&slatepack)?))
} }
/// self send impl /// self send impl
@ -165,34 +149,38 @@ fn slatepack_exchange_test_impl(
let (recipients_1, dec_key_1, sender_1) = match use_encryption { let (recipients_1, dec_key_1, sender_1) = match use_encryption {
true => { true => {
let mut rec_address = xDalekPublicKey::from([0u8; 32]); let mut rec_address = SlatepackAddress::random();
let mut sec_key = StaticSecret::from([0u8; 32]); let mut sec_key = edDalekSecretKey::from_bytes(&[0u8; 32]).unwrap();
wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| { wallet::controller::owner_single_use(Some(wallet1.clone()), mask1, None, |api, m| {
let ed25519_sec_key = api.get_secret_key(m, 0)?; sec_key = api.get_secret_key(m, 0)?;
let mut b = [0u8; 32]; let pub_key = edDalekPublicKey::from(&sec_key);
b.copy_from_slice(&ed25519_sec_key.as_ref()[0..32]); rec_address = SlatepackAddress::new(&pub_key);
sec_key = StaticSecret::from(b);
rec_address = xDalekPublicKey::from(&sec_key);
Ok(()) 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), false => (vec![], None, None),
}; };
let (recipients_2, dec_key_2, sender_2) = match use_encryption { let (recipients_2, dec_key_2, sender_2) = match use_encryption {
true => { true => {
let mut rec_address = xDalekPublicKey::from([0u8; 32]); let mut rec_address = SlatepackAddress::random();
let mut sec_key = StaticSecret::from([0u8; 32]); let mut sec_key = edDalekSecretKey::from_bytes(&[0u8; 32]).unwrap();
wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| { wallet::controller::owner_single_use(Some(wallet2.clone()), mask2, None, |api, m| {
let ed25519_sec_key = api.get_secret_key(m, 0)?; sec_key = api.get_secret_key(m, 0)?;
let mut b = [0u8; 32]; let pub_key = edDalekPublicKey::from(&sec_key);
b.copy_from_slice(&ed25519_sec_key.as_ref()[0..32]); rec_address = SlatepackAddress::new(&pub_key);
sec_key = StaticSecret::from(b);
rec_address = xDalekPublicKey::from(&sec_key);
Ok(()) 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), false => (vec![], None, None),
}; };
@ -232,7 +220,7 @@ fn slatepack_exchange_test_impl(
&send_file, &send_file,
use_armored, use_armored,
use_bin, use_bin,
sender_1, sender_1.clone(),
recipients_2.clone(), recipients_2.clone(),
)?; )?;
api.tx_lock_outputs(m, &slate)?; api.tx_lock_outputs(m, &slate)?;
@ -257,8 +245,8 @@ fn slatepack_exchange_test_impl(
use_armored, use_armored,
use_bin, use_bin,
// re-encrypt for sender! // re-encrypt for sender!
sender_2, sender_2.clone(),
match slatepack.sender { match slatepack.sender.clone() {
Some(s) => vec![s.clone()], Some(s) => vec![s.clone()],
None => vec![], None => vec![],
}, },
@ -326,7 +314,7 @@ fn slatepack_exchange_test_impl(
&send_file, &send_file,
use_armored, use_armored,
use_bin, use_bin,
sender_2, sender_2.clone(),
recipients_1.clone(), recipients_1.clone(),
)?; )?;
Ok(()) Ok(())
@ -352,8 +340,8 @@ fn slatepack_exchange_test_impl(
&receive_file, &receive_file,
use_armored, use_armored,
use_bin, use_bin,
sender_1, sender_1.clone(),
match slatepack.sender { match slatepack.sender.clone() {
Some(s) => vec![s.clone()], Some(s) => vec![s.clone()],
None => vec![], None => vec![],
}, },

View file

@ -20,7 +20,7 @@ mod slatepack;
pub use self::file::PathToSlate; pub use self::file::PathToSlate;
pub use self::http::{HttpSlateSender, SchemeNotHttp}; pub use self::http::{HttpSlateSender, SchemeNotHttp};
pub use self::keybase::{KeybaseAllChannels, KeybaseChannel}; 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::config::{TorConfig, WalletConfig};
use crate::libwallet::{Error, ErrorKind, Slate}; use crate::libwallet::{Error, ErrorKind, Slate};

View file

@ -12,90 +12,59 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::convert::TryFrom;
/// Slatepack Output 'plugin' implementation /// Slatepack Output 'plugin' implementation
use std::fs::File; use std::fs::File;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::PathBuf; use std::path::PathBuf;
use x25519_dalek::PublicKey as xDalekPublicKey; use crate::libwallet::{Error, ErrorKind, Slate, Slatepack, SlatepackBin, Slatepacker};
use x25519_dalek::StaticSecret;
use crate::libwallet::{
Error, ErrorKind, Slate, SlateVersion, Slatepack, SlatepackArmor, SlatepackBin,
VersionedBinSlate, VersionedSlate,
};
use crate::{SlateGetter, SlatePutter}; use crate::{SlateGetter, SlatePutter};
use grin_wallet_util::byte_ser; use grin_wallet_util::byte_ser;
#[derive(Clone)] // And Slate putter impls to output to files
pub struct SlatepackArgs<'a> { pub struct PathToSlatepack<'a> {
pub pathbuf: PathBuf, pub pathbuf: PathBuf,
pub sender: Option<xDalekPublicKey>, pub packer: &'a Slatepacker<'a>,
pub recipients: Vec<xDalekPublicKey>, pub armor_output: bool,
pub dec_key: Option<&'a StaticSecret>,
} }
pub struct PathToSlatepack<'a>(SlatepackArgs<'a>);
impl<'a> PathToSlatepack<'a> { impl<'a> PathToSlatepack<'a> {
/// Create with pathbuf and recipients /// Create with pathbuf and recipients
pub fn new(args: SlatepackArgs<'a>) -> Self { pub fn new(pathbuf: PathBuf, packer: &'a Slatepacker<'a>, armor_output: bool) -> Self {
Self(args) Self {
pathbuf,
packer,
armor_output,
}
} }
pub fn get_slatepack_file_contents(&self) -> Result<Vec<u8>, Error> { pub fn get_slatepack_file_contents(&self) -> Result<Vec<u8>, 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(); let mut data = Vec::new();
pub_tx_f.read_to_end(&mut data)?; pub_tx_f.read_to_end(&mut data)?;
Ok(data) Ok(data)
} }
// return slatepack itself
pub fn deser_slatepack(&self, data: Vec<u8>) -> Result<Slatepack, Error> {
// try as bin first, then as json
let bin_res = byte_ser::from_bytes::<SlatepackBin>(&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<Slatepack, Error> { pub fn get_slatepack(&self) -> Result<Slatepack, Error> {
let data = self.get_slatepack_file_contents()?; let data = self.get_slatepack_file_contents()?;
self.deser_slatepack(data) self.packer.deser_slatepack(data)
}
// Create slatepack from slate and args
pub fn create_slatepack(&self, slate: &Slate) -> Result<Slatepack, Error> {
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)
} }
} }
impl<'a> SlatePutter for PathToSlatepack<'a> { impl<'a> SlatePutter for PathToSlatepack<'a> {
fn put_tx(&self, slate: &Slate, as_bin: bool) -> Result<(), Error> { fn put_tx(&self, slate: &Slate, as_bin: bool) -> Result<(), Error> {
let slatepack = self.create_slatepack(slate)?; let slatepack = self.packer.create_slatepack(slate)?;
let mut pub_tx = File::create(&self.0.pathbuf)?; let mut pub_tx = File::create(&self.pathbuf)?;
if as_bin { if as_bin {
if self.armor_output {
let armored = self.packer.armor_slatepack(&slatepack)?;
pub_tx.write_all(armored.as_bytes())?;
} else {
pub_tx.write_all( pub_tx.write_all(
&byte_ser::to_bytes(&SlatepackBin(slatepack)) &byte_ser::to_bytes(&SlatepackBin(slatepack))
.map_err(|_| ErrorKind::SlatepackSer)?, .map_err(|_| ErrorKind::SlatepackSer)?,
)?; )?;
}
} else { } else {
pub_tx.write_all( pub_tx.write_all(
serde_json::to_string_pretty(&slatepack) serde_json::to_string_pretty(&slatepack)
@ -111,59 +80,7 @@ impl<'a> SlatePutter for PathToSlatepack<'a> {
impl<'a> SlateGetter for PathToSlatepack<'a> { impl<'a> SlateGetter for PathToSlatepack<'a> {
fn get_tx(&self) -> Result<(Slate, bool), Error> { fn get_tx(&self) -> Result<(Slate, bool), Error> {
let data = self.get_slatepack_file_contents()?; let data = self.get_slatepack_file_contents()?;
let mut slatepack = self.deser_slatepack(data)?; let slatepack = self.packer.deser_slatepack(data)?;
slatepack.try_decrypt_payload(self.0.dec_key)?; Ok((self.packer.get_slate(&slatepack)?, true))
let slate = byte_ser::from_bytes::<VersionedBinSlate>(&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<Vec<u8>, 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<Slatepack, Error> {
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::<VersionedBinSlate>(&slatepack.payload).map_err(|e| {
error!("Error reading slate from armored slatepack: {}", e);
ErrorKind::SlatepackDeser
})?;
Ok((Slate::upgrade(slate_bin.into())?, true))
} }
} }

View file

@ -45,8 +45,7 @@ pub mod tor;
pub use crate::adapters::{ pub use crate::adapters::{
create_sender, HttpSlateSender, KeybaseAllChannels, KeybaseChannel, PathToSlate, create_sender, HttpSlateSender, KeybaseAllChannels, KeybaseChannel, PathToSlate,
PathToSlatepack, PathToSlatepackArmored, SlateGetter, SlatePutter, SlateReceiver, SlateSender, PathToSlatepack, SlateGetter, SlatePutter, SlateReceiver, SlateSender,
SlatepackArgs,
}; };
pub use crate::backends::{wallet_db_exists, LMDBBackend}; pub use crate::backends::{wallet_db_exists, LMDBBackend};
pub use crate::error::{Error, ErrorKind}; pub use crate::error::{Error, ErrorKind};

View file

@ -34,6 +34,7 @@ bs58 = "0.3"
age = "0.4" age = "0.4"
curve25519-dalek = "2.0.0" curve25519-dalek = "2.0.0"
secrecy = "0.6" secrecy = "0.6"
bech32 = "0.7"
grin_wallet_util = { path = "../util", version = "4.0.0-alpha.1" } grin_wallet_util = { path = "../util", version = "4.0.0-alpha.1" }
grin_wallet_config = { path = "../config", version = "4.0.0-alpha.1" } grin_wallet_config = { path = "../config", version = "4.0.0-alpha.1" }

View file

@ -207,8 +207,8 @@ pub enum ErrorKind {
SlatepackSer, SlatepackSer,
/// Can't deserialize slate /// Can't deserialize slate
#[fail(display = "Can't Deserialize slatepack")] #[fail(display = "Can't Deserialize slatepack: {}", _0)]
SlatepackDeser, SlatepackDeser(String),
/// Unknown slate version /// Unknown slate version
#[fail(display = "Unknown Slate Version: {}", _0)] #[fail(display = "Unknown Slate Version: {}", _0)]
@ -290,6 +290,10 @@ pub enum ErrorKind {
#[fail(display = "Age error: {}", _0)] #[fail(display = "Age error: {}", _0)]
Age(String), Age(String),
/// Slatepack address parsing error
#[fail(display = "SlatepackAddress error: {}", _0)]
SlatepackAddress(String),
/// Other /// Other
#[fail(display = "Generic error: {}", _0)] #[fail(display = "Generic error: {}", _0)]
GenericError(String), GenericError(String),
@ -426,3 +430,11 @@ impl From<age::Error> for Error {
} }
} }
} }
impl From<bech32::Error> for Error {
fn from(error: bech32::Error) -> Error {
Error {
inner: Context::new(ErrorKind::SlatepackAddress(format!("{}", error))),
}
}
}

View file

@ -61,7 +61,9 @@ pub use crate::slate_versions::{
SlateVersion, VersionedBinSlate, VersionedCoinbase, VersionedSlate, CURRENT_SLATE_VERSION, SlateVersion, VersionedBinSlate, VersionedCoinbase, VersionedSlate, CURRENT_SLATE_VERSION,
GRIN_BLOCK_HEADER_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::owner_updater::StatusMessage;
pub use api_impl::types::{ pub use api_impl::types::{
BlockFees, InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs, NodeHeightResult, BlockFees, InitTxArgs, InitTxSendArgs, IssueInvoiceTxArgs, NodeHeightResult,

View file

@ -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<usize, Error> {
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<Self, Self::Error> {
let (hrp, data) = bech32::decode(&encoded)?;
let bytes = Vec::<u8>::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<Self, Self::Error> {
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<Self, Self::Error> {
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<D>(deserializer: D) -> Result<SlatepackAddress, D::Error>
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<E>(self, value: &str) -> Result<SlatepackAddress, E>
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<W: Writer>(&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<R: Reader>(reader: &mut R) -> Result<SlatepackAddress, ser::Error> {
// 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<u8> = 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(())
}

View file

@ -29,7 +29,7 @@ use std::str;
use super::types::{Slatepack, SlatepackBin}; use super::types::{Slatepack, SlatepackBin};
// Framing and formatting for slate armor // Framing and formatting for slate armor
static HEADER: &str = "BEGINSLATEPACK. "; pub static HEADER: &str = "BEGINSLATEPACK. ";
static FOOTER: &str = ". ENDSLATEPACK."; static FOOTER: &str = ". ENDSLATEPACK.";
const WORD_LENGTH: usize = 15; const WORD_LENGTH: usize = 15;

View file

@ -12,8 +12,12 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
mod address;
mod armor; mod armor;
mod packer;
mod types; mod types;
pub use self::address::SlatepackAddress;
pub use self::armor::SlatepackArmor; pub use self::armor::SlatepackArmor;
pub use self::packer::{Slatepacker, SlatepackerArgs};
pub use self::types::{Slatepack, SlatepackBin}; pub use self::types::{Slatepack, SlatepackBin};

View file

@ -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<SlatepackAddress>,
/// Optional list of recipients, for encryption
pub recipients: Vec<SlatepackAddress>,
/// 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<u8>) -> Result<Slatepack, Error> {
// 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::<SlatepackBin>(&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<Slatepack, Error> {
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<String, Error> {
SlatepackArmor::encode(&slatepack, 3)
}
/// Return/upgrade slate from slatepack
pub fn get_slate(&self, slatepack: &Slatepack) -> Result<Slate, Error> {
let slate_bin =
byte_ser::from_bytes::<VersionedBinSlate>(&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())?)
}
}

View file

@ -13,20 +13,24 @@
// limitations under the License. // limitations under the License.
/// Slatepack Types + Serialization implementation /// 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 x25519_dalek::StaticSecret;
use crate::dalek_ser; use crate::dalek_ser;
use crate::grin_core::ser::{self, Readable, Reader, Writeable, Writer}; use crate::grin_core::ser::{self, Readable, Reader, Writeable, Writer};
use crate::Error; use crate::Error;
use super::SlatepackAddress;
use std::convert::TryInto;
use std::io::{Read, Write}; use std::io::{Read, Write};
pub const SLATEPACK_MAJOR_VERSION: u8 = 1; pub const SLATEPACK_MAJOR_VERSION: u8 = 1;
pub const SLATEPACK_MINOR_VERSION: u8 = 0; pub const SLATEPACK_MINOR_VERSION: u8 = 0;
/// Basic Slatepack definition /// Basic Slatepack definition
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct Slatepack { pub struct Slatepack {
// Required Fields // Required Fields
/// Versioning info /// Versioning info
@ -38,9 +42,8 @@ pub struct Slatepack {
// Optional Fields // Optional Fields
/// Optional Sender address /// Optional Sender address
#[serde(default = "default_sender_none")] #[serde(default = "default_sender_none")]
#[serde(with = "dalek_ser::option_xdalek_pubkey_serde")]
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub sender: Option<xDalekPublicKey>, pub sender: Option<SlatepackAddress>,
// Payload // Payload
/// Binary payload, can be encrypted or plaintext /// Binary payload, can be encrypted or plaintext
@ -51,7 +54,7 @@ pub struct Slatepack {
pub payload: Vec<u8>, pub payload: Vec<u8>,
} }
fn default_sender_none() -> Option<xDalekPublicKey> { fn default_sender_none() -> Option<SlatepackAddress> {
None None
} }
@ -73,21 +76,21 @@ impl Slatepack {
/// return length of optional fields /// return length of optional fields
pub fn opt_fields_len(&self) -> Result<usize, ser::Error> { pub fn opt_fields_len(&self) -> Result<usize, ser::Error> {
let mut retval = 0; let mut retval = 0;
if self.sender.is_some() { if let Some(s) = self.sender.as_ref() {
retval += 32; retval += s.encoded_len().unwrap();
} }
Ok(retval) Ok(retval)
} }
/// age encrypt the payload with the given public key /// age encrypt the payload with the given public key
pub fn try_encrypt_payload(&mut self, recipients: Vec<xDalekPublicKey>) -> Result<(), Error> { pub fn try_encrypt_payload(&mut self, recipients: Vec<SlatepackAddress>) -> Result<(), Error> {
if recipients.is_empty() { if recipients.is_empty() {
return Ok(()); return Ok(());
} }
let rec_keys: Result<Vec<_>, _> = recipients let rec_keys: Result<Vec<_>, _> = recipients
.into_iter() .into_iter()
.map(|pk| { .map(|addr| {
let key = age::keys::RecipientKey::X25519(pk); let key = age::keys::RecipientKey::X25519((&addr).try_into()?);
Ok(key) Ok(key)
}) })
.collect(); .collect();
@ -108,7 +111,7 @@ impl Slatepack {
} }
/// As above, decrypt if needed /// 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 { if self.mode == 0 {
return Ok(()); return Ok(());
} }
@ -116,7 +119,16 @@ impl Slatepack {
Some(k) => k, Some(k) => k,
None => return Ok(()), 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[..])? { let decryptor = match age::Decryptor::new(&self.payload[..])? {
age::Decryptor::Recipients(d) => d, age::Decryptor::Recipients(d) => d,
_ => unreachable!(), _ => unreachable!(),
@ -213,7 +225,7 @@ impl Writeable for SlatepackBin {
// write optional fields // write optional fields
if let Some(s) = sp.sender { if let Some(s) = sp.sender {
writer.write_fixed_bytes(s.as_bytes())?; s.write(writer)?;
}; };
// Now write payload (length prefixed) // Now write payload (length prefixed)
@ -240,11 +252,16 @@ impl Readable for SlatepackBin {
let mut bytes_to_payload = reader.read_u32()?; let mut bytes_to_payload = reader.read_u32()?;
let sender = if opt_flags & 0x01 > 0 { let sender = if opt_flags & 0x01 > 0 {
bytes_to_payload -= 32; let addr = SlatepackAddress::read(reader)?;
let bytes = reader.read_fixed_bytes(32)?; let len = match addr.encoded_len() {
let mut b = [0u8; 32]; Ok(e) => e as u32,
b.copy_from_slice(&bytes[0..32]); Err(e) => {
Some(xDalekPublicKey::from(b)) error!("Cannot parse Slatepack address: {}", e);
return Err(ser::Error::CorruptedData);
}
};
bytes_to_payload -= len;
Some(addr)
} else { } else {
None 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<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_fixed_bytes(self.pub_address.as_bytes())
}
}
impl Readable for RecipientListEntry {
fn read<R: Reader>(reader: &mut R) -> Result<RecipientListEntry, ser::Error> {
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] #[test]
fn slatepack_bin_basic_ser() -> Result<(), grin_wallet_util::byte_ser::Error> { fn slatepack_bin_basic_ser() -> Result<(), grin_wallet_util::byte_ser::Error> {
use grin_wallet_util::byte_ser; use grin_wallet_util::byte_ser;
@ -377,7 +370,6 @@ fn slatepack_bin_basic_ser() -> Result<(), grin_wallet_util::byte_ser::Error> {
#[test] #[test]
fn slatepack_bin_opt_fields_ser() -> Result<(), grin_wallet_util::byte_ser::Error> { fn slatepack_bin_opt_fields_ser() -> Result<(), grin_wallet_util::byte_ser::Error> {
use grin_wallet_util::byte_ser; use grin_wallet_util::byte_ser;
use rand::{thread_rng, Rng};
let slatepack = SlatepackVersion { major: 1, minor: 0 }; let slatepack = SlatepackVersion { major: 1, minor: 0 };
let mut payload: Vec<u8> = Vec::with_capacity(243); let mut payload: Vec<u8> = Vec::with_capacity(243);
for _ in 0..payload.capacity() { 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 // includes optional fields
let bytes: [u8; 32] = thread_rng().gen(); let sender = Some(SlatepackAddress::random());
let sender_secret = StaticSecret::from(bytes);
let sender = Some(xDalekPublicKey::from(&sender_secret));
let sp = Slatepack { let sp = Slatepack {
slatepack, slatepack,
mode: 1, 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 ser = byte_ser::to_bytes(&SlatepackBin(sp.clone()))?;
let deser = byte_ser::from_bytes::<SlatepackBin>(&ser)?.0; let deser = byte_ser::from_bytes::<SlatepackBin>(&ser)?.0;
assert_eq!(sp.slatepack, deser.slatepack); assert_eq!(sp, deser);
assert_eq!(sp.mode, deser.mode);
assert_eq!(
sp.sender.unwrap().as_bytes(),
deser.sender.unwrap().as_bytes()
);
Ok(()) Ok(())
} }
@ -413,19 +398,18 @@ fn slatepack_bin_future() -> Result<(), grin_wallet_util::byte_ser::Error> {
use grin_wallet_util::byte_ser; use grin_wallet_util::byte_ser;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use std::io::Cursor; use std::io::Cursor;
let slatepack = SlatepackVersion { major: 1, minor: 0 }; let slatepack = SlatepackVersion { major: 1, minor: 0 };
let payload_size = 1234; let payload_size = 1234;
let mut payload: Vec<u8> = Vec::with_capacity(payload_size); let mut payload: Vec<u8> = Vec::with_capacity(payload_size);
for _ in 0..payload.capacity() { for _ in 0..payload.capacity() {
payload.push(rand::random()); 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!( println!(
"sender key len: {}", "sender len: {}",
sender.as_ref().unwrap().as_bytes().len() sender.as_ref().unwrap().encoded_len().unwrap()
); );
let sp = Slatepack { 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 // opt fields len (bytes to payload) 4
// bytes 5-8 are opt fields len // bytes 5-8 are opt fields len
// sender 32 // sender 68
let mut opt_fields_len_bytes = [0u8; 4]; let mut opt_fields_len_bytes = [0u8; 4];
opt_fields_len_bytes.copy_from_slice(&ser[5..9]); opt_fields_len_bytes.copy_from_slice(&ser[5..9]);
let mut rdr = Cursor::new(opt_fields_len_bytes.to_vec()); let mut rdr = Cursor::new(opt_fields_len_bytes.to_vec());
let opt_fields_len = rdr.read_u32::<BigEndian>().unwrap(); let opt_fields_len = rdr.read_u32::<BigEndian>().unwrap();
// check this matches what we expect below // 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; 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::<SlatepackBin>(&new_bytes)?.0; let deser = byte_ser::from_bytes::<SlatepackBin>(&new_bytes)?.0;
assert_eq!(sp.slatepack, deser.slatepack); assert_eq!(sp, deser);
assert_eq!(sp.mode, deser.mode);
assert_eq!(
sp.sender.unwrap().as_bytes(),
deser.sender.unwrap().as_bytes()
);
Ok(()) Ok(())
} }