[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:
Yeastplume
2023-04-18 09:57:38 +01:00
committed by GitHub
parent 23baff16e8
commit 063e91d5e5
16 changed files with 1797 additions and 60 deletions
Generated
+65
View File
@@ -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"
+5 -4
View File
@@ -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"
}
}
}
+3 -3
View File
@@ -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"
}
}
}
+5
View File
@@ -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
+1
View File
@@ -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"] }
+9 -7
View File
@@ -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;
+5 -2
View File
@@ -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);
+1 -1
View File
@@ -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,
+30 -22
View File
@@ -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(
+1 -1
View File
@@ -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
View File
@@ -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,
}
}
}
+189 -3
View File
@@ -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(())
}
}
+89 -2
View File
@@ -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;
+2 -2
View File
@@ -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
+382
View File
@@ -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,
}
+518
View File
@@ -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);
}