use crate::core::hash::{DefaultHashable, Hashed};
use crate::core::{committed, Committed};
use crate::libtx::{aggsig, secp_ser};
use crate::ser::{
self, read_multi, PMMRable, ProtocolVersion, Readable, Reader, VerifySortedAndUnique,
Writeable, Writer,
};
use crate::{consensus, global};
use enum_primitive::FromPrimitive;
use keychain::{self, BlindingFactor};
use serde::de;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::cmp::Ordering;
use std::cmp::{max, min};
use std::convert::{TryFrom, TryInto};
use std::fmt::Display;
use std::{error, fmt};
use util::secp;
use util::secp::pedersen::{Commitment, RangeProof};
use util::static_secp_instance;
use util::ToHex;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FeeFields(u64);
impl DefaultHashable for FeeFields {}
impl Writeable for FeeFields {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u64(self.0)
}
}
impl Readable for FeeFields {
fn read<R: Reader>(reader: &mut R) -> Result<Self, ser::Error> {
let fee_fields = reader.read_u64()?;
Ok(Self(fee_fields))
}
}
impl Display for FeeFields {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Serialize for FeeFields {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.collect_str(&self.0)
}
}
impl<'de> Deserialize<'de> for FeeFields {
fn deserialize<D>(deserializer: D) -> Result<FeeFields, D::Error>
where
D: Deserializer<'de>,
{
struct FeeFieldsVisitor;
impl<'de> de::Visitor<'de> for FeeFieldsVisitor {
type Value = FeeFields;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("an 64-bit integer")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let value = value
.parse()
.map_err(|_| E::custom(format!("invalid fee field")))?;
self.visit_u64(value)
}
fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(FeeFields(value))
}
}
deserializer.deserialize_any(FeeFieldsVisitor)
}
}
impl TryFrom<u64> for FeeFields {
type Error = Error;
fn try_from(fee: u64) -> Result<Self, Self::Error> {
if fee == 0 || fee > FeeFields::FEE_MASK {
Err(Error::InvalidFeeFields)
} else {
Ok(Self(fee))
}
}
}
impl From<u32> for FeeFields {
fn from(fee: u32) -> Self {
Self(fee as u64)
}
}
impl From<FeeFields> for u64 {
fn from(fee_fields: FeeFields) -> Self {
fee_fields.0 as u64
}
}
impl FeeFields {
const FEE_BITS: u32 = 40;
const FEE_MASK: u64 = (1u64 << FeeFields::FEE_BITS) - 1;
pub const FEE_SHIFT_BITS: u32 = 4;
pub const FEE_SHIFT_MASK: u64 = (1u64 << FeeFields::FEE_SHIFT_BITS) - 1;
pub fn zero() -> Self {
Self(0)
}
pub fn new(fee_shift: u64, fee: u64) -> Result<Self, Error> {
if fee == 0 || fee > FeeFields::FEE_MASK || fee_shift > FeeFields::FEE_SHIFT_MASK {
Err(Error::InvalidFeeFields)
} else {
Ok(Self((fee_shift << FeeFields::FEE_BITS) | fee))
}
}
pub fn fee_shift(&self) -> u8 {
((self.0 >> FeeFields::FEE_BITS) & FeeFields::FEE_SHIFT_MASK) as u8
}
pub fn fee(&self) -> u64 {
self.0 & FeeFields::FEE_MASK
}
pub fn as_opt(&self) -> Option<Self> {
if self.is_zero() {
None
} else {
Some(*self)
}
}
pub fn is_zero(&self) -> bool {
self.0 == 0
}
}
fn fee_fields_as_int<S>(fee_fields: &FeeFields, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u64(fee_fields.0)
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct NRDRelativeHeight(u16);
impl DefaultHashable for NRDRelativeHeight {}
impl Writeable for NRDRelativeHeight {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u16(self.0)
}
}
impl Readable for NRDRelativeHeight {
fn read<R: Reader>(reader: &mut R) -> Result<Self, ser::Error> {
let x = reader.read_u16()?;
NRDRelativeHeight::try_from(x).map_err(|_| ser::Error::CorruptedData)
}
}
impl TryFrom<u16> for NRDRelativeHeight {
type Error = Error;
fn try_from(height: u16) -> Result<Self, Self::Error> {
if height == 0
|| height
> NRDRelativeHeight::MAX
.try_into()
.expect("WEEK_HEIGHT const should fit in u16")
{
Err(Error::InvalidNRDRelativeHeight)
} else {
Ok(Self(height))
}
}
}
impl TryFrom<u64> for NRDRelativeHeight {
type Error = Error;
fn try_from(height: u64) -> Result<Self, Self::Error> {
Self::try_from(u16::try_from(height).map_err(|_| Error::InvalidNRDRelativeHeight)?)
}
}
impl From<NRDRelativeHeight> for u64 {
fn from(height: NRDRelativeHeight) -> Self {
height.0 as u64
}
}
impl NRDRelativeHeight {
const MAX: u64 = consensus::WEEK_HEIGHT;
pub fn new(height: u64) -> Result<Self, Error> {
NRDRelativeHeight::try_from(height)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum KernelFeatures {
Plain {
#[serde(serialize_with = "fee_fields_as_int")]
fee: FeeFields,
},
Coinbase,
HeightLocked {
#[serde(serialize_with = "fee_fields_as_int")]
fee: FeeFields,
lock_height: u64,
},
NoRecentDuplicate {
#[serde(serialize_with = "fee_fields_as_int")]
fee: FeeFields,
relative_height: NRDRelativeHeight,
},
}
impl KernelFeatures {
const PLAIN_U8: u8 = 0;
const COINBASE_U8: u8 = 1;
const HEIGHT_LOCKED_U8: u8 = 2;
const NO_RECENT_DUPLICATE_U8: u8 = 3;
pub fn as_u8(&self) -> u8 {
match self {
KernelFeatures::Plain { .. } => KernelFeatures::PLAIN_U8,
KernelFeatures::Coinbase => KernelFeatures::COINBASE_U8,
KernelFeatures::HeightLocked { .. } => KernelFeatures::HEIGHT_LOCKED_U8,
KernelFeatures::NoRecentDuplicate { .. } => KernelFeatures::NO_RECENT_DUPLICATE_U8,
}
}
pub fn as_string(&self) -> String {
match self {
KernelFeatures::Plain { .. } => String::from("Plain"),
KernelFeatures::Coinbase => String::from("Coinbase"),
KernelFeatures::HeightLocked { .. } => String::from("HeightLocked"),
KernelFeatures::NoRecentDuplicate { .. } => String::from("NoRecentDuplicate"),
}
}
pub fn kernel_sig_msg(&self) -> Result<secp::Message, Error> {
let x = self.as_u8();
let hash = match self {
KernelFeatures::Plain { fee } => (x, fee).hash(),
KernelFeatures::Coinbase => x.hash(),
KernelFeatures::HeightLocked { fee, lock_height } => (x, fee, lock_height).hash(),
KernelFeatures::NoRecentDuplicate {
fee,
relative_height,
} => (x, fee, relative_height).hash(),
};
let msg = secp::Message::from_slice(&hash.as_bytes())?;
Ok(msg)
}
fn write_v1<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u8(self.as_u8())?;
match self {
KernelFeatures::Plain { fee } => {
fee.write(writer)?;
writer.write_empty_bytes(8)?;
}
KernelFeatures::Coinbase => {
writer.write_empty_bytes(16)?;
}
KernelFeatures::HeightLocked { fee, lock_height } => {
fee.write(writer)?;
writer.write_u64(*lock_height)?;
}
KernelFeatures::NoRecentDuplicate {
fee,
relative_height,
} => {
fee.write(writer)?;
writer.write_empty_bytes(6)?;
relative_height.write(writer)?;
}
};
Ok(())
}
fn write_v2<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u8(self.as_u8())?;
match self {
KernelFeatures::Plain { fee } => {
fee.write(writer)?;
}
KernelFeatures::Coinbase => {
}
KernelFeatures::HeightLocked { fee, lock_height } => {
fee.write(writer)?;
writer.write_u64(*lock_height)?;
}
KernelFeatures::NoRecentDuplicate {
fee,
relative_height,
} => {
fee.write(writer)?;
relative_height.write(writer)?;
}
}
Ok(())
}
fn read_v1<R: Reader>(reader: &mut R) -> Result<KernelFeatures, ser::Error> {
let feature_byte = reader.read_u8()?;
let features = match feature_byte {
KernelFeatures::PLAIN_U8 => {
let fee = FeeFields::read(reader)?;
reader.read_empty_bytes(8)?;
KernelFeatures::Plain { fee }
}
KernelFeatures::COINBASE_U8 => {
reader.read_empty_bytes(16)?;
KernelFeatures::Coinbase
}
KernelFeatures::HEIGHT_LOCKED_U8 => {
let fee = FeeFields::read(reader)?;
let lock_height = reader.read_u64()?;
KernelFeatures::HeightLocked { fee, lock_height }
}
KernelFeatures::NO_RECENT_DUPLICATE_U8 => {
if !global::is_nrd_enabled() {
return Err(ser::Error::CorruptedData);
}
let fee = FeeFields::read(reader)?;
reader.read_empty_bytes(6)?;
let relative_height = NRDRelativeHeight::read(reader)?;
KernelFeatures::NoRecentDuplicate {
fee,
relative_height,
}
}
_ => {
return Err(ser::Error::CorruptedData);
}
};
Ok(features)
}
fn read_v2<R: Reader>(reader: &mut R) -> Result<KernelFeatures, ser::Error> {
let features = match reader.read_u8()? {
KernelFeatures::PLAIN_U8 => {
let fee = FeeFields::read(reader)?;
KernelFeatures::Plain { fee }
}
KernelFeatures::COINBASE_U8 => KernelFeatures::Coinbase,
KernelFeatures::HEIGHT_LOCKED_U8 => {
let fee = FeeFields::read(reader)?;
let lock_height = reader.read_u64()?;
KernelFeatures::HeightLocked { fee, lock_height }
}
KernelFeatures::NO_RECENT_DUPLICATE_U8 => {
if !global::is_nrd_enabled() {
return Err(ser::Error::CorruptedData);
}
let fee = FeeFields::read(reader)?;
let relative_height = NRDRelativeHeight::read(reader)?;
KernelFeatures::NoRecentDuplicate {
fee,
relative_height,
}
}
_ => {
return Err(ser::Error::CorruptedData);
}
};
Ok(features)
}
}
impl Writeable for KernelFeatures {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
if writer.serialization_mode().is_hash_mode() {
return self.write_v1(writer);
}
match writer.protocol_version().value() {
0..=1 => self.write_v1(writer),
2..=ProtocolVersion::MAX => self.write_v2(writer),
}
}
}
impl Readable for KernelFeatures {
fn read<R: Reader>(reader: &mut R) -> Result<KernelFeatures, ser::Error> {
match reader.protocol_version().value() {
0..=1 => KernelFeatures::read_v1(reader),
2..=ProtocolVersion::MAX => KernelFeatures::read_v2(reader),
}
}
}
#[derive(Clone, Eq, Debug, PartialEq, Serialize, Deserialize)]
pub enum Error {
Secp(secp::Error),
Keychain(keychain::Error),
KernelSumMismatch,
TooHeavy,
LockHeight(u64),
RangeProof,
MerkleProof,
InvalidProofMessage,
Committed(committed::Error),
CutThrough,
InvalidOutputFeatures,
InvalidKernelFeatures,
InvalidFeeFields,
InvalidNRDRelativeHeight,
IncorrectSignature,
Serialization(ser::Error),
}
impl error::Error for Error {
fn description(&self) -> &str {
match *self {
_ => "some kind of keychain error",
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
_ => write!(f, "some kind of keychain error"),
}
}
}
impl From<ser::Error> for Error {
fn from(e: ser::Error) -> Error {
Error::Serialization(e)
}
}
impl From<secp::Error> for Error {
fn from(e: secp::Error) -> Error {
Error::Secp(e)
}
}
impl From<keychain::Error> for Error {
fn from(e: keychain::Error) -> Error {
Error::Keychain(e)
}
}
impl From<committed::Error> for Error {
fn from(e: committed::Error) -> Error {
Error::Committed(e)
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct TxKernel {
pub features: KernelFeatures,
#[serde(
serialize_with = "secp_ser::as_hex",
deserialize_with = "secp_ser::commitment_from_hex"
)]
pub excess: Commitment,
#[serde(with = "secp_ser::sig_serde")]
pub excess_sig: secp::Signature,
}
impl DefaultHashable for TxKernel {}
hashable_ord!(TxKernel);
impl ::std::hash::Hash for TxKernel {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
let mut vec = Vec::new();
ser::serialize_default(&mut vec, &self).expect("serialization failed");
::std::hash::Hash::hash(&vec, state);
}
}
impl Writeable for TxKernel {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.features.write(writer)?;
self.excess.write(writer)?;
self.excess_sig.write(writer)?;
Ok(())
}
}
impl Readable for TxKernel {
fn read<R: Reader>(reader: &mut R) -> Result<TxKernel, ser::Error> {
Ok(TxKernel {
features: KernelFeatures::read(reader)?,
excess: Commitment::read(reader)?,
excess_sig: secp::Signature::read(reader)?,
})
}
}
impl PMMRable for TxKernel {
type E = Self;
fn as_elmt(&self) -> Self::E {
self.clone()
}
fn elmt_size() -> Option<u16> {
None
}
}
impl KernelFeatures {
pub fn is_coinbase(&self) -> bool {
match self {
KernelFeatures::Coinbase => true,
_ => false,
}
}
pub fn is_plain(&self) -> bool {
match self {
KernelFeatures::Plain { .. } => true,
_ => false,
}
}
pub fn is_height_locked(&self) -> bool {
match self {
KernelFeatures::HeightLocked { .. } => true,
_ => false,
}
}
pub fn is_nrd(&self) -> bool {
match self {
KernelFeatures::NoRecentDuplicate { .. } => true,
_ => false,
}
}
}
impl TxKernel {
pub fn is_coinbase(&self) -> bool {
self.features.is_coinbase()
}
pub fn is_plain(&self) -> bool {
self.features.is_plain()
}
pub fn is_height_locked(&self) -> bool {
self.features.is_height_locked()
}
pub fn is_nrd(&self) -> bool {
self.features.is_nrd()
}
pub fn excess(&self) -> Commitment {
self.excess
}
pub fn msg_to_sign(&self) -> Result<secp::Message, Error> {
let msg = self.features.kernel_sig_msg()?;
Ok(msg)
}
pub fn verify(&self) -> Result<(), Error> {
let secp = static_secp_instance();
let secp = secp.lock();
let sig = &self.excess_sig;
let pubkey = &self.excess.to_pubkey(&secp)?;
if !aggsig::verify_single(
&secp,
&sig,
&self.msg_to_sign()?,
None,
&pubkey,
Some(&pubkey),
false,
) {
return Err(Error::IncorrectSignature);
}
Ok(())
}
pub fn batch_sig_verify(tx_kernels: &[TxKernel]) -> Result<(), Error> {
let len = tx_kernels.len();
let mut sigs = Vec::with_capacity(len);
let mut pubkeys = Vec::with_capacity(len);
let mut msgs = Vec::with_capacity(len);
let secp = static_secp_instance();
let secp = secp.lock();
for tx_kernel in tx_kernels {
sigs.push(tx_kernel.excess_sig);
pubkeys.push(tx_kernel.excess.to_pubkey(&secp)?);
msgs.push(tx_kernel.msg_to_sign()?);
}
if !aggsig::verify_batch(&secp, &sigs, &msgs, &pubkeys) {
return Err(Error::IncorrectSignature);
}
Ok(())
}
pub fn empty() -> TxKernel {
TxKernel::with_features(KernelFeatures::Plain {
fee: FeeFields::zero(),
})
}
pub fn with_features(features: KernelFeatures) -> TxKernel {
TxKernel {
features,
excess: Commitment::from_vec(vec![0; 33]),
excess_sig: secp::Signature::from_raw_data(&[0; 64]).unwrap(),
}
}
}
#[derive(Clone, Copy)]
pub enum Weighting {
AsTransaction,
AsLimitedTransaction(u64),
AsBlock,
NoLimit,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct TransactionBody {
pub inputs: Inputs,
pub outputs: Vec<Output>,
pub kernels: Vec<TxKernel>,
}
impl Writeable for TransactionBody {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
ser_multiwrite!(
writer,
[write_u64, self.inputs.len() as u64],
[write_u64, self.outputs.len() as u64],
[write_u64, self.kernels.len() as u64]
);
self.inputs.write(writer)?;
self.outputs.write(writer)?;
self.kernels.write(writer)?;
Ok(())
}
}
impl Readable for TransactionBody {
fn read<R: Reader>(reader: &mut R) -> Result<TransactionBody, ser::Error> {
let (num_inputs, num_outputs, num_kernels) =
ser_multiread!(reader, read_u64, read_u64, read_u64);
let tx_block_weight = TransactionBody::weight_by_iok(num_inputs, num_outputs, num_kernels);
if tx_block_weight > global::max_block_weight() {
return Err(ser::Error::TooLargeReadErr);
}
let inputs = match reader.protocol_version().value() {
0..=2 => {
let inputs: Vec<Input> = read_multi(reader, num_inputs)?;
Inputs::from(inputs.as_slice())
}
3..=ser::ProtocolVersion::MAX => {
let inputs: Vec<CommitWrapper> = read_multi(reader, num_inputs)?;
Inputs::from(inputs.as_slice())
}
};
let outputs = read_multi(reader, num_outputs)?;
let kernels = read_multi(reader, num_kernels)?;
let body = TransactionBody::init(inputs, &outputs, &kernels, true)
.map_err(|_| ser::Error::CorruptedData)?;
Ok(body)
}
}
impl Committed for TransactionBody {
fn inputs_committed(&self) -> Vec<Commitment> {
let inputs: Vec<_> = self.inputs().into();
inputs.iter().map(|x| x.commitment()).collect()
}
fn outputs_committed(&self) -> Vec<Commitment> {
self.outputs().iter().map(|x| x.commitment()).collect()
}
fn kernels_committed(&self) -> Vec<Commitment> {
self.kernels().iter().map(|x| x.excess()).collect()
}
}
impl Default for TransactionBody {
fn default() -> TransactionBody {
TransactionBody::empty()
}
}
impl From<Transaction> for TransactionBody {
fn from(tx: Transaction) -> Self {
tx.body
}
}
impl TransactionBody {
pub fn empty() -> TransactionBody {
TransactionBody {
inputs: Inputs::default(),
outputs: vec![],
kernels: vec![],
}
}
pub fn sort(&mut self) {
self.inputs.sort_unstable();
self.outputs.sort_unstable();
self.kernels.sort_unstable();
}
pub fn init(
inputs: Inputs,
outputs: &[Output],
kernels: &[TxKernel],
verify_sorted: bool,
) -> Result<TransactionBody, Error> {
let mut body = TransactionBody {
inputs,
outputs: outputs.to_vec(),
kernels: kernels.to_vec(),
};
if verify_sorted {
body.verify_sorted()?;
} else {
body.sort();
}
Ok(body)
}
pub fn inputs(&self) -> Inputs {
self.inputs.clone()
}
pub fn outputs(&self) -> &[Output] {
&self.outputs
}
pub fn kernels(&self) -> &[TxKernel] {
&self.kernels
}
pub fn with_input(mut self, input: Input) -> TransactionBody {
match &mut self.inputs {
Inputs::CommitOnly(inputs) => {
let commit = input.into();
if let Err(e) = inputs.binary_search(&commit) {
inputs.insert(e, commit)
};
}
Inputs::FeaturesAndCommit(inputs) => {
if let Err(e) = inputs.binary_search(&input) {
inputs.insert(e, input)
};
}
};
self
}
pub fn replace_inputs(mut self, inputs: Inputs) -> TransactionBody {
self.inputs = inputs;
self
}
pub fn with_output(mut self, output: Output) -> TransactionBody {
if let Err(e) = self.outputs.binary_search(&output) {
self.outputs.insert(e, output)
};
self
}
pub fn replace_outputs(mut self, outputs: &[Output]) -> TransactionBody {
self.outputs = outputs.to_vec();
self
}
pub fn with_kernel(mut self, kernel: TxKernel) -> TransactionBody {
if let Err(e) = self.kernels.binary_search(&kernel) {
self.kernels.insert(e, kernel)
};
self
}
pub fn replace_kernel(mut self, kernel: TxKernel) -> TransactionBody {
self.kernels.clear();
self.kernels.push(kernel);
self
}
pub fn fee(&self) -> u64 {
self.kernels
.iter()
.filter_map(|k| match k.features {
KernelFeatures::Coinbase => None,
KernelFeatures::Plain { fee } => Some(fee),
KernelFeatures::HeightLocked { fee, .. } => Some(fee),
KernelFeatures::NoRecentDuplicate { fee, .. } => Some(fee),
})
.fold(0, |acc, fee_fields| acc.saturating_add(fee_fields.fee()))
}
pub fn fee_shift(&self) -> u8 {
self.kernels
.iter()
.filter_map(|k| match k.features {
KernelFeatures::Coinbase => None,
KernelFeatures::Plain { fee } => Some(fee),
KernelFeatures::HeightLocked { fee, .. } => Some(fee),
KernelFeatures::NoRecentDuplicate { fee, .. } => Some(fee),
})
.fold(0, |acc, fee_fields| max(acc, fee_fields.fee_shift()))
}
pub fn shifted_fee(&self) -> u64 {
self.fee() >> self.fee_shift()
}
pub fn aggregate_fee_fields(&self) -> Result<FeeFields, Error> {
FeeFields::new(self.fee_shift() as u64, self.fee())
}
fn overage(&self) -> i64 {
self.fee() as i64
}
pub fn weight(&self) -> u64 {
TransactionBody::weight_by_iok(
self.inputs.len() as u64,
self.outputs.len() as u64,
self.kernels.len() as u64,
)
}
pub fn weight_by_iok(num_inputs: u64, num_outputs: u64, num_kernels: u64) -> u64 {
num_inputs
.saturating_mul(consensus::INPUT_WEIGHT as u64)
.saturating_add(num_outputs.saturating_mul(consensus::OUTPUT_WEIGHT as u64))
.saturating_add(num_kernels.saturating_mul(consensus::KERNEL_WEIGHT as u64))
}
pub fn lock_height(&self) -> u64 {
self.kernels
.iter()
.filter_map(|x| match x.features {
KernelFeatures::HeightLocked { lock_height, .. } => Some(lock_height),
_ => None,
})
.max()
.unwrap_or(0)
}
fn verify_weight(&self, weighting: Weighting) -> Result<(), Error> {
let coinbase_weight = consensus::OUTPUT_WEIGHT + consensus::KERNEL_WEIGHT;
let max_weight = match weighting {
Weighting::AsTransaction => global::max_tx_weight(),
Weighting::AsLimitedTransaction(max_weight) => {
min(global::max_block_weight(), max_weight).saturating_sub(coinbase_weight)
}
Weighting::AsBlock => global::max_block_weight(),
Weighting::NoLimit => {
return Ok(());
}
};
if self.weight() > max_weight {
return Err(Error::TooHeavy);
}
Ok(())
}
fn verify_no_nrd_duplicates(&self) -> Result<(), Error> {
if !global::is_nrd_enabled() {
return Ok(());
}
let mut nrd_excess: Vec<Commitment> = self
.kernels
.iter()
.filter(|x| match x.features {
KernelFeatures::NoRecentDuplicate { .. } => true,
_ => false,
})
.map(|x| x.excess())
.collect();
nrd_excess.sort();
let original_count = nrd_excess.len();
nrd_excess.dedup();
let dedup_count = nrd_excess.len();
if original_count == dedup_count {
Ok(())
} else {
Err(Error::InvalidNRDRelativeHeight)
}
}
fn verify_sorted(&self) -> Result<(), Error> {
self.inputs.verify_sorted_and_unique()?;
self.outputs.verify_sorted_and_unique()?;
self.kernels.verify_sorted_and_unique()?;
Ok(())
}
fn inputs_outputs_committed(&self) -> Vec<Commitment> {
let mut commits = self.inputs_committed();
commits.extend_from_slice(self.outputs_committed().as_slice());
commits.sort_unstable();
commits
}
fn verify_cut_through(&self) -> Result<(), Error> {
let commits = self.inputs_outputs_committed();
for pair in commits.windows(2) {
if pair[0] == pair[1] {
return Err(Error::CutThrough);
}
}
Ok(())
}
pub fn verify_features(&self) -> Result<(), Error> {
self.verify_output_features()?;
self.verify_kernel_features()?;
Ok(())
}
fn verify_output_features(&self) -> Result<(), Error> {
if self.outputs.iter().any(|x| x.is_coinbase()) {
return Err(Error::InvalidOutputFeatures);
}
Ok(())
}
fn verify_kernel_features(&self) -> Result<(), Error> {
if self.kernels.iter().any(|x| x.is_coinbase()) {
return Err(Error::InvalidKernelFeatures);
}
Ok(())
}
pub fn validate_read(&self, weighting: Weighting) -> Result<(), Error> {
self.verify_weight(weighting)?;
self.verify_no_nrd_duplicates()?;
self.verify_sorted()?;
self.verify_cut_through()?;
Ok(())
}
pub fn validate(&self, weighting: Weighting) -> Result<(), Error> {
self.validate_read(weighting)?;
if !self.outputs.is_empty() {
let mut commits = vec![];
let mut proofs = vec![];
for x in &self.outputs {
commits.push(x.commitment());
proofs.push(x.proof);
}
Output::batch_verify_proofs(&commits, &proofs)?;
}
TxKernel::batch_sig_verify(&self.kernels)?;
Ok(())
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Transaction {
#[serde(
serialize_with = "secp_ser::as_hex",
deserialize_with = "secp_ser::blind_from_hex"
)]
pub offset: BlindingFactor,
pub body: TransactionBody,
}
impl DefaultHashable for Transaction {}
impl PartialEq for Transaction {
fn eq(&self, tx: &Transaction) -> bool {
self.body == tx.body && self.offset == tx.offset
}
}
impl Writeable for Transaction {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.offset.write(writer)?;
self.body.write(writer)?;
Ok(())
}
}
impl Readable for Transaction {
fn read<R: Reader>(reader: &mut R) -> Result<Transaction, ser::Error> {
let offset = BlindingFactor::read(reader)?;
let body = TransactionBody::read(reader)?;
let tx = Transaction { offset, body };
tx.validate_read().map_err(|_| ser::Error::CorruptedData)?;
Ok(tx)
}
}
impl Committed for Transaction {
fn inputs_committed(&self) -> Vec<Commitment> {
self.body.inputs_committed()
}
fn outputs_committed(&self) -> Vec<Commitment> {
self.body.outputs_committed()
}
fn kernels_committed(&self) -> Vec<Commitment> {
self.body.kernels_committed()
}
}
impl Default for Transaction {
fn default() -> Transaction {
Transaction::empty()
}
}
impl Transaction {
pub fn empty() -> Transaction {
Transaction {
offset: BlindingFactor::zero(),
body: Default::default(),
}
}
pub fn new(inputs: Inputs, outputs: &[Output], kernels: &[TxKernel]) -> Transaction {
let body =
TransactionBody::init(inputs, outputs, kernels, false).expect("sorting, not verifying");
Transaction {
offset: BlindingFactor::zero(),
body,
}
}
pub fn with_offset(self, offset: BlindingFactor) -> Transaction {
Transaction { offset, ..self }
}
pub fn with_input(self, input: Input) -> Transaction {
Transaction {
body: self.body.with_input(input),
..self
}
}
pub fn with_output(self, output: Output) -> Transaction {
Transaction {
body: self.body.with_output(output),
..self
}
}
pub fn with_kernel(self, kernel: TxKernel) -> Transaction {
Transaction {
body: self.body.with_kernel(kernel),
..self
}
}
pub fn replace_kernel(self, kernel: TxKernel) -> Transaction {
Transaction {
body: self.body.replace_kernel(kernel),
..self
}
}
pub fn inputs(&self) -> Inputs {
self.body.inputs()
}
pub fn outputs(&self) -> &[Output] {
&self.body.outputs()
}
pub fn kernels(&self) -> &[TxKernel] {
&self.body.kernels()
}
pub fn fee(&self) -> u64 {
self.body.fee()
}
pub fn shifted_fee(&self) -> u64 {
self.body.shifted_fee()
}
pub fn aggregate_fee_fields(&self) -> Result<FeeFields, Error> {
self.body.aggregate_fee_fields()
}
pub fn overage(&self) -> i64 {
self.body.overage()
}
pub fn lock_height(&self) -> u64 {
self.body.lock_height()
}
pub fn validate_read(&self) -> Result<(), Error> {
self.body.validate_read(Weighting::AsTransaction)?;
self.body.verify_features()?;
Ok(())
}
pub fn validate(&self, weighting: Weighting) -> Result<(), Error> {
self.body.verify_features()?;
self.body.validate(weighting)?;
self.verify_kernel_sums(self.overage(), self.offset.clone())?;
Ok(())
}
pub fn fee_rate(&self) -> u64 {
self.fee() / self.weight() as u64
}
pub fn weight(&self) -> u64 {
self.body.weight()
}
pub fn accept_fee(&self) -> u64 {
self.weight() * global::get_accept_fee_base()
}
pub fn old_weight_by_iok(num_inputs: u64, num_outputs: u64, num_kernels: u64) -> u64 {
let body_weight = num_outputs
.saturating_mul(4)
.saturating_add(num_kernels)
.saturating_sub(num_inputs);
max(body_weight, 1)
}
pub fn weight_by_iok(num_inputs: u64, num_outputs: u64, num_kernels: u64) -> u64 {
TransactionBody::weight_by_iok(num_inputs, num_outputs, num_kernels)
}
}
pub fn cut_through<'a, 'b, T, U>(
inputs: &'a mut [T],
outputs: &'b mut [U],
) -> Result<(&'a [T], &'b [U], &'a [T], &'b [U]), Error>
where
T: AsRef<Commitment> + Ord,
U: AsRef<Commitment> + Ord,
{
inputs.sort_unstable_by_key(|x| *x.as_ref());
outputs.sort_unstable_by_key(|x| *x.as_ref());
let mut inputs_idx = 0;
let mut outputs_idx = 0;
let mut ncut = 0;
while inputs_idx < inputs.len() && outputs_idx < outputs.len() {
match inputs[inputs_idx]
.as_ref()
.cmp(&outputs[outputs_idx].as_ref())
{
Ordering::Less => {
inputs.swap(inputs_idx - ncut, inputs_idx);
inputs_idx += 1;
}
Ordering::Greater => {
outputs.swap(outputs_idx - ncut, outputs_idx);
outputs_idx += 1;
}
Ordering::Equal => {
inputs_idx += 1;
outputs_idx += 1;
ncut += 1;
}
}
}
while inputs_idx < inputs.len() {
inputs.swap(inputs_idx - ncut, inputs_idx);
inputs_idx += 1;
}
while outputs_idx < outputs.len() {
outputs.swap(outputs_idx - ncut, outputs_idx);
outputs_idx += 1;
}
let (inputs, inputs_cut) = inputs.split_at_mut(inputs.len() - ncut);
let (outputs, outputs_cut) = outputs.split_at_mut(outputs.len() - ncut);
inputs.sort_unstable();
outputs.sort_unstable();
inputs_cut.sort_unstable();
outputs_cut.sort_unstable();
if inputs.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(Error::CutThrough);
}
if outputs.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(Error::CutThrough);
}
Ok((inputs, outputs, inputs_cut, outputs_cut))
}
pub fn aggregate(txs: &[Transaction]) -> Result<Transaction, Error> {
if txs.is_empty() {
return Ok(Transaction::empty());
} else if txs.len() == 1 {
return Ok(txs[0].clone());
}
let (n_inputs, n_outputs, n_kernels) =
txs.iter()
.fold((0, 0, 0), |(inputs, outputs, kernels), tx| {
(
inputs + tx.inputs().len(),
outputs + tx.outputs().len(),
kernels + tx.kernels().len(),
)
});
let mut inputs: Vec<CommitWrapper> = Vec::with_capacity(n_inputs);
let mut outputs: Vec<Output> = Vec::with_capacity(n_outputs);
let mut kernels: Vec<TxKernel> = Vec::with_capacity(n_kernels);
let mut kernel_offsets: Vec<BlindingFactor> = Vec::with_capacity(txs.len());
for tx in txs {
kernel_offsets.push(tx.offset.clone());
let tx_inputs: Vec<_> = tx.inputs().into();
inputs.extend_from_slice(&tx_inputs);
outputs.extend_from_slice(tx.outputs());
kernels.extend_from_slice(tx.kernels());
}
let (inputs, outputs, _, _) = cut_through(&mut inputs, &mut outputs)?;
let total_kernel_offset = committed::sum_kernel_offsets(kernel_offsets, vec![])?;
let tx =
Transaction::new(Inputs::from(inputs), outputs, &kernels).with_offset(total_kernel_offset);
Ok(tx)
}
pub fn deaggregate(mk_tx: Transaction, txs: &[Transaction]) -> Result<Transaction, Error> {
let mut inputs: Vec<CommitWrapper> = vec![];
let mut outputs: Vec<Output> = vec![];
let mut kernels: Vec<TxKernel> = vec![];
let mut kernel_offsets = vec![];
let tx = aggregate(txs)?;
let mk_inputs: Vec<_> = mk_tx.inputs().into();
for mk_input in mk_inputs {
let tx_inputs: Vec<_> = tx.inputs().into();
if !tx_inputs.contains(&mk_input) && !inputs.contains(&mk_input) {
inputs.push(mk_input);
}
}
for mk_output in mk_tx.outputs() {
if !tx.outputs().contains(&mk_output) && !outputs.contains(mk_output) {
outputs.push(*mk_output);
}
}
for mk_kernel in mk_tx.kernels() {
if !tx.kernels().contains(&mk_kernel) && !kernels.contains(mk_kernel) {
kernels.push(*mk_kernel);
}
}
kernel_offsets.push(tx.offset);
let total_kernel_offset = {
let secp = static_secp_instance();
let secp = secp.lock();
let positive_key = vec![mk_tx.offset]
.into_iter()
.filter(|x| *x != BlindingFactor::zero())
.filter_map(|x| x.secret_key(&secp).ok())
.collect::<Vec<_>>();
let negative_keys = kernel_offsets
.into_iter()
.filter(|x| *x != BlindingFactor::zero())
.filter_map(|x| x.secret_key(&secp).ok())
.collect::<Vec<_>>();
if positive_key.is_empty() && negative_keys.is_empty() {
BlindingFactor::zero()
} else {
let sum = secp.blind_sum(positive_key, negative_keys)?;
BlindingFactor::from_secret_key(sum)
}
};
inputs.sort_unstable();
outputs.sort_unstable();
kernels.sort_unstable();
Ok(
Transaction::new(Inputs::from(inputs.as_slice()), &outputs, &kernels)
.with_offset(total_kernel_offset),
)
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct Input {
pub features: OutputFeatures,
#[serde(
serialize_with = "secp_ser::as_hex",
deserialize_with = "secp_ser::commitment_from_hex"
)]
pub commit: Commitment,
}
impl DefaultHashable for Input {}
hashable_ord!(Input);
impl AsRef<Commitment> for Input {
fn as_ref(&self) -> &Commitment {
&self.commit
}
}
impl From<&OutputIdentifier> for Input {
fn from(out: &OutputIdentifier) -> Self {
Input {
features: out.features,
commit: out.commit,
}
}
}
impl Writeable for Input {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.features.write(writer)?;
self.commit.write(writer)?;
Ok(())
}
}
impl Readable for Input {
fn read<R: Reader>(reader: &mut R) -> Result<Input, ser::Error> {
let features = OutputFeatures::read(reader)?;
let commit = Commitment::read(reader)?;
Ok(Input::new(features, commit))
}
}
impl Input {
pub fn new(features: OutputFeatures, commit: Commitment) -> Input {
Input { features, commit }
}
pub fn commitment(&self) -> Commitment {
self.commit
}
pub fn is_coinbase(&self) -> bool {
self.features.is_coinbase()
}
pub fn is_plain(&self) -> bool {
self.features.is_plain()
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
#[serde(transparent)]
pub struct CommitWrapper {
#[serde(
serialize_with = "secp_ser::as_hex",
deserialize_with = "secp_ser::commitment_from_hex"
)]
commit: Commitment,
}
impl DefaultHashable for CommitWrapper {}
hashable_ord!(CommitWrapper);
impl From<Commitment> for CommitWrapper {
fn from(commit: Commitment) -> Self {
CommitWrapper { commit }
}
}
impl From<Input> for CommitWrapper {
fn from(input: Input) -> Self {
CommitWrapper {
commit: input.commitment(),
}
}
}
impl From<&Input> for CommitWrapper {
fn from(input: &Input) -> Self {
CommitWrapper {
commit: input.commitment(),
}
}
}
impl AsRef<Commitment> for CommitWrapper {
fn as_ref(&self) -> &Commitment {
&self.commit
}
}
impl Readable for CommitWrapper {
fn read<R: Reader>(reader: &mut R) -> Result<CommitWrapper, ser::Error> {
let commit = Commitment::read(reader)?;
Ok(CommitWrapper { commit })
}
}
impl Writeable for CommitWrapper {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.commit.write(writer)
}
}
impl From<Inputs> for Vec<CommitWrapper> {
fn from(inputs: Inputs) -> Self {
match inputs {
Inputs::CommitOnly(inputs) => inputs,
Inputs::FeaturesAndCommit(inputs) => {
let mut commits: Vec<_> = inputs.iter().map(|input| input.into()).collect();
commits.sort_unstable();
commits
}
}
}
}
impl From<&Inputs> for Vec<CommitWrapper> {
fn from(inputs: &Inputs) -> Self {
match inputs {
Inputs::CommitOnly(inputs) => inputs.clone(),
Inputs::FeaturesAndCommit(inputs) => {
let mut commits: Vec<_> = inputs.iter().map(|input| input.into()).collect();
commits.sort_unstable();
commits
}
}
}
}
impl CommitWrapper {
pub fn commitment(&self) -> Commitment {
self.commit
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum Inputs {
CommitOnly(Vec<CommitWrapper>),
FeaturesAndCommit(Vec<Input>),
}
impl From<&[Input]> for Inputs {
fn from(inputs: &[Input]) -> Self {
Inputs::FeaturesAndCommit(inputs.to_vec())
}
}
impl From<&[CommitWrapper]> for Inputs {
fn from(commits: &[CommitWrapper]) -> Self {
Inputs::CommitOnly(commits.to_vec())
}
}
impl From<&[OutputIdentifier]> for Inputs {
fn from(outputs: &[OutputIdentifier]) -> Self {
let mut inputs: Vec<_> = outputs
.iter()
.map(|out| Input {
features: out.features,
commit: out.commit,
})
.collect();
inputs.sort_unstable();
Inputs::FeaturesAndCommit(inputs)
}
}
impl Default for Inputs {
fn default() -> Self {
Inputs::CommitOnly(vec![])
}
}
impl Writeable for Inputs {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
if self.is_empty() {
return Ok(());
}
if writer.serialization_mode().is_hash_mode() {
match self {
Inputs::CommitOnly(inputs) => inputs.write(writer)?,
Inputs::FeaturesAndCommit(inputs) => inputs.write(writer)?,
}
} else {
match self {
Inputs::CommitOnly(inputs) => match writer.protocol_version().value() {
0..=2 => return Err(ser::Error::UnsupportedProtocolVersion),
3..=ProtocolVersion::MAX => inputs.write(writer)?,
},
Inputs::FeaturesAndCommit(inputs) => match writer.protocol_version().value() {
0..=2 => inputs.write(writer)?,
3..=ProtocolVersion::MAX => {
let inputs: Vec<CommitWrapper> = self.into();
inputs.write(writer)?;
}
},
}
}
Ok(())
}
}
impl Inputs {
pub fn len(&self) -> usize {
match self {
Inputs::CommitOnly(inputs) => inputs.len(),
Inputs::FeaturesAndCommit(inputs) => inputs.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn verify_sorted_and_unique(&self) -> Result<(), ser::Error> {
match self {
Inputs::CommitOnly(inputs) => inputs.verify_sorted_and_unique(),
Inputs::FeaturesAndCommit(inputs) => inputs.verify_sorted_and_unique(),
}
}
fn sort_unstable(&mut self) {
match self {
Inputs::CommitOnly(inputs) => inputs.sort_unstable(),
Inputs::FeaturesAndCommit(inputs) => inputs.sort_unstable(),
}
}
pub fn version_str(&self) -> &str {
match self {
Inputs::CommitOnly(_) => "v3",
Inputs::FeaturesAndCommit(_) => "v2",
}
}
}
enum_from_primitive! {
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[repr(u8)]
pub enum OutputFeatures {
Plain = 0,
Coinbase = 1,
}
}
impl Writeable for OutputFeatures {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u8(*self as u8)?;
Ok(())
}
}
impl Readable for OutputFeatures {
fn read<R: Reader>(reader: &mut R) -> Result<OutputFeatures, ser::Error> {
let features =
OutputFeatures::from_u8(reader.read_u8()?).ok_or(ser::Error::CorruptedData)?;
Ok(features)
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct Output {
#[serde(flatten)]
pub identifier: OutputIdentifier,
#[serde(
serialize_with = "secp_ser::as_hex",
deserialize_with = "secp_ser::rangeproof_from_hex"
)]
pub proof: RangeProof,
}
impl Ord for Output {
fn cmp(&self, other: &Self) -> Ordering {
self.identifier.cmp(&other.identifier)
}
}
impl PartialOrd for Output {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Output {
fn eq(&self, other: &Self) -> bool {
self.identifier == other.identifier
}
}
impl Eq for Output {}
impl AsRef<Commitment> for Output {
fn as_ref(&self) -> &Commitment {
&self.identifier.commit
}
}
impl Writeable for Output {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.identifier.write(writer)?;
self.proof.write(writer)?;
Ok(())
}
}
impl Readable for Output {
fn read<R: Reader>(reader: &mut R) -> Result<Output, ser::Error> {
Ok(Output {
identifier: OutputIdentifier::read(reader)?,
proof: RangeProof::read(reader)?,
})
}
}
impl OutputFeatures {
pub fn is_coinbase(self) -> bool {
self == OutputFeatures::Coinbase
}
pub fn is_plain(self) -> bool {
self == OutputFeatures::Plain
}
}
impl Output {
pub fn new(features: OutputFeatures, commit: Commitment, proof: RangeProof) -> Output {
Output {
identifier: OutputIdentifier { features, commit },
proof,
}
}
pub fn identifier(&self) -> OutputIdentifier {
self.identifier
}
pub fn commitment(&self) -> Commitment {
self.identifier.commitment()
}
pub fn features(&self) -> OutputFeatures {
self.identifier.features
}
pub fn is_coinbase(&self) -> bool {
self.identifier.is_coinbase()
}
pub fn is_plain(&self) -> bool {
self.identifier.is_plain()
}
pub fn proof(&self) -> RangeProof {
self.proof
}
pub fn proof_bytes(&self) -> &[u8] {
&self.proof.proof[..]
}
pub fn verify_proof(&self) -> Result<(), Error> {
let secp = static_secp_instance();
secp.lock()
.verify_bullet_proof(self.commitment(), self.proof, None)?;
Ok(())
}
pub fn batch_verify_proofs(commits: &[Commitment], proofs: &[RangeProof]) -> Result<(), Error> {
let secp = static_secp_instance();
secp.lock()
.verify_bullet_proof_multi(commits.to_vec(), proofs.to_vec(), None)?;
Ok(())
}
}
impl AsRef<OutputIdentifier> for Output {
fn as_ref(&self) -> &OutputIdentifier {
&self.identifier
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy)]
pub struct OutputIdentifier {
pub features: OutputFeatures,
#[serde(
serialize_with = "secp_ser::as_hex",
deserialize_with = "secp_ser::commitment_from_hex"
)]
pub commit: Commitment,
}
impl DefaultHashable for OutputIdentifier {}
hashable_ord!(OutputIdentifier);
impl AsRef<Commitment> for OutputIdentifier {
fn as_ref(&self) -> &Commitment {
&self.commit
}
}
impl OutputIdentifier {
pub fn new(features: OutputFeatures, commit: &Commitment) -> OutputIdentifier {
OutputIdentifier {
features,
commit: *commit,
}
}
pub fn commitment(&self) -> Commitment {
self.commit
}
pub fn is_coinbase(&self) -> bool {
self.features.is_coinbase()
}
pub fn is_plain(&self) -> bool {
self.features.is_plain()
}
pub fn into_output(self, proof: RangeProof) -> Output {
Output {
identifier: self,
proof,
}
}
}
impl ToHex for OutputIdentifier {
fn to_hex(&self) -> String {
format!("{:b}{}", self.features as u8, self.commit.to_hex())
}
}
impl Writeable for OutputIdentifier {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.features.write(writer)?;
self.commit.write(writer)?;
Ok(())
}
}
impl Readable for OutputIdentifier {
fn read<R: Reader>(reader: &mut R) -> Result<OutputIdentifier, ser::Error> {
Ok(OutputIdentifier {
features: OutputFeatures::read(reader)?,
commit: Commitment::read(reader)?,
})
}
}
impl PMMRable for OutputIdentifier {
type E = Self;
fn as_elmt(&self) -> OutputIdentifier {
*self
}
fn elmt_size() -> Option<u16> {
Some(
(1 + secp::constants::PEDERSEN_COMMITMENT_SIZE)
.try_into()
.unwrap(),
)
}
}
impl From<&Input> for OutputIdentifier {
fn from(input: &Input) -> Self {
OutputIdentifier {
features: input.features,
commit: input.commit,
}
}
}
impl AsRef<OutputIdentifier> for OutputIdentifier {
fn as_ref(&self) -> &OutputIdentifier {
self
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::core::hash::Hash;
use crate::core::id::{ShortId, ShortIdentifiable};
use keychain::{ExtKeychain, Keychain, SwitchCommitmentType};
#[test]
fn test_plain_kernel_ser_deser() {
let keychain = ExtKeychain::from_random_seed(false).unwrap();
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
let commit = keychain
.commit(5, &key_id, SwitchCommitmentType::Regular)
.unwrap();
let sig = secp::Signature::from_raw_data(&[0; 64]).unwrap();
let kernel = TxKernel {
features: KernelFeatures::Plain { fee: 10.into() },
excess: commit,
excess_sig: sig.clone(),
};
for version in vec![ProtocolVersion(1), ProtocolVersion(2)] {
let mut vec = vec![];
ser::serialize(&mut vec, version, &kernel).expect("serialized failed");
let kernel2: TxKernel =
ser::deserialize(&mut &vec[..], version, ser::DeserializationMode::default())
.unwrap();
assert_eq!(kernel2.features, KernelFeatures::Plain { fee: 10.into() });
assert_eq!(kernel2.excess, commit);
assert_eq!(kernel2.excess_sig, sig.clone());
}
let mut vec = vec![];
ser::serialize_default(&mut vec, &kernel).expect("serialized failed");
let kernel2: TxKernel = ser::deserialize_default(&mut &vec[..]).unwrap();
assert_eq!(kernel2.features, KernelFeatures::Plain { fee: 10.into() });
assert_eq!(kernel2.excess, commit);
assert_eq!(kernel2.excess_sig, sig.clone());
}
#[test]
fn test_height_locked_kernel_ser_deser() {
let keychain = ExtKeychain::from_random_seed(false).unwrap();
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
let commit = keychain
.commit(5, &key_id, SwitchCommitmentType::Regular)
.unwrap();
let sig = secp::Signature::from_raw_data(&[0; 64]).unwrap();
let kernel = TxKernel {
features: KernelFeatures::HeightLocked {
fee: 10.into(),
lock_height: 100,
},
excess: commit,
excess_sig: sig.clone(),
};
for version in vec![ProtocolVersion(1), ProtocolVersion(2)] {
let mut vec = vec![];
ser::serialize(&mut vec, version, &kernel).expect("serialized failed");
let kernel2: TxKernel =
ser::deserialize(&mut &vec[..], version, ser::DeserializationMode::default())
.unwrap();
assert_eq!(kernel.features, kernel2.features);
assert_eq!(kernel2.excess, commit);
assert_eq!(kernel2.excess_sig, sig.clone());
}
let mut vec = vec![];
ser::serialize_default(&mut vec, &kernel).expect("serialized failed");
let kernel2: TxKernel = ser::deserialize_default(&mut &vec[..]).unwrap();
assert_eq!(kernel.features, kernel2.features);
assert_eq!(kernel2.excess, commit);
assert_eq!(kernel2.excess_sig, sig.clone());
}
#[test]
fn test_nrd_kernel_ser_deser() {
global::set_local_nrd_enabled(true);
let keychain = ExtKeychain::from_random_seed(false).unwrap();
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
let commit = keychain
.commit(5, &key_id, SwitchCommitmentType::Regular)
.unwrap();
let sig = secp::Signature::from_raw_data(&[0; 64]).unwrap();
let kernel = TxKernel {
features: KernelFeatures::NoRecentDuplicate {
fee: 10.into(),
relative_height: NRDRelativeHeight(100),
},
excess: commit,
excess_sig: sig.clone(),
};
for version in vec![ProtocolVersion(1), ProtocolVersion(2)] {
let mut vec = vec![];
ser::serialize(&mut vec, version, &kernel).expect("serialized failed");
let kernel2: TxKernel =
ser::deserialize(&mut &vec[..], version, ser::DeserializationMode::default())
.unwrap();
assert_eq!(kernel.features, kernel2.features);
assert_eq!(kernel2.excess, commit);
assert_eq!(kernel2.excess_sig, sig.clone());
}
let mut vec = vec![];
ser::serialize_default(&mut vec, &kernel).expect("serialized failed");
let kernel2: TxKernel = ser::deserialize_default(&mut &vec[..]).unwrap();
assert_eq!(kernel.features, kernel2.features);
assert_eq!(kernel2.excess, commit);
assert_eq!(kernel2.excess_sig, sig.clone());
}
#[test]
fn nrd_kernel_verify_sig() {
let keychain = ExtKeychain::from_random_seed(false).unwrap();
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
let mut kernel = TxKernel::with_features(KernelFeatures::NoRecentDuplicate {
fee: 10.into(),
relative_height: NRDRelativeHeight(100),
});
let msg = kernel.msg_to_sign().unwrap();
let excess = keychain
.commit(0, &key_id, SwitchCommitmentType::Regular)
.unwrap();
let skey = keychain
.derive_key(0, &key_id, SwitchCommitmentType::Regular)
.unwrap();
let pubkey = excess.to_pubkey(&keychain.secp()).unwrap();
let excess_sig =
aggsig::sign_single(&keychain.secp(), &msg, &skey, None, Some(&pubkey)).unwrap();
kernel.excess = excess;
kernel.excess_sig = excess_sig;
assert_eq!(kernel.verify(), Ok(()));
kernel.features = KernelFeatures::NoRecentDuplicate {
fee: 9.into(),
relative_height: NRDRelativeHeight(100),
};
assert_eq!(kernel.verify(), Err(Error::IncorrectSignature));
kernel.features = KernelFeatures::NoRecentDuplicate {
fee: 10.into(),
relative_height: NRDRelativeHeight(101),
};
assert_eq!(kernel.verify(), Err(Error::IncorrectSignature));
kernel.features = KernelFeatures::Plain { fee: 10.into() };
assert_eq!(kernel.verify(), Err(Error::IncorrectSignature));
kernel.features = KernelFeatures::NoRecentDuplicate {
fee: 10.into(),
relative_height: NRDRelativeHeight(100),
};
assert_eq!(kernel.verify(), Ok(()));
}
#[test]
fn commit_consistency() {
let keychain = ExtKeychain::from_seed(&[0; 32], false).unwrap();
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
let commit = keychain
.commit(1003, &key_id, SwitchCommitmentType::Regular)
.unwrap();
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
let commit_2 = keychain
.commit(1003, &key_id, SwitchCommitmentType::Regular)
.unwrap();
assert!(commit == commit_2);
}
#[test]
fn input_short_id() {
let keychain = ExtKeychain::from_seed(&[0; 32], false).unwrap();
let key_id = ExtKeychain::derive_key_id(1, 1, 0, 0, 0);
let commit = keychain
.commit(5, &key_id, SwitchCommitmentType::Regular)
.unwrap();
let input = Input {
features: OutputFeatures::Plain,
commit,
};
let block_hash =
Hash::from_hex("3a42e66e46dd7633b57d1f921780a1ac715e6b93c19ee52ab714178eb3a9f673")
.unwrap();
let nonce = 0;
let short_id = input.short_id(&block_hash, nonce);
assert_eq!(short_id, ShortId::from_hex("c4b05f2ba649").unwrap());
let input = Input {
features: OutputFeatures::Coinbase,
commit,
};
let short_id = input.short_id(&block_hash, nonce);
assert_eq!(short_id, ShortId::from_hex("3f0377c624e9").unwrap());
}
#[test]
fn kernel_features_serialization() -> Result<(), Error> {
let mut vec = vec![];
ser::serialize_default(&mut vec, &(0u8, 10u64, 0u64))?;
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..])?;
assert_eq!(features, KernelFeatures::Plain { fee: 10.into() });
let mut vec = vec![];
ser::serialize_default(&mut vec, &(1u8, 0u64, 0u64))?;
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..])?;
assert_eq!(features, KernelFeatures::Coinbase);
let mut vec = vec![];
ser::serialize_default(&mut vec, &(2u8, 10u64, 100u64))?;
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..])?;
assert_eq!(
features,
KernelFeatures::HeightLocked {
fee: 10.into(),
lock_height: 100
}
);
let mut vec = vec![];
ser::serialize_default(&mut vec, &(3u8, 10u64, 100u16)).expect("serialized failed");
let res: Result<KernelFeatures, _> = ser::deserialize_default(&mut &vec[..]);
assert_eq!(res.err(), Some(ser::Error::CorruptedData));
let mut vec = vec![];
ser::serialize_default(&mut vec, &(4u8)).expect("serialized failed");
let res: Result<KernelFeatures, _> = ser::deserialize_default(&mut &vec[..]);
assert_eq!(res.err(), Some(ser::Error::CorruptedData));
Ok(())
}
#[test]
fn kernel_features_serialization_nrd_enabled() -> Result<(), Error> {
global::set_local_nrd_enabled(true);
let mut vec = vec![];
ser::serialize_default(&mut vec, &(3u8, 10u64, 100u16))?;
let features: KernelFeatures = ser::deserialize_default(&mut &vec[..])?;
assert_eq!(
features,
KernelFeatures::NoRecentDuplicate {
fee: 10.into(),
relative_height: NRDRelativeHeight(100)
}
);
vec.clear();
ser::serialize_default(&mut vec, &(3u8, 10u64, 0u16))?;
let res: Result<KernelFeatures, _> = ser::deserialize_default(&mut &vec[..]);
assert_eq!(res.err(), Some(ser::Error::CorruptedData));
vec.clear();
let invalid_height = consensus::WEEK_HEIGHT + 1;
ser::serialize_default(&mut vec, &(3u8, 10u64, invalid_height as u16))?;
let res: Result<KernelFeatures, _> = ser::deserialize_default(&mut &vec[..]);
assert_eq!(res.err(), Some(ser::Error::CorruptedData));
let mut vec = vec![];
ser::serialize_default(&mut vec, &(4u8))?;
let res: Result<KernelFeatures, _> = ser::deserialize_default(&mut &vec[..]);
assert_eq!(res.err(), Some(ser::Error::CorruptedData));
Ok(())
}
}