// Copyright 2021 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. //! This module contains old slate versions and conversions to the newest slate version //! Used for serialization and deserialization of slates in a backwards compatible way. //! Versions earlier than V3 are removed for the 4.0.0 release, but versioning code //! remains for future needs use crate::slate::Slate; use crate::slate_versions::v4::{CoinbaseV4, SlateV4}; use crate::slate_versions::v4_bin::SlateV4Bin; use crate::slate_versions::v5::{CoinbaseV5, SlateV5}; use crate::slate_versions::v5_bin::SlateV5Bin; use crate::types::CbData; use crate::Error; use std::convert::TryFrom; pub mod ser; #[allow(missing_docs)] pub mod v4; #[allow(missing_docs)] pub mod v4_bin; #[allow(missing_docs)] pub mod v5; #[allow(missing_docs)] pub mod v5_bin; /// The most recent version of the slate pub const CURRENT_SLATE_VERSION: u16 = 5; /// The grin block header this slate is intended to be compatible with pub const GRIN_BLOCK_HEADER_VERSION: u16 = 3; /// Existing versions of the slate #[derive(EnumIter, Serialize, Deserialize, Clone, Debug, PartialEq, PartialOrd, Eq, Ord)] pub enum SlateVersion { /// V5 (Most Current) V5, /// V4 V4, } #[derive(Debug, Serialize, Deserialize)] #[serde(untagged)] /// Versions are ordered newest to oldest so serde attempts to /// deserialize newer versions first, then falls back to older versions. pub enum VersionedSlate { /// Current (5.0.0 Onwards?) V5(SlateV5), /// Current (4.0.0) V4(SlateV4), } impl VersionedSlate { /// Return slate version pub fn version(&self) -> SlateVersion { match *self { VersionedSlate::V5(_) => SlateVersion::V4, VersionedSlate::V4(_) => SlateVersion::V4, } } /// convert this slate type to a specified older version pub fn into_version(slate: Slate, version: SlateVersion) -> Result<VersionedSlate, Error> { match version { SlateVersion::V5 => Ok(VersionedSlate::V5(slate.into())), SlateVersion::V4 => Ok(VersionedSlate::V4(slate.into())), } } } impl From<VersionedSlate> for Slate { fn from(slate: VersionedSlate) -> Slate { match slate { VersionedSlate::V5(s) => Slate::from(s), VersionedSlate::V4(s) => Slate::from(s), } } } #[derive(Deserialize, Serialize)] #[serde(untagged)] /// Binary versions, can only be parsed 1:1 into the appropriate /// version, and VersionedSlate can up/downgrade from there pub enum VersionedBinSlate { /// Version 4, binary V4(SlateV4Bin), /// Version 5, binary V5(SlateV5Bin), } impl TryFrom<VersionedSlate> for VersionedBinSlate { type Error = Error; fn try_from(slate: VersionedSlate) -> Result<VersionedBinSlate, Error> { match slate { VersionedSlate::V5(s) => Ok(VersionedBinSlate::V5(SlateV5Bin(s))), VersionedSlate::V4(s) => Ok(VersionedBinSlate::V4(SlateV4Bin(s))), } } } impl From<VersionedBinSlate> for VersionedSlate { fn from(slate: VersionedBinSlate) -> VersionedSlate { match slate { VersionedBinSlate::V5(s) => VersionedSlate::V5(s.0), VersionedBinSlate::V4(s) => VersionedSlate::V4(s.0), } } } #[derive(Deserialize, Serialize)] #[serde(untagged)] /// Versions are ordered newest to oldest so serde attempts to /// deserialize newer versions first, then falls back to older versions. pub enum VersionedCoinbase { /// Current supported coinbase version. V5(CoinbaseV5), /// Previous version (no difference) V4(CoinbaseV4), } impl VersionedCoinbase { /// convert this coinbase data to a specific versioned representation for the json api. pub fn into_version(cb: CbData, version: SlateVersion) -> VersionedCoinbase { match version { SlateVersion::V5 => VersionedCoinbase::V5(cb.into()), SlateVersion::V4 => VersionedCoinbase::V4(cb.into()), } } } #[cfg(test)] mod tests { use crate::grin_core::core::transaction::{FeeFields, OutputFeatures}; use crate::grin_util::from_hex; use crate::grin_util::secp::key::PublicKey; use crate::grin_util::secp::pedersen::{Commitment, RangeProof}; use crate::grin_util::secp::Signature; use crate::slate::{KernelFeaturesArgs, ParticipantData, PaymentInfo, PaymentMemo}; use crate::slate_versions::v5::{CommitsV5, SlateV5}; use crate::{slate, Error, Slate, VersionedBinSlate, VersionedSlate}; use chrono::{DateTime, NaiveDateTime, Utc}; use ed25519_dalek::PublicKey as DalekPublicKey; use ed25519_dalek::Signature as DalekSignature; use grin_core::global::{set_local_chain_type, ChainTypes}; use grin_keychain::{ExtKeychain, Keychain, SwitchCommitmentType}; use grin_wallet_util::byte_ser::from_bytes; use std::convert::TryInto; // Populate a test internal slate with all fields to test conversions fn populate_test_slate() -> Result<Slate, Error> { let keychain = ExtKeychain::from_random_seed(true).unwrap(); let switch = SwitchCommitmentType::Regular; let mut slate_internal = Slate::blank(2, false); let id1 = ExtKeychain::derive_key_id(1, 1, 0, 0, 0); let id2 = ExtKeychain::derive_key_id(1, 1, 1, 0, 0); let skey1 = keychain.derive_key(0, &id1, switch).unwrap(); let skey2 = keychain.derive_key(0, &id2, switch).unwrap(); let xs = PublicKey::from_secret_key(keychain.secp(), &skey1).unwrap(); let nonce = PublicKey::from_secret_key(keychain.secp(), &skey2).unwrap(); let part = ParticipantData { public_blind_excess: xs, public_nonce: nonce, part_sig: None, }; let part2 = ParticipantData { public_blind_excess: xs, public_nonce: nonce, part_sig: Some(Signature::from_raw_data(&[11; 64]).unwrap()), }; slate_internal.participant_data.push(part.clone()); slate_internal.participant_data.push(part2); slate_internal.participant_data.push(part); // Another temp slate to convert commit data into internal 'transaction' like data // add some random commit data let slate_tmp = Slate::blank(1, false); let mut v5 = SlateV5::from(slate_tmp); let com1 = CommitsV5 { f: OutputFeatures::Plain.into(), c: Commitment::from_vec([3u8; 1].to_vec()), p: None, }; let com2 = CommitsV5 { f: OutputFeatures::Plain.into(), c: Commitment::from_vec([4u8; 1].to_vec()), p: Some(RangeProof::zero()), }; let mut coms = vec![]; coms.push(com1.clone()); coms.push(com1.clone()); coms.push(com1.clone()); coms.push(com2); v5.coms = Some(coms); slate_internal.tx = slate::tx_from_slate_v5(&v5); // basic fields slate_internal.amount = 23820323; slate_internal.kernel_features = 1; slate_internal.num_participants = 2; slate_internal.kernel_features_args = Some(KernelFeaturesArgs { lock_height: 2323223, }); // current style payment proof let raw_pubkey_str = "d03c09e9c19bb74aa9ea44e0fe5ae237a9bf40bddf0941064a80913a4459c8bb"; let b = from_hex(raw_pubkey_str).unwrap(); let d_pkey = DalekPublicKey::from_bytes(&b).unwrap(); // Need to remove milliseconds component for comparison. Won't be serialized let ts = NaiveDateTime::from_timestamp(Utc::now().timestamp(), 0); let ts = DateTime::<Utc>::from_utc(ts, Utc); let pm = PaymentMemo { memo_type: 0, memo: [9; 32], }; let psig = DalekSignature::from_bytes(&[0u8; 64]).unwrap(); slate_internal.payment_proof = Some(PaymentInfo { sender_address: Some(d_pkey.clone()), receiver_address: d_pkey.clone(), timestamp: ts.clone(), promise_signature: Some(psig), memo: Some(pm), }); Ok(slate_internal) } #[test] fn slatepack_version_v4_v5() -> Result<(), Error> { set_local_chain_type(ChainTypes::Mainnet); // Convert V5 slate into V4 slate, check result let slate_internal = populate_test_slate()?; let v5 = VersionedSlate::V5(slate_internal.clone().into()); let v4 = VersionedSlate::V4(slate_internal.into()); let v5_converted: Slate = v5.into(); let v4_converted: Slate = v4.into(); assert!(v5_converted.payment_proof.as_ref().unwrap().memo.is_some()); // Converted from v4 will not have memos and ts will be zeroed out assert!(v4_converted.payment_proof.as_ref().unwrap().memo.is_none()); assert_eq!( v4_converted .payment_proof .as_ref() .unwrap() .timestamp .timestamp(), 0 ); Ok(()) } #[test] fn slatepack_version_v4_v5_bin() -> Result<(), Error> { set_local_chain_type(ChainTypes::Mainnet); // Convert V5 slate into V4 slate, check result let slate_internal = populate_test_slate()?; let v5 = VersionedSlate::V5(slate_internal.clone().into()); let v5_bin: VersionedBinSlate = v5.try_into().unwrap(); let v4 = VersionedSlate::V4(slate_internal.into()); let v4_bin: VersionedBinSlate = v4.try_into().unwrap(); let v5_versioned: VersionedSlate = v5_bin.into(); let v4_versioned: VersionedSlate = v4_bin.into(); let v5_converted: Slate = v5_versioned.into(); let v4_converted: Slate = v4_versioned.into(); assert!(v5_converted.payment_proof.as_ref().unwrap().memo.is_some()); // Converted from v4 will not have memos and ts will be zeroed out assert!(v4_converted.payment_proof.as_ref().unwrap().memo.is_none()); assert_eq!( v4_converted .payment_proof .as_ref() .unwrap() .timestamp .timestamp(), 0 ); Ok(()) } }