From 1042a634d85d76e8df9281f49d2f34a0cfd45394 Mon Sep 17 00:00:00 2001 From: danda Date: Wed, 13 Oct 2021 16:39:48 -0700 Subject: [PATCH] feat: add DbcPacket and DerivedKeySet --- src/dbc_packet.rs | 211 ++++++++++++++++++++++++++++++++++++++++++++++ src/error.rs | 3 + src/lib.rs | 2 + 3 files changed, 216 insertions(+) create mode 100644 src/dbc_packet.rs diff --git a/src/dbc_packet.rs b/src/dbc_packet.rs new file mode 100644 index 0000000..e6d3828 --- /dev/null +++ b/src/dbc_packet.rs @@ -0,0 +1,211 @@ +// Copyright 2021 MaidSafe.net limited. +// +// This SAFE Network Software is licensed to you under The General Public License (GPL), version 3. +// Unless required by applicable law or agreed to in writing, the SAFE Network Software distributed +// under the GPL Licence is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. Please review the Licences for the specific language governing +// permissions and limitations relating to use of the SAFE Network Software. + +use crate::{Dbc, Error, PublicKey, Result}; +use blsttc::SecretKeySet; +use rand::Rng; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::convert::Into; + +/// Represents a public key and optional +/// secret key, plus a random derivation index. +#[derive(Clone, Deserialize, Serialize)] +pub struct DerivedKeySet { + public_key: PublicKey, + derivation_index: [u8; 32], // Todo: make this an Fr with new blsttc. + + #[serde( + serialize_with = "serialize_option_sks", + deserialize_with = "deserialize_option_sks" + )] + secret_key_set: Option, +} + +impl DerivedKeySet { + /// public_key getter + pub fn public_key(&self) -> &PublicKey { + &self.public_key + } + + /// derivation_index getter + pub fn derivation_index(&self) -> &[u8; 32] { + &self.derivation_index + } + + /// secret_key_set getter + pub fn secret_key_set(&self) -> &Option { + &self.secret_key_set + } + + /// derives a public key + pub fn derived_public_key(&self) -> PublicKey { + self.public_key.derive_child(&self.derivation_index) + } + + // generates a random derivation index + fn random_derivation_index() -> [u8; 32] { + rand::thread_rng().gen() + } +} + +impl From for DerivedKeySet { + /// construct a DerivedKeySet with random derivation index from a PublicKey + fn from(public_key: PublicKey) -> Self { + let derivation_index = Self::random_derivation_index(); + Self { + public_key, + derivation_index, + secret_key_set: None, + } + } +} + +impl From for DerivedKeySet { + /// construct a DerivedKeySet with random derivation index from a SecretKeySet + fn from(secret_key_set: SecretKeySet) -> Self { + Self { + public_key: secret_key_set.public_keys().public_key(), + derivation_index: Self::random_derivation_index(), + secret_key_set: Some(secret_key_set), + } + } +} + +type Memo = Option<[u8; 32]>; + +/// Represents a DBC plus some metadata that is useful +/// for transporting DBCs between individuals, but is not +/// necessary for Mint operations. As such, this is a +/// client-only struct that the payment sender would +/// create before passing to the recipient. +/// +/// The MintNodes never use it. +/// +/// When creating a DBC, the sender should start with +/// a well-known public_use_key provided by the recipient. +/// +/// The sender then obtains DBC owner key via: +/// DerivedKeySet::from(well_known).derived_public_key(). +/// +/// The owner key is then passed into DbcContent::new(). +/// +/// After reissue, the client constructs the Dbc and then +/// puts it into a DbcPacket along with the DerivedKeySet +/// and an optional memo for the recipient. +/// +#[derive(Clone, Deserialize, Serialize)] +pub struct DbcPacket { + dbc: Dbc, + owner_keyset: DerivedKeySet, + + // todo/tbd: + // 1. Do we actually need/want a memo field for each DBC? Or only at Payment level? + // 2. Given that this is "text", should we use a (utf-8) String instead of raw bytes? + // 3. is it better to use a variable length Vec than fixed byte array? + // 4. If using a String or Vec, how do we enforce a max-len when deserializing? + // Perhaps requires a custom deserializer, eg serde deserialize_with="fn" + memo: Memo, +} + +impl DbcPacket { + /// Create a new DbcPacket. + /// validates that the DerivedKeySet matches the Dbc owner + pub fn new(dbc: Dbc, owner_keyset: DerivedKeySet, memo: Memo) -> Result { + let dp = DbcPacket { + dbc, + owner_keyset, + memo, + }; + dp.verify_owner_derivation_index()?; + Ok(dp) + } + + fn verify_owner_derivation_index(&self) -> Result<()> { + match self.dbc.owner() == self.owner_keyset.derived_public_key() { + true => Ok(()), + false => Err(Error::DerivedOwnerKeyDoesNotMatch), + } + } + + /// dbc getter + pub fn dbc(&self) -> &Dbc { + &self.dbc + } + + /// owner_keyset getter + /// + /// returns the DerivedKeySet representing the owner's + /// reusable well-known key and a random (one-time) + /// derivation index. + pub fn owner_keyset(&self) -> &DerivedKeySet { + &self.owner_keyset + } + + /// memo getter + /// + /// Returns the memo, which may be empty + pub fn memo(&self) -> &Memo { + &self.memo + } +} + +#[allow(clippy::from_over_into)] +impl Into for DbcPacket { + fn into(self) -> Dbc { + self.dbc + } +} + +// Serializes an Option +fn serialize_option_sks(input: &Option, s: S) -> Result +where + S: Serializer, +{ + let o = input.as_ref().map(|sks| SecretKeySetWrapper(sks.clone())); + o.serialize(s) +} + +// Deserializes an Option +fn deserialize_option_sks<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let o = Option::deserialize(deserializer)?; + Ok(o.map(|SecretKeySetWrapper(sks)| sks)) +} + +// A wrapper struct for (de)-serializing a SecretKeySet +#[derive(Serialize, Deserialize)] +struct SecretKeySetWrapper( + #[serde( + serialize_with = "SecretKeySetWrapper::serialize_sks", + deserialize_with = "SecretKeySetWrapper::deserialize_sks" + )] + SecretKeySet, +); + +impl SecretKeySetWrapper { + // serialize a SecretKeySet + fn serialize_sks(sks: &SecretKeySet, s: S) -> Result + where + S: Serializer, + { + s.serialize_bytes(&sks.to_bytes()) + } + + // deserialize a SecretKeySet + fn deserialize_sks<'de, D>(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + use serde::de::Error; + let vbytes = Vec::::deserialize(deserializer)?; + let sks = SecretKeySet::from_bytes(vbytes).map_err(|e| Error::custom(e.to_string()))?; + Ok(sks) + } +} diff --git a/src/error.rs b/src/error.rs index 2c8387c..a4f1a3e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -66,6 +66,9 @@ pub enum Error { #[error("RangeProof error: {0}")] RangeProof(#[from] bulletproofs::ProofError), + #[error("Derived owner key does not match")] + DerivedOwnerKeyDoesNotMatch, + #[error("Decryption error: {0}")] DecryptionBySharesFailed(#[from] blsttc::error::Error), diff --git a/src/lib.rs b/src/lib.rs index 401c026..a8bde06 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ use std::fmt; mod builder; mod dbc; mod dbc_content; +mod dbc_packet; mod dbc_transaction; mod error; mod key_manager; @@ -23,6 +24,7 @@ pub use crate::{ builder::{DbcBuilder, Output, ReissueRequestBuilder, TransactionBuilder}, dbc::Dbc, dbc_content::{Amount, AmountSecrets, DbcContent}, + dbc_packet::{DbcPacket, DerivedKeySet}, dbc_transaction::DbcTransaction, error::{Error, Result}, key_manager::{