grin-wallet/libwallet/src/slate_versions/mod.rs
Yeastplume b3d90c92e8
[Contracts] Slatepack v5 Deserialization fix (#698)
* add V5 deserialization test + fixes

* clarify comment

* upwrap fix during v4 deserialization

* further unwrap removal
2023-11-21 13:51:46 +00:00

332 lines
10 KiB
Rust

// 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
/// NB (IMPORTANT): Ensure the slates are listed in reverse chronological
/// order (latest first)
pub enum VersionedBinSlate {
/// Version 5, binary
V5(SlateV5Bin),
/// Version 4, binary
V4(SlateV4Bin),
}
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)]
pub mod tests {
use crate::grin_core::core::transaction::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, Slatepacker, SlatepackerArgs, 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 std::convert::TryInto;
// Populate a test internal slate with all fields to test conversions
pub 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_opt(Utc::now().timestamp(), 0).unwrap();
let ts = DateTime::<Utc>::from_utc(ts, Utc);
let pm = PaymentMemo {
memo_type: 1,
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 ser_deser_current_slate() -> Result<(), Error> {
let slate_internal = populate_test_slate()?;
// Serialize slate into slatepack
let slatepacker_args = SlatepackerArgs {
sender: None,
recipients: vec![],
dec_key: None,
};
let slate_packer = Slatepacker::new(slatepacker_args);
let slate_packed = slate_packer.create_slatepack(&slate_internal).unwrap();
let slate_unpacked = slate_packer.get_slate(&slate_packed).unwrap();
// Just verifying payment proof for now, extend later to cover EQ for full slate if needs
// be
assert_eq!(slate_internal.payment_proof, slate_unpacked.payment_proof);
Ok(())
}
#[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(())
}
}