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 = [
"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",

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" }
[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 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<xDalekPublicKey>,
recipients: Vec<xDalekPublicKey>,
sender: Option<SlatepackAddress>,
recipients: Vec<SlatepackAddress>,
) -> 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![],
},

View file

@ -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};

View file

@ -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<xDalekPublicKey>,
pub recipients: Vec<xDalekPublicKey>,
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<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();
pub_tx_f.read_to_end(&mut 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> {
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<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)
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::<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))
let slatepack = self.packer.deser_slatepack(data)?;
Ok((self.packer.get_slate(&slatepack)?, true))
}
}

View file

@ -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};

View file

@ -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" }

View file

@ -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<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,
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,

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};
// Framing and formatting for slate armor
static HEADER: &str = "BEGINSLATEPACK. ";
pub static HEADER: &str = "BEGINSLATEPACK. ";
static FOOTER: &str = ". ENDSLATEPACK.";
const WORD_LENGTH: usize = 15;

View file

@ -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};

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.
/// 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<xDalekPublicKey>,
pub sender: Option<SlatepackAddress>,
// Payload
/// Binary payload, can be encrypted or plaintext
@ -51,7 +54,7 @@ pub struct Slatepack {
pub payload: Vec<u8>,
}
fn default_sender_none() -> Option<xDalekPublicKey> {
fn default_sender_none() -> Option<SlatepackAddress> {
None
}
@ -73,21 +76,21 @@ impl Slatepack {
/// return length of optional fields
pub fn opt_fields_len(&self) -> Result<usize, ser::Error> {
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<xDalekPublicKey>) -> Result<(), Error> {
pub fn try_encrypt_payload(&mut self, recipients: Vec<SlatepackAddress>) -> Result<(), Error> {
if recipients.is_empty() {
return Ok(());
}
let rec_keys: Result<Vec<_>, _> = 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<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]
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<u8> = 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::<SlatepackBin>(&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<u8> = 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::<BigEndian>().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::<SlatepackBin>(&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(())
}