Skip to content

Commit

Permalink
Add BulletproofPlus and view tags for v15 hardfork (#116)
Browse files Browse the repository at this point in the history
* Add support for De/serialization of view tags

* Add support for BulletproofPlus

* Add a test for a Bulletproof+ transaction
also using view tags

* run cargo clippy

* run cargo clippy #2

* use view tags to speed up output finding
output finding for TxOutTarget::ToTaggedKey was also broken and should be
fixed

* update CHANGELOG

* test for when the view tag is not a false positive
Also correct comment for A1 value
  • Loading branch information
Boog900 committed Sep 5, 2022
1 parent 631a460 commit 6676ac2
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 29 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Change `u64` and `VarInt` to `Amount` where it makes sense to by [@LeoNero](https://github.com/LeoNero) ([#113](https://github.com/monero-rs/monero-rs/pull/113))
- Add `FromHex` for `Hash`, `Hash8`, `PaymentId`, and `Address` [@LeoNero](https://github.com/LeoNero) ([#114](https://github.com/monero-rs/monero-rs/pull/114))
- Add `ToHex` for `Address` [@LeoNero](https://github.com/LeoNero) ([#114](https://github.com/monero-rs/monero-rs/pull/114))
- Add support for (de)serialization of BulletproofPlus and view tags by [@Boog900](https://github.com/Boog900) ([#116](https://github.com/monero-rs/monero-rs/pull/116))

### Changed

- Use view tags, when available, to speedup owned output finding by [@Boog900](https://github.com/Boog900) ([#116](https://github.com/monero-rs/monero-rs/pull/116))

## [0.17.2] - 2022-07-19

Expand Down
61 changes: 49 additions & 12 deletions src/blockdata/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

use crate::consensus::encode::{self, serialize, Decodable, VarInt};
use crate::cryptonote::hash;
use crate::cryptonote::onetime_key::{KeyRecoverer, SubKeyChecker};
use crate::cryptonote::onetime_key::{KeyGenerator, KeyRecoverer, SubKeyChecker};
use crate::cryptonote::subaddress::Index;
use crate::util::amount::Amount;
use crate::util::key::{KeyPair, PrivateKey, PublicKey, ViewPair};
Expand All @@ -41,7 +41,7 @@ use std::{fmt, io};
use serde_crate::{Deserialize, Serialize};

/// Errors possible when manipulating transactions.
#[derive(Error, Clone, Copy, Debug, PartialEq)]
#[derive(Error, Clone, Copy, Debug, PartialEq, Eq)]
pub enum Error {
/// No transaction public key found in extra.
#[error("No transaction public key found")]
Expand All @@ -62,7 +62,7 @@ pub enum Error {

/// The key image used in transaction inputs [`TxIn`] to commit to the use of an output one-time
/// public key as in [`TxOutTarget::ToKey`].
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "serde_crate"))]
pub struct KeyImage {
Expand All @@ -74,7 +74,7 @@ impl_consensus_encoding!(KeyImage, image);

/// A transaction input, either a coinbase spend or a one-time key spend which defines the ring
/// size and the key image to avoid double spend.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "serde_crate"))]
pub enum TxIn {
Expand All @@ -94,9 +94,9 @@ pub enum TxIn {
},
}

/// Type of output formats, only [`TxOutTarget::ToKey`] is used, other formats are legacy to the
/// Type of output formats, only [`TxOutTarget::ToKey`] and [`TxOutTarget::ToTaggedKey`] are used, other formats are legacy to the
/// original cryptonote implementation.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "serde_crate"))]
pub enum TxOutTarget {
Expand All @@ -112,6 +112,13 @@ pub enum TxOutTarget {
/// The one-time public key of that output.
key: PublicKey,
},
/// A one-time public key output with a view tag.
ToTaggedKey {
/// The one-time public key of that output.
key: PublicKey,
/// The view tag of that output.
view_tag: u8,
},
/// A script hash output, not used.
ToScriptHash {
/// The script hash
Expand All @@ -125,21 +132,38 @@ impl TxOutTarget {
match self {
TxOutTarget::ToScript { keys, .. } => Some(keys.clone()),
TxOutTarget::ToKey { key } => Some(vec![*key]),
TxOutTarget::ToTaggedKey { key, .. } => Some(vec![*key]),
TxOutTarget::ToScriptHash { .. } => None,
}
}

/// Returns the one-time public key if this is a [`TxOutTarget::ToKey`] and `None` otherwise.
/// Returns the one-time public key if this is a [`TxOutTarget::ToKey`] or [`TxOutTarget::ToTaggedKey`] and `None` otherwise.
pub fn as_one_time_key(&self) -> Option<&PublicKey> {
match self {
TxOutTarget::ToKey { key } => Some(key),
TxOutTarget::ToTaggedKey { key, .. } => Some(key),
_ => None,
}
}

/// Derives a view tag and checks if it matches the outputs view tag,
/// if no view tag is present the default is true.
pub fn check_view_tag(&self, rv: PublicKey, index: u8) -> bool {
match self {
TxOutTarget::ToTaggedKey { key: _, view_tag } => {
// https://github.com/monero-project/monero/blob/b6a029f222abada36c7bc6c65899a4ac969d7dee/src/crypto/crypto.cpp#L753
let salt: Vec<u8> = vec![118, 105, 101, 119, 95, 116, 97, 103];
let rv = rv.as_bytes().to_vec();
let buf = [salt, rv, Vec::from([index])].concat();
*view_tag == hash::Hash::new(buf).as_bytes()[0]
}
_ => true,
}
}
}

/// A transaction output, can be consumed by a [`TxIn`] input of the matching format.
#[derive(Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "serde_crate"))]
pub struct TxOut {
Expand Down Expand Up @@ -276,7 +300,7 @@ impl<'a> OwnedTxOut<'a> {
/// public key.
///
/// Extra field is composed of typed sub fields of variable or fixed length.
#[derive(Debug, Clone, Default, PartialEq)]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "serde_crate"))]
pub struct ExtraField(pub Vec<SubField>);
Expand Down Expand Up @@ -311,7 +335,7 @@ impl ExtraField {
/// Each sub-field contains a sub-field tag followed by sub-field content of fixed or variable
/// length, in variable length case the length is encoded with a [`VarInt`] before the content
/// itself.
#[derive(Debug, PartialEq, Clone)]
#[derive(Debug, PartialEq, Eq, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "serde_crate"))]
pub enum SubField {
Expand Down Expand Up @@ -360,7 +384,7 @@ impl fmt::Display for SubField {
///
/// As transaction prefix implements [`hash::Hashable`] it is possible to generate the transaction
/// prefix hash with `tx_prefix.hash()`.
#[derive(Debug, Clone, Default, PartialEq)]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "serde", serde(crate = "serde_crate"))]
pub struct TransactionPrefix {
Expand Down Expand Up @@ -451,7 +475,11 @@ impl TransactionPrefix {
.zip(tx_pubkeys.iter())
.filter_map(|((i, out), tx_pubkey)| {
let key = out.target.as_one_time_key()?;
let sub_index = checker.check(i, key, tx_pubkey)?;
let keygen = KeyGenerator::from_key(checker.keys, *tx_pubkey);
if !out.target.check_view_tag(keygen.rv, i as u8) {
return None;
}
let sub_index = checker.check_with_key_generator(keygen, i, key)?;

Some((i, out, sub_index, tx_pubkey))
})
Expand Down Expand Up @@ -886,6 +914,10 @@ impl Decodable for TxOutTarget {
0x2 => Ok(TxOutTarget::ToKey {
key: Decodable::consensus_decode(r)?,
}),
0x3 => Ok(TxOutTarget::ToTaggedKey {
key: Decodable::consensus_decode(r)?,
view_tag: Decodable::consensus_decode(r)?,
}),
_ => Err(encode::Error::ParseFailed("Invalid output type")),
}
}
Expand All @@ -899,6 +931,11 @@ impl crate::consensus::encode::Encodable for TxOutTarget {
let len = 0x2u8.consensus_encode(w)?;
Ok(len + key.consensus_encode(w)?)
}
TxOutTarget::ToTaggedKey { key, view_tag } => {
let mut len = 0x3u8.consensus_encode(w)?;
len += key.consensus_encode(w)?;
Ok(len + view_tag.consensus_encode(w)?)
}
_ => Err(io::Error::new(
io::ErrorKind::Interrupted,
Error::ScriptNotSupported,
Expand Down
12 changes: 12 additions & 0 deletions src/cryptonote/onetime_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,18 @@ impl<'a> SubKeyChecker<'a> {
self.table
.get(&(key - PublicKey::from_private_key(&keygen.get_rvn_scalar(index))))
}

/// Same as check but uses a pre-generated KeyGenerator
pub fn check_with_key_generator(
&self,
keygen: KeyGenerator,
index: usize,
key: &PublicKey,
) -> Option<&Index> {
// D' = P - Hs(v*8*R || n)*G
self.table
.get(&(key - PublicKey::from_private_key(&keygen.get_rvn_scalar(index))))
}
}

/// Helper to compute onetime private keys.
Expand Down

0 comments on commit 6676ac2

Please sign in to comment.