From 49e03831aead92bb0184c6ff6cb7b7d38b1e5153 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 18 May 2021 14:30:14 +1000 Subject: [PATCH] Make PrivateKey and PublicKey type aliases To minimise breaking changes, we introduce extension traits that provide most of the old functionality that was defined on the wrapper types. Fixes #13. --- Cargo.toml | 3 + src/blockdata/transaction.rs | 64 ++--- src/cryptonote/hash.rs | 2 +- src/cryptonote/onetime_key.rs | 192 +++++++------- src/cryptonote/subaddress.rs | 52 ++-- src/util/address.rs | 49 ++-- src/util/key.rs | 481 +++++++--------------------------- src/util/ringct.rs | 28 +- tests/recover_outputs.rs | 30 ++- 9 files changed, 314 insertions(+), 587 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3c0213d4..91826bd1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,3 +51,6 @@ serde_json = "1" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] + +[patch.crates-io] +curve25519-dalek = { git = "https://github.com/thomaseizinger/curve25519-dalek", rev = "8cc9ad36bd30ceab6073ff64655473c6b5aa4aab" } diff --git a/src/blockdata/transaction.rs b/src/blockdata/transaction.rs index e2582629..b2ab463d 100644 --- a/src/blockdata/transaction.rs +++ b/src/blockdata/transaction.rs @@ -24,7 +24,7 @@ use crate::consensus::encode::{self, serialize, Decodable, Encodable, VarInt}; use crate::cryptonote::hash; use crate::cryptonote::onetime_key::{KeyRecoverer, SubKeyChecker}; use crate::cryptonote::subaddress::Index; -use crate::util::key::{KeyPair, PrivateKey, PublicKey, ViewPair}; +use crate::util::key::{EdwardsPointExt, KeyPair, PrivateKey, PublicKey, ViewPair}; use crate::util::ringct::{Opening, RctSig, RctSigBase, RctSigPrunable, RctType, Signature}; use hex::encode as hex_encode; @@ -34,7 +34,7 @@ use std::ops::Range; use std::{fmt, io}; use crate::cryptonote::hash::Hashable; -use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint}; +use curve25519_dalek::edwards::EdwardsPoint; use curve25519_dalek::scalar::Scalar; #[cfg(feature = "serde_support")] @@ -166,7 +166,7 @@ impl TxOut { /// ```rust /// use monero::blockdata::transaction::Transaction; /// use monero::consensus::encode::deserialize; -/// use monero::util::key::{KeyPair, PrivateKey, PublicKey, ViewPair}; +/// use monero::util::key::{KeyPair, PrivateKey, PublicKey, ViewPair, EdwardsPointExt, ScalarExt}; /// # use std::str::FromStr; /// /// # let raw_tx = hex::decode("02000102000bb2e38c0189ea01a9bc02a533fe02a90705fd0540745f59f49374365304f8b4d5da63b444b2d74a40f8007ea44940c15cbbc80c9d106802000267f0f669ead579c1067cbffdf67c4af80b0287c549a10463122b4860fe215f490002b6a2e2f35a93d637ff7d25e20da326cee8e92005d3b18b3c425dabe8336568992c01d6c75cf8c76ac458123f2a498512eb65bb3cecba346c8fcfc516dc0c88518bb90209016f82359eb1fe71d604f0dce9470ed5fd4624bb9fce349a0e8317eabf4172f78a8b27dec6ea1a46da10ed8620fa8367c6391eaa8aabf4ebf660d9fe0eb7e9dfa08365a089ad2df7bce7ef776467898d5ca8947152923c54a1c5030e0c2f01035c555ff4285dcc44dfadd6bc37ec8b9354c045c6590446a81c7f53d8f199cace3faa7f17b3b8302a7cbb3881e8fdc23cca0275c9245fdc2a394b8d3ae73911e3541b10e7725cdeef5e0307bc218caefaafe97c102f39c8ce78f62cccf23c69baf0af55933c9d384ceaf07488f2f1ac7343a593449afd54d1065f6a1a4658845817e4b0e810afc4ca249096e463f9f368625fa37d5bbcbe87af68ce3c4d630f93a66defa4205b178f4e9fa04107bd535c7a4b2251df2dad255e470b611ffe00078c2916fc1eb2af1273e0df30dd1c74b6987b9885e7916b6ca711cbd4b7b50576e51af1439e9ed9e33eb97d8faba4e3bd46066a5026a1940b852d965c1db455d1401687ccaccc524e000b05966763564b7deb8fd64c7fb3d649897c94583dca1558893b071f5e6700dad139f3c6f973c7a43b207ee3e67dc7f7f18b52df442258200c7fe6d16685127da1df9b0d93d764c2659599bc6d300ae33bf8b7c2a504317da90ea2f0bb2af09bd531feae57cb4a0273d8add62fadfc6d43402372e5caf854e112b88417936f1a9c4045d48b5b0b7703d96801b35ff66c716cddbee1b92407aa069a162c163071710e28ccddf6fb560feea32485f2c54a477ae23fd8210427eabe4288cbe0ecbef4ed19ca049ceded424d9f839da957f56ffeb73060ea15498fcbc2d73606e85e963a667dafdb2641fb91862c07b98c1fdae8fadf514600225036dd63c22cdadb57d2125ebf30bc77f7ea0bc0dafb484bf01434954c5053b9c8a143f06972f80fa66788ea1e3425dc0104a9e3674729967b9819552ebb172418da0e4b3778ad4b3d6acd8f354ba09e54bbc8604540010e1e1e4d3066515aed457bd3399c0ce787236dbcd3923de4fb8faded10199b33c1251191612ab5526c1cf0cd55a0aeaed3f7a955ceced16dabdbeb0a2a19a9fdb5aa8c4fc8767cf70e4ad1838518bc6b9de7c420c1f57636579a14a5a8bdacd24e61a68adede8a2e07416c25409dd91ab78905bc99bab4ab4fb9e4ea628e09a271837769c4e67e580dcd5485e12e4e308cb4509686a7484a71f7dfe334499808c7122f07d45d89230b1f19ed86f675b7fec44ef5f3b178ae0af92ff114bd96baa264604fea5a762307bdce6cb483b7bc780d32ed5343fcc3aa306997f211dc075f6dfd66035c1db10bef8656fefbb45645264d401682e42fe3e05906f79d65481b87508f1a4c434e0d1dfc247d4276306f801a6b57e4e4a525177bae24e0bd88a216597d9db44f2604c29d8a5f74e7b934f55048690b5dcefd6489a81aa64c1edb49b320faab94130e603d99e455cfd828bca782176192ece95e9b967fe3dd698574cf0c0b6926970b156e1134658de657de42c4930e72b49c0d94da66c330ab188c10f0d2f578590f31bcac6fcff7e21f9ff67ae1a40d5a03b19301dcbbadc1aa9392795cf81f1401ec16d986a7f96fbb9e8e12ce04a2226e26b78117a4dfb757c6a44481ff68bb0909e7010988cd37146fb45d4cca4ba490aae323bb51a12b6864f88ea6897aa700ee9142eaf0880844083026f044a5e3dba4aae08578cb057976001beb27b5110c41fe336bf7879733739ce22fb31a1a6ac2c900d6d6c6facdbc60085e5c93d502542cfea90dbc62d4e061b7106f09f9c4f6c1b5506dd0550eb8b2bf17678b140de33a10ba676829092e6a13445d1857d06c715eea4492ff864f0b34d178a75a0f1353078f83cfee1440b0a20e64abbd0cab5c6e7083486002970a4904f8371805d1a0ee4aea8524168f0f39d2dfc55f545a98a031841a740e8422a62e123c8303021fb81afbb76d1120c0fbc4d3d97ba69f4e2fe086822ece2047c9ccea507008654c199238a5d17f009aa2dd081f7901d0688aa15311865a319ccba8de4023027235b5725353561c5f1185f6a063fb32fc65ef6e90339d406a6884d66be49d03daaf116ee4b65ef80dd3052a13157b929f98640c0bbe99c8323ce3419a136403dc3f7a95178c3966d2d7bdecf516a28eb2cf8cddb3a0463dc7a6248883f7be0a10aae1bb50728ec9b8880d6011b366a850798f6d7fe07103695dded3f371ca097c1d3596967320071d7f548938afe287cb9b8fae761fa592425623dcbf653028").unwrap(); @@ -327,7 +327,9 @@ pub enum SubField { impl fmt::Display for SubField { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { match self { - SubField::TxPublicKey(public_key) => writeln!(fmt, "Tx public Key: {}", public_key), + SubField::TxPublicKey(public_key) => { + writeln!(fmt, "Tx public Key: {}", public_key.display_hex()) + } SubField::Nonce(nonce) => { let nonce_str = hex_encode(serialize(nonce)); writeln!(fmt, "Nonce: {}", nonce_str) @@ -337,7 +339,7 @@ impl fmt::Display for SubField { SubField::AdditionalPublickKey(keys) => { writeln!(fmt, "Additional publick keys: ")?; for key in keys { - writeln!(fmt, "key: {}", key)?; + writeln!(fmt, "key: {}", key.display_hex())?; } Ok(()) } @@ -445,9 +447,6 @@ impl TransactionPrefix { .ok_or(Error::MissingEcdhInfo)?; let actual_commitment = rct_sig_base.out_pk.get(i).ok_or(Error::MissingCommitment)?; - let actual_commitment = CompressedEdwardsY(actual_commitment.mask.key) - .decompress() - .ok_or(Error::InvalidCommitment)?; let opening = ecdh_info .open_commitment(pair, tx_pubkey, i, &actual_commitment) @@ -972,15 +971,15 @@ impl Encodable for Transaction { #[cfg(test)] mod tests { - use std::str::FromStr; - use super::{ExtraField, Transaction, TransactionPrefix}; use crate::blockdata::transaction::{SubField, TxIn, TxOutTarget}; use crate::consensus::encode::{deserialize, deserialize_partial, serialize, VarInt}; use crate::cryptonote::hash::Hashable; - use crate::util::key::{PrivateKey, PublicKey, ViewPair}; + use crate::util::key::{EdwardsPointExt, PrivateKey, PublicKey, ViewPair}; use crate::util::ringct::{RctSig, RctSigBase, RctType}; use crate::TxOut; + use hex_literal::hex; + use std::convert::TryFrom; #[test] fn deserialize_transaction_prefix() { @@ -1024,14 +1023,13 @@ mod tests { #[test] fn find_outputs() { - let view = PrivateKey::from_str( - "77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404", - ) - .unwrap(); - let b = PrivateKey::from_str( - "8163466f1883598e6dd14027b8da727057165da91485834314f5500a65846f09", - ) - .unwrap(); + let view = PrivateKey::from_bits(hex!( + "77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404" + )); + let b = PrivateKey::from_bits(hex!( + "8163466f1883598e6dd14027b8da727057165da91485834314f5500a65846f09" + )); + let spend = PublicKey::from_private_key(&b); let viewpair = ViewPair { view, spend }; @@ -1062,7 +1060,7 @@ mod tests { fn test_tx_hash() { let tx = "f8ad7c58e6fce1792dd78d764ce88a11db0e3c3bb484d868ae05a7321fb6c6b0"; - let pk_extra = vec![ + let pk_extra = [ 179, 155, 220, 223, 213, 23, 81, 160, 95, 232, 87, 102, 151, 63, 70, 249, 139, 40, 110, 16, 51, 193, 175, 208, 38, 120, 65, 191, 155, 139, 1, 4, ]; @@ -1076,19 +1074,15 @@ mod tests { outputs: vec![TxOut { amount: VarInt(1550800739964), target: TxOutTarget::ToKey { - key: PublicKey::from_slice( - hex::decode( - "e2e19d8badb15e77c8e1f441cf6acd9bcde34a07cae82bbe5ff9629bf88e6e81", - ) - .unwrap() - .as_slice(), - ) + key: PublicKey::try_from(hex!( + "e2e19d8badb15e77c8e1f441cf6acd9bcde34a07cae82bbe5ff9629bf88e6e81" + )) .unwrap(), }, }], extra: ExtraField { 0: vec![ - SubField::TxPublicKey(PublicKey::from_slice(pk_extra.as_slice()).unwrap()), + SubField::TxPublicKey(PublicKey::try_from(pk_extra).unwrap()), SubField::Nonce(vec![ 196, 37, 4, 0, 27, 37, 187, 163, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]), @@ -1120,7 +1114,7 @@ mod tests { fn test_tx_hash_fail() { let tx = "f8ad7c58e6fce1792dd78d764ce88a11db0e3c3bb484d868ae05a7321fb6c6b0"; - let pk_extra = vec![ + let pk_extra = [ 179, 155, 220, 223, 213, 23, 81, 160, 95, 232, 87, 102, 151, 63, 70, 249, 139, 40, 110, 16, 51, 193, 175, 208, 38, 120, 65, 191, 155, 139, 1, 4, ]; @@ -1134,19 +1128,15 @@ mod tests { outputs: vec![TxOut { amount: VarInt(1550800739964), target: TxOutTarget::ToKey { - key: PublicKey::from_slice( - hex::decode( - "e2e19d8badb15e77c8e1f441cf6acd9bcde34a07cae82bbe5ff9629bf88e6e81", - ) - .unwrap() - .as_slice(), - ) + key: PublicKey::try_from(hex!( + "e2e19d8badb15e77c8e1f441cf6acd9bcde34a07cae82bbe5ff9629bf88e6e81" + )) .unwrap(), }, }], extra: ExtraField { 0: vec![ - SubField::TxPublicKey(PublicKey::from_slice(pk_extra.as_slice()).unwrap()), + SubField::TxPublicKey(PublicKey::try_from(pk_extra).unwrap()), SubField::Nonce(vec![ 196, 37, 4, 0, 27, 37, 187, 163, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]), diff --git a/src/cryptonote/hash.rs b/src/cryptonote/hash.rs index db036caa..a8236ae3 100644 --- a/src/cryptonote/hash.rs +++ b/src/cryptonote/hash.rs @@ -58,7 +58,7 @@ impl Hash { /// Return the scalar of the hash as a little endian number modulo `l` (curve order). pub fn as_scalar(&self) -> PrivateKey { - PrivateKey::from_scalar(Scalar::from_bytes_mod_order(self.0)) + Scalar::from_bytes_mod_order(self.0) } /// Hash a stream of bytes and return its scalar representation. diff --git a/src/cryptonote/onetime_key.rs b/src/cryptonote/onetime_key.rs index 9f1d706c..73e4c946 100644 --- a/src/cryptonote/onetime_key.rs +++ b/src/cryptonote/onetime_key.rs @@ -27,7 +27,7 @@ //! use monero::{PublicKey, PrivateKey}; //! use monero::cryptonote::onetime_key::SubKeyChecker; //! use monero::cryptonote::subaddress::Index; -//! use monero::util::key::ViewPair; +//! use monero::util::key::{ViewPair, EdwardsPointExt, ScalarExt}; //! //! let view = PrivateKey::from_str("bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07").unwrap(); //! let secret_spend = PrivateKey::from_str("e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907").unwrap(); @@ -39,11 +39,11 @@ //! }; //! //! let one_time_pk = -//! PublicKey::from_str("e3e77faca64b5997ac1f75763e87713d03d9e2896edec65843ffd2970ef1dde6") +//! PublicKey::from_hex("e3e77faca64b5997ac1f75763e87713d03d9e2896edec65843ffd2970ef1dde6") //! .unwrap(); //! //! let tx_pubkey = -//! PublicKey::from_str("5d1402db663eda8cef4f6782b66321e4a990f746aca249c973e098ba2c0837c1") +//! PublicKey::from_hex("5d1402db663eda8cef4f6782b66321e4a990f746aca249c973e098ba2c0837c1") //! .unwrap(); //! //! let checker = SubKeyChecker::new(&viewpair, 0..3, 0..3); @@ -62,8 +62,9 @@ use std::ops::Range; use crate::consensus::encode::{Encodable, VarInt}; use crate::cryptonote::hash; use crate::cryptonote::subaddress::{self, get_spend_secret_key, Index}; -use crate::util::key::{KeyPair, PrivateKey, PublicKey, ViewPair}; +use crate::util::key::{EdwardsPointExt, KeyPair, PrivateKey, PublicKey, ViewPair}; use crate::util::EIGHT; +use curve25519_dalek::edwards::CompressedEdwardsY; /// Helper to generate onetime public keys (ephemeral keys) used in transactions. #[derive(Debug, Clone)] @@ -79,7 +80,7 @@ impl KeyGenerator { /// generate onetime keys for output indexes from an address when sending funds. pub fn from_random(view: PublicKey, spend: PublicKey, random: PrivateKey) -> Self { // Computes r*8*V - let rv = random * EIGHT * &view; + let rv = random * EIGHT * view; KeyGenerator { spend, rv } } @@ -87,7 +88,7 @@ impl KeyGenerator { /// used to scan if some outputs contains onetime keys owned by the view pair. pub fn from_key(keys: &ViewPair, random: PublicKey) -> Self { // Computes v*8*R - let rv = keys.view * EIGHT * &random; + let rv = keys.view * EIGHT * random; KeyGenerator { spend: keys.spend, rv, @@ -128,7 +129,7 @@ impl KeyGenerator { #[derive(Debug, Clone)] pub struct SubKeyChecker<'a> { /// Table of public spend keys and their corresponding indexes. - pub table: HashMap, + pub table: HashMap, /// The root view pair `(v, S)`. pub keys: &'a ViewPair, } @@ -145,7 +146,7 @@ impl<'a> SubKeyChecker<'a> { minor: min, }; let spend = subaddress::get_spend_public_key(keys, index); - table.insert(spend, index); + table.insert(spend.compress(), index); }); }); SubKeyChecker { table, keys } @@ -158,7 +159,7 @@ impl<'a> SubKeyChecker<'a> { let keygen = KeyGenerator::from_key(self.keys, *tx_pubkey); // D' = P - Hs(v*8*R || n)*G self.table - .get(&(key - PublicKey::from_private_key(&keygen.get_rvn_scalar(index)))) + .get(&(key - PublicKey::from_private_key(&keygen.get_rvn_scalar(index))).compress()) } } @@ -203,23 +204,20 @@ impl<'a> KeyRecoverer<'a> { #[cfg(test)] mod tests { - use std::str::FromStr; - - use super::{KeyGenerator, KeyRecoverer, SubKeyChecker}; + use super::*; use crate::cryptonote::subaddress::Index; use crate::util::key::{KeyPair, PrivateKey, PublicKey, ViewPair}; + use hex_literal::hex; + use std::convert::TryFrom; #[test] fn one_time_key_generator() { - let secret_view = PrivateKey::from_str( - "bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07", - ) - .unwrap(); - - let secret_spend = PrivateKey::from_str( - "e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907", - ) - .unwrap(); + let secret_view = PrivateKey::from_bits(hex!( + "bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07" + )); + let secret_spend = PrivateKey::from_bits(hex!( + "e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907" + )); let public_spend = PublicKey::from_private_key(&secret_spend); @@ -228,13 +226,15 @@ mod tests { spend: public_spend, }; - let one_time_pk = - PublicKey::from_str("e3e77faca64b5997ac1f75763e87713d03d9e2896edec65843ffd2970ef1dde6") - .unwrap(); + let one_time_pk = PublicKey::try_from(hex!( + "e3e77faca64b5997ac1f75763e87713d03d9e2896edec65843ffd2970ef1dde6" + )) + .unwrap(); - let tx_pubkey = - PublicKey::from_str("5d1402db663eda8cef4f6782b66321e4a990f746aca249c973e098ba2c0837c1") - .unwrap(); + let tx_pubkey = PublicKey::try_from(hex!( + "5d1402db663eda8cef4f6782b66321e4a990f746aca249c973e098ba2c0837c1" + )) + .unwrap(); let generator = KeyGenerator::from_key(&viewpair, tx_pubkey); @@ -245,35 +245,35 @@ mod tests { #[test] fn one_time_key_recover() { - let secret_view = PrivateKey::from_str( - "bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07", - ) - .unwrap(); - - let secret_spend = PrivateKey::from_str( - "e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907", - ) - .unwrap(); + let secret_view = PrivateKey::from_bits(hex!( + "bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07" + )); + let secret_spend = PrivateKey::from_bits(hex!( + "e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907" + )); let keypair = KeyPair { view: secret_view, spend: secret_spend, }; - let one_time_sk = PrivateKey::from_str( - "afaebe00bcb29e233c2717e4574c7c8b114890571430bd1427d835ed7339050e", - ) - .unwrap(); + let one_time_sk = PrivateKey::from_bits(hex!( + "afaebe00bcb29e233c2717e4574c7c8b114890571430bd1427d835ed7339050e" + )); let one_time_pk = PublicKey::from_private_key(&one_time_sk); assert_eq!( - "e3e77faca64b5997ac1f75763e87713d03d9e2896edec65843ffd2970ef1dde6", - one_time_pk.to_string() + PublicKey::try_from(hex!( + "e3e77faca64b5997ac1f75763e87713d03d9e2896edec65843ffd2970ef1dde6" + )) + .unwrap(), + one_time_pk ); - let tx_pubkey = - PublicKey::from_str("5d1402db663eda8cef4f6782b66321e4a990f746aca249c973e098ba2c0837c1") - .unwrap(); + let tx_pubkey = PublicKey::try_from(hex!( + "5d1402db663eda8cef4f6782b66321e4a990f746aca249c973e098ba2c0837c1" + )) + .unwrap(); let index = 1; let sub_index = Index::default(); @@ -282,8 +282,10 @@ mod tests { let rec_one_time_sk = recoverer.recover(index, sub_index); assert_eq!( - "afaebe00bcb29e233c2717e4574c7c8b114890571430bd1427d835ed7339050e", - rec_one_time_sk.to_string() + PrivateKey::from_bits(hex!( + "afaebe00bcb29e233c2717e4574c7c8b114890571430bd1427d835ed7339050e" + )), + rec_one_time_sk ); assert_eq!(one_time_pk, PublicKey::from_private_key(&rec_one_time_sk)); @@ -291,35 +293,35 @@ mod tests { #[test] fn one_time_subkey_recover() { - let secret_view = PrivateKey::from_str( - "bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07", - ) - .unwrap(); - - let secret_spend = PrivateKey::from_str( - "e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907", - ) - .unwrap(); + let secret_view = PrivateKey::from_bits(hex!( + "bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07" + )); + let secret_spend = PrivateKey::from_bits(hex!( + "e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907" + )); let keypair = KeyPair { view: secret_view, spend: secret_spend, }; - let one_time_sk = PrivateKey::from_str( - "9650bef0bff89132c91f2244d909e0d65acd13415a46efcb933e6c10b7af4c01", - ) - .unwrap(); + let one_time_sk = PrivateKey::from_bits(hex!( + "9650bef0bff89132c91f2244d909e0d65acd13415a46efcb933e6c10b7af4c01" + )); let one_time_pk = PublicKey::from_private_key(&one_time_sk); assert_eq!( - "b6a2e2f35a93d637ff7d25e20da326cee8e92005d3b18b3c425dabe833656899", - one_time_pk.to_string() + PublicKey::try_from(hex!( + "b6a2e2f35a93d637ff7d25e20da326cee8e92005d3b18b3c425dabe833656899" + )) + .unwrap(), + one_time_pk ); - let tx_pubkey = - PublicKey::from_str("d6c75cf8c76ac458123f2a498512eb65bb3cecba346c8fcfc516dc0c88518bb9") - .unwrap(); + let tx_pubkey = PublicKey::try_from(hex!( + "d6c75cf8c76ac458123f2a498512eb65bb3cecba346c8fcfc516dc0c88518bb9" + )) + .unwrap(); let index = 1; let sub_index = Index { major: 0, minor: 1 }; @@ -328,8 +330,8 @@ mod tests { let rec_one_time_sk = recoverer.recover(index, sub_index); assert_eq!( - "9650bef0bff89132c91f2244d909e0d65acd13415a46efcb933e6c10b7af4c01", - rec_one_time_sk.to_string() + hex!("9650bef0bff89132c91f2244d909e0d65acd13415a46efcb933e6c10b7af4c01"), + rec_one_time_sk.to_bytes() ); assert_eq!(one_time_pk, PublicKey::from_private_key(&rec_one_time_sk)); @@ -337,15 +339,13 @@ mod tests { #[test] fn one_time_key_checker() { - let secret_view = PrivateKey::from_str( - "bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07", - ) - .unwrap(); + let secret_view = PrivateKey::from_bits(hex!( + "bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07" + )); - let secret_spend = PrivateKey::from_str( - "e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907", - ) - .unwrap(); + let secret_spend = PrivateKey::from_bits(hex!( + "e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907" + )); let public_spend = PublicKey::from_private_key(&secret_spend); @@ -354,13 +354,15 @@ mod tests { spend: public_spend, }; - let one_time_pk = - PublicKey::from_str("e3e77faca64b5997ac1f75763e87713d03d9e2896edec65843ffd2970ef1dde6") - .unwrap(); + let one_time_pk = PublicKey::try_from(hex!( + "e3e77faca64b5997ac1f75763e87713d03d9e2896edec65843ffd2970ef1dde6" + )) + .unwrap(); - let tx_pubkey = - PublicKey::from_str("5d1402db663eda8cef4f6782b66321e4a990f746aca249c973e098ba2c0837c1") - .unwrap(); + let tx_pubkey = PublicKey::try_from(hex!( + "5d1402db663eda8cef4f6782b66321e4a990f746aca249c973e098ba2c0837c1" + )) + .unwrap(); let checker = SubKeyChecker::new(&viewpair, 0..3, 0..3); @@ -374,15 +376,13 @@ mod tests { #[test] fn one_time_subkey_checker() { - let secret_view = PrivateKey::from_str( - "bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07", - ) - .unwrap(); + let secret_view = PrivateKey::from_bits(hex!( + "bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07" + )); - let secret_spend = PrivateKey::from_str( - "e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907", - ) - .unwrap(); + let secret_spend = PrivateKey::from_bits(hex!( + "e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907" + )); let public_spend = PublicKey::from_private_key(&secret_spend); @@ -391,13 +391,15 @@ mod tests { spend: public_spend, }; - let one_time_pk = - PublicKey::from_str("b6a2e2f35a93d637ff7d25e20da326cee8e92005d3b18b3c425dabe833656899") - .unwrap(); + let one_time_pk = PublicKey::try_from(hex!( + "b6a2e2f35a93d637ff7d25e20da326cee8e92005d3b18b3c425dabe833656899" + )) + .unwrap(); - let tx_pubkey = - PublicKey::from_str("d6c75cf8c76ac458123f2a498512eb65bb3cecba346c8fcfc516dc0c88518bb9") - .unwrap(); + let tx_pubkey = PublicKey::try_from(hex!( + "d6c75cf8c76ac458123f2a498512eb65bb3cecba346c8fcfc516dc0c88518bb9" + )) + .unwrap(); let checker = SubKeyChecker::new(&viewpair, 0..3, 0..3); diff --git a/src/cryptonote/subaddress.rs b/src/cryptonote/subaddress.rs index 8f298854..e55c3879 100644 --- a/src/cryptonote/subaddress.rs +++ b/src/cryptonote/subaddress.rs @@ -29,7 +29,7 @@ use crate::consensus::encode::Encodable; use crate::cryptonote::hash; use crate::network::Network; use crate::util::address::Address; -use crate::util::key::{KeyPair, PrivateKey, PublicKey, ViewPair}; +use crate::util::key::{EdwardsPointExt, KeyPair, PrivateKey, PublicKey, ViewPair}; /// A sub-address index with `major` and `minor` indexes, primary address is `0/0`. /// @@ -138,7 +138,7 @@ pub fn get_public_keys(keys: &ViewPair, index: Index) -> (PublicKey, PublicKey) // Get S' from (v, S) let spend = get_spend_public_key(keys, index); // V' = v*S' - let view = keys.view * &spend; + let view = keys.view * spend; (view, spend) } @@ -155,23 +155,21 @@ pub fn get_subaddress(keys: &ViewPair, index: Index, network: Option) - #[cfg(test)] mod tests { - use std::str::FromStr; - - use super::{get_public_keys, get_subaddress, Index}; + use super::*; use crate::network::Network; use crate::util::key::{PrivateKey, PublicKey, ViewPair}; + use hex_literal::hex; + use std::convert::TryFrom; #[test] #[allow(non_snake_case)] fn get_subkeys_test() { - let a = PrivateKey::from_str( - "77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404", - ) - .unwrap(); - let b = PrivateKey::from_str( - "8163466f1883598e6dd14027b8da727057165da91485834314f5500a65846f09", - ) - .unwrap(); + let a = PrivateKey::from_bits(hex!( + "77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404" + )); + let b = PrivateKey::from_bits(hex!( + "8163466f1883598e6dd14027b8da727057165da91485834314f5500a65846f09" + )); let B = PublicKey::from_private_key(&b); //let keypair = KeyPair { view: a, spend: b }; let viewpair = ViewPair { view: a, spend: B }; @@ -183,26 +181,30 @@ mod tests { let (sub_view_pub, sub_spend_pub) = get_public_keys(&viewpair, index); assert_eq!( - "601782bdde614e9ba664048a27b7407df4b76ae2e50a85fcc168a4c1766b3edf", - sub_view_pub.to_string() + PublicKey::try_from(hex!( + "601782bdde614e9ba664048a27b7407df4b76ae2e50a85fcc168a4c1766b3edf" + )) + .unwrap(), + sub_view_pub ); assert_eq!( - "c25179ddef2ca4728fb691dd71561dc9f2e7e6b2a14284a4fe5441d7757aea02", - sub_spend_pub.to_string() + PublicKey::try_from(hex!( + "c25179ddef2ca4728fb691dd71561dc9f2e7e6b2a14284a4fe5441d7757aea02" + )) + .unwrap(), + sub_spend_pub ); } #[test] #[allow(non_snake_case)] fn get_subaddress_test() { - let a = PrivateKey::from_str( - "77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404", - ) - .unwrap(); - let b = PrivateKey::from_str( - "8163466f1883598e6dd14027b8da727057165da91485834314f5500a65846f09", - ) - .unwrap(); + let a = PrivateKey::from_bits(hex!( + "77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404" + )); + let b = PrivateKey::from_bits(hex!( + "8163466f1883598e6dd14027b8da727057165da91485834314f5500a65846f09" + )); let B = PublicKey::from_private_key(&b); //let keypair = KeyPair { view: a, spend: b }; let viewpair = ViewPair { view: a, spend: B }; diff --git a/src/util/address.rs b/src/util/address.rs index 103ecc5c..1a6040e7 100644 --- a/src/util/address.rs +++ b/src/util/address.rs @@ -44,8 +44,9 @@ use base58_monero::base58; use keccak_hash::keccak_256; use crate::network::{self, Network}; -use crate::util::key::{KeyPair, PublicKey, ViewPair}; +use crate::util::key::{EdwardsPointExt, KeyPair, PublicKey, ViewPair}; +use curve25519_dalek::edwards::CompressedEdwardsY; use thiserror::Error; /// Potential errors encountered when manipulating addresses. @@ -151,9 +152,9 @@ pub struct Address { /// The address type. pub addr_type: AddressType, /// The address spend public key. - pub public_spend: PublicKey, + pub public_spend: CompressedEdwardsY, /// The address view public key. - pub public_view: PublicKey, + pub public_view: CompressedEdwardsY, } impl Address { @@ -162,8 +163,8 @@ impl Address { Address { network, addr_type: AddressType::Standard, - public_spend, - public_view, + public_spend: public_spend.compress(), + public_view: public_view.compress(), } } @@ -176,8 +177,8 @@ impl Address { Address { network, addr_type: AddressType::SubAddress, - public_spend, - public_view, + public_spend: public_spend.compress(), + public_view: public_view.compress(), } } @@ -191,8 +192,8 @@ impl Address { Address { network, addr_type: AddressType::Integrated(payment_id), - public_spend, - public_view, + public_spend: public_spend.compress(), + public_view: public_view.compress(), } } @@ -202,8 +203,8 @@ impl Address { Address { network, addr_type: AddressType::Standard, - public_spend: keys.spend, - public_view, + public_spend: keys.spend.compress(), + public_view: public_view.compress(), } } @@ -214,8 +215,8 @@ impl Address { Address { network, addr_type: AddressType::Standard, - public_spend, - public_view, + public_spend: public_spend.compress(), + public_view: public_view.compress(), } } @@ -224,10 +225,11 @@ impl Address { pub fn from_bytes(bytes: &[u8]) -> Result { let network = Network::from_u8(bytes[0])?; let addr_type = AddressType::from_slice(&bytes, network)?; - let public_spend = - PublicKey::from_slice(&bytes[1..33]).map_err(|_| Error::InvalidFormat)?; - let public_view = - PublicKey::from_slice(&bytes[33..65]).map_err(|_| Error::InvalidFormat)?; + let public_spend = CompressedEdwardsY::from_slice(&bytes[1..33]); + let public_view = CompressedEdwardsY::from_slice(&bytes[33..65]); + + public_spend.decompress().ok_or(Error::InvalidFormat)?; + public_view.decompress().ok_or(Error::InvalidFormat)?; let mut verify_checksum = [0u8; 32]; let (checksum_bytes, checksum) = match addr_type { @@ -312,15 +314,16 @@ mod tests { use std::str::FromStr; use super::{base58, Address, AddressType, Network, PaymentId, PublicKey}; + use std::convert::TryFrom; #[test] fn deserialize_address() { - let pub_spend = PublicKey::from_slice(&[ + let pub_spend = PublicKey::try_from([ 226, 187, 17, 117, 6, 188, 105, 177, 58, 207, 205, 42, 205, 229, 251, 129, 118, 253, 21, 245, 49, 67, 36, 75, 62, 12, 80, 90, 244, 194, 108, 210, ]) .unwrap(); - let pub_view = PublicKey::from_slice(&[ + let pub_view = PublicKey::try_from([ 220, 115, 195, 55, 189, 88, 136, 78, 63, 32, 41, 33, 168, 205, 245, 3, 139, 234, 109, 64, 198, 179, 53, 108, 247, 77, 183, 25, 172, 59, 113, 115, ]) @@ -343,12 +346,12 @@ mod tests { #[test] fn deserialize_integrated_address() { - let pub_spend = PublicKey::from_slice(&[ + let pub_spend = PublicKey::try_from([ 17, 81, 127, 230, 166, 35, 81, 36, 161, 94, 154, 206, 60, 98, 195, 62, 12, 11, 234, 133, 228, 196, 77, 3, 68, 188, 84, 78, 94, 109, 238, 44, ]) .unwrap(); - let pub_view = PublicKey::from_slice(&[ + let pub_view = PublicKey::try_from([ 115, 212, 211, 204, 198, 30, 73, 70, 235, 52, 160, 200, 39, 215, 134, 239, 249, 129, 47, 156, 14, 116, 18, 191, 112, 207, 139, 208, 54, 59, 92, 115, ]) @@ -370,12 +373,12 @@ mod tests { #[test] fn deserialize_sub_address() { - let pub_spend = PublicKey::from_slice(&[ + let pub_spend = PublicKey::try_from([ 212, 104, 103, 28, 131, 98, 226, 228, 37, 244, 133, 145, 213, 157, 184, 232, 6, 146, 127, 69, 187, 95, 33, 143, 9, 102, 181, 189, 230, 223, 231, 7, ]) .unwrap(); - let pub_view = PublicKey::from_slice(&[ + let pub_view = PublicKey::try_from([ 154, 155, 57, 25, 23, 70, 165, 134, 222, 126, 85, 60, 127, 96, 21, 243, 108, 152, 150, 87, 66, 59, 161, 121, 206, 130, 170, 233, 69, 102, 128, 103, ]) diff --git a/src/util/key.rs b/src/util/key.rs index d436566d..76c758be 100644 --- a/src/util/key.rs +++ b/src/util/key.rs @@ -21,12 +21,12 @@ //! //! ```rust //! use std::str::FromStr; -//! use monero::util::key::{Error, PrivateKey, PublicKey}; +//! use monero::util::key::{Error, PrivateKey, PublicKey, EdwardsPointExt, ScalarExt}; //! //! // parse private key from hex //! let privkey = PrivateKey::from_str("77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404")?; //! // parse public key from hex -//! let pubkey_parsed = PublicKey::from_str("eac2cc96e0ae684388e3185d5277e51313bff98b9ad4a12dcd9205f20d37f1a3")?; +//! let pubkey_parsed = PublicKey::from_hex("eac2cc96e0ae684388e3185d5277e51313bff98b9ad4a12dcd9205f20d37f1a3")?; //! //! // or get the public key from private key //! let pubkey = PublicKey::from_private_key(&privkey); @@ -41,17 +41,19 @@ //! //! ```rust //! use std::str::FromStr; -//! use monero::util::key::{Error, PrivateKey, PublicKey}; +//! use monero::util::key::{Error, PrivateKey, PublicKey, EdwardsPointExt, ScalarExt}; +//! use std::convert::TryFrom; +//! use hex_literal::hex; //! //! let priv1 = PrivateKey::from_str("77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404")?; //! let priv2 = PrivateKey::from_str("8163466f1883598e6dd14027b8da727057165da91485834314f5500a65846f09")?; //! let priv_res = priv1 + priv2; -//! assert_eq!("f8f4b37bedf12a2178c0adcc2565b42a212c133861cb28cdf48abf310c3ce40d", priv_res.to_string()); +//! assert_eq!(PrivateKey::from_bits(hex!("f8f4b37bedf12a2178c0adcc2565b42a212c133861cb28cdf48abf310c3ce40d")), priv_res); //! //! let pub1 = PublicKey::from_private_key(&priv1); //! let pub2 = PublicKey::from_private_key(&priv2); //! let pub_res = pub1 + pub2; -//! assert_eq!("d35ad191b220a627977bb2912ea21fd59b24937f46c1d3814dbcb7943ff1f9f2", pub_res.to_string()); +//! assert_eq!(PublicKey::try_from(hex!("d35ad191b220a627977bb2912ea21fd59b24937f46c1d3814dbcb7943ff1f9f2")).unwrap(), pub_res); //! //! let pubkey = PublicKey::from_private_key(&priv_res); //! assert_eq!(pubkey, pub_res); @@ -59,25 +61,18 @@ //! ``` //! -use std::convert::TryFrom; -use std::hash::{Hash, Hasher}; -use std::ops::{Add, Mul, Sub}; -use std::str::FromStr; -use std::{fmt, io, ops}; +use std::{fmt, io}; -use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE; use curve25519_dalek::edwards::{CompressedEdwardsY, EdwardsPoint}; use curve25519_dalek::scalar::Scalar; use hex_literal::hex; -use rand::{CryptoRng, RngCore}; use thiserror::Error; use crate::consensus::encode::{self, Decodable, Encodable}; use crate::cryptonote::hash; use conquer_once::Lazy; -#[cfg(feature = "serde_support")] -use serde::{Deserialize, Serialize}; +use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; /// Potential errors encountered during key decoding. #[derive(Error, Debug, PartialEq)] @@ -96,183 +91,50 @@ pub enum Error { Hex(#[from] hex::FromHexError), } -/// A private key, a valid curve25519 scalar. -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub struct PrivateKey { - /// The actual curve25519 scalar. - pub scalar: Scalar, -} - -impl PrivateKey { - /// Generate random private key. - pub fn random(rng: &mut R) -> Self { - Self { - scalar: Scalar::random(rng), - } - } - - /// Serialize the private key as bytes. - pub fn as_bytes(&self) -> &[u8] { - self.scalar.as_bytes() - } - - /// Serialize the private key to bytes. - pub fn to_bytes(&self) -> [u8; 32] { - self.scalar.to_bytes() - } - - /// Deserialize a private key from a slice. - pub fn from_slice(data: &[u8]) -> Result { - if data.len() != 32 { - return Err(Error::InvalidLength); - } - let mut bytes = [0u8; 32]; - bytes.copy_from_slice(data); - let scalar = match Scalar::from_canonical_bytes(bytes) { - Some(scalar) => scalar, - None => { - return Err(Error::NotCanonicalScalar); - } - }; - Ok(PrivateKey { scalar }) - } - - /// Create a secret key from a raw curve25519 scalar. - pub fn from_scalar(scalar: Scalar) -> PrivateKey { - PrivateKey { scalar } - } -} - -impl TryFrom<[u8; 32]> for PrivateKey { - type Error = Error; - - fn try_from(value: [u8; 32]) -> Result { - Self::from_slice(&value) - } -} - -impl TryFrom<&[u8]> for PrivateKey { - type Error = Error; +/// A private key in Monero is simply a Scalar. +pub type PrivateKey = Scalar; - fn try_from(value: &[u8]) -> Result { - Self::from_slice(value) - } +/// Extension trait for things we want to do with scalars that are not (yet) present on [`Scalar`]. +pub trait ScalarExt: Sized { + /// Construct a [`Scalar`] from a slice of bytes. + fn from_slice(bytes: &[u8]) -> Result; + /// Construct a [`Scalar`] from a hex-encoded string. + fn from_str(string: &str) -> Result; + /// Returns an adaptor that implements [`fmt::Display`]. + fn display_hex(&self) -> DisplayHexAdaptor<'_, Self>; } -impl<'a, 'b> Add<&'b PrivateKey> for &'a PrivateKey { - type Output = PrivateKey; - - fn add(self, other: &'b PrivateKey) -> Self::Output { - let scalar = self.scalar + other.scalar; - PrivateKey { scalar } - } -} - -impl<'a> Add for &'a PrivateKey { - type Output = PrivateKey; - - fn add(self, other: PrivateKey) -> Self::Output { - let scalar = self.scalar + other.scalar; - PrivateKey { scalar } - } -} - -impl<'b> Add<&'b PrivateKey> for PrivateKey { - type Output = PrivateKey; - - fn add(self, other: &'b PrivateKey) -> Self::Output { - let scalar = self.scalar + other.scalar; - PrivateKey { scalar } - } -} - -impl Add for PrivateKey { - type Output = PrivateKey; - - fn add(self, other: PrivateKey) -> Self::Output { - let scalar = self.scalar + other.scalar; - PrivateKey { scalar } - } -} - -impl Mul for PrivateKey { - type Output = PrivateKey; - - fn mul(self, other: u8) -> Self::Output { - let other: Scalar = other.into(); - PrivateKey { - scalar: self.scalar * other, - } - } -} - -impl Mul for PrivateKey { - type Output = PrivateKey; - - fn mul(self, other: Scalar) -> Self::Output { - PrivateKey { - scalar: self.scalar * other, - } - } -} - -impl Mul for PrivateKey { - type Output = PrivateKey; - - fn mul(self, other: PrivateKey) -> Self::Output { - PrivateKey { - scalar: self.scalar * other.scalar, - } - } -} - -impl<'b> Mul<&'b PublicKey> for PrivateKey { - type Output = PublicKey; - - fn mul(self, other: &'b PublicKey) -> Self::Output { - let point = self.scalar * other.point(); - PublicKey { - point: point.compress(), +impl ScalarExt for Scalar { + fn from_slice(bytes: &[u8]) -> Result { + if bytes.len() != 32 { + return Err(Error::InvalidLength); } - } -} -impl<'a, 'b> Mul<&'b PublicKey> for &'a PrivateKey { - type Output = PublicKey; + let mut buffer = [0u8; 32]; + buffer.copy_from_slice(bytes); - fn mul(self, other: &'b PublicKey) -> Self::Output { - let point = self.scalar * other.point(); - PublicKey { - point: point.compress(), - } + Self::from_canonical_bytes(buffer).ok_or(Error::NotCanonicalScalar) } -} -impl fmt::Display for PrivateKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", hex::encode(&self[..])) - } -} + fn from_str(string: &str) -> Result { + let mut buffer = [0u8; 32]; + hex::decode_to_slice(string, &mut buffer)?; -impl FromStr for PrivateKey { - type Err = Error; - fn from_str(s: &str) -> Result { - let bytes = hex::decode(s)?; - Self::from_slice(&bytes[..]) + Self::from_canonical_bytes(buffer).ok_or(Error::NotCanonicalScalar) } -} -impl ops::Index for PrivateKey { - type Output = [u8]; - fn index(&self, _: ops::RangeFull) -> &[u8] { - self.as_bytes() + fn display_hex(&self) -> DisplayHexAdaptor<'_, Self> { + DisplayHexAdaptor { inner: self } } } impl Decodable for PrivateKey { fn consensus_decode(d: &mut D) -> Result { let bytes: [u8; 32] = Decodable::consensus_decode(d)?; - Ok(PrivateKey::from_slice(&bytes)?) + let scalar = PrivateKey::from_canonical_bytes(bytes) + .ok_or(encode::Error::Key(Error::NotCanonicalScalar))?; + + Ok(scalar) } } @@ -283,220 +145,75 @@ impl Encodable for PrivateKey { } /// A public key, a valid edward point on the curve. -#[derive(PartialEq, Eq, Copy, Clone)] -#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))] -pub struct PublicKey { - /// The actual Ed25519 point. - pub point: CompressedEdwardsY, -} - -impl PublicKey { - /// Serialize a public key as bytes. - pub fn as_bytes(&self) -> &[u8] { - self.point.as_bytes() - } - - /// Serialize a public key to bytes. - pub fn to_bytes(&self) -> [u8; 32] { - self.point.to_bytes() - } +pub type PublicKey = EdwardsPoint; - /// Deserialize a public key from a slice. - pub fn from_slice(data: &[u8]) -> Result { - if data.len() != 32 { - return Err(Error::InvalidLength); - } - let point = CompressedEdwardsY::from_slice(data); - match point.decompress() { - Some(_) => (), - None => { - return Err(Error::InvalidPoint); - } - }; - Ok(PublicKey { point }) - } - - /// Generate a public key from the private key. - pub fn from_private_key(privkey: &PrivateKey) -> PublicKey { - let point = &privkey.scalar * &ED25519_BASEPOINT_TABLE; - PublicKey { - point: point.compress(), - } - } - - /// Get the decompressed edward point of the public key. - fn point(&self) -> EdwardsPoint { - self.point - .decompress() - .expect("PublicKey Can only be created if a valid point is found. QED") - } -} - -impl TryFrom<[u8; 32]> for PublicKey { - type Error = Error; - - fn try_from(value: [u8; 32]) -> Result { - Self::from_slice(&value) - } -} - -impl TryFrom<&[u8]> for PublicKey { - type Error = Error; - - fn try_from(value: &[u8]) -> Result { - Self::from_slice(value) - } +/// Extension trait for things we want to do with edwards points that are not (yet) present on [`EdwardsPoint`]. +pub trait EdwardsPointExt: Sized { + /// Construct an [`EdwardsPoint`] from a [`Scalar`]. + /// + /// Essentially, this computes the "public key" of a "private key". + fn from_private_key(key: &PrivateKey) -> Self; + /// Returns an adaptor that implements [`fmt::Display`]. + fn display_hex(&self) -> DisplayHexAdaptor<'_, Self>; + /// Construct an [`EdwardsPoint`] from a hex-encoded string. + fn from_hex(string: &str) -> Result; } -impl<'a, 'b> Add<&'b PublicKey> for &'a PublicKey { - type Output = PublicKey; - - fn add(self, other: &'b PublicKey) -> Self::Output { - let point = self.point() + other.point(); - PublicKey { - point: point.compress(), - } +impl EdwardsPointExt for EdwardsPoint { + fn from_private_key(key: &PrivateKey) -> Self { + key * ED25519_BASEPOINT_POINT } -} -impl<'a> Add for &'a PublicKey { - type Output = PublicKey; - - fn add(self, other: PublicKey) -> Self::Output { - let point = self.point() + other.point(); - PublicKey { - point: point.compress(), - } + fn display_hex(&self) -> DisplayHexAdaptor<'_, Self> { + DisplayHexAdaptor { inner: self } } -} -impl<'b> Add<&'b PublicKey> for PublicKey { - type Output = PublicKey; + fn from_hex(string: &str) -> Result { + let mut buffer = [0u8; 32]; + hex::decode_to_slice(string, &mut buffer)?; - fn add(self, other: &'b PublicKey) -> Self::Output { - let point = self.point() + other.point(); - PublicKey { - point: point.compress(), - } - } -} - -impl Add for PublicKey { - type Output = PublicKey; - - fn add(self, other: PublicKey) -> Self::Output { - let point = self.point() + other.point(); - PublicKey { - point: point.compress(), - } - } -} - -impl<'a, 'b> Sub<&'b PublicKey> for &'a PublicKey { - type Output = PublicKey; - - fn sub(self, other: &'b PublicKey) -> Self::Output { - let point = self.point() - other.point(); - PublicKey { - point: point.compress(), - } - } -} - -impl<'a> Sub for &'a PublicKey { - type Output = PublicKey; - - fn sub(self, other: PublicKey) -> Self::Output { - let point = self.point() - other.point(); - PublicKey { - point: point.compress(), - } - } -} - -impl<'b> Sub<&'b PublicKey> for PublicKey { - type Output = PublicKey; - - fn sub(self, other: &'b PublicKey) -> Self::Output { - let point = self.point() - other.point(); - PublicKey { - point: point.compress(), - } - } -} - -impl Sub for PublicKey { - type Output = PublicKey; - - fn sub(self, other: PublicKey) -> Self::Output { - let point = self.point() - other.point(); - PublicKey { - point: point.compress(), - } - } -} - -impl<'b> Mul<&'b PrivateKey> for PublicKey { - type Output = PublicKey; - - fn mul(self, other: &'b PrivateKey) -> Self::Output { - let point = self.point() * other.scalar; - PublicKey { - point: point.compress(), - } - } -} - -impl fmt::Display for PublicKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", hex::encode(&self[..])) - } -} - -impl fmt::Debug for PublicKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", hex::encode(&self[..])) + CompressedEdwardsY(buffer) + .decompress() + .ok_or(Error::InvalidPoint) } } -#[allow(clippy::derive_hash_xor_eq)] -impl Hash for PublicKey { - fn hash(&self, state: &mut H) { - self.as_bytes().hash(state); - } +/// Adaptor struct for printing type `T` as hex. +pub struct DisplayHexAdaptor<'a, T> { + inner: &'a T, } -impl FromStr for PublicKey { - type Err = Error; - fn from_str(s: &str) -> Result { - let bytes = hex::decode(s)?; - Self::from_slice(&bytes[..]) +impl<'a> fmt::Display for DisplayHexAdaptor<'a, EdwardsPoint> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self.inner.compress().as_bytes())) } } -impl ops::Index for PublicKey { - type Output = [u8]; - fn index(&self, _: ops::RangeFull) -> &[u8] { - self.as_bytes() +impl<'a> fmt::Display for DisplayHexAdaptor<'a, Scalar> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", hex::encode(self.inner.as_bytes())) } } impl Decodable for PublicKey { fn consensus_decode(d: &mut D) -> Result { let bytes: [u8; 32] = Decodable::consensus_decode(d)?; - Ok(PublicKey::from_slice(&bytes)?) + let compressed_edwards = CompressedEdwardsY(bytes).decompress(); + let point = compressed_edwards.ok_or(encode::Error::Key(Error::InvalidPoint))?; + + Ok(point) } } impl Encodable for PublicKey { fn consensus_encode(&self, s: &mut S) -> Result { - self.to_bytes().consensus_encode(s) + self.compress().to_bytes().consensus_encode(s) } } impl hash::Hashable for PublicKey { fn hash(&self) -> hash::Hash { - hash::Hash::hash(self.as_bytes()) + hash::Hash::hash(&self.compress().to_bytes()) } } @@ -551,52 +268,58 @@ impl From<&KeyPair> for ViewPair { #[cfg(test)] mod tests { - use std::str::FromStr; - - use super::{PrivateKey, PublicKey}; + use super::*; + use hex_literal::hex; + use std::convert::TryFrom; #[test] fn public_key_from_secret() { - let privkey = PrivateKey::from_str( - "77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404", - ) - .unwrap(); + let privkey = PrivateKey::from_bits(hex!( + "77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404" + )); + + let public_key = PublicKey::from_private_key(&privkey); + assert_eq!( - "eac2cc96e0ae684388e3185d5277e51313bff98b9ad4a12dcd9205f20d37f1a3", - PublicKey::from_private_key(&privkey).to_string() + PublicKey::try_from(hex!( + "eac2cc96e0ae684388e3185d5277e51313bff98b9ad4a12dcd9205f20d37f1a3" + )) + .unwrap(), + public_key ); } #[test] fn parse_public_key() { - assert!(PublicKey::from_str( + assert!(PublicKey::try_from(hex!( "eac2cc96e0ae684388e3185d5277e51313bff98b9ad4a12dcd9205f20d37f1a3" - ) + )) .is_ok()); } #[test] fn add_privkey_and_pubkey() { - let priv1 = PrivateKey::from_str( - "77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404", - ) - .unwrap(); - let priv2 = PrivateKey::from_str( - "8163466f1883598e6dd14027b8da727057165da91485834314f5500a65846f09", - ) - .unwrap(); + let priv1 = PrivateKey::from_bits(hex!( + "77916d0cd56ed1920aef6ca56d8a41bac915b68e4c46a589e0956e27a7b77404" + )); + let priv2 = PrivateKey::from_bits(hex!( + "8163466f1883598e6dd14027b8da727057165da91485834314f5500a65846f09" + )); let priv_res = priv1 + priv2; assert_eq!( - "f8f4b37bedf12a2178c0adcc2565b42a212c133861cb28cdf48abf310c3ce40d", - priv_res.to_string() + hex!("f8f4b37bedf12a2178c0adcc2565b42a212c133861cb28cdf48abf310c3ce40d"), + priv_res.to_bytes() ); let pub1 = PublicKey::from_private_key(&priv1); let pub2 = PublicKey::from_private_key(&priv2); let pub_res = pub1 + pub2; assert_eq!( - "d35ad191b220a627977bb2912ea21fd59b24937f46c1d3814dbcb7943ff1f9f2", - pub_res.to_string() + PublicKey::try_from(hex!( + "d35ad191b220a627977bb2912ea21fd59b24937f46c1d3814dbcb7943ff1f9f2" + )) + .unwrap(), + pub_res ); let pubkey = PublicKey::from_private_key(&priv_res); diff --git a/src/util/ringct.rs b/src/util/ringct.rs index 23b641b5..14ef27fd 100644 --- a/src/util/ringct.rs +++ b/src/util/ringct.rs @@ -35,7 +35,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "serde_support")] use serde_big_array_unchecked_docs::*; -use crate::util::key::H; +use crate::util::key::{EdwardsPointExt, ScalarExt, H}; use curve25519_dalek::constants::ED25519_BASEPOINT_POINT; use curve25519_dalek::edwards::EdwardsPoint; use thiserror::Error; @@ -150,9 +150,9 @@ pub enum EcdhInfo { /// Standard format, before `Bulletproof2`. Standard { /// Mask value. - mask: Key, + mask: Scalar, /// Amount value. - amount: Key, + amount: Scalar, }, /// Bulletproof format. Bulletproof { @@ -190,11 +190,9 @@ impl EcdhInfo { EcdhInfo::Standard { mask, amount } => { let shared_sec1 = hash::Hash::hash(shared_key.as_bytes()).to_bytes(); let shared_sec2 = hash::Hash::hash(&shared_sec1).to_bytes(); - let mask_scalar = Scalar::from_bytes_mod_order(mask.key) - - Scalar::from_bytes_mod_order(shared_sec1); + let mask_scalar = mask - Scalar::from_bytes_mod_order(shared_sec1); - let amount_scalar = Scalar::from_bytes_mod_order(amount.key) - - Scalar::from_bytes_mod_order(shared_sec2); + let amount_scalar = amount - Scalar::from_bytes_mod_order(shared_sec2); // get first 64 bits (d2b in rctTypes.cpp) let amount_significant_bytes = amount_scalar.to_bytes()[0..8] .try_into() @@ -204,8 +202,8 @@ impl EcdhInfo { } // ecdhDecode in rctOps.cpp if (v2) EcdhInfo::Bulletproof { amount } => { - let amount = xor_amount(amount.0, shared_key.scalar); - let mask = mask(shared_key.scalar); + let amount = xor_amount(amount.0, shared_key); + let mask = mask(shared_key); (u64::from_le_bytes(amount), mask) } @@ -260,7 +258,7 @@ fn mask(scalar: Scalar) -> Scalar { commitment_key.extend(scalar.as_bytes()); // yt in Z2M p 53 - hash::Hash::hash_to_scalar(&commitment_key).scalar + hash::Hash::hash_to_scalar(&commitment_key) } impl fmt::Display for EcdhInfo { @@ -268,8 +266,8 @@ impl fmt::Display for EcdhInfo { match self { EcdhInfo::Standard { mask, amount } => { writeln!(fmt, "Standard")?; - writeln!(fmt, "Mask: {}", mask)?; - writeln!(fmt, "Amount: {}", amount)?; + writeln!(fmt, "Mask: {}", mask.display_hex())?; + writeln!(fmt, "Amount: {}", amount.display_hex())?; } EcdhInfo::Bulletproof { amount } => { writeln!(fmt, "Bulletproof2")?; @@ -438,7 +436,7 @@ pub struct RctSigBase { /// Ecdh info vector. pub ecdh_info: Vec, /// Out pk vector. - pub out_pk: Vec, + pub out_pk: Vec, } impl fmt::Display for RctSigBase { @@ -452,7 +450,7 @@ impl fmt::Display for RctSigBase { writeln!(fmt, "Ecdh info: {}", ecdh)?; } for out in &self.out_pk { - writeln!(fmt, "Out pk: {}", out)?; + writeln!(fmt, "Out pk: {}", out.display_hex())?; } Ok(()) } @@ -620,7 +618,7 @@ pub struct RctSigPrunable { /// CSLAG signatures. pub Clsags: Vec, /// Pseudo out vector. - pub pseudo_outs: Vec, + pub pseudo_outs: Vec, } impl RctSigPrunable { diff --git a/tests/recover_outputs.rs b/tests/recover_outputs.rs index bb63761f..bab17347 100644 --- a/tests/recover_outputs.rs +++ b/tests/recover_outputs.rs @@ -1,6 +1,8 @@ +use hex_literal::hex; use monero::blockdata::transaction::Transaction; use monero::consensus::encode::deserialize; -use monero::util::key::{KeyPair, PrivateKey, PublicKey, ViewPair}; +use monero::util::key::{EdwardsPointExt, KeyPair, PrivateKey, PublicKey, ViewPair}; +use std::convert::TryFrom; const TRANSACTION: &str = "02000102000bb2e38c0189ea01a9bc02a533fe02a90705fd0540745f59f49374365304f8b4d5da63b444b2d74a40f8007ea44940c15cbbc80c9d106802000267f0f669ead579c1067cbffdf67c4af80b0287c549a10463122b4860fe215f490002b6a2e2f35a93d637ff7d25e20da326cee8e92005d3b18b3c425dabe8336568992c01d6c75cf8c76ac458123f2a498512eb65bb3cecba346c8fcfc516dc0c88518bb90209016f82359eb1fe71d604f0dce9470ed5fd4624bb9fce349a0e8317eabf4172f78a8b27dec6ea1a46da10ed8620fa8367c6391eaa8aabf4ebf660d9fe0eb7e9dfa08365a089ad2df7bce7ef776467898d5ca8947152923c54a1c5030e0c2f01035c555ff4285dcc44dfadd6bc37ec8b9354c045c6590446a81c7f53d8f199cace3faa7f17b3b8302a7cbb3881e8fdc23cca0275c9245fdc2a394b8d3ae73911e3541b10e7725cdeef5e0307bc218caefaafe97c102f39c8ce78f62cccf23c69baf0af55933c9d384ceaf07488f2f1ac7343a593449afd54d1065f6a1a4658845817e4b0e810afc4ca249096e463f9f368625fa37d5bbcbe87af68ce3c4d630f93a66defa4205b178f4e9fa04107bd535c7a4b2251df2dad255e470b611ffe00078c2916fc1eb2af1273e0df30dd1c74b6987b9885e7916b6ca711cbd4b7b50576e51af1439e9ed9e33eb97d8faba4e3bd46066a5026a1940b852d965c1db455d1401687ccaccc524e000b05966763564b7deb8fd64c7fb3d649897c94583dca1558893b071f5e6700dad139f3c6f973c7a43b207ee3e67dc7f7f18b52df442258200c7fe6d16685127da1df9b0d93d764c2659599bc6d300ae33bf8b7c2a504317da90ea2f0bb2af09bd531feae57cb4a0273d8add62fadfc6d43402372e5caf854e112b88417936f1a9c4045d48b5b0b7703d96801b35ff66c716cddbee1b92407aa069a162c163071710e28ccddf6fb560feea32485f2c54a477ae23fd8210427eabe4288cbe0ecbef4ed19ca049ceded424d9f839da957f56ffeb73060ea15498fcbc2d73606e85e963a667dafdb2641fb91862c07b98c1fdae8fadf514600225036dd63c22cdadb57d2125ebf30bc77f7ea0bc0dafb484bf01434954c5053b9c8a143f06972f80fa66788ea1e3425dc0104a9e3674729967b9819552ebb172418da0e4b3778ad4b3d6acd8f354ba09e54bbc8604540010e1e1e4d3066515aed457bd3399c0ce787236dbcd3923de4fb8faded10199b33c1251191612ab5526c1cf0cd55a0aeaed3f7a955ceced16dabdbeb0a2a19a9fdb5aa8c4fc8767cf70e4ad1838518bc6b9de7c420c1f57636579a14a5a8bdacd24e61a68adede8a2e07416c25409dd91ab78905bc99bab4ab4fb9e4ea628e09a271837769c4e67e580dcd5485e12e4e308cb4509686a7484a71f7dfe334499808c7122f07d45d89230b1f19ed86f675b7fec44ef5f3b178ae0af92ff114bd96baa264604fea5a762307bdce6cb483b7bc780d32ed5343fcc3aa306997f211dc075f6dfd66035c1db10bef8656fefbb45645264d401682e42fe3e05906f79d65481b87508f1a4c434e0d1dfc247d4276306f801a6b57e4e4a525177bae24e0bd88a216597d9db44f2604c29d8a5f74e7b934f55048690b5dcefd6489a81aa64c1edb49b320faab94130e603d99e455cfd828bca782176192ece95e9b967fe3dd698574cf0c0b6926970b156e1134658de657de42c4930e72b49c0d94da66c330ab188c10f0d2f578590f31bcac6fcff7e21f9ff67ae1a40d5a03b19301dcbbadc1aa9392795cf81f1401ec16d986a7f96fbb9e8e12ce04a2226e26b78117a4dfb757c6a44481ff68bb0909e7010988cd37146fb45d4cca4ba490aae323bb51a12b6864f88ea6897aa700ee9142eaf0880844083026f044a5e3dba4aae08578cb057976001beb27b5110c41fe336bf7879733739ce22fb31a1a6ac2c900d6d6c6facdbc60085e5c93d502542cfea90dbc62d4e061b7106f09f9c4f6c1b5506dd0550eb8b2bf17678b140de33a10ba676829092e6a13445d1857d06c715eea4492ff864f0b34d178a75a0f1353078f83cfee1440b0a20e64abbd0cab5c6e7083486002970a4904f8371805d1a0ee4aea8524168f0f39d2dfc55f545a98a031841a740e8422a62e123c8303021fb81afbb76d1120c0fbc4d3d97ba69f4e2fe086822ece2047c9ccea507008654c199238a5d17f009aa2dd081f7901d0688aa15311865a319ccba8de4023027235b5725353561c5f1185f6a063fb32fc65ef6e90339d406a6884d66be49d03daaf116ee4b65ef80dd3052a13157b929f98640c0bbe99c8323ce3419a136403dc3f7a95178c3966d2d7bdecf516a28eb2cf8cddb3a0463dc7a6248883f7be0a10aae1bb50728ec9b8880d6011b366a850798f6d7fe07103695dded3f371ca097c1d3596967320071d7f548938afe287cb9b8fae761fa592425623dcbf653028"; @@ -9,13 +11,12 @@ fn recover_output_and_amount() { let raw_tx = hex::decode(TRANSACTION).unwrap(); let tx = deserialize::(&raw_tx).expect("Raw tx deserialization failed"); - let secret_view_bytes = - hex::decode("bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07").unwrap(); - let secret_view = PrivateKey::from_slice(&secret_view_bytes).unwrap(); - - let secret_spend_bytes = - hex::decode("e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907").unwrap(); - let secret_spend = PrivateKey::from_slice(&secret_spend_bytes).unwrap(); + let secret_view = PrivateKey::from_bits(hex!( + "bcfdda53205318e1c14fa0ddca1a45df363bb427972981d0249d0f4652a7df07" + )); + let secret_spend = PrivateKey::from_bits(hex!( + "e5f4301d32f3bdaef814a835a18aaaa24b13cc76cf01a832a7852faf9322e907" + )); let public_spend = PublicKey::from_private_key(&secret_spend); // Keypair used to recover the ephemeral spend key of an output @@ -41,12 +42,17 @@ fn recover_output_and_amount() { // Recover the ephemeral private spend key let private_key = out.recover_key(&keypair); assert_eq!( - "9650bef0bff89132c91f2244d909e0d65acd13415a46efcb933e6c10b7af4c01", - format!("{}", private_key) + PrivateKey::from_bits(hex!( + "9650bef0bff89132c91f2244d909e0d65acd13415a46efcb933e6c10b7af4c01" + )), + private_key ); assert_eq!( - "b6a2e2f35a93d637ff7d25e20da326cee8e92005d3b18b3c425dabe833656899", - format!("{}", PublicKey::from_private_key(&private_key)) + PublicKey::try_from(hex!( + "b6a2e2f35a93d637ff7d25e20da326cee8e92005d3b18b3c425dabe833656899" + )) + .unwrap(), + PublicKey::from_private_key(&private_key) ); let amount = out.amount();