mirror of
https://github.com/jwinterm/grin-wallet.git
synced 2026-10-17 15:21:39 +02:00
[Contracts] Experimental Slate v5 (#677)
* beginning to add and modify slate version 5 * completion of conversions from V5 to V4 Slate * timestamp and memo fields * upgrade/downgrade serialization of v5 slates * add v5 binary slate versions + start of tests * add bin slate ser/deser to tests * ensure serialization of timestamp always excludes milliseconds * start to update v5 tests, update v5 documentation * add fn to generate populated internal slate for conversion testing * add basic tests to convert all slate versions
This commit is contained in:
Generated
+65
@@ -732,6 +732,41 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2 1.0.42",
|
||||
"quote 1.0.20",
|
||||
"strsim 0.10.0",
|
||||
"syn 1.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote 1.0.20",
|
||||
"syn 1.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "5.3.4"
|
||||
@@ -1637,6 +1672,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"serde_with",
|
||||
"sha2 0.8.2",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
@@ -1938,6 +1974,12 @@ dependencies = [
|
||||
"syn 1.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
@@ -3709,6 +3751,29 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
"serde_with_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2 1.0.42",
|
||||
"quote 1.0.20",
|
||||
"syn 1.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_yaml"
|
||||
version = "0.8.26"
|
||||
|
||||
@@ -52,6 +52,7 @@ pub trait ForeignRpc {
|
||||
"Ok": {
|
||||
"foreign_api_version": 2,
|
||||
"supported_slate_versions": [
|
||||
"V5",
|
||||
"V4"
|
||||
]
|
||||
}
|
||||
@@ -141,7 +142,7 @@ pub trait ForeignRpc {
|
||||
}
|
||||
],
|
||||
"sta": "S1",
|
||||
"ver": "4:2"
|
||||
"ver": "5:2"
|
||||
},
|
||||
null,
|
||||
null
|
||||
@@ -176,7 +177,7 @@ pub trait ForeignRpc {
|
||||
}
|
||||
],
|
||||
"sta": "S2",
|
||||
"ver": "4:2"
|
||||
"ver": "5:2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,7 +206,7 @@ pub trait ForeignRpc {
|
||||
"method": "finalize_tx",
|
||||
"id": 1,
|
||||
"params": [{
|
||||
"ver": "4:2",
|
||||
"ver": "5:2",
|
||||
"id": "0436430c-2b02-624c-2032-570501212b00",
|
||||
"sta": "I2",
|
||||
"off": "383bc9df0dd332629520a0a72f8dd7f0e97d579dccb4dbdc8592aa3d424c846c",
|
||||
@@ -275,7 +276,7 @@ pub trait ForeignRpc {
|
||||
}
|
||||
],
|
||||
"sta": "I3",
|
||||
"ver": "4:2"
|
||||
"ver": "5:2"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,7 +504,7 @@ pub trait OwnerRpc {
|
||||
}
|
||||
],
|
||||
"sta": "S1",
|
||||
"ver": "4:2"
|
||||
"ver": "5:2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -570,7 +570,7 @@ pub trait OwnerRpc {
|
||||
}
|
||||
],
|
||||
"sta": "I1",
|
||||
"ver": "4:2"
|
||||
"ver": "5:2"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -941,7 +941,7 @@ pub trait OwnerRpc {
|
||||
"id": "0436430c-2b02-624c-2032-570501212b00",
|
||||
"sigs": [],
|
||||
"sta": "S3",
|
||||
"ver": "4:3"
|
||||
"ver": "5:3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +179,10 @@ impl HttpSlateSender {
|
||||
return Err(Error::ClientCallback(report));
|
||||
}
|
||||
|
||||
if supported_slate_versions.contains(&"V5".to_owned()) {
|
||||
return Ok(SlateVersion::V5);
|
||||
}
|
||||
|
||||
if supported_slate_versions.contains(&"V4".to_owned()) {
|
||||
return Ok(SlateVersion::V4);
|
||||
}
|
||||
@@ -223,6 +227,7 @@ impl SlateSender for HttpSlateSender {
|
||||
self.launch_tor()?;
|
||||
|
||||
let slate_send = match self.check_other_version(&url_str)? {
|
||||
SlateVersion::V5 => VersionedSlate::into_version(slate.clone(), SlateVersion::V5)?,
|
||||
SlateVersion::V4 => VersionedSlate::into_version(slate.clone(), SlateVersion::V4)?,
|
||||
};
|
||||
// Note: not using easy-jsonrpc as don't want the dependencies in this crate
|
||||
|
||||
@@ -16,6 +16,7 @@ rand = "0.6"
|
||||
serde = "1"
|
||||
serde_derive = "1"
|
||||
serde_json = "1"
|
||||
serde_with = { version = "1", features = ["chrono"] }
|
||||
log = "0.4"
|
||||
uuid = { version = "0.8", features = ["serde", "v4"] }
|
||||
chrono = { version = "0.4.11", features = ["serde"] }
|
||||
|
||||
@@ -116,14 +116,16 @@ where
|
||||
let excess = ret_slate.calc_excess(keychain.secp())?;
|
||||
|
||||
if let Some(ref mut p) = ret_slate.payment_proof {
|
||||
let sig = tx::create_payment_proof_signature(
|
||||
ret_slate.amount,
|
||||
&excess,
|
||||
p.sender_address,
|
||||
address::address_from_derivation_path(&keychain, &parent_key_id, 0)?,
|
||||
)?;
|
||||
if let Some(saddr) = p.sender_address {
|
||||
let sig = tx::create_payment_proof_signature(
|
||||
ret_slate.amount,
|
||||
&excess,
|
||||
saddr,
|
||||
address::address_from_derivation_path(&keychain, &parent_key_id, 0)?,
|
||||
)?;
|
||||
|
||||
p.receiver_signature = Some(sig);
|
||||
p.promise_signature = Some(sig);
|
||||
}
|
||||
}
|
||||
|
||||
ret_slate.amount = 0;
|
||||
|
||||
@@ -38,6 +38,7 @@ use crate::{
|
||||
SlatepackAddress, Slatepacker, SlatepackerArgs, TxLogEntryType, ViewWallet, WalletInitStatus,
|
||||
WalletInst, WalletLCProvider,
|
||||
};
|
||||
use chrono::prelude::{DateTime, NaiveDateTime, Utc};
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::SecretKey as DalekSecretKey;
|
||||
use ed25519_dalek::Verifier;
|
||||
@@ -568,9 +569,11 @@ where
|
||||
let sender_address = OnionV3Address::from_private(&sec_addr_key.0)?;
|
||||
|
||||
slate.payment_proof = Some(PaymentInfo {
|
||||
sender_address: sender_address.to_ed25519()?,
|
||||
sender_address: Some(sender_address.to_ed25519()?),
|
||||
receiver_address: a.pub_key,
|
||||
receiver_signature: None,
|
||||
promise_signature: None,
|
||||
timestamp: DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc),
|
||||
memo: None,
|
||||
});
|
||||
|
||||
context.payment_proof_derivation_index = Some(deriv_path);
|
||||
|
||||
@@ -205,7 +205,7 @@ where
|
||||
let sender_address = OnionV3Address::from_private(&sender_key.0)?;
|
||||
t.payment_proof = Some(StoredProofInfo {
|
||||
receiver_address: p.receiver_address,
|
||||
receiver_signature: p.receiver_signature,
|
||||
receiver_signature: p.promise_signature,
|
||||
sender_address: sender_address.to_ed25519()?,
|
||||
sender_address_path,
|
||||
sender_signature: None,
|
||||
|
||||
@@ -421,25 +421,27 @@ where
|
||||
}
|
||||
|
||||
if let Some(ref p) = slate.clone().payment_proof {
|
||||
let derivation_index = match context.payment_proof_derivation_index {
|
||||
Some(i) => i,
|
||||
None => 0,
|
||||
};
|
||||
let keychain = wallet.keychain(keychain_mask)?;
|
||||
let parent_key_id = wallet.parent_key_id();
|
||||
let excess = slate.calc_excess(keychain.secp())?;
|
||||
let sender_key =
|
||||
address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?;
|
||||
let sender_address = OnionV3Address::from_private(&sender_key.0)?;
|
||||
let sig =
|
||||
create_payment_proof_signature(slate.amount, &excess, p.sender_address, sender_key)?;
|
||||
tx.payment_proof = Some(StoredProofInfo {
|
||||
receiver_address: p.receiver_address,
|
||||
receiver_signature: p.receiver_signature,
|
||||
sender_address_path: derivation_index,
|
||||
sender_address: sender_address.to_ed25519()?,
|
||||
sender_signature: Some(sig),
|
||||
})
|
||||
if let Some(saddr) = p.sender_address {
|
||||
let derivation_index = match context.payment_proof_derivation_index {
|
||||
Some(i) => i,
|
||||
None => 0,
|
||||
};
|
||||
let keychain = wallet.keychain(keychain_mask)?;
|
||||
let parent_key_id = wallet.parent_key_id();
|
||||
let excess = slate.calc_excess(keychain.secp())?;
|
||||
let sender_key =
|
||||
address::address_from_derivation_path(&keychain, &parent_key_id, derivation_index)?;
|
||||
let sender_address = OnionV3Address::from_private(&sender_key.0)?;
|
||||
let sig = create_payment_proof_signature(slate.amount, &excess, saddr, sender_key)?;
|
||||
tx.payment_proof = Some(StoredProofInfo {
|
||||
receiver_address: p.receiver_address,
|
||||
receiver_signature: p.promise_signature,
|
||||
sender_address_path: derivation_index,
|
||||
sender_address: sender_address.to_ed25519()?,
|
||||
sender_signature: Some(sig),
|
||||
})
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
wallet.store_tx(&format!("{}", tx.tx_slate_id.unwrap()), slate.tx_or_err()?)?;
|
||||
@@ -561,9 +563,15 @@ where
|
||||
let orig_sender_sk =
|
||||
address::address_from_derivation_path(&keychain, parent_key_id, index)?;
|
||||
let orig_sender_address = OnionV3Address::from_private(&orig_sender_sk.0)?;
|
||||
if p.sender_address != orig_sender_address.to_ed25519()? {
|
||||
if let Some(saddr) = p.sender_address {
|
||||
if saddr != orig_sender_address.to_ed25519()? {
|
||||
return Err(Error::PaymentProof(
|
||||
"Sender address on slate does not match original sender address".to_owned(),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return Err(Error::PaymentProof(
|
||||
"Sender address on slate does not match original sender address".to_owned(),
|
||||
"Sender address on slate is not provided".to_owned(),
|
||||
));
|
||||
}
|
||||
|
||||
@@ -577,7 +585,7 @@ where
|
||||
&slate.calc_excess(&keychain.secp())?,
|
||||
orig_sender_address.to_ed25519()?,
|
||||
)?;
|
||||
let sig = match p.receiver_signature {
|
||||
let sig = match p.promise_signature {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
return Err(Error::PaymentProof(
|
||||
|
||||
@@ -33,7 +33,7 @@ use grin_wallet_util as util;
|
||||
use blake2_rfc as blake2;
|
||||
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde_with;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
|
||||
+492
-13
@@ -28,6 +28,7 @@ use crate::grin_util::secp::key::{PublicKey, SecretKey};
|
||||
use crate::grin_util::secp::pedersen::Commitment;
|
||||
use crate::grin_util::secp::Signature;
|
||||
use crate::grin_util::{secp, static_secp_instance};
|
||||
use chrono::prelude::{DateTime, NaiveDateTime, Utc};
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::Signature as DalekSignature;
|
||||
use serde::ser::{Serialize, Serializer};
|
||||
@@ -39,18 +40,36 @@ use crate::slate_versions::v4::{
|
||||
CommitsV4, KernelFeaturesArgsV4, OutputFeaturesV4, ParticipantDataV4, PaymentInfoV4,
|
||||
SlateStateV4, SlateV4, VersionCompatInfoV4,
|
||||
};
|
||||
use crate::slate_versions::v5::{
|
||||
CommitsV5, KernelFeaturesArgsV5, OutputFeaturesV5, ParticipantDataV5, PaymentInfoV5,
|
||||
PaymentMemoV5, SlateStateV5, SlateV5, VersionCompatInfoV5,
|
||||
};
|
||||
use crate::slate_versions::VersionedSlate;
|
||||
use crate::slate_versions::{CURRENT_SLATE_VERSION, GRIN_BLOCK_HEADER_VERSION};
|
||||
use crate::Context;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PaymentMemo {
|
||||
// The type of memo
|
||||
// 0x00 is directly embedded additional payment details
|
||||
// 0x01 represents the blake2b hash of an arbitrary invoice document
|
||||
pub memo_type: u8,
|
||||
// memo data itself
|
||||
pub memo: [u8; 32],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PaymentInfo {
|
||||
/// Sender address
|
||||
pub sender_address: DalekPublicKey,
|
||||
pub sender_address: Option<DalekPublicKey>,
|
||||
/// Receiver address
|
||||
pub receiver_address: DalekPublicKey,
|
||||
/// Receiver signature
|
||||
pub receiver_signature: Option<DalekSignature>,
|
||||
/// Promise signature
|
||||
pub promise_signature: Option<DalekSignature>,
|
||||
/// Timestamp (seconds)
|
||||
pub timestamp: DateTime<Utc>,
|
||||
/// Memo
|
||||
pub memo: Option<PaymentMemo>,
|
||||
}
|
||||
|
||||
/// Public data for each participant in the slate
|
||||
@@ -226,10 +245,11 @@ impl Slate {
|
||||
|
||||
/// Upgrade a versioned slate
|
||||
pub fn upgrade(v_slate: VersionedSlate) -> Result<Slate, Error> {
|
||||
let v4: SlateV4 = match v_slate {
|
||||
VersionedSlate::V4(s) => s,
|
||||
let internal: Slate = match v_slate {
|
||||
VersionedSlate::V4(s) => s.into(),
|
||||
VersionedSlate::V5(s) => s.into(),
|
||||
};
|
||||
Ok(v4.into())
|
||||
Ok(internal.into())
|
||||
}
|
||||
/// Compact the slate for initial sending, storing the excess + offset explicit
|
||||
/// and removing my input/output data
|
||||
@@ -699,13 +719,458 @@ impl Serialize for Slate {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let v4 = SlateV4::from(self);
|
||||
v4.serialize(serializer)
|
||||
let v5 = SlateV5::from(self);
|
||||
v5.serialize(serializer)
|
||||
}
|
||||
}
|
||||
// Current slate version to versioned conversions
|
||||
|
||||
// Slate to versioned
|
||||
////// V5
|
||||
impl From<Slate> for SlateV5 {
|
||||
fn from(slate: Slate) -> SlateV5 {
|
||||
let Slate {
|
||||
num_participants: num_parts,
|
||||
id,
|
||||
state,
|
||||
tx: _,
|
||||
amount,
|
||||
fee_fields,
|
||||
kernel_features,
|
||||
ttl_cutoff_height: ttl,
|
||||
offset: off,
|
||||
participant_data,
|
||||
version_info,
|
||||
payment_proof,
|
||||
kernel_features_args,
|
||||
} = slate.clone();
|
||||
let participant_data = map_vec!(participant_data, |data| ParticipantDataV5::from(data));
|
||||
let ver = VersionCompatInfoV5::from(&version_info);
|
||||
let payment_proof = match payment_proof {
|
||||
Some(p) => Some(PaymentInfoV5::from(&p)),
|
||||
None => None,
|
||||
};
|
||||
let feat_args = match kernel_features_args {
|
||||
Some(a) => Some(KernelFeaturesArgsV5::from(&a)),
|
||||
None => None,
|
||||
};
|
||||
let sta = SlateStateV5::from(&state);
|
||||
SlateV5 {
|
||||
num_parts,
|
||||
id,
|
||||
sta,
|
||||
coms: (&slate).into(),
|
||||
amt: amount,
|
||||
fee: fee_fields,
|
||||
feat: kernel_features,
|
||||
ttl,
|
||||
off,
|
||||
sigs: participant_data,
|
||||
ver,
|
||||
proof: payment_proof,
|
||||
feat_args,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Slate> for SlateV5 {
|
||||
fn from(slate: &Slate) -> SlateV5 {
|
||||
let Slate {
|
||||
num_participants: num_parts,
|
||||
id,
|
||||
state,
|
||||
tx: _,
|
||||
amount,
|
||||
fee_fields,
|
||||
kernel_features,
|
||||
ttl_cutoff_height: ttl,
|
||||
offset,
|
||||
participant_data,
|
||||
version_info,
|
||||
payment_proof,
|
||||
kernel_features_args,
|
||||
} = slate;
|
||||
let num_parts = *num_parts;
|
||||
let id = *id;
|
||||
let amount = *amount;
|
||||
let fee_fields = *fee_fields;
|
||||
let feat = *kernel_features;
|
||||
let ttl = *ttl;
|
||||
let off = offset.clone();
|
||||
let participant_data = map_vec!(participant_data, |data| ParticipantDataV5::from(data));
|
||||
let ver = VersionCompatInfoV5::from(version_info);
|
||||
let payment_proof = match payment_proof {
|
||||
Some(p) => Some(PaymentInfoV5::from(p)),
|
||||
None => None,
|
||||
};
|
||||
let sta = SlateStateV5::from(state);
|
||||
let feat_args = match kernel_features_args {
|
||||
Some(a) => Some(KernelFeaturesArgsV5::from(a)),
|
||||
None => None,
|
||||
};
|
||||
SlateV5 {
|
||||
num_parts,
|
||||
id,
|
||||
sta,
|
||||
coms: slate.into(),
|
||||
amt: amount,
|
||||
fee: fee_fields,
|
||||
feat,
|
||||
ttl,
|
||||
off,
|
||||
sigs: participant_data,
|
||||
ver,
|
||||
proof: payment_proof,
|
||||
feat_args,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Slate> for Option<Vec<CommitsV5>> {
|
||||
fn from(slate: &Slate) -> Self {
|
||||
match slate.tx {
|
||||
None => None,
|
||||
Some(ref tx) => {
|
||||
let mut ret_vec = vec![];
|
||||
match tx.inputs() {
|
||||
Inputs::CommitOnly(_) => panic!("commit only inputs unsupported"),
|
||||
Inputs::FeaturesAndCommit(ref inputs) => {
|
||||
for input in inputs {
|
||||
ret_vec.push(input.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
for output in tx.outputs() {
|
||||
ret_vec.push(output.into());
|
||||
}
|
||||
Some(ret_vec)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ParticipantData> for ParticipantDataV5 {
|
||||
fn from(data: &ParticipantData) -> ParticipantDataV5 {
|
||||
let ParticipantData {
|
||||
public_blind_excess,
|
||||
public_nonce,
|
||||
part_sig,
|
||||
} = data;
|
||||
let public_blind_excess = *public_blind_excess;
|
||||
let public_nonce = *public_nonce;
|
||||
let part_sig = *part_sig;
|
||||
ParticipantDataV5 {
|
||||
xs: public_blind_excess,
|
||||
nonce: public_nonce,
|
||||
part: part_sig,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SlateState> for SlateStateV5 {
|
||||
fn from(data: &SlateState) -> SlateStateV5 {
|
||||
match data {
|
||||
SlateState::Unknown => SlateStateV5::Unknown,
|
||||
SlateState::Standard1 => SlateStateV5::Standard1,
|
||||
SlateState::Standard2 => SlateStateV5::Standard2,
|
||||
SlateState::Standard3 => SlateStateV5::Standard3,
|
||||
SlateState::Invoice1 => SlateStateV5::Invoice1,
|
||||
SlateState::Invoice2 => SlateStateV5::Invoice2,
|
||||
SlateState::Invoice3 => SlateStateV5::Invoice3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&KernelFeaturesArgs> for KernelFeaturesArgsV5 {
|
||||
fn from(data: &KernelFeaturesArgs) -> KernelFeaturesArgsV5 {
|
||||
let KernelFeaturesArgs { lock_height } = data;
|
||||
let lock_hgt = *lock_height;
|
||||
KernelFeaturesArgsV5 { lock_hgt }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&VersionCompatInfo> for VersionCompatInfoV5 {
|
||||
fn from(data: &VersionCompatInfo) -> VersionCompatInfoV5 {
|
||||
let VersionCompatInfo {
|
||||
version,
|
||||
block_header_version,
|
||||
} = data;
|
||||
let version = *version;
|
||||
let block_header_version = *block_header_version;
|
||||
VersionCompatInfoV5 {
|
||||
version,
|
||||
block_header_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PaymentInfo> for PaymentInfoV5 {
|
||||
fn from(data: &PaymentInfo) -> PaymentInfoV5 {
|
||||
let PaymentInfo {
|
||||
sender_address,
|
||||
receiver_address,
|
||||
promise_signature,
|
||||
timestamp,
|
||||
memo,
|
||||
} = data;
|
||||
let sender_address = *sender_address;
|
||||
// TODO: If not provided and we need to downgrade to V5,
|
||||
// Provide a blank key insted. Consider whether this should fail
|
||||
// instead, noting that `try_from`isn't currently used in any versioning
|
||||
// logic
|
||||
// Also note the zeroized ed25519 public key has a known private key, check if
|
||||
// this could ever possibly become an issue
|
||||
let saddr = match sender_address {
|
||||
Some(a) => a,
|
||||
None => DalekPublicKey::from_bytes(&[0u8; 32]).unwrap(),
|
||||
};
|
||||
let receiver_address = *receiver_address;
|
||||
let promise_signature = *promise_signature;
|
||||
let timestamp = *timestamp;
|
||||
let memo = match memo {
|
||||
Some(m) => Some(PaymentMemoV5 {
|
||||
memo_type: m.memo_type,
|
||||
memo: m.memo,
|
||||
}),
|
||||
None => None,
|
||||
};
|
||||
PaymentInfoV5 {
|
||||
saddr,
|
||||
raddr: receiver_address,
|
||||
psig: promise_signature,
|
||||
ts: timestamp,
|
||||
memo: memo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OutputFeatures> for OutputFeaturesV5 {
|
||||
fn from(of: OutputFeatures) -> OutputFeaturesV5 {
|
||||
let index = match of {
|
||||
OutputFeatures::Plain => 0,
|
||||
OutputFeatures::Coinbase => 1,
|
||||
};
|
||||
OutputFeaturesV5(index)
|
||||
}
|
||||
}
|
||||
|
||||
///// V4
|
||||
impl From<SlateV5> for Slate {
|
||||
fn from(slate: SlateV5) -> Slate {
|
||||
let SlateV5 {
|
||||
num_parts: num_participants,
|
||||
id,
|
||||
sta,
|
||||
coms: _,
|
||||
amt: amount,
|
||||
fee: fee_fields,
|
||||
feat: kernel_features,
|
||||
ttl: ttl_cutoff_height,
|
||||
off: offset,
|
||||
sigs: participant_data,
|
||||
ver,
|
||||
proof: payment_proof,
|
||||
feat_args,
|
||||
} = slate.clone();
|
||||
let participant_data = map_vec!(participant_data, |data| ParticipantData::from(data));
|
||||
let version_info = VersionCompatInfo::from(&ver);
|
||||
let payment_proof = match &payment_proof {
|
||||
Some(p) => Some(PaymentInfo::from(p)),
|
||||
None => None,
|
||||
};
|
||||
let kernel_features_args = match &feat_args {
|
||||
Some(a) => Some(KernelFeaturesArgs::from(a)),
|
||||
None => None,
|
||||
};
|
||||
let state = SlateState::from(&sta);
|
||||
Slate {
|
||||
num_participants,
|
||||
id,
|
||||
state,
|
||||
tx: (&slate).into(),
|
||||
amount,
|
||||
fee_fields,
|
||||
kernel_features,
|
||||
ttl_cutoff_height,
|
||||
offset,
|
||||
participant_data,
|
||||
version_info,
|
||||
payment_proof,
|
||||
kernel_features_args,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tx_from_slate_v5(slate: &SlateV5) -> Option<Transaction> {
|
||||
let coms = match slate.coms.as_ref() {
|
||||
Some(c) => c,
|
||||
None => return None,
|
||||
};
|
||||
let secp = static_secp_instance();
|
||||
let secp = secp.lock();
|
||||
let mut calc_slate = Slate::blank(2, false);
|
||||
calc_slate.fee_fields = slate.fee;
|
||||
for d in slate.sigs.iter() {
|
||||
calc_slate.participant_data.push(ParticipantData {
|
||||
public_blind_excess: d.xs,
|
||||
public_nonce: d.nonce,
|
||||
part_sig: d.part,
|
||||
});
|
||||
}
|
||||
let excess = match calc_slate.calc_excess(&secp) {
|
||||
Ok(e) => e,
|
||||
Err(_) => Commitment::from_vec(vec![0]),
|
||||
};
|
||||
let excess_sig = match calc_slate.finalize_signature(&secp) {
|
||||
Ok(s) => s,
|
||||
Err(_) => Signature::from_raw_data(&[0; 64]).unwrap(),
|
||||
};
|
||||
let kernel = TxKernel {
|
||||
features: match slate.feat {
|
||||
0 => KernelFeatures::Plain { fee: slate.fee },
|
||||
1 => KernelFeatures::HeightLocked {
|
||||
fee: slate.fee,
|
||||
lock_height: match slate.feat_args.as_ref() {
|
||||
Some(a) => a.lock_hgt,
|
||||
None => 0,
|
||||
},
|
||||
},
|
||||
_ => KernelFeatures::Plain { fee: slate.fee },
|
||||
},
|
||||
excess,
|
||||
excess_sig,
|
||||
};
|
||||
let mut tx = Slate::empty_transaction().with_kernel(kernel);
|
||||
|
||||
let mut outputs = vec![];
|
||||
let mut inputs = vec![];
|
||||
|
||||
for c in coms.iter() {
|
||||
match &c.p {
|
||||
Some(p) => {
|
||||
outputs.push(Output::new(c.f.into(), c.c, p.clone()));
|
||||
}
|
||||
None => {
|
||||
inputs.push(Input {
|
||||
features: c.f.into(),
|
||||
commit: c.c,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tx.body = tx
|
||||
.body
|
||||
.replace_inputs(inputs.as_slice().into())
|
||||
.replace_outputs(outputs.as_slice());
|
||||
tx.offset = slate.off.clone();
|
||||
Some(tx)
|
||||
}
|
||||
|
||||
// Node's Transaction object and lock height to SlateV5 `coms`
|
||||
impl From<&SlateV5> for Option<Transaction> {
|
||||
fn from(slate: &SlateV5) -> Option<Transaction> {
|
||||
tx_from_slate_v5(slate)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ParticipantDataV5> for ParticipantData {
|
||||
fn from(data: &ParticipantDataV5) -> ParticipantData {
|
||||
let ParticipantDataV5 {
|
||||
xs: public_blind_excess,
|
||||
nonce: public_nonce,
|
||||
part: part_sig,
|
||||
} = data;
|
||||
let public_blind_excess = *public_blind_excess;
|
||||
let public_nonce = *public_nonce;
|
||||
let part_sig = *part_sig;
|
||||
ParticipantData {
|
||||
public_blind_excess,
|
||||
public_nonce,
|
||||
part_sig,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&KernelFeaturesArgsV5> for KernelFeaturesArgs {
|
||||
fn from(data: &KernelFeaturesArgsV5) -> KernelFeaturesArgs {
|
||||
let KernelFeaturesArgsV5 { lock_hgt } = data;
|
||||
let lock_height = *lock_hgt;
|
||||
KernelFeaturesArgs { lock_height }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SlateStateV5> for SlateState {
|
||||
fn from(data: &SlateStateV5) -> SlateState {
|
||||
match data {
|
||||
SlateStateV5::Unknown => SlateState::Unknown,
|
||||
SlateStateV5::Standard1 => SlateState::Standard1,
|
||||
SlateStateV5::Standard2 => SlateState::Standard2,
|
||||
SlateStateV5::Standard3 => SlateState::Standard3,
|
||||
SlateStateV5::Invoice1 => SlateState::Invoice1,
|
||||
SlateStateV5::Invoice2 => SlateState::Invoice2,
|
||||
SlateStateV5::Invoice3 => SlateState::Invoice3,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&VersionCompatInfoV5> for VersionCompatInfo {
|
||||
fn from(data: &VersionCompatInfoV5) -> VersionCompatInfo {
|
||||
let VersionCompatInfoV5 {
|
||||
version,
|
||||
block_header_version,
|
||||
} = data;
|
||||
let version = *version;
|
||||
let block_header_version = *block_header_version;
|
||||
VersionCompatInfo {
|
||||
version,
|
||||
block_header_version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&PaymentInfoV5> for PaymentInfo {
|
||||
fn from(data: &PaymentInfoV5) -> PaymentInfo {
|
||||
let PaymentInfoV5 {
|
||||
saddr: sender_address,
|
||||
raddr: receiver_address,
|
||||
psig: promise_signature,
|
||||
ts: timestamp,
|
||||
memo,
|
||||
} = data;
|
||||
let sender_address = *sender_address;
|
||||
let receiver_address = *receiver_address;
|
||||
let promise_signature = *promise_signature;
|
||||
let timestamp = *timestamp;
|
||||
let memo: Option<PaymentMemo> = match memo {
|
||||
Some(m) => {
|
||||
//memo_ret.copy_from_slice(&grin_util::from_hex(m.memo).unwrap_or_default()[0..32]);
|
||||
Some(PaymentMemo {
|
||||
memo_type: m.memo_type,
|
||||
memo: m.memo,
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
PaymentInfo {
|
||||
sender_address: Some(sender_address),
|
||||
receiver_address,
|
||||
promise_signature: promise_signature,
|
||||
timestamp,
|
||||
memo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OutputFeaturesV5> for OutputFeatures {
|
||||
fn from(of: OutputFeaturesV5) -> OutputFeatures {
|
||||
match of.0 {
|
||||
1 => OutputFeatures::Coinbase,
|
||||
0 | _ => OutputFeatures::Plain,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///////// V4
|
||||
impl From<Slate> for SlateV4 {
|
||||
fn from(slate: Slate) -> SlateV4 {
|
||||
let Slate {
|
||||
@@ -888,13 +1353,25 @@ impl From<&PaymentInfo> for PaymentInfoV4 {
|
||||
let PaymentInfo {
|
||||
sender_address,
|
||||
receiver_address,
|
||||
receiver_signature,
|
||||
promise_signature: receiver_signature,
|
||||
timestamp: _,
|
||||
memo: _,
|
||||
} = data;
|
||||
let sender_address = *sender_address;
|
||||
// TODO: If not provided and we need to downgrade to V4,
|
||||
// Provide a blank key insted. Consider whether this should fail
|
||||
// instead, noting that `try_from`isn't currently used in any versioning
|
||||
// logic
|
||||
// Also note the zeroized ed25519 public key has a known private key, check if
|
||||
// this could ever possibly become an issue
|
||||
let saddr = match sender_address {
|
||||
Some(a) => a,
|
||||
None => DalekPublicKey::from_bytes(&[0u8; 32]).unwrap(),
|
||||
};
|
||||
let receiver_address = *receiver_address;
|
||||
let receiver_signature = *receiver_signature;
|
||||
PaymentInfoV4 {
|
||||
saddr: sender_address,
|
||||
saddr,
|
||||
raddr: receiver_address,
|
||||
rsig: receiver_signature,
|
||||
}
|
||||
@@ -1097,9 +1574,11 @@ impl From<&PaymentInfoV4> for PaymentInfo {
|
||||
let receiver_address = *receiver_address;
|
||||
let receiver_signature = *receiver_signature;
|
||||
PaymentInfo {
|
||||
sender_address,
|
||||
sender_address: Some(sender_address),
|
||||
receiver_address,
|
||||
receiver_signature,
|
||||
promise_signature: receiver_signature,
|
||||
timestamp: DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(0, 0), Utc),
|
||||
memo: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
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;
|
||||
@@ -30,9 +32,13 @@ pub mod ser;
|
||||
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 = 4;
|
||||
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;
|
||||
@@ -40,7 +46,9 @@ 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 {
|
||||
/// V4 (most current)
|
||||
/// V5 (Most Current)
|
||||
V5,
|
||||
/// V4
|
||||
V4,
|
||||
}
|
||||
|
||||
@@ -49,7 +57,9 @@ pub enum SlateVersion {
|
||||
/// Versions are ordered newest to oldest so serde attempts to
|
||||
/// deserialize newer versions first, then falls back to older versions.
|
||||
pub enum VersionedSlate {
|
||||
/// Current (4.0.0 Onwards )
|
||||
/// Current (5.0.0 Onwards?)
|
||||
V5(SlateV5),
|
||||
/// Current (4.0.0)
|
||||
V4(SlateV4),
|
||||
}
|
||||
|
||||
@@ -57,6 +67,7 @@ impl VersionedSlate {
|
||||
/// Return slate version
|
||||
pub fn version(&self) -> SlateVersion {
|
||||
match *self {
|
||||
VersionedSlate::V5(_) => SlateVersion::V4,
|
||||
VersionedSlate::V4(_) => SlateVersion::V4,
|
||||
}
|
||||
}
|
||||
@@ -64,6 +75,7 @@ impl VersionedSlate {
|
||||
/// 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())),
|
||||
}
|
||||
}
|
||||
@@ -72,6 +84,7 @@ impl VersionedSlate {
|
||||
impl From<VersionedSlate> for Slate {
|
||||
fn from(slate: VersionedSlate) -> Slate {
|
||||
match slate {
|
||||
VersionedSlate::V5(s) => Slate::from(s),
|
||||
VersionedSlate::V4(s) => Slate::from(s),
|
||||
}
|
||||
}
|
||||
@@ -84,12 +97,15 @@ impl From<VersionedSlate> for Slate {
|
||||
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))),
|
||||
}
|
||||
}
|
||||
@@ -98,6 +114,7 @@ impl TryFrom<VersionedSlate> for VersionedBinSlate {
|
||||
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),
|
||||
}
|
||||
}
|
||||
@@ -109,6 +126,8 @@ impl From<VersionedBinSlate> for VersionedSlate {
|
||||
/// 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),
|
||||
}
|
||||
|
||||
@@ -116,7 +135,174 @@ 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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,7 +472,7 @@ pub mod option_dalek_sig_base64 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes slates 'version_info' field
|
||||
/// Serializes slates 'version_info' field - V4
|
||||
pub mod version_info_v4 {
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
@@ -514,7 +514,49 @@ pub mod version_info_v4 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes slates 'state' field
|
||||
/// Serializes slates 'version_info' field - V5
|
||||
pub mod version_info_v5 {
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
|
||||
use crate::slate_versions::v5::VersionCompatInfoV5;
|
||||
|
||||
///
|
||||
pub fn serialize<S>(v: &VersionCompatInfoV5, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&format!("{}:{}", v.version, v.block_header_version))
|
||||
}
|
||||
|
||||
///
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<VersionCompatInfoV5, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
String::deserialize(deserializer).and_then(|s| {
|
||||
let mut retval = VersionCompatInfoV5 {
|
||||
version: 0,
|
||||
block_header_version: 0,
|
||||
};
|
||||
let v: Vec<&str> = s.split(':').collect();
|
||||
if v.len() != 2 {
|
||||
return Err(Error::custom("Cannot parse version"));
|
||||
}
|
||||
match u16::from_str_radix(v[0], 10) {
|
||||
Ok(u) => retval.version = u,
|
||||
Err(e) => return Err(Error::custom(format!("Cannot parse version: {}", e))),
|
||||
}
|
||||
match u16::from_str_radix(v[1], 10) {
|
||||
Ok(u) => retval.block_header_version = u,
|
||||
Err(e) => return Err(Error::custom(format!("Cannot parse version: {}", e))),
|
||||
}
|
||||
Ok(retval)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes slates 'state' field - V4
|
||||
pub mod slate_state_v4 {
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
@@ -559,6 +601,51 @@ pub mod slate_state_v4 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes slates 'state' field - V5
|
||||
pub mod slate_state_v5 {
|
||||
use serde::de::Error;
|
||||
use serde::{Deserialize, Deserializer, Serializer};
|
||||
|
||||
use crate::slate_versions::v5::SlateStateV5;
|
||||
|
||||
///
|
||||
pub fn serialize<S>(st: &SlateStateV5, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let label = match st {
|
||||
SlateStateV5::Unknown => "NA",
|
||||
SlateStateV5::Standard1 => "S1",
|
||||
SlateStateV5::Standard2 => "S2",
|
||||
SlateStateV5::Standard3 => "S3",
|
||||
SlateStateV5::Invoice1 => "I1",
|
||||
SlateStateV5::Invoice2 => "I2",
|
||||
SlateStateV5::Invoice3 => "I3",
|
||||
};
|
||||
serializer.serialize_str(label)
|
||||
}
|
||||
|
||||
///
|
||||
pub fn deserialize<'de, D>(deserializer: D) -> Result<SlateStateV5, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
String::deserialize(deserializer).and_then(|s| {
|
||||
let retval = match s.as_str() {
|
||||
"NA" => SlateStateV5::Unknown,
|
||||
"S1" => SlateStateV5::Standard1,
|
||||
"S2" => SlateStateV5::Standard2,
|
||||
"S3" => SlateStateV5::Standard3,
|
||||
"I1" => SlateStateV5::Invoice1,
|
||||
"I2" => SlateStateV5::Invoice2,
|
||||
"I3" => SlateStateV5::Invoice3,
|
||||
_ => return Err(Error::custom("Invalid Slate state")),
|
||||
};
|
||||
Ok(retval)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Serializes an secp256k1 pubkey to base64
|
||||
pub mod uuid_base64 {
|
||||
use base64;
|
||||
|
||||
@@ -64,7 +64,7 @@ impl Readable for SlateStateV4 {
|
||||
}
|
||||
|
||||
/// Allow serializing of Uuids not defined in crate
|
||||
struct UuidWrap(Uuid);
|
||||
pub struct UuidWrap(pub Uuid);
|
||||
|
||||
impl Writeable for UuidWrap {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), grin_ser::Error> {
|
||||
@@ -82,7 +82,7 @@ impl Readable for UuidWrap {
|
||||
}
|
||||
|
||||
/// Helper struct to serialize optional fields efficiently
|
||||
struct SlateOptFields {
|
||||
pub struct SlateOptFields {
|
||||
/// num parts, default 2
|
||||
pub num_parts: u8,
|
||||
/// amt, default 0
|
||||
|
||||
@@ -0,0 +1,382 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
//! Contains V5 of the slate (version as yet undetermined)
|
||||
//!
|
||||
//! TODO: Should be considered experimental and remain in an experimental branch
|
||||
//! until such time as the relevant RFCs are accepted
|
||||
//!
|
||||
//! Changes from V4:
|
||||
//! #### Top-Level Slate Struct
|
||||
//! *
|
||||
//! #### PaymentInfoV5
|
||||
//! * `saddr`, i.e. `sender_address` in main Slate becomes optional
|
||||
//! * `rsig` is renamed to `psig`, corresponding to rename of `receiver_signature` to `promise_signature` in main Slate
|
||||
//!
|
||||
//! * `ts` added (`timestamp` in main slate). Serialized as i64 representing epoch time in seconds
|
||||
//! * `memo` added as optional MemoV5 Struct, which contains:
|
||||
//! * `memo_type`: u8
|
||||
//! * 0x00 = payment details directly embedded
|
||||
//! * 0x01 = Blake2b hash of an arbitrary invoice document
|
||||
//! * `memo`: [u8;32] the memo data itself
|
||||
|
||||
use crate::grin_core::core::FeeFields;
|
||||
use crate::grin_core::core::{Input, Output, TxKernel};
|
||||
use crate::grin_core::libtx::secp_ser;
|
||||
use crate::grin_keychain::{BlindingFactor, Identifier};
|
||||
use crate::grin_util::secp;
|
||||
use crate::grin_util::secp::key::PublicKey;
|
||||
use crate::grin_util::secp::pedersen::{Commitment, RangeProof};
|
||||
use crate::grin_util::secp::Signature;
|
||||
use crate::{slate_versions::ser, CbData};
|
||||
use chrono::prelude::{DateTime, Utc};
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::Signature as DalekSignature;
|
||||
use serde_with::TimestampSeconds;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct SlateV5 {
|
||||
// Required Fields
|
||||
/// Versioning info
|
||||
#[serde(with = "ser::version_info_v5")]
|
||||
pub ver: VersionCompatInfoV5,
|
||||
/// Unique transaction ID, selected by sender
|
||||
pub id: Uuid,
|
||||
/// Slate state
|
||||
#[serde(with = "ser::slate_state_v5")]
|
||||
pub sta: SlateStateV5,
|
||||
/// Offset, modified by each participant inserting inputs
|
||||
/// as the transaction progresses
|
||||
#[serde(
|
||||
serialize_with = "secp_ser::as_hex",
|
||||
deserialize_with = "secp_ser::blind_from_hex"
|
||||
)]
|
||||
#[serde(default = "default_offset_zero")]
|
||||
#[serde(skip_serializing_if = "offset_is_zero")]
|
||||
pub off: BlindingFactor,
|
||||
// Optional fields depending on state
|
||||
/// The number of participants intended to take part in this transaction
|
||||
#[serde(default = "default_num_participants_2")]
|
||||
#[serde(skip_serializing_if = "num_parts_is_2")]
|
||||
pub num_parts: u8,
|
||||
/// base amount (excluding fee)
|
||||
#[serde(with = "secp_ser::string_or_u64")]
|
||||
#[serde(skip_serializing_if = "u64_is_blank")]
|
||||
#[serde(default = "default_u64_0")]
|
||||
pub amt: u64,
|
||||
/// fee
|
||||
#[serde(skip_serializing_if = "fee_is_zero")]
|
||||
#[serde(default = "default_fee")]
|
||||
pub fee: FeeFields,
|
||||
/// kernel features, if any
|
||||
#[serde(skip_serializing_if = "u8_is_blank")]
|
||||
#[serde(default = "default_u8_0")]
|
||||
pub feat: u8,
|
||||
/// TTL, the block height at which wallets
|
||||
/// should refuse to process the transaction and unlock all
|
||||
#[serde(with = "secp_ser::string_or_u64")]
|
||||
#[serde(skip_serializing_if = "u64_is_blank")]
|
||||
#[serde(default = "default_u64_0")]
|
||||
pub ttl: u64,
|
||||
// Structs always required
|
||||
/// Participant data, each participant in the transaction will
|
||||
/// insert their public data here. For now, 0 is sender and 1
|
||||
/// is receiver, though this will change for multi-party
|
||||
pub sigs: Vec<ParticipantDataV5>,
|
||||
// Situational, but required at some point in the tx
|
||||
/// Inputs/Output commits added to slate
|
||||
#[serde(default = "default_coms_none")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub coms: Option<Vec<CommitsV5>>,
|
||||
// Optional Structs
|
||||
/// Payment Proof
|
||||
#[serde(default = "default_payment_none")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub proof: Option<PaymentInfoV5>,
|
||||
/// Kernel features arguments
|
||||
#[serde(default = "default_kernel_features_none")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub feat_args: Option<KernelFeaturesArgsV5>,
|
||||
}
|
||||
|
||||
fn default_payment_none() -> Option<PaymentInfoV5> {
|
||||
None
|
||||
}
|
||||
|
||||
fn default_offset_zero() -> BlindingFactor {
|
||||
BlindingFactor::zero()
|
||||
}
|
||||
|
||||
fn offset_is_zero(o: &BlindingFactor) -> bool {
|
||||
*o == BlindingFactor::zero()
|
||||
}
|
||||
|
||||
fn default_coms_none() -> Option<Vec<CommitsV5>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn default_u64_0() -> u64 {
|
||||
0
|
||||
}
|
||||
|
||||
fn num_parts_is_2(n: &u8) -> bool {
|
||||
*n == 2
|
||||
}
|
||||
|
||||
fn default_num_participants_2() -> u8 {
|
||||
2
|
||||
}
|
||||
|
||||
fn default_kernel_features_none() -> Option<KernelFeaturesArgsV5> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Slate state definition
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub enum SlateStateV5 {
|
||||
/// Unknown, coming from earlier versions of the slate
|
||||
Unknown,
|
||||
/// Standard flow, freshly init
|
||||
Standard1,
|
||||
/// Standard flow, return journey
|
||||
Standard2,
|
||||
/// Standard flow, ready for transaction posting
|
||||
Standard3,
|
||||
/// Invoice flow, freshly init
|
||||
Invoice1,
|
||||
///Invoice flow, return journey
|
||||
Invoice2,
|
||||
/// Invoice flow, ready for tranasction posting
|
||||
Invoice3,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
/// Kernel features arguments definition
|
||||
pub struct KernelFeaturesArgsV5 {
|
||||
/// Lock height, for HeightLocked
|
||||
pub lock_hgt: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct VersionCompatInfoV5 {
|
||||
/// The current version of the slate format
|
||||
pub version: u16,
|
||||
/// Version of grin block header this slate is compatible with
|
||||
pub block_header_version: u16,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ParticipantDataV5 {
|
||||
/// Public key corresponding to private blinding factor
|
||||
#[serde(with = "secp_ser::pubkey_serde")]
|
||||
pub xs: PublicKey,
|
||||
/// Public key corresponding to private nonce
|
||||
#[serde(with = "secp_ser::pubkey_serde")]
|
||||
pub nonce: PublicKey,
|
||||
/// Public partial signature
|
||||
#[serde(default = "default_part_sig_none")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "secp_ser::option_sig_serde")]
|
||||
pub part: Option<Signature>,
|
||||
}
|
||||
|
||||
fn default_part_sig_none() -> Option<Signature> {
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PaymentMemoV5 {
|
||||
// The type of memo
|
||||
// 0x00 is directly embedded additional payment details
|
||||
// 0x01 represents the blake2b hash of an arbitrary invoice document
|
||||
pub memo_type: u8,
|
||||
// memo data itself
|
||||
pub memo: [u8; 32],
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||
pub struct PaymentInfoV5 {
|
||||
#[serde(with = "ser::dalek_pubkey_serde")]
|
||||
pub saddr: DalekPublicKey,
|
||||
#[serde(with = "ser::dalek_pubkey_serde")]
|
||||
pub raddr: DalekPublicKey,
|
||||
#[serde_as(as = "TimestampSeconds<i64>")]
|
||||
pub ts: DateTime<Utc>,
|
||||
#[serde(default = "default_promise_signature_none")]
|
||||
#[serde(with = "ser::option_dalek_sig_serde")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub psig: Option<DalekSignature>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub memo: Option<PaymentMemoV5>,
|
||||
}
|
||||
|
||||
fn default_promise_signature_none() -> Option<DalekSignature> {
|
||||
None
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
pub struct CommitsV5 {
|
||||
/// Options for an output's structure or use
|
||||
#[serde(default = "default_output_feature")]
|
||||
#[serde(skip_serializing_if = "output_feature_is_plain")]
|
||||
pub f: OutputFeaturesV5,
|
||||
/// The homomorphic commitment representing the output amount
|
||||
#[serde(
|
||||
serialize_with = "secp_ser::as_hex",
|
||||
deserialize_with = "secp_ser::commitment_from_hex"
|
||||
)]
|
||||
pub c: Commitment,
|
||||
/// A proof that the commitment is in the right range
|
||||
/// Only applies for transaction outputs
|
||||
#[serde(with = "ser::option_rangeproof_hex")]
|
||||
#[serde(default = "default_range_proof")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub p: Option<RangeProof>,
|
||||
}
|
||||
|
||||
impl From<&Output> for CommitsV5 {
|
||||
fn from(out: &Output) -> CommitsV5 {
|
||||
CommitsV5 {
|
||||
f: out.features().into(),
|
||||
c: out.commitment(),
|
||||
p: Some(out.proof()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This will need to be reworked once we no longer support input features with "commit only" inputs.
|
||||
impl From<&Input> for CommitsV5 {
|
||||
fn from(input: &Input) -> CommitsV5 {
|
||||
CommitsV5 {
|
||||
f: input.features.into(),
|
||||
c: input.commitment(),
|
||||
p: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn default_output_feature() -> OutputFeaturesV5 {
|
||||
OutputFeaturesV5(0)
|
||||
}
|
||||
|
||||
fn output_feature_is_plain(o: &OutputFeaturesV5) -> bool {
|
||||
o.0 == 0
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Copy, Debug, Clone, PartialEq, Eq)]
|
||||
pub struct OutputFeaturesV5(pub u8);
|
||||
|
||||
pub fn sig_is_blank(s: &secp::Signature) -> bool {
|
||||
for b in s.to_raw_data().iter() {
|
||||
if *b != 0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn default_range_proof() -> Option<RangeProof> {
|
||||
None
|
||||
}
|
||||
|
||||
fn u64_is_blank(u: &u64) -> bool {
|
||||
*u == 0
|
||||
}
|
||||
|
||||
fn default_u8_0() -> u8 {
|
||||
0
|
||||
}
|
||||
|
||||
fn u8_is_blank(u: &u8) -> bool {
|
||||
*u == 0
|
||||
}
|
||||
|
||||
fn fee_is_zero(f: &FeeFields) -> bool {
|
||||
f.is_zero()
|
||||
}
|
||||
|
||||
fn default_fee() -> FeeFields {
|
||||
FeeFields::zero()
|
||||
}
|
||||
|
||||
/// A mining node requests new coinbase via the foreign api every time a new candidate block is built.
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct CoinbaseV5 {
|
||||
/// Output
|
||||
output: CbOutputV5,
|
||||
/// Kernel
|
||||
kernel: CbKernelV5,
|
||||
/// Key Id
|
||||
key_id: Option<Identifier>,
|
||||
}
|
||||
|
||||
impl From<CbData> for CoinbaseV5 {
|
||||
fn from(cb: CbData) -> CoinbaseV5 {
|
||||
CoinbaseV5 {
|
||||
output: CbOutputV5::from(&cb.output),
|
||||
kernel: CbKernelV5::from(&cb.kernel),
|
||||
key_id: cb.key_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Output> for CbOutputV5 {
|
||||
fn from(output: &Output) -> CbOutputV5 {
|
||||
CbOutputV5 {
|
||||
features: CbOutputFeatures::Coinbase,
|
||||
commit: output.commitment(),
|
||||
proof: output.proof(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TxKernel> for CbKernelV5 {
|
||||
fn from(kernel: &TxKernel) -> CbKernelV5 {
|
||||
CbKernelV5 {
|
||||
features: CbKernelFeatures::Coinbase,
|
||||
excess: kernel.excess,
|
||||
excess_sig: kernel.excess_sig,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
enum CbOutputFeatures {
|
||||
Coinbase,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
enum CbKernelFeatures {
|
||||
Coinbase,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
struct CbOutputV5 {
|
||||
features: CbOutputFeatures,
|
||||
#[serde(serialize_with = "secp_ser::as_hex")]
|
||||
commit: Commitment,
|
||||
#[serde(serialize_with = "secp_ser::as_hex")]
|
||||
proof: RangeProof,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
struct CbKernelV5 {
|
||||
features: CbKernelFeatures,
|
||||
#[serde(serialize_with = "secp_ser::as_hex")]
|
||||
excess: Commitment,
|
||||
#[serde(with = "secp_ser::sig_serde")]
|
||||
excess_sig: secp::Signature,
|
||||
}
|
||||
@@ -0,0 +1,518 @@
|
||||
// Copyright 2023 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.
|
||||
|
||||
//! Wraps a V5 Slate into a V5 Binary slate
|
||||
|
||||
use crate::grin_core::core::transaction::{FeeFields, OutputFeatures};
|
||||
use crate::grin_core::ser as grin_ser;
|
||||
use crate::grin_core::ser::{Readable, Reader, Writeable, Writer};
|
||||
use crate::grin_keychain::BlindingFactor;
|
||||
use crate::grin_util::secp::key::PublicKey;
|
||||
use crate::grin_util::secp::pedersen::{Commitment, RangeProof};
|
||||
use crate::grin_util::secp::Signature;
|
||||
use chrono::{DateTime, NaiveDateTime, Utc};
|
||||
use ed25519_dalek::PublicKey as DalekPublicKey;
|
||||
use ed25519_dalek::Signature as DalekSignature;
|
||||
use grin_wallet_util::byte_ser::from_bytes;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::slate_versions::v5::{
|
||||
CommitsV5, KernelFeaturesArgsV5, ParticipantDataV5, PaymentInfoV5, PaymentMemoV5, SlateStateV5,
|
||||
SlateV5, VersionCompatInfoV5,
|
||||
};
|
||||
|
||||
use crate::slate_versions::v4_bin::{SlateOptFields, UuidWrap};
|
||||
|
||||
impl Writeable for SlateStateV5 {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), grin_ser::Error> {
|
||||
let b = match self {
|
||||
SlateStateV5::Unknown => 0,
|
||||
SlateStateV5::Standard1 => 1,
|
||||
SlateStateV5::Standard2 => 2,
|
||||
SlateStateV5::Standard3 => 3,
|
||||
SlateStateV5::Invoice1 => 4,
|
||||
SlateStateV5::Invoice2 => 5,
|
||||
SlateStateV5::Invoice3 => 6,
|
||||
};
|
||||
writer.write_u8(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for SlateStateV5 {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SlateStateV5, grin_ser::Error> {
|
||||
let b = reader.read_u8()?;
|
||||
let sta = match b {
|
||||
0 => SlateStateV5::Unknown,
|
||||
1 => SlateStateV5::Standard1,
|
||||
2 => SlateStateV5::Standard2,
|
||||
3 => SlateStateV5::Standard3,
|
||||
4 => SlateStateV5::Invoice1,
|
||||
5 => SlateStateV5::Invoice2,
|
||||
6 => SlateStateV5::Invoice3,
|
||||
_ => SlateStateV5::Unknown,
|
||||
};
|
||||
Ok(sta)
|
||||
}
|
||||
}
|
||||
|
||||
struct SigsWrap(Vec<ParticipantDataV5>);
|
||||
struct SigsWrapRef<'a>(&'a Vec<ParticipantDataV5>);
|
||||
|
||||
impl<'a> Writeable for SigsWrapRef<'a> {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), grin_ser::Error> {
|
||||
writer.write_u8(self.0.len() as u8)?;
|
||||
for s in self.0.iter() {
|
||||
//0 means part sig is not yet included
|
||||
//1 means part sig included
|
||||
if s.part.is_some() {
|
||||
writer.write_u8(1)?;
|
||||
} else {
|
||||
writer.write_u8(0)?;
|
||||
}
|
||||
s.xs.write(writer)?;
|
||||
s.nonce.write(writer)?;
|
||||
if let Some(s) = s.part {
|
||||
s.write(writer)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for SigsWrap {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SigsWrap, grin_ser::Error> {
|
||||
let sigs_len = reader.read_u8()?;
|
||||
let sigs = {
|
||||
let mut ret = vec![];
|
||||
for _ in 0..sigs_len as usize {
|
||||
let has_partial = reader.read_u8()?;
|
||||
let c = ParticipantDataV5 {
|
||||
xs: PublicKey::read(reader)?,
|
||||
nonce: PublicKey::read(reader)?,
|
||||
part: match has_partial {
|
||||
1 => Some(Signature::read(reader)?),
|
||||
0 | _ => None,
|
||||
},
|
||||
};
|
||||
ret.push(c);
|
||||
}
|
||||
ret
|
||||
};
|
||||
Ok(SigsWrap(sigs))
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialization of optional structs
|
||||
struct SlateOptStructsRef<'a> {
|
||||
/// coms, default none
|
||||
pub coms: &'a Option<Vec<CommitsV5>>,
|
||||
///// proof, default none
|
||||
pub proof: &'a Option<PaymentInfoV5>,
|
||||
}
|
||||
|
||||
/// Serialization of optional structs
|
||||
struct SlateOptStructs {
|
||||
/// coms, default none
|
||||
pub coms: Option<Vec<CommitsV5>>,
|
||||
/// proof, default none
|
||||
pub proof: Option<PaymentInfoV5>,
|
||||
}
|
||||
|
||||
impl<'a> Writeable for SlateOptStructsRef<'a> {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), grin_ser::Error> {
|
||||
// Status byte, bits determing which optional structs are serialized
|
||||
// 0 0 0 0 0 0 1 1
|
||||
// p c
|
||||
let mut status = 0u8;
|
||||
if self.coms.is_some() {
|
||||
status |= 0x01
|
||||
};
|
||||
if self.proof.is_some() {
|
||||
status |= 0x02
|
||||
};
|
||||
writer.write_u8(status)?;
|
||||
if let Some(c) = self.coms {
|
||||
ComsWrapRef(&c).write(writer)?;
|
||||
}
|
||||
if let Some(p) = self.proof {
|
||||
ProofWrapRef(&p).write(writer)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for SlateOptStructs {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SlateOptStructs, grin_ser::Error> {
|
||||
let status = reader.read_u8()?;
|
||||
let coms = if status & 0x01 > 0 {
|
||||
Some(ComsWrap::read(reader)?.0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let proof = if status & 0x02 > 0 {
|
||||
Some(ProofWrap::read(reader)?.0)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(SlateOptStructs { coms, proof })
|
||||
}
|
||||
}
|
||||
|
||||
struct ComsWrap(Vec<CommitsV5>);
|
||||
struct ComsWrapRef<'a>(&'a Vec<CommitsV5>);
|
||||
|
||||
impl<'a> Writeable for ComsWrapRef<'a> {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), grin_ser::Error> {
|
||||
writer.write_u16(self.0.len() as u16)?;
|
||||
for o in self.0.iter() {
|
||||
//0 means input
|
||||
//1 means output with proof
|
||||
if o.p.is_some() {
|
||||
writer.write_u8(1)?;
|
||||
} else {
|
||||
writer.write_u8(0)?;
|
||||
}
|
||||
OutputFeatures::from(o.f).write(writer)?;
|
||||
o.c.write(writer)?;
|
||||
if let Some(p) = o.p {
|
||||
p.write(writer)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for ComsWrap {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<ComsWrap, grin_ser::Error> {
|
||||
let coms_len = reader.read_u16()?;
|
||||
let coms = {
|
||||
let mut ret = vec![];
|
||||
for _ in 0..coms_len as usize {
|
||||
let is_output = reader.read_u8()?;
|
||||
let c = CommitsV5 {
|
||||
f: OutputFeatures::read(reader)?.into(),
|
||||
c: Commitment::read(reader)?,
|
||||
p: match is_output {
|
||||
1 => Some(RangeProof::read(reader)?),
|
||||
0 | _ => None,
|
||||
},
|
||||
};
|
||||
ret.push(c);
|
||||
}
|
||||
ret
|
||||
};
|
||||
Ok(ComsWrap(coms))
|
||||
}
|
||||
}
|
||||
|
||||
struct ProofWrap(PaymentInfoV5);
|
||||
struct ProofWrapRef<'a>(&'a PaymentInfoV5);
|
||||
|
||||
impl<'a> Writeable for ProofWrapRef<'a> {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), grin_ser::Error> {
|
||||
writer.write_fixed_bytes(self.0.saddr.to_bytes())?;
|
||||
writer.write_fixed_bytes(self.0.raddr.to_bytes())?;
|
||||
writer.write_i64(self.0.ts.timestamp())?;
|
||||
match self.0.psig {
|
||||
Some(s) => {
|
||||
writer.write_u8(1)?;
|
||||
writer.write_fixed_bytes(&s.to_bytes().to_vec())?;
|
||||
}
|
||||
None => writer.write_u8(0)?,
|
||||
}
|
||||
match &self.0.memo {
|
||||
Some(s) => {
|
||||
writer.write_u8(1)?;
|
||||
writer.write_u8(s.memo_type)?;
|
||||
writer.write_fixed_bytes(&s.memo)?;
|
||||
}
|
||||
None => writer.write_u8(0)?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for ProofWrap {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<ProofWrap, grin_ser::Error> {
|
||||
let saddr = DalekPublicKey::from_bytes(&reader.read_fixed_bytes(32)?).unwrap();
|
||||
let raddr = DalekPublicKey::from_bytes(&reader.read_fixed_bytes(32)?).unwrap();
|
||||
let ts_raw: i64 = reader.read_i64().unwrap();
|
||||
let ts = DateTime::<Utc>::from_utc(NaiveDateTime::from_timestamp(ts_raw, 0), Utc);
|
||||
let psig = match reader.read_u8()? {
|
||||
0 => None,
|
||||
1 | _ => Some(DalekSignature::try_from(&reader.read_fixed_bytes(64)?[..]).unwrap()),
|
||||
};
|
||||
let memo = match reader.read_u8()? {
|
||||
0 => None,
|
||||
1 | _ => Some(PaymentMemoV5 {
|
||||
memo_type: reader.read_u8().unwrap(),
|
||||
memo: reader.read_fixed_bytes(32)?.try_into().unwrap_or_default(),
|
||||
}),
|
||||
};
|
||||
Ok(ProofWrap(PaymentInfoV5 {
|
||||
saddr,
|
||||
raddr,
|
||||
ts,
|
||||
psig,
|
||||
memo,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SlateV5Bin(pub SlateV5);
|
||||
|
||||
impl From<SlateV5> for SlateV5Bin {
|
||||
fn from(slate: SlateV5) -> SlateV5Bin {
|
||||
SlateV5Bin(slate)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SlateV5Bin> for SlateV5 {
|
||||
fn from(slate: SlateV5Bin) -> SlateV5 {
|
||||
slate.0
|
||||
}
|
||||
}
|
||||
|
||||
impl serde::Serialize for SlateV5Bin {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut vec = vec![];
|
||||
grin_ser::serialize(&mut vec, grin_ser::ProtocolVersion(4), self)
|
||||
.map_err(|err| serde::ser::Error::custom(err.to_string()))?;
|
||||
serializer.serialize_bytes(&vec)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for SlateV5Bin {
|
||||
fn deserialize<D>(deserializer: D) -> Result<SlateV5Bin, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct SlateV5BinVisitor;
|
||||
|
||||
impl<'de> serde::de::Visitor<'de> for SlateV5BinVisitor {
|
||||
type Value = SlateV5Bin;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(formatter, "a serialised binary V5 slate")
|
||||
}
|
||||
|
||||
fn visit_bytes<E>(self, value: &[u8]) -> Result<SlateV5Bin, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
let mut reader = std::io::Cursor::new(value.to_vec());
|
||||
let s = grin_ser::deserialize(
|
||||
&mut reader,
|
||||
grin_ser::ProtocolVersion(4),
|
||||
grin_ser::DeserializationMode::default(),
|
||||
)
|
||||
.map_err(|err| serde::de::Error::custom(err.to_string()))?;
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_bytes(SlateV5BinVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Writeable for SlateV5Bin {
|
||||
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), grin_ser::Error> {
|
||||
let v5 = &self.0;
|
||||
writer.write_u16(v5.ver.version)?;
|
||||
writer.write_u16(v5.ver.block_header_version)?;
|
||||
(UuidWrap(v5.id)).write(writer)?;
|
||||
v5.sta.write(writer)?;
|
||||
v5.off.write(writer)?;
|
||||
SlateOptFields {
|
||||
num_parts: v5.num_parts,
|
||||
amt: v5.amt,
|
||||
fee: v5.fee,
|
||||
feat: v5.feat,
|
||||
ttl: v5.ttl,
|
||||
}
|
||||
.write(writer)?;
|
||||
(SigsWrapRef(&v5.sigs)).write(writer)?;
|
||||
SlateOptStructsRef {
|
||||
coms: &v5.coms,
|
||||
proof: &v5.proof,
|
||||
}
|
||||
.write(writer)?;
|
||||
// Write lock height for height locked kernels
|
||||
if v5.feat == 2 {
|
||||
let lock_hgt = match &v5.feat_args {
|
||||
Some(l) => l.lock_hgt,
|
||||
None => 0,
|
||||
};
|
||||
writer.write_u64(lock_hgt)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Readable for SlateV5Bin {
|
||||
fn read<R: Reader>(reader: &mut R) -> Result<SlateV5Bin, grin_ser::Error> {
|
||||
let ver = VersionCompatInfoV5 {
|
||||
version: reader.read_u16()?,
|
||||
block_header_version: reader.read_u16()?,
|
||||
};
|
||||
let id = UuidWrap::read(reader)?.0;
|
||||
let sta = SlateStateV5::read(reader)?;
|
||||
let off = BlindingFactor::read(reader)?;
|
||||
|
||||
let opts = SlateOptFields::read(reader)?;
|
||||
let sigs = SigsWrap::read(reader)?.0;
|
||||
let opt_structs = SlateOptStructs::read(reader)?;
|
||||
|
||||
let feat_args = if opts.feat == 2 {
|
||||
Some(KernelFeaturesArgsV5 {
|
||||
lock_hgt: reader.read_u64()?,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(SlateV5Bin(SlateV5 {
|
||||
ver,
|
||||
id,
|
||||
sta,
|
||||
off,
|
||||
num_parts: opts.num_parts,
|
||||
amt: opts.amt,
|
||||
fee: opts.fee,
|
||||
feat: opts.feat,
|
||||
ttl: opts.ttl,
|
||||
sigs,
|
||||
coms: opt_structs.coms,
|
||||
proof: opt_structs.proof,
|
||||
feat_args,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn slate_v5_serialize_deserialize() {
|
||||
use crate::grin_util::from_hex;
|
||||
use crate::grin_util::secp::key::PublicKey;
|
||||
use crate::Slate;
|
||||
use grin_core::global::{set_local_chain_type, ChainTypes};
|
||||
use grin_keychain::{ExtKeychain, Keychain, SwitchCommitmentType};
|
||||
set_local_chain_type(ChainTypes::Mainnet);
|
||||
let slate = Slate::blank(1, false);
|
||||
let mut v5 = SlateV5::from(slate);
|
||||
use chrono::prelude::*;
|
||||
|
||||
let keychain = ExtKeychain::from_random_seed(true).unwrap();
|
||||
let switch = SwitchCommitmentType::Regular;
|
||||
// add some sig data
|
||||
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 = ParticipantDataV5 {
|
||||
xs,
|
||||
nonce,
|
||||
part: None,
|
||||
};
|
||||
let part2 = ParticipantDataV5 {
|
||||
xs,
|
||||
nonce,
|
||||
part: Some(Signature::from_raw_data(&[11; 64]).unwrap()),
|
||||
};
|
||||
v5.sigs.push(part.clone());
|
||||
v5.sigs.push(part2);
|
||||
v5.sigs.push(part);
|
||||
|
||||
// add some random commit data
|
||||
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);
|
||||
v5.amt = 234324899824;
|
||||
v5.feat = 1;
|
||||
v5.num_parts = 2;
|
||||
v5.feat_args = Some(KernelFeaturesArgsV5 { lock_hgt: 23092039 });
|
||||
let v4_1 = v5.clone();
|
||||
let v4_1_copy = v5.clone();
|
||||
|
||||
let v4_bin = SlateV5Bin(v5);
|
||||
let mut vec = Vec::new();
|
||||
let _ = grin_ser::serialize_default(&mut vec, &v4_bin).expect("serialization failed");
|
||||
let b4_bin_2: SlateV5Bin = grin_ser::deserialize_default(&mut &vec[..]).unwrap();
|
||||
let v4_2 = b4_bin_2.0.clone();
|
||||
assert_eq!(v4_1.ver, v4_2.ver);
|
||||
assert_eq!(v4_1.id, v4_2.id);
|
||||
assert_eq!(v4_1.amt, v4_2.amt);
|
||||
assert_eq!(v4_1.fee, v4_2.fee);
|
||||
let v4_2_coms = v4_2.coms.as_ref().unwrap().clone();
|
||||
for (i, c) in v4_1.coms.unwrap().iter().enumerate() {
|
||||
assert_eq!(c.f, v4_2_coms[i].f);
|
||||
assert_eq!(c.c, v4_2_coms[i].c);
|
||||
assert_eq!(c.p, v4_2_coms[i].p);
|
||||
}
|
||||
assert_eq!(v4_1.sigs, v4_2.sigs);
|
||||
assert_eq!(v4_1.proof, v4_2.proof);
|
||||
|
||||
// Include Payment proof, remove coms to mix it up a bit
|
||||
let mut v5 = v4_1_copy;
|
||||
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 = PaymentMemoV5 {
|
||||
memo_type: 0,
|
||||
memo: [9; 32],
|
||||
};
|
||||
v5.proof = Some(PaymentInfoV5 {
|
||||
raddr: d_pkey.clone(),
|
||||
saddr: d_pkey.clone(),
|
||||
ts: ts.clone(),
|
||||
psig: None,
|
||||
memo: Some(pm),
|
||||
});
|
||||
v5.coms = None;
|
||||
let v5_1 = v5.clone();
|
||||
let v5_bin = SlateV5Bin(v5);
|
||||
let mut vec = Vec::new();
|
||||
let _ = grin_ser::serialize_default(&mut vec, &v5_bin).expect("serialization failed");
|
||||
let b4_bin_2: SlateV5Bin = grin_ser::deserialize_default(&mut &vec[..]).unwrap();
|
||||
let v5_2 = b4_bin_2.0.clone();
|
||||
assert_eq!(v5_1.ver, v5_2.ver);
|
||||
assert_eq!(v5_1.id, v5_2.id);
|
||||
assert_eq!(v5_1.amt, v5_2.amt);
|
||||
assert_eq!(v5_1.fee, v5_2.fee);
|
||||
assert!(v5_1.coms.is_none());
|
||||
assert_eq!(v5_1.sigs, v5_2.sigs);
|
||||
assert_eq!(v5_1.proof, v5_2.proof);
|
||||
}
|
||||
Reference in New Issue
Block a user