use crate::core::hash::{DefaultHashable, Hashed};
use crate::core::verifier_cache::VerifierCache;
use crate::core::{committed, Committed};
use crate::keychain::{self, BlindingFactor};
use crate::libtx::secp_ser;
use crate::ser::{
self, read_multi, FixedLength, PMMRable, Readable, Reader, VerifySortedAndUnique, Writeable,
Writer,
};
use crate::util;
use crate::util::secp;
use crate::util::secp::pedersen::{Commitment, RangeProof};
use crate::util::static_secp_instance;
use crate::util::RwLock;
use crate::{consensus, global};
use enum_primitive::FromPrimitive;
use std::cmp::Ordering;
use std::cmp::{max, min};
use std::sync::Arc;
use std::{error, fmt};
enum_from_primitive! {
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[repr(u8)]
pub enum KernelFeatures {
Plain = 0,
Coinbase = 1,
HeightLocked = 2,
}
}
impl DefaultHashable for KernelFeatures {}
impl Writeable for KernelFeatures {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u8(*self as u8)?;
Ok(())
}
}
impl Readable for KernelFeatures {
fn read(reader: &mut dyn Reader) -> Result<KernelFeatures, ser::Error> {
let features =
KernelFeatures::from_u8(reader.read_u8()?).ok_or(ser::Error::CorruptedData)?;
Ok(features)
}
}
#[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),
AggregationError,
CutThrough,
InvalidOutputFeatures,
InvalidKernelFeatures,
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)]
pub struct TxKernel {
pub features: KernelFeatures,
#[serde(with = "secp_ser::string_or_u64")]
pub fee: u64,
#[serde(with = "secp_ser::string_or_u64")]
pub lock_height: u64,
#[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(&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)?;
ser_multiwrite!(writer, [write_u64, self.fee], [write_u64, self.lock_height]);
self.excess.write(writer)?;
self.excess_sig.write(writer)?;
Ok(())
}
}
impl Readable for TxKernel {
fn read(reader: &mut dyn Reader) -> Result<TxKernel, ser::Error> {
Ok(TxKernel {
features: KernelFeatures::read(reader)?,
fee: reader.read_u64()?,
lock_height: reader.read_u64()?,
excess: Commitment::read(reader)?,
excess_sig: secp::Signature::read(reader)?,
})
}
}
impl PMMRable for TxKernel {
type E = TxKernelEntry;
fn as_elmt(&self) -> TxKernelEntry {
TxKernelEntry::from_kernel(self)
}
}
impl KernelFeatures {
pub fn is_coinbase(&self) -> bool {
*self == KernelFeatures::Coinbase
}
pub fn is_plain(&self) -> bool {
*self == KernelFeatures::Plain
}
pub fn is_height_locked(&self) -> bool {
*self == KernelFeatures::HeightLocked
}
}
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 excess(&self) -> Commitment {
self.excess
}
pub fn msg_to_sign(&self) -> Result<secp::Message, Error> {
let msg = kernel_sig_msg(self.fee, self.lock_height, self.features)?;
Ok(msg)
}
pub fn verify(&self) -> Result<(), Error> {
if self.is_coinbase() && self.fee != 0 || !self.is_height_locked() && self.lock_height != 0
{
return Err(Error::InvalidKernelFeatures);
}
let secp = static_secp_instance();
let secp = secp.lock();
let sig = &self.excess_sig;
let pubkey = &self.excess.to_pubkey(&secp)?;
if !secp::aggsig::verify_single(
&secp,
&sig,
&self.msg_to_sign()?,
None,
&pubkey,
Some(&pubkey),
None,
false,
) {
return Err(Error::IncorrectSignature);
}
Ok(())
}
pub fn empty() -> TxKernel {
TxKernel {
features: KernelFeatures::Plain,
fee: 0,
lock_height: 0,
excess: Commitment::from_vec(vec![0; 33]),
excess_sig: secp::Signature::from_raw_data(&[0; 64]).unwrap(),
}
}
pub fn with_fee(self, fee: u64) -> TxKernel {
TxKernel { fee, ..self }
}
pub fn with_lock_height(self, lock_height: u64) -> TxKernel {
TxKernel {
features: kernel_features(lock_height),
lock_height,
..self
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TxKernelEntry {
pub kernel: TxKernel,
}
impl Writeable for TxKernelEntry {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.kernel.write(writer)?;
Ok(())
}
}
impl Readable for TxKernelEntry {
fn read(reader: &mut Reader) -> Result<TxKernelEntry, ser::Error> {
let kernel = TxKernel::read(reader)?;
Ok(TxKernelEntry { kernel })
}
}
impl TxKernelEntry {
pub fn excess(&self) -> Commitment {
self.kernel.excess
}
pub fn verify(&self) -> Result<(), Error> {
self.kernel.verify()
}
pub fn from_kernel(kernel: &TxKernel) -> TxKernelEntry {
TxKernelEntry {
kernel: kernel.clone(),
}
}
}
impl From<TxKernel> for TxKernelEntry {
fn from(kernel: TxKernel) -> Self {
TxKernelEntry { kernel }
}
}
impl FixedLength for TxKernelEntry {
const LEN: usize = 17
+ secp::constants::PEDERSEN_COMMITMENT_SIZE
+ secp::constants::AGG_SIGNATURE_SIZE;
}
#[derive(Clone, Copy)]
pub enum Weighting {
AsTransaction,
AsLimitedTransaction(usize),
AsBlock,
NoLimit,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TransactionBody {
pub inputs: Vec<Input>,
pub outputs: Vec<Output>,
pub kernels: Vec<TxKernel>,
}
impl PartialEq for TransactionBody {
fn eq(&self, l: &TransactionBody) -> bool {
self.inputs == l.inputs && self.outputs == l.outputs && self.kernels == l.kernels
}
}
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(reader: &mut dyn Reader) -> Result<TransactionBody, ser::Error> {
let (input_len, output_len, kernel_len) =
ser_multiread!(reader, read_u64, read_u64, read_u64);
let tx_block_weight = TransactionBody::weight_as_block(
input_len as usize,
output_len as usize,
kernel_len as usize,
);
if tx_block_weight > global::max_block_weight() {
return Err(ser::Error::TooLargeReadErr);
}
let inputs = read_multi(reader, input_len)?;
let outputs = read_multi(reader, output_len)?;
let kernels = read_multi(reader, kernel_len)?;
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> {
self.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 TransactionBody {
pub fn empty() -> TransactionBody {
TransactionBody {
inputs: vec![],
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: Vec<Input>,
outputs: Vec<Output>,
kernels: Vec<TxKernel>,
verify_sorted: bool,
) -> Result<TransactionBody, Error> {
let mut body = TransactionBody {
inputs,
outputs,
kernels,
};
if verify_sorted {
body.verify_sorted()?;
} else {
body.sort();
}
Ok(body)
}
pub fn with_input(mut self, input: Input) -> TransactionBody {
self.inputs
.binary_search(&input)
.err()
.map(|e| self.inputs.insert(e, input));
self
}
pub fn with_output(mut self, output: Output) -> TransactionBody {
self.outputs
.binary_search(&output)
.err()
.map(|e| self.outputs.insert(e, output));
self
}
pub fn with_kernel(mut self, kernel: TxKernel) -> TransactionBody {
self.kernels
.binary_search(&kernel)
.err()
.map(|e| self.kernels.insert(e, kernel));
self
}
fn fee(&self) -> u64 {
self.kernels
.iter()
.fold(0, |acc, ref x| acc.saturating_add(x.fee))
}
fn overage(&self) -> i64 {
self.fee() as i64
}
pub fn body_weight(&self) -> usize {
TransactionBody::weight(self.inputs.len(), self.outputs.len(), self.kernels.len())
}
pub fn body_weight_as_block(&self) -> usize {
TransactionBody::weight_as_block(self.inputs.len(), self.outputs.len(), self.kernels.len())
}
pub fn weight(input_len: usize, output_len: usize, kernel_len: usize) -> usize {
let body_weight = output_len
.saturating_mul(4)
.saturating_add(kernel_len)
.saturating_sub(input_len);
max(body_weight, 1)
}
pub fn weight_as_block(input_len: usize, output_len: usize, kernel_len: usize) -> usize {
input_len
.saturating_mul(consensus::BLOCK_INPUT_WEIGHT)
.saturating_add(output_len.saturating_mul(consensus::BLOCK_OUTPUT_WEIGHT))
.saturating_add(kernel_len.saturating_mul(consensus::BLOCK_KERNEL_WEIGHT))
}
pub fn lock_height(&self) -> u64 {
self.kernels
.iter()
.map(|x| x.lock_height)
.max()
.unwrap_or(0)
}
fn verify_weight(&self, weighting: Weighting) -> Result<(), Error> {
let coinbase_weight = consensus::BLOCK_OUTPUT_WEIGHT + consensus::BLOCK_KERNEL_WEIGHT;
let max_weight = match weighting {
Weighting::AsTransaction => global::max_block_weight().saturating_sub(coinbase_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.body_weight_as_block() > max_weight {
return Err(Error::TooHeavy);
}
Ok(())
}
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 verify_cut_through(&self) -> Result<(), Error> {
let mut inputs = self.inputs.iter().map(|x| x.hash()).peekable();
let mut outputs = self.outputs.iter().map(|x| x.hash()).peekable();
while let (Some(ih), Some(oh)) = (inputs.peek(), outputs.peek()) {
match ih.cmp(oh) {
Ordering::Less => {
inputs.next();
}
Ordering::Greater => {
outputs.next();
}
Ordering::Equal => {
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_sorted()?;
self.verify_cut_through()?;
Ok(())
}
pub fn validate(
&self,
weighting: Weighting,
verifier: Arc<RwLock<dyn VerifierCache>>,
) -> Result<(), Error> {
self.validate_read(weighting)?;
let outputs = {
let mut verifier = verifier.write();
verifier.filter_rangeproof_unverified(&self.outputs)
};
if !outputs.is_empty() {
let mut commits = vec![];
let mut proofs = vec![];
for x in &outputs {
commits.push(x.commit);
proofs.push(x.proof);
}
Output::batch_verify_proofs(&commits, &proofs)?;
}
let kernels = {
let mut verifier = verifier.write();
verifier.filter_kernel_sig_unverified(&self.kernels)
};
for x in &kernels {
x.verify()?;
}
{
let mut verifier = verifier.write();
verifier.add_rangeproof_verified(outputs);
verifier.add_kernel_sig_verified(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 Into<TransactionBody> for Transaction {
fn into(self) -> TransactionBody {
self.body
}
}
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(reader: &mut dyn Reader) -> 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: Vec<Input>, outputs: Vec<Output>, kernels: Vec<TxKernel>) -> Transaction {
let offset = BlindingFactor::zero();
let body =
TransactionBody::init(inputs, outputs, kernels, false).expect("sorting, not verifying");
Transaction { offset, 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 inputs(&self) -> &Vec<Input> {
&self.body.inputs
}
pub fn inputs_mut(&mut self) -> &mut Vec<Input> {
&mut self.body.inputs
}
pub fn outputs(&self) -> &Vec<Output> {
&self.body.outputs
}
pub fn outputs_mut(&mut self) -> &mut Vec<Output> {
&mut self.body.outputs
}
pub fn kernels(&self) -> &Vec<TxKernel> {
&self.body.kernels
}
pub fn kernels_mut(&mut self) -> &mut Vec<TxKernel> {
&mut self.body.kernels
}
pub fn fee(&self) -> u64 {
self.body.fee()
}
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,
verifier: Arc<RwLock<dyn VerifierCache>>,
) -> Result<(), Error> {
self.body.validate(weighting, verifier)?;
self.body.verify_features()?;
self.verify_kernel_sums(self.overage(), self.offset.clone())?;
Ok(())
}
pub fn fee_to_weight(&self) -> u64 {
self.fee() * 1_000 / self.tx_weight() as u64
}
pub fn tx_weight(&self) -> usize {
self.body.body_weight()
}
pub fn tx_weight_as_block(&self) -> usize {
self.body.body_weight_as_block()
}
pub fn weight(input_len: usize, output_len: usize, kernel_len: usize) -> usize {
TransactionBody::weight(input_len, output_len, kernel_len)
}
}
pub fn cut_through(inputs: &mut Vec<Input>, outputs: &mut Vec<Output>) -> Result<(), Error> {
outputs.sort_unstable();
if outputs.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(Error::AggregationError);
}
inputs.sort_unstable();
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].hash().cmp(&outputs[outputs_idx].hash()) {
Ordering::Less => {
inputs[inputs_idx - ncut] = inputs[inputs_idx];
inputs_idx += 1;
}
Ordering::Greater => {
outputs[outputs_idx - ncut] = outputs[outputs_idx];
outputs_idx += 1;
}
Ordering::Equal => {
inputs_idx += 1;
outputs_idx += 1;
ncut += 1;
}
}
}
outputs.drain(outputs_idx - ncut..outputs_idx);
inputs.drain(inputs_idx - ncut..inputs_idx);
Ok(())
}
pub fn aggregate(mut txs: Vec<Transaction>) -> Result<Transaction, Error> {
if txs.is_empty() {
return Ok(Transaction::empty());
} else if txs.len() == 1 {
return Ok(txs.pop().unwrap());
}
let mut n_inputs = 0;
let mut n_outputs = 0;
let mut n_kernels = 0;
for tx in txs.iter() {
n_inputs += tx.body.inputs.len();
n_outputs += tx.body.outputs.len();
n_kernels += tx.body.kernels.len();
}
let mut inputs: Vec<Input> = 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 mut tx in txs {
kernel_offsets.push(tx.offset);
inputs.append(&mut tx.body.inputs);
outputs.append(&mut tx.body.outputs);
kernels.append(&mut tx.body.kernels);
}
cut_through(&mut inputs, &mut outputs)?;
kernels.sort_unstable();
let total_kernel_offset = committed::sum_kernel_offsets(kernel_offsets, vec![])?;
let tx = Transaction::new(inputs, outputs, kernels).with_offset(total_kernel_offset);
Ok(tx)
}
pub fn deaggregate(mk_tx: Transaction, txs: Vec<Transaction>) -> Result<Transaction, Error> {
let mut inputs: Vec<Input> = vec![];
let mut outputs: Vec<Output> = vec![];
let mut kernels: Vec<TxKernel> = vec![];
let mut kernel_offsets = vec![];
let tx = aggregate(txs)?;
for mk_input in mk_tx.body.inputs {
if !tx.body.inputs.contains(&mk_input) && !inputs.contains(&mk_input) {
inputs.push(mk_input);
}
}
for mk_output in mk_tx.body.outputs {
if !tx.body.outputs.contains(&mk_output) && !outputs.contains(&mk_output) {
outputs.push(mk_output);
}
}
for mk_kernel in mk_tx.body.kernels {
if !tx.body.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();
let tx = Transaction::new(inputs, outputs, kernels).with_offset(total_kernel_offset);
Ok(tx)
}
#[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 ::std::hash::Hash for Input {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
let mut vec = Vec::new();
ser::serialize(&mut vec, &self).expect("serialization failed");
::std::hash::Hash::hash(&vec, state);
}
}
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(reader: &mut dyn Reader) -> 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()
}
}
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(reader: &mut dyn Reader) -> 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 {
pub features: OutputFeatures,
#[serde(
serialize_with = "secp_ser::as_hex",
deserialize_with = "secp_ser::commitment_from_hex"
)]
pub commit: Commitment,
#[serde(
serialize_with = "secp_ser::as_hex",
deserialize_with = "secp_ser::rangeproof_from_hex"
)]
pub proof: RangeProof,
}
impl DefaultHashable for Output {}
hashable_ord!(Output);
impl ::std::hash::Hash for Output {
fn hash<H: ::std::hash::Hasher>(&self, state: &mut H) {
let mut vec = Vec::new();
ser::serialize(&mut vec, &self).expect("serialization failed");
::std::hash::Hash::hash(&vec, state);
}
}
impl Writeable for Output {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
self.features.write(writer)?;
self.commit.write(writer)?;
if writer.serialization_mode() != ser::SerializationMode::Hash {
writer.write_bytes(&self.proof)?
}
Ok(())
}
}
impl Readable for Output {
fn read(reader: &mut dyn Reader) -> Result<Output, ser::Error> {
Ok(Output {
features: OutputFeatures::read(reader)?,
commit: Commitment::read(reader)?,
proof: RangeProof::read(reader)?,
})
}
}
impl PMMRable for Output {
type E = OutputIdentifier;
fn as_elmt(&self) -> OutputIdentifier {
OutputIdentifier::from_output(self)
}
}
impl OutputFeatures {
pub fn is_coinbase(&self) -> bool {
*self == OutputFeatures::Coinbase
}
pub fn is_plain(&self) -> bool {
*self == OutputFeatures::Plain
}
}
impl Output {
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 proof(&self) -> RangeProof {
self.proof
}
pub fn verify_proof(&self) -> Result<(), Error> {
let secp = static_secp_instance();
secp.lock()
.verify_bullet_proof(self.commit, self.proof, None)?;
Ok(())
}
pub fn batch_verify_proofs(
commits: &Vec<Commitment>,
proofs: &Vec<RangeProof>,
) -> Result<(), Error> {
let secp = static_secp_instance();
secp.lock()
.verify_bullet_proof_multi(commits.clone(), proofs.clone(), None)?;
Ok(())
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct OutputIdentifier {
pub features: OutputFeatures,
pub commit: Commitment,
}
impl DefaultHashable for OutputIdentifier {}
impl OutputIdentifier {
pub fn new(features: OutputFeatures, commit: &Commitment) -> OutputIdentifier {
OutputIdentifier {
features,
commit: *commit,
}
}
pub fn commitment(&self) -> Commitment {
self.commit
}
pub fn from_output(output: &Output) -> OutputIdentifier {
OutputIdentifier {
features: output.features,
commit: output.commit,
}
}
pub fn into_output(self, proof: RangeProof) -> Output {
Output {
proof,
features: self.features,
commit: self.commit,
}
}
pub fn from_input(input: &Input) -> OutputIdentifier {
OutputIdentifier {
features: input.features,
commit: input.commit,
}
}
pub fn to_hex(&self) -> String {
format!(
"{:b}{}",
self.features as u8,
util::to_hex(self.commit.0.to_vec()),
)
}
}
impl FixedLength for OutputIdentifier {
const LEN: usize = 1 + secp::constants::PEDERSEN_COMMITMENT_SIZE;
}
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(reader: &mut dyn Reader) -> Result<OutputIdentifier, ser::Error> {
Ok(OutputIdentifier {
features: OutputFeatures::read(reader)?,
commit: Commitment::read(reader)?,
})
}
}
impl From<Output> for OutputIdentifier {
fn from(out: Output) -> Self {
OutputIdentifier {
features: out.features,
commit: out.commit,
}
}
}
pub fn kernel_sig_msg(
fee: u64,
lock_height: u64,
features: KernelFeatures,
) -> Result<secp::Message, Error> {
let valid_features = match features {
KernelFeatures::Coinbase => fee == 0 && lock_height == 0,
KernelFeatures::Plain => lock_height == 0,
KernelFeatures::HeightLocked => true,
};
if !valid_features {
return Err(Error::InvalidKernelFeatures);
}
let hash = match features {
KernelFeatures::Coinbase => (features).hash(),
KernelFeatures::Plain => (features, fee).hash(),
KernelFeatures::HeightLocked => (features, fee, lock_height).hash(),
};
Ok(secp::Message::from_slice(&hash.as_bytes())?)
}
pub fn kernel_features(lock_height: u64) -> KernelFeatures {
if lock_height > 0 {
KernelFeatures::HeightLocked
} else {
KernelFeatures::Plain
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::core::hash::Hash;
use crate::core::id::{ShortId, ShortIdentifiable};
use crate::keychain::{ExtKeychain, Keychain, SwitchCommitmentType};
use crate::util::secp;
#[test]
fn test_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,
lock_height: 0,
excess: commit,
excess_sig: sig.clone(),
fee: 10,
};
let mut vec = vec![];
ser::serialize(&mut vec, &kernel).expect("serialized failed");
let kernel2: TxKernel = ser::deserialize(&mut &vec[..]).unwrap();
assert_eq!(kernel2.features, KernelFeatures::Plain);
assert_eq!(kernel2.lock_height, 0);
assert_eq!(kernel2.excess, commit);
assert_eq!(kernel2.excess_sig, sig.clone());
assert_eq!(kernel2.fee, 10);
let kernel = TxKernel {
features: KernelFeatures::HeightLocked,
lock_height: 100,
excess: commit,
excess_sig: sig.clone(),
fee: 10,
};
let mut vec = vec![];
ser::serialize(&mut vec, &kernel).expect("serialized failed");
let kernel2: TxKernel = ser::deserialize(&mut &vec[..]).unwrap();
assert_eq!(kernel2.features, KernelFeatures::HeightLocked);
assert_eq!(kernel2.lock_height, 100);
assert_eq!(kernel2.excess, commit);
assert_eq!(kernel2.excess_sig, sig.clone());
assert_eq!(kernel2.fee, 10);
}
#[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: 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: commit,
};
let short_id = input.short_id(&block_hash, nonce);
assert_eq!(short_id, ShortId::from_hex("3f0377c624e9").unwrap());
}
#[test]
fn kernel_features_serialization() {
let features = KernelFeatures::from_u8(0).unwrap();
assert_eq!(features, KernelFeatures::Plain);
let features = KernelFeatures::from_u8(1).unwrap();
assert_eq!(features, KernelFeatures::Coinbase);
let features = KernelFeatures::from_u8(2).unwrap();
assert_eq!(features, KernelFeatures::HeightLocked);
let features = KernelFeatures::from_u8(3);
assert_eq!(features, None);
}
}