diff --git a/Cargo.lock b/Cargo.lock index f71361a488..731827b627 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1093,41 +1093,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2 1.0.37", - "quote 1.0.18", - "strsim 0.10.0", - "syn 1.0.91", -] - -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core", - "quote 1.0.18", - "syn 1.0.91", -] - [[package]] name = "debugid" version = "0.7.2" @@ -2115,12 +2080,6 @@ dependencies = [ "tokio-rustls 0.23.2", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" version = "0.1.5" @@ -4841,7 +4800,6 @@ dependencies = [ "rocket_contrib", "serde", "serde_derive", - "serde_with", ] [[package]] @@ -5141,7 +5099,6 @@ name = "mc-transaction-core" version = "1.3.0-pre0" dependencies = [ "aes", - "assert_matches", "bulletproofs-og", "crc", "curve25519-dalek", @@ -7368,29 +7325,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "946fa04a8ac43ff78a1f4b811990afb9ddbdf5890b46d6dda0ba1998230138b7" -dependencies = [ - "rustversion", - "serde", - "serde_with_macros", -] - -[[package]] -name = "serde_with_macros" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" -dependencies = [ - "darling", - "proc-macro2 1.0.37", - "quote 1.0.18", - "syn 1.0.91", -] - [[package]] name = "serial_test" version = "0.6.0" diff --git a/android-bindings/src/bindings.rs b/android-bindings/src/bindings.rs index 6590126d97..7c2e3933a1 100644 --- a/android-bindings/src/bindings.rs +++ b/android-bindings/src/bindings.rs @@ -48,7 +48,7 @@ use mc_transaction_core::{ ring_signature::KeyImage, tokens::Mob, tx::{Tx, TxOut, TxOutConfirmationNumber, TxOutMembershipProof}, - Amount, BlockVersion, CompressedCommitment, MaskedAmount, Token, + BlockVersion, CompressedCommitment, MaskedAmount, Token, }; use mc_transaction_std::{ @@ -1603,13 +1603,12 @@ pub unsafe extern "C" fn Java_com_mobilecoin_lib_TransactionBuilder_init_1jni( // FIXME #1595: The token id should be a parameter and not hard coded to Mob // here let token_id = Mob::ID; - let fee_amount = Amount::new(Mob::MINIMUM_FEE, token_id); let tx_builder = TransactionBuilder::new_with_box( block_version, - fee_amount, + token_id, fog_resolver.clone(), memo_builder_box, - )?; + ); Ok(env.set_rust_field(obj, RUST_OBJ_FIELD, tx_builder)?) }) @@ -1693,19 +1692,12 @@ pub unsafe extern "C" fn Java_com_mobilecoin_lib_TransactionBuilder_add_1output( let value = jni_big_int_to_u64(env, value)?; - // TODO (GH #1867): If you want to do mixed transactions, use something other - // than fee_token_id here. - let amount = Amount { - value: value as u64, - token_id: tx_builder.get_fee_token_id(), - }; - let recipient: MutexGuard = env.get_rust_field(recipient, RUST_OBJ_FIELD)?; let mut rng = McRng::default(); let (tx_out, confirmation_number) = - tx_builder.add_output(amount, &recipient, &mut rng)?; + tx_builder.add_output(value as u64, &recipient, &mut rng)?; if !confirmation_number_out.is_null() { let len = env.get_array_length(confirmation_number_out)?; if len as usize >= confirmation_number.to_vec().len() { @@ -1751,15 +1743,8 @@ pub unsafe extern "C" fn Java_com_mobilecoin_lib_TransactionBuilder_add_1change_ ChangeDestination::from(&*source_account_key); let mut rng = McRng::default(); - // TODO (GH #1867): If you want to do mixed transactions, use something other - // than fee_token_id here. - let amount = Amount { - value: value as u64, - token_id: tx_builder.get_fee_token_id(), - }; - let (tx_out, confirmation_number) = - tx_builder.add_change_output(amount, &change_destination, &mut rng)?; + tx_builder.add_change_output(value, &change_destination, &mut rng)?; if !confirmation_number_out.is_null() { let len = env.get_array_length(confirmation_number_out)?; if len as usize >= confirmation_number.to_vec().len() { diff --git a/api/proto/external.proto b/api/proto/external.proto index 55b6dc1991..990057b0be 100644 --- a/api/proto/external.proto +++ b/api/proto/external.proto @@ -242,49 +242,20 @@ message TxPrefix { // The block index at which this transaction is no longer valid. uint64 tombstone_block = 4; - // Token id for the fee of this transaction - fixed64 fee_token_id = 5; + // Token id for this transaction + fixed64 token_id = 5; } -// A ring mlsag is a group-ring signature conferring spending authority of one TxOut -// which is part of a TxIn. message RingMLSAG { - // The initial challenge value for the ring signature CurveScalar c_zero = 1; - // The "responses", one for each input which is signed repeated CurveScalar responses = 2; - // The key image is a hash unique to the "true" spent input. This cannot - // be linked back to determine the true spent input, but the input cannot be - // spent again without producing the same key image value, so this is used to - // prevent double-spends. KeyImage key_image = 3; } message SignatureRctBulletproofs { - // A ring-signature, one for each TxIn, producing one pseudo-output and key image. repeated RingMLSAG ring_signatures = 1; - // The amount commitments for each pseudo-output. - // There must be one of these for each TxIn. repeated CompressedRistretto pseudo_output_commitments = 2; - // Before mixed transactions feature, there is one range proof for all pseudo-output - // and output commitments, whose serialized bytes appear here. - // After mixed transactions feature, this field is empty. - bytes range_proof_bytes = 3; - // Before mixed transactions feature, this field is empty. - // After mixed transactions feature, this field contains one range proof for each - // token id which appears in the transaction, in sorted order of token ids. - // It range-proofs the pseudo-outputs and outputs with that token id, in the order - // that they appear in the transaction. - repeated bytes range_proofs = 4; - // The token ids of each pseudo ouptut. There must be one of these for each TxIn. - // Before mixed transactions feature, this field is empty, and the token ids of - // all pseudo-outputs are inferred from the tx.prefix.fee_token_id. - repeated fixed64 pseudo_output_token_ids = 5; - // The token ids of each output. There must be one of these for each output of the Tx. - // (tx.prefix.outputs). - // Before mixed transactions feature, this field is empty, and the token ids of - // all outputs are inferred from the tx.prefix.fee_token_id. - repeated fixed64 output_token_ids = 6; + bytes range_proofs = 3; } message Tx { diff --git a/api/src/convert/signature_rct_bulletproofs.rs b/api/src/convert/signature_rct_bulletproofs.rs index 2c7db3dd6d..490c77a078 100644 --- a/api/src/convert/signature_rct_bulletproofs.rs +++ b/api/src/convert/signature_rct_bulletproofs.rs @@ -5,7 +5,6 @@ use mc_transaction_core::{ ring_signature::{RingMLSAG, SignatureRctBulletproofs}, CompressedCommitment, }; -use protobuf::RepeatedField; use std::convert::TryFrom; impl From<&SignatureRctBulletproofs> for external::SignatureRctBulletproofs { @@ -26,10 +25,7 @@ impl From<&SignatureRctBulletproofs> for external::SignatureRctBulletproofs { .collect(); signature.set_pseudo_output_commitments(pseudo_output_commitments.into()); - signature.set_range_proof_bytes(source.range_proof_bytes.clone()); - signature.set_range_proofs(RepeatedField::from_vec(source.range_proofs.clone())); - signature.set_pseudo_output_token_ids(source.pseudo_output_token_ids.clone()); - signature.set_output_token_ids(source.output_token_ids.clone()); + signature.set_range_proofs(source.range_proof_bytes.clone()); signature } @@ -50,18 +46,12 @@ impl TryFrom<&external::SignatureRctBulletproofs> for SignatureRctBulletproofs { .push(CompressedCommitment::try_from(pseudo_output_commitment)?); } - let range_proof_bytes = source.get_range_proof_bytes().to_vec(); - let range_proofs = source.get_range_proofs().to_vec(); - let pseudo_output_token_ids = source.get_pseudo_output_token_ids().to_vec(); - let output_token_ids = source.get_output_token_ids().to_vec(); + let range_proof_bytes = source.get_range_proofs().to_vec(); Ok(SignatureRctBulletproofs { ring_signatures, pseudo_output_commitments, range_proof_bytes, - range_proofs, - pseudo_output_token_ids, - output_token_ids, }) } } diff --git a/api/src/convert/tx.rs b/api/src/convert/tx.rs index 45459dbf0a..488f548a88 100644 --- a/api/src/convert/tx.rs +++ b/api/src/convert/tx.rs @@ -34,7 +34,7 @@ mod tests { onetime_keys::recover_onetime_private_key, tokens::Mob, tx::{Tx, TxOut, TxOutMembershipProof}, - Amount, BlockVersion, Token, + BlockVersion, Token, }; use mc_transaction_core_test_utils::MockFogResolver; use mc_transaction_std::{EmptyMemoBuilder, InputCredentials, TransactionBuilder}; @@ -72,11 +72,10 @@ mod tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, Mob::ID), + Mob::ID, MockFogResolver::default(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); let ring: Vec = minted_outputs.clone(); let public_key = RistrettoPublic::try_from(&minted_outputs[0].public_key).unwrap(); @@ -107,11 +106,7 @@ mod tests { transaction_builder.add_input(input_credentials); transaction_builder.set_fee(0).unwrap(); transaction_builder - .add_output( - Amount::new(65536, Mob::ID), - &bob.default_subaddress(), - &mut rng, - ) + .add_output(65536, &bob.default_subaddress(), &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); diff --git a/api/src/convert/tx_prefix.rs b/api/src/convert/tx_prefix.rs index 0815686e2a..2d6a4e0f44 100644 --- a/api/src/convert/tx_prefix.rs +++ b/api/src/convert/tx_prefix.rs @@ -18,7 +18,7 @@ impl From<&tx::TxPrefix> for external::TxPrefix { tx_prefix.set_fee(source.fee); - tx_prefix.set_fee_token_id(source.fee_token_id); + tx_prefix.set_token_id(source.token_id); tx_prefix.set_tombstone_block(source.tombstone_block); @@ -47,7 +47,7 @@ impl TryFrom<&external::TxPrefix> for tx::TxPrefix { inputs, outputs, fee: source.get_fee(), - fee_token_id: source.get_fee_token_id(), + token_id: source.get_token_id(), tombstone_block: source.get_tombstone_block(), }; Ok(tx_prefix) diff --git a/consensus/enclave/impl/src/lib.rs b/consensus/enclave/impl/src/lib.rs index 5c9becf46a..1975702b90 100644 --- a/consensus/enclave/impl/src/lib.rs +++ b/consensus/enclave/impl/src/lib.rs @@ -236,10 +236,10 @@ impl SgxConsensusEnclave { // We need to make sure all transactions are valid. We also ensure they all // point at the same root membership element. for (tx, proofs) in transactions_with_proofs.iter() { - let fee_token_id = TokenId::from(tx.prefix.fee_token_id); + let token_id = TokenId::from(tx.prefix.token_id); let minimum_fee = ct_min_fees - .get(&fee_token_id) + .get(&token_id) .ok_or(TransactionValidationError::TokenNotYetConfigured)?; mc_transaction_core::validation::validate( @@ -685,12 +685,12 @@ impl ConsensusEnclave for SgxConsensusEnclave { .decrypt_bytes(locally_encrypted_tx.0)?; let tx: Tx = mc_util_serial::decode(&decrypted_bytes)?; - let fee_token_id = TokenId::from(tx.prefix.fee_token_id); + let token_id = TokenId::from(tx.prefix.token_id); // Validate. let mut csprng = McRng::default(); let minimum_fee = ct_min_fee_map - .get(&fee_token_id) + .get(&token_id) .ok_or(TransactionValidationError::TokenNotYetConfigured)?; mc_transaction_core::validation::validate( &tx, @@ -782,8 +782,8 @@ impl ConsensusEnclave for SgxConsensusEnclave { // Compute the total fees for each known token id, for tx's in this block. let mut total_fees: CtTokenMap = ct_min_fee_map.keys().cloned().collect(); for tx in transactions.iter() { - let fee_token_id = TokenId::from(tx.prefix.fee_token_id); - total_fees.add(&fee_token_id, tx.prefix.fee as u128); + let token_id = TokenId::from(tx.prefix.token_id); + total_fees.add(&token_id, tx.prefix.fee as u128); } // Sort the token ids which did not appear to the end. diff --git a/consensus/service/src/validators.rs b/consensus/service/src/validators.rs index 3c716979d0..77eabf9686 100644 --- a/consensus/service/src/validators.rs +++ b/consensus/service/src/validators.rs @@ -536,7 +536,10 @@ mod combine_tests { let tx_secret_key_for_txo = RistrettoPrivate::from_random(&mut rng); let tx_out = TxOut::new( - Amount::new(123, Mob::ID), + Amount { + value: 123, + token_id: Mob::ID, + }, &alice.default_subaddress(), &tx_secret_key_for_txo, Default::default(), @@ -575,19 +578,14 @@ mod combine_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, Mob::ID), + Mob::ID, MockFogResolver::default(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); transaction_builder.add_input(input_credentials); transaction_builder.set_fee(0).unwrap(); transaction_builder - .add_output( - Amount::new(123, Mob::ID), - &bob.default_subaddress(), - &mut rng, - ) + .add_output(123, &bob.default_subaddress(), &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -620,7 +618,10 @@ mod combine_tests { let tx_secret_key_for_txo = RistrettoPrivate::from_random(&mut rng); let tx_out = TxOut::new( - Amount::new(88, Mob::ID), + Amount { + value: 88, + token_id: Mob::ID, + }, &alice.default_subaddress(), &tx_secret_key_for_txo, Default::default(), @@ -635,11 +636,10 @@ mod combine_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, Mob::ID), + Mob::ID, MockFogResolver::default(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); // Create InputCredentials to spend the TxOut. let onetime_private_key = recover_onetime_private_key( @@ -669,11 +669,7 @@ mod combine_tests { transaction_builder.add_input(input_credentials); transaction_builder.set_fee(0).unwrap(); transaction_builder - .add_output( - Amount::new(88, Mob::ID), - &bob.default_subaddress(), - &mut rng, - ) + .add_output(88, &bob.default_subaddress(), &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -741,19 +737,14 @@ mod combine_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, Mob::ID), + Mob::ID, MockFogResolver::default(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); transaction_builder.add_input(input_credentials); transaction_builder.set_fee(0).unwrap(); transaction_builder - .add_output( - Amount::new(123, Mob::ID), - &bob.default_subaddress(), - &mut rng, - ) + .add_output(123, &bob.default_subaddress(), &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -783,19 +774,14 @@ mod combine_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, Mob::ID), + Mob::ID, MockFogResolver::default(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); transaction_builder.add_input(input_credentials); transaction_builder.set_fee(0).unwrap(); transaction_builder - .add_output( - Amount::new(123, Mob::ID), - &recipient_account.default_subaddress(), - &mut rng, - ) + .add_output(123, &recipient_account.default_subaddress(), &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -810,7 +796,10 @@ mod combine_tests { // The transaction keys. let tx_secret_key_for_txo = RistrettoPrivate::from_random(&mut rng); let tx_out = TxOut::new( - Amount::new(123, Mob::ID), + Amount { + value: 123, + token_id: Mob::ID, + }, &alice.default_subaddress(), &tx_secret_key_for_txo, Default::default(), @@ -848,19 +837,14 @@ mod combine_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, Mob::ID), + Mob::ID, MockFogResolver::default(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); transaction_builder.add_input(input_credentials); transaction_builder.set_fee(0).unwrap(); transaction_builder - .add_output( - Amount::new(123, Mob::ID), - &recipient_account.default_subaddress(), - &mut rng, - ) + .add_output(123, &recipient_account.default_subaddress(), &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -890,7 +874,10 @@ mod combine_tests { // Create two TxOuts that were sent to Alice. let tx_out1 = TxOut::new( - Amount::new(123, Mob::ID), + Amount { + value: 123, + token_id: Mob::ID, + }, &alice.default_subaddress(), &RistrettoPrivate::from_random(&mut rng), Default::default(), @@ -898,7 +885,10 @@ mod combine_tests { .unwrap(); let tx_out2 = TxOut::new( - Amount::new(123, Mob::ID), + Amount { + value: 123, + token_id: Mob::ID, + }, &alice.default_subaddress(), &RistrettoPrivate::from_random(&mut rng), Default::default(), @@ -940,19 +930,14 @@ mod combine_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, Mob::ID), + Mob::ID, MockFogResolver::default(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); transaction_builder.add_input(input_credentials); transaction_builder.set_fee(0).unwrap(); transaction_builder - .add_output( - Amount::new(123, Mob::ID), - &bob.default_subaddress(), - &mut rng, - ) + .add_output(123, &bob.default_subaddress(), &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -983,19 +968,14 @@ mod combine_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, Mob::ID), + Mob::ID, MockFogResolver::default(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); transaction_builder.add_input(input_credentials); transaction_builder.set_fee(0).unwrap(); transaction_builder - .add_output( - Amount::new(123, Mob::ID), - &recipient_account.default_subaddress(), - &mut rng, - ) + .add_output(123, &recipient_account.default_subaddress(), &mut rng) .unwrap(); let mut tx = transaction_builder.build(&mut rng).unwrap(); @@ -1011,7 +991,10 @@ mod combine_tests { // The transaction keys. let tx_secret_key_for_txo = RistrettoPrivate::from_random(&mut rng); let tx_out = TxOut::new( - Amount::new(123, Mob::ID), + Amount { + value: 123, + token_id: Mob::ID, + }, &alice.default_subaddress(), &tx_secret_key_for_txo, Default::default(), @@ -1049,19 +1032,14 @@ mod combine_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, Mob::ID), + Mob::ID, MockFogResolver::default(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); transaction_builder.add_input(input_credentials); transaction_builder.set_fee(0).unwrap(); transaction_builder - .add_output( - Amount::new(123, Mob::ID), - &recipient_account.default_subaddress(), - &mut rng, - ) + .add_output(123, &recipient_account.default_subaddress(), &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); diff --git a/fog/distribution/src/main.rs b/fog/distribution/src/main.rs index d7c6c0a8ec..5d3424e757 100755 --- a/fog/distribution/src/main.rs +++ b/fog/distribution/src/main.rs @@ -715,18 +715,17 @@ fn build_tx( // Use token id for first spendable tx out let token_id = spendable_txouts.first().unwrap().amount.token_id; - // FIXME: This needs to be the fee for the current token, not MOB. - // However, bootstrapping non MOB tokens is not supported right now. - let fee_amount = Amount::new(MOB_FEE.load(Ordering::SeqCst), token_id); - // Create tx_builder. let mut tx_builder = TransactionBuilder::new( block_version, - fee_amount, + token_id, fog_resolver, EmptyMemoBuilder::default(), - ) - .unwrap(); + ); + + // FIXME: This needs to be the fee for the current token, not MOB. + // However, bootstrapping non MOB tokens is not supported right now. + tx_builder.set_fee(MOB_FEE.load(Ordering::SeqCst)).unwrap(); // Unzip each vec of tuples into a tuple of vecs. let mut rings_and_proofs: Vec<(Vec, Vec)> = rings @@ -815,7 +814,7 @@ fn build_tx( let target_address = to_account.default_subaddress(); tx_builder - .add_output(Amount { value, token_id }, &target_address, &mut rng) + .add_output(value, &target_address, &mut rng) .expect("failed to add output"); } } diff --git a/fog/sample-paykit/src/client.rs b/fog/sample-paykit/src/client.rs index 0d2a8f769d..2b6ab151fd 100644 --- a/fog/sample-paykit/src/client.rs +++ b/fog/sample-paykit/src/client.rs @@ -606,13 +606,9 @@ fn build_transaction_helper( memo_builder.set_sender_credential(SenderMemoCredential::from(source_account_key)); memo_builder.enable_destination_memo(); - TransactionBuilder::new( - block_version, - Amount::new(fee, amount.token_id), - fog_resolver, - memo_builder, - )? + TransactionBuilder::new(block_version, amount.token_id, fog_resolver, memo_builder) }; + tx_builder.set_fee(fee)?; let input_amount = inputs .iter() @@ -704,17 +700,13 @@ fn build_transaction_helper( // Resolve account server key if the receiver specifies an account service in // their public address tx_builder - .add_output(amount, target_address, rng) + .add_output(amount.value, target_address, rng) .map_err(Error::AddOutput)?; let change_destination = ChangeDestination::from(source_account_key); tx_builder - .add_change_output( - Amount::new(change, amount.token_id), - &change_destination, - rng, - ) + .add_change_output(change, &change_destination, rng) .map_err(|err| { log::error!(logger, "Could not add change due to {:?}", err); Error::AddOutput(err) diff --git a/libmobilecoin/src/transaction.rs b/libmobilecoin/src/transaction.rs index 846b84aba0..75e1fb2228 100644 --- a/libmobilecoin/src/transaction.rs +++ b/libmobilecoin/src/transaction.rs @@ -19,7 +19,7 @@ use mc_transaction_core::{ ring_signature::KeyImage, tokens::Mob, tx::{TxOut, TxOutConfirmationNumber, TxOutMembershipProof}, - Amount, BlockVersion, CompressedCommitment, EncryptedMemo, MaskedAmount, Token, + BlockVersion, CompressedCommitment, EncryptedMemo, MaskedAmount, Token, }; use mc_transaction_std::{ @@ -378,12 +378,14 @@ pub extern "C" fn mc_transaction_builder_create( let mut transaction_builder = TransactionBuilder::new_with_box( block_version, - Amount::new(fee, token_id), + token_id, fog_resolver, memo_builder_box, - ) - .expect("Could not create transaction builder"); + ); + transaction_builder + .set_fee(fee) + .expect("failure not expected"); transaction_builder.set_tombstone_block(tombstone_block); Some(transaction_builder) }) @@ -496,13 +498,6 @@ pub extern "C" fn mc_transaction_builder_add_output( .as_slice_mut_of_len(TxOutConfirmationNumber::size()) .expect("out_tx_out_confirmation_number length is insufficient"); - // TODO (GH #1867): If you want to support mixed transactions, use something - // other than fee_token_id here. - let amount = Amount { - value: amount, - token_id: transaction_builder.get_fee_token_id(), - }; - let (tx_out, confirmation) = transaction_builder.add_output(amount, &recipient_address, &mut rng)?; @@ -546,13 +541,6 @@ pub extern "C" fn mc_transaction_builder_add_change_output( .as_slice_mut_of_len(TxOutConfirmationNumber::size()) .expect("out_tx_out_confirmation_number length is insufficient"); - // TODO (GH #1867): If you want to support mixed transactions, use something - // other than fee_token_id here. - let amount = Amount { - value: amount, - token_id: transaction_builder.get_fee_token_id(), - }; - let (tx_out, confirmation) = transaction_builder.add_change_output(amount, &change_destination, &mut rng)?; diff --git a/mobilecoind-json/Cargo.toml b/mobilecoind-json/Cargo.toml index 89de9eea6d..fccb79389b 100644 --- a/mobilecoind-json/Cargo.toml +++ b/mobilecoind-json/Cargo.toml @@ -22,7 +22,6 @@ rocket = { version = "0.4.10", default-features = false } rocket_contrib = { version = "0.4.10", default-features = false, features = ["json"] } serde = "1.0" serde_derive = "1.0" -serde_with = "1.12" [dev-dependencies] mc-crypto-keys = { path = "../crypto/keys" } diff --git a/mobilecoind-json/src/bin/main.rs b/mobilecoind-json/src/bin/main.rs index ea98e95e61..7ac1126e20 100644 --- a/mobilecoind-json/src/bin/main.rs +++ b/mobilecoind-json/src/bin/main.rs @@ -295,8 +295,12 @@ fn create_request_code( // Generate b58 code let mut req = mc_mobilecoind_api::CreateRequestCodeRequest::new(); req.set_receiver(receiver); - if let Some(value) = request.value { - req.set_value(u64::from(value)); + if let Some(value) = request.value.clone() { + req.set_value( + value + .parse::() + .map_err(|err| format!("Failed to parse value field: {}", err))?, + ); } if let Some(memo) = request.memo.clone() { req.set_memo(memo); @@ -388,14 +392,21 @@ fn build_and_submit( // Generate an outlay let mut outlay = mc_mobilecoind_api::Outlay::new(); outlay.set_receiver(public_address); - outlay.set_value(transfer.request_data.value.into()); + outlay.set_value( + transfer + .request_data + .value + .parse::() + .map_err(|err| format!("Failed to parse request_code.amount: {}", err))?, + ); // Get max_input_utxo_value. let max_input_utxo_value = transfer .max_input_utxo_value - .as_ref() - .map(u64::from) - .unwrap_or(0); + .clone() + .unwrap_or_else(|| "0".to_owned()) // A value of 0 disables the max limit. + .parse::() + .map_err(|err| format!("Failed to parse max_input_utxo_value: {}", err))?; // Send the payment request let mut req = mc_mobilecoind_api::SendPaymentRequest::new(); @@ -405,7 +416,11 @@ fn build_and_submit( req.set_max_input_utxo_value(max_input_utxo_value); if let Some(subaddress) = transfer.change_subaddress.as_ref() { req.set_override_change_subaddress(true); - req.set_change_subaddress(u64::from(subaddress)) + req.set_change_subaddress( + subaddress + .parse::() + .map_err(|err| format!("Failed to parse change subaddress: {}", err))?, + ) } let resp = state @@ -434,14 +449,18 @@ fn pay_address_code( hex::decode(monitor_hex).map_err(|err| format!("Failed to decode monitor hex: {}", err))?; // Get amount. - let amount = u64::from(transfer.value); + let amount = transfer + .value + .parse::() + .map_err(|err| format!("Failed parsing amount: {}", err))?; // Get max_input_utxo_value. let max_input_utxo_value = transfer .max_input_utxo_value - .as_ref() - .map(u64::from) - .unwrap_or(0); + .clone() + .unwrap_or_else(|| "0".to_owned()) // A value of 0 disables the max limit. + .parse::() + .map_err(|err| format!("Failed to parse max_input_utxo_value: {}", err))?; // Send the pay address code request let mut req = mc_mobilecoind_api::PayAddressCodeRequest::new(); @@ -452,7 +471,11 @@ fn pay_address_code( req.set_max_input_utxo_value(max_input_utxo_value); if let Some(subaddress) = transfer.change_subaddress.as_ref() { req.set_override_change_subaddress(true); - req.set_change_subaddress(u64::from(subaddress)) + req.set_change_subaddress( + subaddress + .parse::() + .map_err(|err| format!("Failed to parse change subaddress: {}", err))?, + ) } let resp = state @@ -486,7 +509,13 @@ fn generate_request_code_transaction( // Generate an outlay let mut outlay = mc_mobilecoind_api::Outlay::new(); outlay.set_receiver(public_address); - outlay.set_value(request.transfer.value.into()); + outlay.set_value( + request + .transfer + .value + .parse::() + .map_err(|err| format!("Failed to parse amount: {}", err))?, + ); let inputs: Vec = request .input_list diff --git a/mobilecoind-json/src/data_types.rs b/mobilecoind-json/src/data_types.rs index debbd3f1dc..3b55beae09 100644 --- a/mobilecoind-json/src/data_types.rs +++ b/mobilecoind-json/src/data_types.rs @@ -11,35 +11,6 @@ use protobuf::RepeatedField; use serde_derive::{Deserialize, Serialize}; use std::convert::TryFrom; -// Represents u64 using string, when serializing to Json -// Javascript integers are not 64 bit, and so it is not really proper json. -// Using string avoids issues with some json parsers not handling large numbers -// well. -// -// This does not rely on the serde-json arbitrary precision feature, which -// (we fear) might break other things (e.g. https://github.com/serde-rs/json/issues/505) -#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] -#[serde(transparent)] -pub struct JsonU64(#[serde(with = "serde_with::rust::display_fromstr")] pub u64); - -impl From<&u64> for JsonU64 { - fn from(src: &u64) -> Self { - Self(*src) - } -} - -impl From<&JsonU64> for u64 { - fn from(src: &JsonU64) -> u64 { - src.0 - } -} - -impl From for u64 { - fn from(src: JsonU64) -> u64 { - src.0 - } -} - #[derive(Deserialize, Default, Debug)] pub struct JsonPasswordRequest { pub password: String, @@ -177,7 +148,7 @@ pub struct JsonUnspentTxOut { pub tx_out: JsonTxOut, pub subaddress_index: u64, pub key_image: String, - pub value: JsonU64, + pub value: String, // Needs to be String since Javascript ints are not 64 bit. pub attempted_spend_height: u64, pub attempted_spend_tombstone: u64, pub monitor_id: String, @@ -189,7 +160,7 @@ impl From<&mc_mobilecoind_api::UnspentTxOut> for JsonUnspentTxOut { tx_out: src.get_tx_out().into(), subaddress_index: src.get_subaddress_index(), key_image: hex::encode(&src.get_key_image().get_data()), - value: JsonU64(src.value), + value: src.value.to_string(), attempted_spend_height: src.get_attempted_spend_height(), attempted_spend_tombstone: src.get_attempted_spend_tombstone(), monitor_id: hex::encode(&src.get_monitor_id()), @@ -216,7 +187,11 @@ impl TryFrom<&JsonUnspentTxOut> for mc_mobilecoind_api::UnspentTxOut { ); utxo.set_subaddress_index(src.subaddress_index); utxo.set_key_image(key_image); - utxo.set_value(src.value.into()); + utxo.set_value( + src.value + .parse::() + .map_err(|err| format!("Failed to parse u64 from value: {}", err))?, + ); utxo.set_attempted_spend_height(src.attempted_spend_height); utxo.set_attempted_spend_tombstone(src.attempted_spend_tombstone); utxo.set_monitor_id( @@ -248,7 +223,7 @@ impl From<&mc_mobilecoind_api::GetUnspentTxOutListResponse> for JsonUtxosRespons #[derive(Deserialize, Default, Debug)] pub struct JsonCreateRequestCodeRequest { pub receiver: JsonPublicAddress, - pub value: Option, + pub value: Option, pub memo: Option, } @@ -365,7 +340,7 @@ impl TryFrom<&JsonPublicAddress> for PublicAddress { #[derive(Deserialize, Serialize, Default, Debug)] pub struct JsonParseRequestCodeResponse { pub receiver: JsonPublicAddress, - pub value: JsonU64, + pub value: String, pub memo: String, } @@ -373,7 +348,7 @@ impl From<&mc_mobilecoind_api::ParseRequestCodeResponse> for JsonParseRequestCod fn from(src: &mc_mobilecoind_api::ParseRequestCodeResponse) -> Self { Self { receiver: JsonPublicAddress::from(src.get_receiver()), - value: JsonU64(src.get_value()), + value: src.get_value().to_string(), memo: src.get_memo().to_string(), } } @@ -453,8 +428,8 @@ impl From<&mc_mobilecoind_api::ReceiverTxReceipt> for JsonReceiverTxReceipt { #[derive(Deserialize, Serialize, Default, Debug)] pub struct JsonSendPaymentRequest { pub request_data: JsonParseRequestCodeResponse, - pub max_input_utxo_value: Option, - pub change_subaddress: Option, + pub max_input_utxo_value: Option, // String due to u64 limitation. + pub change_subaddress: Option, } #[derive(Deserialize, Serialize, Default, Debug)] @@ -479,21 +454,21 @@ impl From<&mc_mobilecoind_api::SendPaymentResponse> for JsonSendPaymentResponse #[derive(Deserialize, Serialize, Debug)] pub struct JsonPayAddressCodeRequest { pub receiver_b58_address_code: String, - pub value: JsonU64, - pub max_input_utxo_value: Option, - pub change_subaddress: Option, + pub value: String, + pub max_input_utxo_value: Option, + pub change_subaddress: Option, } #[derive(Deserialize, Serialize, Default, Debug, Clone)] pub struct JsonOutlay { - pub value: JsonU64, + pub value: String, pub receiver: JsonPublicAddress, } impl From<&mc_mobilecoind_api::Outlay> for JsonOutlay { fn from(src: &mc_mobilecoind_api::Outlay) -> Self { Self { - value: JsonU64(src.get_value()), + value: src.get_value().to_string(), receiver: src.get_receiver().into(), } } @@ -504,7 +479,11 @@ impl TryFrom<&JsonOutlay> for mc_mobilecoind_api::Outlay { fn try_from(src: &JsonOutlay) -> Result { let mut outlay = mc_mobilecoind_api::Outlay::new(); - outlay.set_value(src.value.into()); + outlay.set_value( + src.value + .parse::() + .map_err(|err| format!("Failed to parse u64 from value: {}", err))?, + ); outlay.set_receiver( PublicAddress::try_from(&src.receiver) .map_err(|err| format!("Could not convert receiver: {}", err))?, @@ -517,7 +496,7 @@ impl TryFrom<&JsonOutlay> for mc_mobilecoind_api::Outlay { #[derive(Deserialize, Serialize, Default, Debug, Clone)] pub struct JsonMaskedAmount { pub commitment: String, - pub masked_value: JsonU64, + pub masked_value: String, pub masked_token_id: String, } @@ -525,7 +504,7 @@ impl From<&MaskedAmount> for JsonMaskedAmount { fn from(src: &MaskedAmount) -> Self { Self { commitment: hex::encode(src.get_commitment().get_data()), - masked_value: JsonU64(src.get_masked_value()), + masked_value: src.get_masked_value().to_string(), masked_token_id: hex::encode(src.get_masked_token_id()), } } @@ -564,7 +543,12 @@ impl TryFrom<&JsonTxOut> for mc_api::external::TxOut { ); let mut masked_amount = MaskedAmount::new(); masked_amount.set_commitment(commitment); - masked_amount.set_masked_value(src.masked_amount.masked_value.into()); + masked_amount.set_masked_value( + src.masked_amount + .masked_value + .parse::() + .map_err(|err| format!("Failed to parse u64 from value: {}", err))?, + ); masked_amount.set_masked_token_id( hex::decode(&src.masked_amount.masked_token_id) .map_err(|err| format!("Failed to decode masked token id hex: {}", err))?, @@ -605,8 +589,8 @@ impl TryFrom<&JsonTxOut> for mc_api::external::TxOut { #[derive(Deserialize, Serialize, Default, Debug, Clone)] pub struct JsonRange { - pub from: JsonU64, - pub to: JsonU64, + pub from: String, + pub to: String, } #[derive(Deserialize, Serialize, Default, Debug, Clone)] @@ -619,8 +603,8 @@ impl From<&TxOutMembershipElement> for JsonTxOutMembershipElement { fn from(src: &TxOutMembershipElement) -> Self { Self { range: JsonRange { - from: JsonU64(src.get_range().get_from()), - to: JsonU64(src.get_range().get_to()), + from: src.get_range().get_from().to_string(), + to: src.get_range().get_to().to_string(), }, hash: hex::encode(src.get_hash().get_data()), } @@ -629,16 +613,16 @@ impl From<&TxOutMembershipElement> for JsonTxOutMembershipElement { #[derive(Deserialize, Serialize, Default, Debug, Clone)] pub struct JsonTxOutMembershipProof { - pub index: JsonU64, - pub highest_index: JsonU64, + pub index: String, + pub highest_index: String, pub elements: Vec, } impl From<&TxOutMembershipProof> for JsonTxOutMembershipProof { fn from(src: &TxOutMembershipProof) -> Self { Self { - index: JsonU64(src.get_index()), - highest_index: JsonU64(src.get_highest_index()), + index: src.get_index().to_string(), + highest_index: src.get_highest_index().to_string(), elements: src .get_elements() .iter() @@ -655,8 +639,20 @@ impl TryFrom<&JsonTxOutMembershipProof> for TxOutMembershipProof { let mut elements: Vec = Vec::new(); for element in &src.elements { let mut range = mc_api::external::Range::new(); - range.set_from(element.range.from.into()); - range.set_to(element.range.to.into()); + range.set_from( + element + .range + .from + .parse::() + .map_err(|err| format!("Failed to parse u64 from range.from: {}", err))?, + ); + range.set_to( + element + .range + .to + .parse::() + .map_err(|err| format!("Failed to parse u64 from range.to: {}", err))?, + ); let mut hash = TxOutMembershipHash::new(); hash.set_data( @@ -671,8 +667,16 @@ impl TryFrom<&JsonTxOutMembershipProof> for TxOutMembershipProof { } let mut proof = TxOutMembershipProof::new(); - proof.set_index(src.index.into()); - proof.set_highest_index(src.highest_index.into()); + proof.set_index( + src.index + .parse::() + .map_err(|err| format!("Failed to parse u64 from index: {}", err))?, + ); + proof.set_highest_index( + src.highest_index + .parse::() + .map_err(|err| format!("Failed to parse u64 from highest_index: {}", err))?, + ); proof.set_elements(RepeatedField::from_vec(elements)); Ok(proof) @@ -761,8 +765,8 @@ impl TryFrom<&JsonTxIn> for TxIn { pub struct JsonTxPrefix { pub inputs: Vec, pub outputs: Vec, - pub fee: JsonU64, - tombstone_block: JsonU64, + pub fee: String, + tombstone_block: String, } impl From<&TxPrefix> for JsonTxPrefix { @@ -770,8 +774,8 @@ impl From<&TxPrefix> for JsonTxPrefix { Self { inputs: src.get_inputs().iter().map(JsonTxIn::from).collect(), outputs: src.get_outputs().iter().map(JsonTxOut::from).collect(), - fee: JsonU64(src.get_fee()), - tombstone_block: JsonU64(src.get_tombstone_block()), + fee: src.get_fee().to_string(), + tombstone_block: src.get_tombstone_block().to_string(), } } } @@ -797,8 +801,16 @@ impl TryFrom<&JsonTxPrefix> for TxPrefix { let mut prefix = TxPrefix::new(); prefix.set_inputs(RepeatedField::from_vec(inputs)); prefix.set_outputs(RepeatedField::from_vec(outputs)); - prefix.set_fee(src.fee.into()); - prefix.set_tombstone_block(src.tombstone_block.into()); + prefix.set_fee( + src.fee + .parse::() + .map_err(|err| format!("Failed to parse u64 from fee: {}", err))?, + ); + prefix.set_tombstone_block( + src.tombstone_block + .parse::() + .map_err(|err| format!("Failed to parse u64 from tombstone_block: {}", err))?, + ); Ok(prefix) } @@ -829,10 +841,7 @@ impl From<&RingMLSAG> for JsonRingMLSAG { pub struct JsonSignatureRctBulletproofs { pub ring_signatures: Vec, pub pseudo_output_commitments: Vec, - pub range_proof_bytes: String, - pub range_proofs: Vec, - pub pseudo_output_token_ids: Vec, - pub output_token_ids: Vec, + pub range_proofs: String, } impl From<&SignatureRctBulletproofs> for JsonSignatureRctBulletproofs { @@ -848,10 +857,7 @@ impl From<&SignatureRctBulletproofs> for JsonSignatureRctBulletproofs { .iter() .map(|x| hex::encode(x.get_data())) .collect(), - range_proof_bytes: hex::encode(src.get_range_proof_bytes()), - range_proofs: src.get_range_proofs().iter().map(hex::encode).collect(), - pseudo_output_token_ids: src.pseudo_output_token_ids.iter().map(Into::into).collect(), - output_token_ids: src.output_token_ids.iter().map(Into::into).collect(), + range_proofs: hex::encode(src.get_range_proofs()), } } } @@ -904,31 +910,9 @@ impl TryFrom<&JsonSignatureRctBulletproofs> for SignatureRctBulletproofs { let mut signature = SignatureRctBulletproofs::new(); signature.set_ring_signatures(RepeatedField::from_vec(ring_sigs)); signature.set_pseudo_output_commitments(RepeatedField::from_vec(commitments)); - let range_proof_bytes = hex::decode(&src.range_proof_bytes).map_err(|err| { - format!( - "Could not decode top-level range proof from hex '{}': {}", - &src.range_proof_bytes, err - ) - })?; - signature.set_range_proof_bytes(range_proof_bytes); - let range_proofs = src - .range_proofs - .iter() - .map(|hex_str| { - hex::decode(hex_str).map_err(|err| { - format!( - "Could not decode range proof from hex '{}': {}", - hex_str, err - ) - }) - }) - .collect::>()?; - signature.set_range_proofs(range_proofs); - - signature.set_pseudo_output_token_ids( - src.pseudo_output_token_ids.iter().map(Into::into).collect(), - ); - signature.set_output_token_ids(src.output_token_ids.iter().map(Into::into).collect()); + let proofs_bytes = hex::decode(&src.range_proofs) + .map_err(|err| format!("Could not decode from hex: {}", err))?; + signature.set_range_proofs(proofs_bytes); Ok(signature) } @@ -1195,30 +1179,30 @@ impl From<&mc_mobilecoind_api::GetTxStatusAsReceiverResponse> for JsonStatusResp #[derive(Serialize, Default, Debug)] pub struct JsonLedgerInfoResponse { - pub block_count: JsonU64, - pub txo_count: JsonU64, + pub block_count: String, + pub txo_count: String, } impl From<&mc_mobilecoind_api::GetLedgerInfoResponse> for JsonLedgerInfoResponse { fn from(src: &mc_mobilecoind_api::GetLedgerInfoResponse) -> Self { Self { - block_count: JsonU64(src.block_count), - txo_count: JsonU64(src.txo_count), + block_count: src.block_count.to_string(), + txo_count: src.txo_count.to_string(), } } } #[derive(Serialize, Default, Debug)] pub struct JsonBlockInfoResponse { - pub key_image_count: JsonU64, - pub txo_count: JsonU64, + pub key_image_count: String, + pub txo_count: String, } impl From<&mc_mobilecoind_api::GetBlockInfoResponse> for JsonBlockInfoResponse { fn from(src: &mc_mobilecoind_api::GetBlockInfoResponse) -> Self { Self { - key_image_count: JsonU64(src.key_image_count), - txo_count: JsonU64(src.txo_count), + key_image_count: src.key_image_count.to_string(), + txo_count: src.txo_count.to_string(), } } } @@ -1228,8 +1212,8 @@ pub struct JsonBlockDetailsResponse { pub block_id: String, pub version: u32, pub parent_id: String, - pub index: JsonU64, - pub cumulative_txo_count: JsonU64, + pub index: String, + pub cumulative_txo_count: String, pub contents_hash: String, pub key_images: Vec, pub txos: Vec, @@ -1243,8 +1227,8 @@ impl From<&mc_mobilecoind_api::GetBlockResponse> for JsonBlockDetailsResponse { block_id: hex::encode(&block.get_id().get_data()), version: block.get_version(), parent_id: hex::encode(&block.get_parent_id().get_data()), - index: JsonU64(block.get_index()), - cumulative_txo_count: JsonU64(block.get_cumulative_txo_count()), + index: block.get_index().to_string(), + cumulative_txo_count: block.get_cumulative_txo_count().to_string(), contents_hash: hex::encode(&block.get_contents_hash().get_data()), key_images: src .get_key_images() @@ -1262,7 +1246,7 @@ pub struct JsonProcessedTxOut { pub subaddress_index: u64, pub public_key: String, pub key_image: String, - pub value: JsonU64, + pub value: String, // Needs to be String since Javascript ints are not 64 bit. pub direction: String, } @@ -1279,7 +1263,7 @@ impl From<&mc_mobilecoind_api::ProcessedTxOut> for JsonProcessedTxOut { subaddress_index: src.subaddress_index, public_key: hex::encode(&src.get_public_key().get_data()), key_image: hex::encode(&src.get_key_image().get_data()), - value: JsonU64(src.value), + value: src.value.to_string(), direction: direction_str.to_owned(), } } @@ -1462,25 +1446,10 @@ mod test { .pseudo_output_commitments, proto2.get_tx().get_signature().pseudo_output_commitments ); - assert_eq!( - proto_proposal.get_tx().get_signature().range_proof_bytes, - proto2.get_tx().get_signature().range_proof_bytes, - ); assert_eq!( proto_proposal.get_tx().get_signature().range_proofs, proto2.get_tx().get_signature().range_proofs ); - assert_eq!( - proto_proposal - .get_tx() - .get_signature() - .pseudo_output_token_ids, - proto2.get_tx().get_signature().pseudo_output_token_ids, - ); - assert_eq!( - proto_proposal.get_tx().get_signature().output_token_ids, - proto2.get_tx().get_signature().output_token_ids, - ); assert_eq!(proto_proposal.get_tx().signature, proto2.get_tx().signature); assert_eq!(proto_proposal.tx, proto2.tx); diff --git a/mobilecoind/src/payments.rs b/mobilecoind/src/payments.rs index c3b6f0ed62..41eacd0bfc 100644 --- a/mobilecoind/src/payments.rs +++ b/mobilecoind/src/payments.rs @@ -21,7 +21,7 @@ use mc_transaction_core::{ onetime_keys::recover_onetime_private_key, ring_signature::KeyImage, tx::{Tx, TxOut, TxOutConfirmationNumber, TxOutMembershipProof}, - Amount, BlockIndex, BlockVersion, TokenId, + BlockIndex, BlockVersion, TokenId, }; use mc_transaction_std::{ ChangeDestination, EmptyMemoBuilder, InputCredentials, MemoBuilder, TransactionBuilder, @@ -876,17 +876,19 @@ impl = - opt_memo_builder.unwrap_or_else(|| Box::new(EmptyMemoBuilder::default())); - let fee_amount = Amount::new(fee, token_id); - let mut tx_builder = - TransactionBuilder::new_with_box(block_version, fee_amount, fog_resolver, memo_builder) - .map_err(|err| { - Error::TxBuild(format!("Error creating transaction builder: {}", err)) - })?; + // Create tx_builder. + let mut tx_builder = TransactionBuilder::new( + block_version, + token_id, + fog_resolver, + EmptyMemoBuilder::default(), + ); + + tx_builder + .set_fee(fee) + .map_err(|err| Error::TxBuild(format!("Error setting fee: {}", err)))?; // Unzip each vec of tuples into a tuple of vecs. let mut rings_and_proofs: Vec<(Vec, Vec)> = rings @@ -970,14 +972,8 @@ impl 0 { - // TODO: If you want to support mixed transactions, use outlay-specific token id - // here - let change_amount = Amount { - value: change, - token_id, - }; - let change_dest = ChangeDestination::from_subaddress_index(from_account_key, change_subaddress); tx_builder - .add_change_output(change_amount, &change_dest, rng) + .add_change_output(change, &change_dest, rng) .map_err(|err| Error::TxBuild(format!("failed adding output (change): {}", err)))?; } diff --git a/mobilecoind/src/service.rs b/mobilecoind/src/service.rs index 6ff79a01ac..0deb9a6de2 100644 --- a/mobilecoind/src/service.rs +++ b/mobilecoind/src/service.rs @@ -3040,13 +3040,12 @@ mod test { let monitor_id = mobilecoind_db.add_monitor(&data).unwrap(); let mut transaction_builder = TransactionBuilder::new( BLOCK_VERSION, - Amount::new(Mob::MINIMUM_FEE, Mob::ID), + Mob::ID, MockFogResolver::default(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); let (tx_out, tx_confirmation) = transaction_builder - .add_output(Amount::new(10, Mob::ID), &receiver.subaddress(0), &mut rng) + .add_output(10, &receiver.subaddress(0), &mut rng) .unwrap(); add_txos_to_ledger_db(BLOCK_VERSION, &mut ledger_db, &[tx_out.clone()], &mut rng); @@ -5514,14 +5513,13 @@ mod test { let mut transaction_builder = TransactionBuilder::new( BLOCK_VERSION, - Amount::new(Mob::MINIMUM_FEE, Mob::ID), + Mob::ID, MockFogResolver::default(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); let (tx_out, _tx_confirmation) = transaction_builder .add_output( - Amount::new(10, Mob::ID), + 10, &account_key.subaddress(DEFAULT_SUBADDRESS_INDEX), &mut rng, ) @@ -5627,14 +5625,13 @@ mod test { let mut transaction_builder = TransactionBuilder::new( BLOCK_VERSION, - Amount::new(Mob::MINIMUM_FEE, Mob::ID), + Mob::ID, MockFogResolver::default(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); let (tx_out, _tx_confirmation) = transaction_builder .add_output( - Amount::new(10, Mob::ID), + 10, &account_key.subaddress(DEFAULT_SUBADDRESS_INDEX), &mut rng, ) diff --git a/transaction/core/Cargo.toml b/transaction/core/Cargo.toml index 53f73a462a..a4c0e84847 100644 --- a/transaction/core/Cargo.toml +++ b/transaction/core/Cargo.toml @@ -52,7 +52,6 @@ features = ["default-code-coverage"] curve25519-dalek = { version = "4.0.0-pre.2", default-features = false, features = ["nightly", "u64_backend"] } [dev-dependencies] -assert_matches = "1.5" rand = "0.8" rand_hc = "0.3" tempdir = "0.3" diff --git a/transaction/core/src/amount/mod.rs b/transaction/core/src/amount/mod.rs index acf4a6bc3c..9e4aa453ef 100644 --- a/transaction/core/src/amount/mod.rs +++ b/transaction/core/src/amount/mod.rs @@ -41,13 +41,6 @@ pub struct Amount { pub token_id: TokenId, } -impl Amount { - /// Create a new amount - pub fn new(value: u64, token_id: TokenId) -> Self { - Self { value, token_id } - } -} - /// A commitment to an amount of MobileCoin or a related token, as it appears on /// the blockchain. This is a "blinded" commitment, and only the sender and /// receiver know the value and token id. diff --git a/transaction/core/src/blockchain/block_version.rs b/transaction/core/src/blockchain/block_version.rs index 5be2331ed3..9ef0eb81e8 100644 --- a/transaction/core/src/blockchain/block_version.rs +++ b/transaction/core/src/blockchain/block_version.rs @@ -57,7 +57,7 @@ impl FromStr for BlockVersion { impl BlockVersion { /// The maximum value of block_version that this build of /// mc-transaction-core has support for - pub const MAX: Self = Self(3); + pub const MAX: Self = Self(2); /// Refers to the block version number at network launch. pub const ZERO: Self = Self(0); @@ -68,9 +68,6 @@ impl BlockVersion { /// Constant for block version two pub const TWO: Self = Self(2); - /// Constant for block version three - pub const THREE: Self = Self(3); - /// Iterator over block versions from one up to max, inclusive. For use in /// tests. pub fn iterator() -> BlockVersionIterator { @@ -105,12 +102,6 @@ impl BlockVersion { pub fn mlsags_sign_extended_message_digest(&self) -> bool { self.0 >= 2 } - - /// Mixed transactions [MCIP #31](https://github.com/mobilecoinfoundation/mcips/pull/31) - /// are introduced in block version 3 - pub fn mixed_transactions_are_supported(&self) -> bool { - self.0 >= 3 - } } impl Deref for BlockVersion { diff --git a/transaction/core/src/range_proofs/error.rs b/transaction/core/src/range_proofs/error.rs index 5429795dba..0f4789ea3d 100644 --- a/transaction/core/src/range_proofs/error.rs +++ b/transaction/core/src/range_proofs/error.rs @@ -6,7 +6,7 @@ use bulletproofs_og::ProofError; use displaydoc::Display; /// An error which can occur in connection to a range proof -#[derive(Debug, Clone, Eq, Display, PartialEq)] +#[derive(Debug, Display, PartialEq)] pub enum Error { /// ProofError: `{0:?}` ProofError(ProofError), diff --git a/transaction/core/src/ring_signature/error.rs b/transaction/core/src/ring_signature/error.rs index 3d86a69763..e65d9365b6 100644 --- a/transaction/core/src/ring_signature/error.rs +++ b/transaction/core/src/ring_signature/error.rs @@ -2,13 +2,13 @@ //! Errors which can occur in connection to ring signatures -use crate::{range_proofs::error::Error as RangeProofError, TokenId}; -use alloc::string::{String, ToString}; use displaydoc::Display; use serde::{Deserialize, Serialize}; /// An error which can occur in connection to a ring signature -#[derive(Clone, Debug, Deserialize, Display, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] +#[derive( + Clone, Copy, Debug, Display, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize, +)] pub enum Error { /// Incorrect length for array copy, provided `{0}`, required `{1}`. LengthMismatch(usize, usize), @@ -49,41 +49,11 @@ pub enum Error { */ ValueNotConserved, - /// Invalid RangeProof: {0} - RangeProof(String), - - /// RangeProof Deserialization failed - RangeProofDeserialization, + /// Invalid RangeProof + RangeProofError, /// TokenId is not allowed at this block version TokenIdNotAllowed, - - /// Missing pseudo-output token ids - MissingPseudoOutputTokenIds, - - /// Missing output token ids - MissingOutputTokenIds, - - /// Pseudo-output token ids not allowed at this block version - PseudoOutputTokenIdsNotAllowed, - - /// Output token ids not allowed at this block version - OutputTokenIdsNotAllowed, - - /// Mixed token ids in transactions not allowed at this block version - MixedTransactionsNotAllowed, - - /// Too many range proofs for the block version - TooManyRangeProofs, - - /// Unexpected range proof for the block version - UnexpectedRangeProof, - - /// Missing expected range proofs (expected: {0}, found: {1}) - MissingRangeProofs(usize, usize), - - /// No commitments were found for {0}, this is a logic error - NoCommitmentsForTokenId(TokenId), } impl From for Error { @@ -91,9 +61,3 @@ impl From for Error { Error::LengthMismatch(src.found, src.expected) } } - -impl From for Error { - fn from(src: RangeProofError) -> Self { - Error::RangeProof(src.to_string()) - } -} diff --git a/transaction/core/src/ring_signature/generator_cache.rs b/transaction/core/src/ring_signature/generator_cache.rs deleted file mode 100644 index e7162a25a0..0000000000 --- a/transaction/core/src/ring_signature/generator_cache.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2018-2022 The MobileCoin Foundation - -//! A simple generator cache - -use super::{generators, PedersenGens}; -use crate::TokenId; -use alloc::collections::BTreeMap; - -/// GeneratorCache is a simple object which caches computations of -/// generator: TokenId -> PedersenGens -/// -/// This is intended just to be used in the scope of constructing or validating -/// a single transaction, and we therefore don't require it to be constant-time. -#[derive(Default, Clone)] -pub struct GeneratorCache { - cache: BTreeMap, -} - -impl GeneratorCache { - /// Get (and if necessary, cache) the Pedersen Generators corresponding to - /// a particular token id. - pub fn get(&mut self, token_id: TokenId) -> &PedersenGens { - self.cache - .entry(token_id) - .or_insert_with(|| generators(*token_id)) - } -} diff --git a/transaction/core/src/ring_signature/mod.rs b/transaction/core/src/ring_signature/mod.rs index 2c2bf7d4b6..d7bdb68998 100644 --- a/transaction/core/src/ring_signature/mod.rs +++ b/transaction/core/src/ring_signature/mod.rs @@ -14,14 +14,12 @@ use mc_crypto_keys::RistrettoPublic; mod curve_scalar; mod error; -mod generator_cache; mod key_image; mod mlsag; mod rct_bulletproofs; pub use curve_scalar::*; pub use error::Error; -pub use generator_cache::*; pub use key_image::*; pub use mlsag::*; pub use rct_bulletproofs::*; diff --git a/transaction/core/src/ring_signature/rct_bulletproofs.rs b/transaction/core/src/ring_signature/rct_bulletproofs.rs index 9c844bd743..836799f3c5 100644 --- a/transaction/core/src/ring_signature/rct_bulletproofs.rs +++ b/transaction/core/src/ring_signature/rct_bulletproofs.rs @@ -8,13 +8,10 @@ extern crate alloc; -use alloc::{collections::BTreeSet, vec, vec::Vec}; +use alloc::vec::Vec; use bulletproofs_og::RangeProof; use core::convert::TryFrom; -use curve25519_dalek::{ - ristretto::{CompressedRistretto, RistrettoPoint}, - traits::Identity, -}; +use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint}; use mc_common::HashSet; use mc_crypto_digestible::{DigestTranscript, Digestible, MerlinTranscript}; use mc_crypto_keys::{CompressedRistrettoPublic, RistrettoPrivate}; @@ -26,30 +23,10 @@ use crate::{ constants::FEE_BLINDING, domain_separators::EXTENDED_MESSAGE_DOMAIN_TAG, range_proofs::{check_range_proofs, generate_range_proofs}, - ring_signature::{mlsag::RingMLSAG, Error, GeneratorCache, KeyImage, Scalar}, - Amount, BlockVersion, Commitment, CompressedCommitment, + ring_signature::{generators, mlsag::RingMLSAG, Error, KeyImage, Scalar}, + BlockVersion, Commitment, CompressedCommitment, }; -/// The secrets corresponding to an input needed to create a signature -#[derive(Clone, Debug)] -pub struct InputSecret { - /// The one-time private key for the output we are trying to spend - pub onetime_private_key: RistrettoPrivate, - /// The amount of the output - pub amount: Amount, - /// The blinding factor of the output we are trying to spend - pub blinding: Scalar, -} - -/// The secrets corresponding to an output needed to create a signature -#[derive(Clone, Debug)] -pub struct OutputSecret { - /// The amount of the output we are creating - pub amount: Amount, - /// The blinding factor of the output we are creating - pub blinding: Scalar, -} - /// An RCT_TYPE_BULLETPROOFS_2 signature #[derive(Clone, Digestible, Eq, PartialEq, Serialize, Deserialize, Message)] pub struct SignatureRctBulletproofs { @@ -65,29 +42,9 @@ pub struct SignatureRctBulletproofs { /// This contains range_proof.to_bytes(). It is stored this way so that this /// struct may derive Default, which is a requirement for serializing /// with Prost. - /// - /// Note: This is EMPTY if mixed transactions are enabled #[prost(bytes, tag = "3")] #[digestible(never_omit)] pub range_proof_bytes: Vec, - - /// A range proof, one for each token id that is used in the transaction. - /// - /// The range proofs correspond to the sorted order of token ids used. - /// - /// Note: This is EMPTY if mixed transactions is not enabled - #[prost(bytes, repeated, tag = "4")] - pub range_proofs: Vec>, - - /// Token id for each pseudo_output. This must have the same length as - /// `pseudo_output_commitments`, after mixed transactions feature. - #[prost(fixed64, repeated, tag = "5")] - pub pseudo_output_token_ids: Vec, - - /// Token id for each output. This must have the same length as - /// `prefix.outputs`, after mixed transactions feature - #[prost(fixed64, repeated, tag = "6")] - pub output_token_ids: Vec, } impl SignatureRctBulletproofs { @@ -110,9 +67,10 @@ impl SignatureRctBulletproofs { message: &[u8; 32], rings: &[Vec<(CompressedRistrettoPublic, CompressedCommitment)>], real_input_indices: &[usize], - input_secrets: &[InputSecret], - output_secrets: &[OutputSecret], - fee: Amount, + input_secrets: &[(RistrettoPrivate, u64, Scalar)], + output_values_and_blindings: &[(u64, Scalar)], + fee: u64, + token_id: u64, rng: &mut CSPRNG, ) -> Result { sign_with_balance_check( @@ -121,8 +79,9 @@ impl SignatureRctBulletproofs { rings, real_input_indices, input_secrets, - output_secrets, + output_values_and_blindings, fee, + token_id, true, rng, ) @@ -137,8 +96,7 @@ impl SignatureRctBulletproofs { /// commitments. /// * `output_commitments` - Output amount commitments. /// * `fee` - Value of the implicit fee output. - /// * `fee_token_id` - This determines the pedersen generator for fee - /// commitment + /// * `token id` - This determines the pedersen generator for commitments /// * `rng` - randomness pub fn verify( &self, @@ -146,10 +104,11 @@ impl SignatureRctBulletproofs { message: &[u8; 32], rings: &[Vec<(CompressedRistrettoPublic, CompressedCommitment)>], output_commitments: &[CompressedCommitment], - fee: Amount, + fee: u64, + token_id: u64, rng: &mut CSPRNG, ) -> Result<(), Error> { - if !block_version.masked_token_id_feature_is_supported() && fee.token_id != 0 { + if !block_version.masked_token_id_feature_is_supported() && token_id != 0 { return Err(Error::TokenIdNotAllowed); } @@ -169,23 +128,6 @@ impl SignatureRctBulletproofs { )); } - // pseudo output token ids must be provided if mixed transactions is enabled - if block_version.mixed_transactions_are_supported() { - if self.pseudo_output_commitments.len() != self.pseudo_output_token_ids.len() { - return Err(Error::MissingPseudoOutputTokenIds); - } - if output_commitments.len() != self.output_token_ids.len() { - return Err(Error::MissingOutputTokenIds); - } - } else { - if !self.pseudo_output_token_ids.is_empty() { - return Err(Error::PseudoOutputTokenIdsNotAllowed); - } - if !self.output_token_ids.is_empty() { - return Err(Error::OutputTokenIdsNotAllowed); - } - } - // Key images must be unique. { let key_images_are_unique = { @@ -213,32 +155,11 @@ impl SignatureRctBulletproofs { decompressed_pseudo_output_commitments.push(commitment); } - // Collect list of of unique token ids - let token_ids = { - let mut token_ids = BTreeSet::default(); - token_ids.insert(fee.token_id); - for token_id in &self.output_token_ids { - token_ids.insert(token_id.into()); - } - for token_id in &self.pseudo_output_token_ids { - token_ids.insert(token_id.into()); - } - token_ids - }; - - // Get a generator cache - let mut generator_cache = GeneratorCache::default(); + // Compute the pedersen generators for this token_id + let generator = generators(token_id); // pseudo_output_commitments and output commitments must be in [0, 2^64). - // this is done differently depending on if mixed transactions are supported - if !block_version.mixed_transactions_are_supported() { - // Before mixed transactions, we expect the range proof to appear in - // self.range_proof_bytes, not self.range_proofs - if !self.range_proofs.is_empty() { - return Err(Error::TooManyRangeProofs); - } - - let generator = generator_cache.get(fee.token_id); + { let commitments: Vec = self .pseudo_output_commitments .iter() @@ -247,111 +168,31 @@ impl SignatureRctBulletproofs { .collect(); let range_proof = RangeProof::from_bytes(&self.range_proof_bytes) - .map_err(|_e| Error::RangeProofDeserialization)?; - - check_range_proofs(&range_proof, &commitments, generator, rng)? - } else { - // When mixed transactions are supported, self.range_proofs should contain - // a range proof corresponding to each token id used in the transaction, in - // sorted order. range_proof_bytes should be empty - if !self.range_proof_bytes.is_empty() { - return Err(Error::UnexpectedRangeProof); - } - if token_ids.len() != self.range_proofs.len() { - return Err(Error::MissingRangeProofs( - token_ids.len(), - self.range_proofs.len(), - )); - } - - // For each used token id, and range proof, we have to pick out the matching - // outputs and pseudo outputs and verify the range proof. - for (token_id, range_proof) in token_ids.iter().zip(self.range_proofs.iter()) { - let generator = generator_cache.get(*token_id); - - let commitments: Vec = self - .pseudo_output_commitments - .iter() - .zip(self.pseudo_output_token_ids.iter()) - .chain(output_commitments.iter().zip(self.output_token_ids.iter())) - .filter_map(|(compressed_commitment, this_token_id)| { - if token_id == this_token_id { - Some(compressed_commitment.point) - } else { - None - } - }) - .collect(); + .map_err(|_e| Error::RangeProofError)?; - if commitments.is_empty() { - return Err(Error::NoCommitmentsForTokenId(*token_id)); - } - - let range_proof = RangeProof::from_bytes(range_proof) - .map_err(|_e| Error::RangeProofDeserialization)?; - - check_range_proofs(&range_proof, &commitments, generator, rng)? - } + check_range_proofs(&range_proof, &commitments, &generator, rng) + .map_err(|_e| Error::RangeProofError)?; } - // Transaction must be balanced (not create or destroy value). - // + // Compute sum of pseudo outputs + let sum_of_pseudo_output_commitments: RistrettoPoint = + decompressed_pseudo_output_commitments + .iter() + .map(|commitment| commitment.point) + .sum(); + // Output commitments - pseudo_outputs must be zero. - // - // Note: Why does this imply that the transaction is balanced? - // - // Each input is a Pedersen commitment v_i H_j + b_i G - // - // Here v_i is the i'th value, b_i is the i'th blinding factor, H_j is - // the base for j'th token id, G is the base for the blinding factors. - // - // If we expand and collect terms, then output commitments - pseudo output - // commitments can be thought of as a vector: - // - // sum_j w_j H_j + (sum of output blinding factors - sum of pseudo-output - // blinding factors) G - // - // where w_j = sum of output values - sum of pseudo output values in token id j. - // - // We are checking that this sum is equal to the zero point (zero vector). - // - // At this point, we appeal to the "orthogonality" of the bases G, H_1, H_2, ... - // We assume that it is infeasible for anyone to find a nontrivial linear - // relation between them. - // - // (See generators - // module doc for justification of this, but basically it's because we hash to - // curve.) - // - // Therefore, if someone submits a transaction where this sum is zero, and they - // cannot not know any "nontrivial" linear combination of G, H_1, ... - // then this must be a trivial linear combination. - // A trivial linear combination is one where all the coefficients are zero. - // This implies that w_j = 0 for all j, which implies that value was not created - // or destroyed in any token id, as required. - // - // So we don't need to do a separate loop here once per token id, we can just - // add everything together and check for zero. { - // Compute sum of pseudo outputs - let sum_of_pseudo_output_commitments: RistrettoPoint = - decompressed_pseudo_output_commitments - .iter() - .map(|commitment| commitment.point) - .sum(); let sum_of_output_commitments: RistrettoPoint = decompressed_output_commitments .iter() .map(|commitment| commitment.point) .sum(); // The implicit fee output. - let generator = generator_cache.get(fee.token_id); - let fee_commitment = generator.commit(Scalar::from(fee.value), *FEE_BLINDING); + let fee_commitment = generator.commit(Scalar::from(fee), *FEE_BLINDING); let difference = sum_of_output_commitments + fee_commitment - sum_of_pseudo_output_commitments; - // RistrettoPoint::identity() is the zero point of Ristretto group, this is the - // same as generator.commit(Zero, Zero) and is faster. - if difference != RistrettoPoint::identity() { + if difference != generator.commit(Scalar::zero(), Scalar::zero()) { return Err(Error::ValueNotConserved); } } @@ -362,7 +203,6 @@ impl SignatureRctBulletproofs { message, &self.pseudo_output_commitments, &self.range_proof_bytes, - &self.range_proofs, ); // Each MLSAG must be valid. @@ -392,11 +232,11 @@ impl SignatureRctBulletproofs { /// * `message` - The messages to be signed, e.g. Hash(TxPrefix). /// * `rings` - One or more rings of one-time addresses and amount commitments. /// * `real_input_indices` - The index of the real input in each ring. -/// * `input_secrets` - Input secret for each real input. -/// * `output_values_and_blindings` - Output secret for each output amount +/// * `input_secrets` - One-time private key, amount value, and amount blinding +/// for each real input. +/// * `output_values_and_blindings` - Value and blinding for each output amount /// commitment. /// * `fee` - Value of the implicit fee output. -/// * `fee_token_id` - Token id of the fee output. /// * `check_value_is_preserved` - If true, check that the value of inputs /// * `rng` - randomness fn sign_with_balance_check( @@ -404,33 +244,17 @@ fn sign_with_balance_check( message: &[u8; 32], rings: &[Vec<(CompressedRistrettoPublic, CompressedCommitment)>], real_input_indices: &[usize], - input_secrets: &[InputSecret], - output_secrets: &[OutputSecret], - fee: Amount, + input_secrets: &[(RistrettoPrivate, u64, Scalar)], + output_values_and_blindings: &[(u64, Scalar)], + fee: u64, + token_id: u64, check_value_is_preserved: bool, rng: &mut CSPRNG, ) -> Result { - if !block_version.masked_token_id_feature_is_supported() && fee.token_id != 0 { + if !block_version.masked_token_id_feature_is_supported() && token_id != 0 { return Err(Error::TokenIdNotAllowed); } - // input and output token ids must match fee_token_id if mixed transactions is - // not enabled - if !block_version.mixed_transactions_are_supported() { - if input_secrets - .iter() - .any(|sec| sec.amount.token_id != fee.token_id) - { - return Err(Error::MixedTransactionsNotAllowed); - } - if output_secrets - .iter() - .any(|sec| sec.amount.token_id != fee.token_id) - { - return Err(Error::MixedTransactionsNotAllowed); - } - } - if rings.is_empty() { return Err(Error::NoInputs); } @@ -471,7 +295,10 @@ fn sign_with_balance_check( // // Note: This implicit fee output is not the same as the accumulated fee output // produced by the enclave -- the blinding of that output is not zero. - let sum_of_output_blindings: Scalar = output_secrets.iter().map(|secret| secret.blinding).sum(); + let sum_of_output_blindings: Scalar = output_values_and_blindings + .iter() + .map(|(_, blinding)| blinding) + .sum(); let sum_of_pseudo_output_blindings: Scalar = pseudo_output_blindings.iter().sum(); let last_blinding: Scalar = sum_of_output_blindings - sum_of_pseudo_output_blindings; @@ -481,136 +308,59 @@ fn sign_with_balance_check( let pseudo_output_values_and_blindings: Vec<(u64, Scalar)> = input_secrets .iter() .zip(pseudo_output_blindings.iter()) - .map(|(secret, blinding)| (secret.amount.value, *blinding)) + .map(|((_, value, _), blinding)| (*value, *blinding)) .collect(); - // Create a pedersen generator cache - let mut generator_cache = GeneratorCache::default(); + // Compute the pedersen generators for this token_id + let generator = generators(token_id); - // Range proof is present when mixed transactions are not supported, the set of - // range proofs is present when they are. - let (range_proof, range_proofs) = if !block_version.mixed_transactions_are_supported() { + let (range_proof, commitments) = { // The implicit fee output is omitted from the range proof because it is known. - let generator = generator_cache.get(fee.token_id); let (values, blindings): (Vec<_>, Vec<_>) = pseudo_output_values_and_blindings .iter() - .cloned() - .chain( - output_secrets - .iter() - .map(|secret| (secret.amount.value, secret.blinding)), - ) + .chain(output_values_and_blindings.iter()) + .map(|(value, blinding)| (*value, *blinding)) .unzip(); - let (range_proof, _commitments) = - generate_range_proofs(&values, &blindings, generator, rng)?; - - (range_proof.to_bytes().to_vec(), vec![]) - } else { - let mut range_proofs = Vec::default(); - - // Collect list of of unique token ids - let token_ids = { - let mut token_ids = BTreeSet::default(); - token_ids.insert(fee.token_id); - for secret in input_secrets { - token_ids.insert(secret.amount.token_id); - } - for secret in output_secrets { - token_ids.insert(secret.amount.token_id); - } - token_ids - }; - - for token_id in token_ids { - let generator = generator_cache.get(token_id); - - // The input blinding is not the same as corresponding pseudo-output blinding - let (values, blindings): (Vec<_>, Vec<_>) = input_secrets - .iter() - .zip(pseudo_output_blindings.iter()) - .filter_map(|(secret, blinding)| { - if secret.amount.token_id == token_id { - Some((secret.amount.value, *blinding)) - } else { - None - } - }) - .chain(output_secrets.iter().filter_map(|secret| { - if secret.amount.token_id == token_id { - Some((secret.amount.value, secret.blinding)) - } else { - None - } - })) - .unzip(); - - if values.is_empty() { - return Err(Error::NoCommitmentsForTokenId(token_id)); - } - - let (range_proof, _commitments) = - generate_range_proofs(&values, &blindings, generator, rng)?; - - range_proofs.push(range_proof.to_bytes()); - } - - (vec![], range_proofs) + generate_range_proofs(&values, &blindings, &generator, rng) + .map_err(|_e| Error::RangeProofError)? }; - // The actual pseudo output commitments use the blindings from - // `pseudo_output_blinding` and not the original true input. - let pseudo_output_commitments: Vec = input_secrets - .iter() - .zip(pseudo_output_blindings.iter()) - .map(|(secret, blinding)| { - generator_cache - .get(secret.amount.token_id) - .commit(Scalar::from(secret.amount.value), *blinding) - }) - .collect(); - if check_value_is_preserved { - let sum_of_output_commitments: RistrettoPoint = output_secrets + let sum_of_output_commitments: RistrettoPoint = output_values_and_blindings .iter() - .map(|secret| { - generator_cache - .get(secret.amount.token_id) - .commit(Scalar::from(secret.amount.value), secret.blinding) - }) + .map(|(value, blinding)| generator.commit(Scalar::from(*value), *blinding)) .sum(); - let sum_of_pseudo_output_commitments: RistrettoPoint = - pseudo_output_commitments.iter().sum(); + let sum_of_pseudo_output_commitments: RistrettoPoint = pseudo_output_values_and_blindings + .iter() + .map(|(value, blinding)| generator.commit(Scalar::from(*value), *blinding)) + .sum(); // The implicit fee output. - let generator = generator_cache.get(fee.token_id); - let fee_commitment = generator.commit(Scalar::from(fee.value), *FEE_BLINDING); + let fee_commitment = generator.commit(Scalar::from(fee), *FEE_BLINDING); let difference = sum_of_output_commitments + fee_commitment - sum_of_pseudo_output_commitments; - // RistrettoPoint::identity() is the zero point of Ristretto group, this is the - // same as generator.commit(Zero, Zero) and is faster. - if difference != RistrettoPoint::identity() { + if difference != generator.commit(Scalar::zero(), Scalar::zero()) { return Err(Error::ValueNotConserved); } } - // The actual pseudo output commitments use the blindings from - // `pseudo_output_blinding` and not the original true input. - let pseudo_output_commitments: Vec = pseudo_output_commitments - .into_iter() - .map(|point| CompressedCommitment::from(&point.compress())) + let pseudo_output_commitments: Vec = commitments + .iter() + .take(num_inputs) + .map(CompressedCommitment::from) .collect(); // Extend the message with the range proof and pseudo_output_commitments. // This ensures that they are signed by each RingMLSAG. + let range_proof_bytes = range_proof.to_bytes(); let extended_message_digest = compute_extended_message_either_version( block_version, message, &pseudo_output_commitments, - &range_proof, - &range_proofs, + &range_proof_bytes, ); // Prove that the signer is allowed to spend a public key in each ring, and that @@ -618,48 +368,25 @@ fn sign_with_balance_check( let mut ring_signatures: Vec = Vec::new(); for i in 0..num_inputs { let real_index = real_input_indices[i]; - let input_secret = &input_secrets[i]; - let generator = generator_cache.get(input_secret.amount.token_id); + let (onetime_private_key, value, blinding) = input_secrets[i]; let ring_signature = RingMLSAG::sign( &extended_message_digest, &rings[i], real_index, - &input_secret.onetime_private_key, - input_secret.amount.value, - &input_secret.blinding, + &onetime_private_key, + value, + &blinding, &pseudo_output_blindings[i], - generator, + &generator, rng, )?; ring_signatures.push(ring_signature); } - let mut pseudo_output_token_ids: Vec = input_secrets - .iter() - .map(|secret| *secret.amount.token_id) - .collect(); - let mut output_token_ids: Vec = output_secrets - .iter() - .map(|secret| *secret.amount.token_id) - .collect(); - - if !block_version.mixed_transactions_are_supported() { - pseudo_output_token_ids.clear(); - output_token_ids.clear(); - assert!(!range_proof.is_empty()); - assert!(range_proofs.is_empty()); - } else { - assert!(range_proof.is_empty()); - assert!(!range_proofs.is_empty()); - } - Ok(SignatureRctBulletproofs { ring_signatures, pseudo_output_commitments, - range_proof_bytes: range_proof, - range_proofs, - pseudo_output_token_ids, - output_token_ids, + range_proof_bytes, }) } @@ -669,17 +396,10 @@ fn compute_extended_message_either_version( message: &[u8], pseudo_output_commitments: &[CompressedCommitment], range_proof_bytes: &[u8], - range_proofs: &[Vec], ) -> Vec { if block_version.mlsags_sign_extended_message_digest() { // New-style extended message using merlin - digest_extended_message( - message, - pseudo_output_commitments, - range_proof_bytes, - range_proofs, - ) - .to_vec() + digest_extended_message(message, pseudo_output_commitments, range_proof_bytes).to_vec() } else { // Old-style extended message extend_message(message, pseudo_output_commitments, range_proof_bytes) @@ -691,13 +411,11 @@ fn digest_extended_message( message: &[u8], pseudo_output_commitments: &[CompressedCommitment], range_proof_bytes: &[u8], - range_proofs: &[Vec], ) -> [u8; 32] { let mut transcript = MerlinTranscript::new(EXTENDED_MESSAGE_DOMAIN_TAG.as_bytes()); message.append_to_transcript(b"message", &mut transcript); pseudo_output_commitments.append_to_transcript(b"pseudo_output_commitments", &mut transcript); - range_proof_bytes.append_to_transcript_allow_omit(b"range_proof_bytes", &mut transcript); - range_proofs.append_to_transcript_allow_omit(b"range_proofs", &mut transcript); + range_proof_bytes.append_to_transcript(b"range_proof_bytes", &mut transcript); let mut output = [0u8; 32]; transcript.extract_digest(&mut output); @@ -728,10 +446,9 @@ mod rct_bulletproofs_tests { use crate::{ range_proofs::generate_range_proofs, ring_signature::{generators, Error, KeyImage, PedersenGens}, - CompressedCommitment, TokenId, + CompressedCommitment, }; use alloc::vec::Vec; - use assert_matches::assert_matches; use core::convert::TryInto; use curve25519_dalek::scalar::Scalar; use mc_crypto_keys::{CompressedRistrettoPublic, RistrettoPrivate, RistrettoPublic}; @@ -754,21 +471,21 @@ mod rct_bulletproofs_tests { /// One-time private key, amount value, and amount blinding for each /// real input. - input_secrets: Vec, + input_secrets: Vec<(RistrettoPrivate, u64, Scalar)>, /// Value and blinding for each output amount commitment. - output_secrets: Vec, + output_values_and_blindings: Vec<(u64, Scalar)>, /// Block Version block_version: BlockVersion, /// Token id - fee_token_id: TokenId, + token_id: u64, } impl SignatureParams { fn generator(&self) -> PedersenGens { - generators(*self.fee_token_id) + generators(self.token_id) } fn random( @@ -776,50 +493,32 @@ mod rct_bulletproofs_tests { num_inputs: usize, num_mixins: usize, rng: &mut RNG, - ) -> Self { - Self::random_mixed(block_version, num_inputs, num_mixins, 1, rng) - } - - fn random_mixed( - block_version: BlockVersion, - num_inputs: usize, - num_mixins: usize, - num_token_ids: usize, - rng: &mut RNG, ) -> Self { let mut message = [0u8; 32]; rng.fill_bytes(&mut message); - if !block_version.mixed_transactions_are_supported() && num_token_ids != 1 { - panic!("more than one token id not supported at this block version"); - } - - let mut token_ids: Vec = (0..num_token_ids).map(|_| rng.next_u64()).collect(); - - if !block_version.masked_token_id_feature_is_supported() { - token_ids[0] = 0; - } - - // First token id is the fee - let fee_token_id = TokenId::from(token_ids[0]); + let token_id = if block_version.masked_token_id_feature_is_supported() { + rng.next_u64() + } else { + 0 + }; - let mut generator_cache = GeneratorCache::default(); + let generator = generators(token_id); let mut rings = Vec::new(); let mut real_input_indices = Vec::new(); let mut input_secrets = Vec::new(); - for i in 0..num_inputs { + for _i in 0..num_inputs { let mut ring: Vec<(CompressedRistrettoPublic, CompressedCommitment)> = Vec::new(); // Create random mixins. for _i in 0..num_mixins { - let generator = generator_cache.get(fee_token_id); let address = CompressedRistrettoPublic::from(RistrettoPublic::from_random(rng)); let commitment = { let value = rng.next_u64(); let blinding = Scalar::random(rng); - CompressedCommitment::new(value, blinding, generator) + CompressedCommitment::new(value, blinding, &generator) }; ring.push((address, commitment)); } @@ -830,33 +529,22 @@ mod rct_bulletproofs_tests { let value = rng.next_u64(); let blinding = Scalar::random(rng); - - let token_id = TokenId::from(token_ids[i % token_ids.len()]); - let generator = generator_cache.get(token_id); - - let commitment = CompressedCommitment::new(value, blinding, generator); + let commitment = CompressedCommitment::new(value, blinding, &generator); let real_index = rng.next_u64() as usize % (num_mixins + 1); ring.insert(real_index, (onetime_public_key, commitment)); rings.push(ring); real_input_indices.push(real_index); - input_secrets.push(InputSecret { - onetime_private_key, - amount: Amount::new(value, token_id), - blinding, - }); + input_secrets.push((onetime_private_key, value, blinding)); } // Create one output with the same value as each input. - let output_secrets: Vec<_> = input_secrets + let output_values_and_blindings: Vec<_> = input_secrets .iter() - .map(|secret| { + .map(|(_, value, _)| { let blinding = Scalar::random(rng); - OutputSecret { - amount: secret.amount, - blinding, - } + (*value, blinding) }) .collect(); @@ -865,21 +553,17 @@ mod rct_bulletproofs_tests { rings, real_input_indices, input_secrets, - output_secrets, + output_values_and_blindings, block_version, - fee_token_id, + token_id, } } fn get_output_commitments(&self) -> Vec { - self.output_secrets + self.output_values_and_blindings .iter() - .map(|secret| { - CompressedCommitment::new( - secret.amount.value, - secret.blinding, - &generators(*secret.amount.token_id), - ) + .map(|(value, blinding)| { + CompressedCommitment::new(*value, *blinding, &self.generator()) }) .collect() } @@ -895,8 +579,9 @@ mod rct_bulletproofs_tests { &self.rings, &self.real_input_indices, &self.input_secrets, - &self.output_secrets, - Amount::new(fee, self.fee_token_id), + &self.output_values_and_blindings, + fee, + self.token_id, rng, ) } @@ -912,8 +597,9 @@ mod rct_bulletproofs_tests { &self.rings, &self.real_input_indices, &self.input_secrets, - &self.output_secrets, - Amount::new(fee, self.fee_token_id), + &self.output_values_and_blindings, + fee, + self.token_id, false, rng, ) @@ -929,7 +615,7 @@ mod rct_bulletproofs_tests { num_inputs in 1..8usize, num_mixins in 1..17usize, seed in any::<[u8; 32]>(), - block_version in 1..=3u32, + block_version in 1..=2u32, ) { let block_version: BlockVersion = block_version.try_into().unwrap(); let mut rng: StdRng = SeedableRng::from_seed(seed); @@ -951,7 +637,7 @@ mod rct_bulletproofs_tests { num_inputs in 1..8usize, num_mixins in 1..17usize, seed in any::<[u8; 32]>(), - block_version in 1..=3u32, + block_version in 1..=2u32, ) { let block_version: BlockVersion = block_version.try_into().unwrap(); let mut rng: StdRng = SeedableRng::from_seed(seed); @@ -972,7 +658,7 @@ mod rct_bulletproofs_tests { num_inputs in 1..8usize, num_mixins in 1..17usize, seed in any::<[u8; 32]>(), - block_version in 1..=3u32, + block_version in 1..=2u32, ) { let block_version: BlockVersion = block_version.try_into().unwrap(); let mut rng: StdRng = SeedableRng::from_seed(seed); @@ -997,7 +683,7 @@ mod rct_bulletproofs_tests { num_inputs in 1..8usize, num_mixins in 1..17usize, seed in any::<[u8; 32]>(), - block_version in 1..=3u32, + block_version in 1..=2u32, ) { let block_version: BlockVersion = block_version.try_into().unwrap(); let mut rng: StdRng = SeedableRng::from_seed(seed); @@ -1010,33 +696,8 @@ mod rct_bulletproofs_tests { ¶ms.message, ¶ms.rings, ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), - &mut rng, - ); - result.unwrap(); - } - - #[test] - // `verify` should accept valid signatures with mixed token ids. - fn verify_accepts_valid_signatures_mixed_token_ids( - num_inputs in 1..8usize, - num_mixins in 1..17usize, - num_token_ids in 2..4usize, - seed in any::<[u8; 32]>(), - block_version in 3..=3u32, - ) { - let block_version: BlockVersion = block_version.try_into().unwrap(); - let mut rng: StdRng = SeedableRng::from_seed(seed); - let params = SignatureParams::random_mixed(block_version, num_inputs, num_mixins, num_token_ids, &mut rng); - let fee = 0; - let signature = params.sign(fee, &mut rng).unwrap(); - - let result = signature.verify( - block_version, - ¶ms.message, - ¶ms.rings, - ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), + fee, + params.token_id, &mut rng, ); result.unwrap(); @@ -1048,7 +709,7 @@ mod rct_bulletproofs_tests { num_inputs in 1..8usize, num_mixins in 1..17usize, seed in any::<[u8; 32]>(), - block_version in 1..=3u32, + block_version in 1..=2u32, ) { let block_version: BlockVersion = block_version.try_into().unwrap(); let mut rng: StdRng = SeedableRng::from_seed(seed); @@ -1066,7 +727,8 @@ mod rct_bulletproofs_tests { ¶ms.message, ¶ms.rings, ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), + fee, + params.token_id, &mut rng, ); @@ -1079,7 +741,7 @@ mod rct_bulletproofs_tests { num_inputs in 1..8usize, num_mixins in 1..17usize, seed in any::<[u8; 32]>(), - block_version in 1..=3u32, + block_version in 1..=2u32, ) { let block_version: BlockVersion = block_version.try_into().unwrap(); let mut rng: StdRng = SeedableRng::from_seed(seed); @@ -1088,7 +750,8 @@ mod rct_bulletproofs_tests { // Modify an output value { let index = rng.next_u64() as usize % (num_inputs); - params.output_secrets[index].amount.value = rng.next_u64(); + let (_value, blinding) = params.output_values_and_blindings[index]; + params.output_values_and_blindings[index] = (rng.next_u64(), blinding); } // Sign, without checking that value is preserved. @@ -1099,7 +762,8 @@ mod rct_bulletproofs_tests { ¶ms.message, ¶ms.rings, ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), + fee, + params.token_id, &mut rng, ); @@ -1139,52 +803,12 @@ mod rct_bulletproofs_tests { ¶ms.message, ¶ms.rings, ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), + fee, + params.token_id, &mut rng, ); - assert_matches!(result, Err(Error::RangeProof(_))); - } - - #[test] - // `verify` rejects a signature with invalid range proof, block version >=3. - fn test_verify_rejects_signature_with_invalid_range_proof_block_version3( - num_inputs in 1..8usize, - num_mixins in 1..17usize, - seed in any::<[u8; 32]>(), - block_version in 3..=3u32, - ) { - let block_version: BlockVersion = block_version.try_into().unwrap(); - let mut rng: StdRng = SeedableRng::from_seed(seed); - let params = SignatureParams::random(block_version, num_inputs, num_mixins, &mut rng); - let fee = 0; - let mut signature = params.sign(fee, &mut rng).unwrap(); - - // Modify the range proof - let wrong_range_proof = { - let values = [13; 6]; - let blindings: Vec = values - .iter() - .map(|_value| Scalar::random(&mut rng)) - .collect(); - let (range_proof, _commitments) = - generate_range_proofs(&values, &blindings, ¶ms.generator(), &mut rng).unwrap(); - range_proof - }; - - signature.range_proofs[0] = wrong_range_proof.to_bytes(); - - let result = signature.verify( - block_version, - ¶ms.message, - ¶ms.rings, - ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), - &mut rng, - ); - - - assert_matches!(result, Err(Error::RangeProof(_))); + assert_eq!(result, Err(Error::RangeProofError)); } #[test] @@ -1193,7 +817,7 @@ mod rct_bulletproofs_tests { num_inputs in 4..8usize, num_mixins in 1..17usize, seed in any::<[u8; 32]>(), - block_version in 1..=3u32, + block_version in 1..=2u32, ) { let block_version: BlockVersion = block_version.try_into().unwrap(); let mut rng: StdRng = SeedableRng::from_seed(seed); @@ -1202,8 +826,8 @@ mod rct_bulletproofs_tests { // Duplicate one of the rings. params.rings[2] = params.rings[3].clone(); - params.output_secrets[2] = params.output_secrets[3].clone(); - params.input_secrets[2] = params.input_secrets[3].clone(); + params.output_values_and_blindings[2] = params.output_values_and_blindings[3]; + params.input_secrets[2] = params.input_secrets[3]; params.real_input_indices[2] = params.real_input_indices[3]; let signature = params.sign(fee, &mut rng).unwrap(); @@ -1213,7 +837,8 @@ mod rct_bulletproofs_tests { ¶ms.message, ¶ms.rings, ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), + fee, + params.token_id, &mut rng, ); @@ -1226,7 +851,7 @@ mod rct_bulletproofs_tests { num_inputs in 4..8usize, num_mixins in 1..17usize, seed in any::<[u8; 32]>(), - block_version in 1..=3u32, + block_version in 1..=2u32, ) { let block_version: BlockVersion = block_version.try_into().unwrap(); let mut rng: StdRng = SeedableRng::from_seed(seed); @@ -1251,14 +876,13 @@ mod rct_bulletproofs_tests { num_inputs in 2..8usize, num_mixins in 1..17usize, seed in any::<[u8; 32]>(), - block_version in 1..=3u32, + block_version in 1..=2u32, ) { let block_version: BlockVersion = block_version.try_into().unwrap(); let mut rng: StdRng = SeedableRng::from_seed(seed); let mut params = SignatureParams::random(block_version, num_inputs, num_mixins, &mut rng); // Remove one of the outputs, and use its value as the fee. This conserves value. - let popped_secret = params.output_secrets.pop().unwrap(); - let fee = popped_secret.amount.value; + let (fee, _) = params.output_values_and_blindings.pop().unwrap(); let signature = params.sign(fee, &mut rng).unwrap(); @@ -1267,7 +891,8 @@ mod rct_bulletproofs_tests { ¶ms.message, ¶ms.rings, ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), + fee, + params.token_id, &mut rng, ); result.unwrap(); @@ -1279,7 +904,8 @@ mod rct_bulletproofs_tests { ¶ms.message, ¶ms.rings, ¶ms.get_output_commitments(), - Amount::new(wrong_fee, params.fee_token_id), + wrong_fee, + params.token_id, &mut rng, ) { Err(Error::ValueNotConserved) => {} // Expected @@ -1288,10 +914,11 @@ mod rct_bulletproofs_tests { } _ => panic!("Unexpected success") } + } #[test] - // block version one signatures should not validate at block version two + // block version two signatures should not validate at block version two fn validate_block_version_one_as_two_should_fail( num_inputs in 2..8usize, num_mixins in 1..17usize, @@ -1300,8 +927,7 @@ mod rct_bulletproofs_tests { let mut rng: StdRng = SeedableRng::from_seed(seed); let mut params = SignatureParams::random(BlockVersion::ONE, num_inputs, num_mixins, &mut rng); // Remove one of the outputs, and use its value as the fee. This conserves value. - let popped_secret = params.output_secrets.pop().unwrap(); - let fee = popped_secret.amount.value; + let (fee, _) = params.output_values_and_blindings.pop().unwrap(); let signature = params.sign(fee, &mut rng).unwrap(); @@ -1310,7 +936,8 @@ mod rct_bulletproofs_tests { ¶ms.message, ¶ms.rings, ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), + fee, + params.token_id, &mut rng, ); assert!(result.is_err()); @@ -1326,8 +953,7 @@ mod rct_bulletproofs_tests { let mut rng: StdRng = SeedableRng::from_seed(seed); let mut params = SignatureParams::random(BlockVersion::TWO, num_inputs, num_mixins, &mut rng); // Remove one of the outputs, and use its value as the fee. This conserves value. - let popped_secret = params.output_secrets.pop().unwrap(); - let fee = popped_secret.amount.value; + let (fee, _) = params.output_values_and_blindings.pop().unwrap(); let signature = params.sign(fee, &mut rng).unwrap(); @@ -1336,7 +962,8 @@ mod rct_bulletproofs_tests { ¶ms.message, ¶ms.rings, ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), + fee, + params.token_id, &mut rng, ); assert!(result.is_err()); @@ -1352,8 +979,7 @@ mod rct_bulletproofs_tests { let mut rng: StdRng = SeedableRng::from_seed(seed); let mut params = SignatureParams::random(BlockVersion::TWO, num_inputs, num_mixins, &mut rng); // Remove one of the outputs, and use its value as the fee. This conserves value. - let popped_secret = params.output_secrets.pop().unwrap(); - let fee = popped_secret.amount.value; + let (fee, _) = params.output_values_and_blindings.pop().unwrap(); let signature = params.sign(fee, &mut rng).unwrap(); @@ -1362,7 +988,8 @@ mod rct_bulletproofs_tests { ¶ms.message, ¶ms.rings, ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), + fee, + params.token_id, &mut rng, ).unwrap(); @@ -1372,11 +999,11 @@ mod rct_bulletproofs_tests { ¶ms.message, ¶ms.rings, ¶ms.get_output_commitments(), - Amount::new(fee, TokenId::from(*params.fee_token_id + 1)), + fee, + params.token_id + 1, &mut rng, ); - - assert_matches!(result, Err(Error::RangeProof(_))); + assert_eq!(result, Err(Error::RangeProofError)); } #[test] @@ -1389,91 +1016,12 @@ mod rct_bulletproofs_tests { let mut rng: StdRng = SeedableRng::from_seed(seed); let mut params = SignatureParams::random(BlockVersion::ONE, num_inputs, num_mixins, &mut rng); // Remove one of the outputs, and use its value as the fee. This conserves value. - let popped_secret = params.output_secrets.pop().unwrap(); - let fee = popped_secret.amount.value; + let (fee, _) = params.output_values_and_blindings.pop().unwrap(); - params.fee_token_id = 1.into(); + params.token_id = 1; assert_eq!(params.sign(fee, &mut rng), Err(Error::TokenIdNotAllowed)); } - #[test] - // signatures with mixed tokens should not work if the output token ids are tampered with - fn test_verify_signature_rejects_change_to_output_token_id( - num_inputs in 2..8usize, - num_mixins in 1..17usize, - num_token_ids in 2..4usize, - seed in any::<[u8; 32]>(), - block_version in 3..=3u32, - ) { - let block_version = BlockVersion::try_from(block_version).unwrap(); - let mut rng: StdRng = SeedableRng::from_seed(seed); - let params = SignatureParams::random_mixed(block_version, num_inputs, num_mixins,num_token_ids, &mut rng); - - let fee = 0; - let mut signature = params.sign(fee, &mut rng).unwrap(); - - signature.verify( - block_version, - ¶ms.message, - ¶ms.rings, - ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), - &mut rng, - ).unwrap(); - - signature.output_token_ids[0] = signature.output_token_ids[1]; - - let result = signature.verify( - block_version, - ¶ms.message, - ¶ms.rings, - ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), - &mut rng, - ); - - assert_matches!(result, Err(Error::RangeProof(_))); - } - - #[test] - // signatures with mixed tokens should not work if the pseudo-output token ids are tampered with - fn test_verify_signature_rejects_change_to_pseudo_output_token_id( - num_inputs in 2..8usize, - num_mixins in 1..17usize, - num_token_ids in 2..4usize, - seed in any::<[u8; 32]>(), - block_version in 3..=3u32, - ) { - let block_version = BlockVersion::try_from(block_version).unwrap(); - let mut rng: StdRng = SeedableRng::from_seed(seed); - let params = SignatureParams::random_mixed(block_version, num_inputs, num_mixins, num_token_ids, &mut rng); - - let fee = 0; - let mut signature = params.sign(fee, &mut rng).unwrap(); - - signature.verify( - block_version, - ¶ms.message, - ¶ms.rings, - ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), - &mut rng, - ).unwrap(); - - signature.pseudo_output_token_ids[0] = signature.pseudo_output_token_ids[1]; - - let result = signature.verify( - block_version, - ¶ms.message, - ¶ms.rings, - ¶ms.get_output_commitments(), - Amount::new(fee, params.fee_token_id), - &mut rng, - ); - - assert_matches!(result, Err(Error::RangeProof(_))); - } - } // end proptest } diff --git a/transaction/core/src/token.rs b/transaction/core/src/token.rs index 4a7fae50bb..7b41bf57d2 100644 --- a/transaction/core/src/token.rs +++ b/transaction/core/src/token.rs @@ -19,12 +19,6 @@ impl From for TokenId { } } -impl From<&u64> for TokenId { - fn from(src: &u64) -> Self { - Self(*src) - } -} - impl fmt::Display for TokenId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) diff --git a/transaction/core/src/tx.rs b/transaction/core/src/tx.rs index f039924a8e..60cfb64912 100644 --- a/transaction/core/src/tx.rs +++ b/transaction/core/src/tx.rs @@ -164,9 +164,9 @@ pub struct TxPrefix { #[prost(uint64, tag = "4")] pub tombstone_block: u64, - /// Token id for the fee output of this transaction + /// Token id for this transaction #[prost(fixed64, tag = "5")] - pub fee_token_id: u64, + pub token_id: u64, } impl TxPrefix { @@ -181,14 +181,15 @@ impl TxPrefix { pub fn new( inputs: Vec, outputs: Vec, - fee: Amount, + fee: u64, + token_id: u64, tombstone_block: u64, ) -> TxPrefix { TxPrefix { inputs, outputs, - fee: fee.value, - fee_token_id: *fee.token_id, + fee, + token_id, tombstone_block, } } @@ -648,7 +649,7 @@ mod tests { inputs: vec![tx_in], outputs: vec![tx_out], fee: Mob::MINIMUM_FEE, - fee_token_id: *Mob::ID, + token_id: *Mob::ID, tombstone_block: 23, }; @@ -711,7 +712,7 @@ mod tests { inputs: vec![tx_in], outputs: vec![tx_out], fee: Mob::MINIMUM_FEE, - fee_token_id: *Mob::ID, + token_id: *Mob::ID, tombstone_block: 23, }; diff --git a/transaction/core/src/tx_error.rs b/transaction/core/src/tx_error.rs index 29e5c0162c..e6bf28bed5 100644 --- a/transaction/core/src/tx_error.rs +++ b/transaction/core/src/tx_error.rs @@ -65,14 +65,6 @@ pub enum NewMemoError { OutputsAfterChange, /// Changing the fee after the change output is not supported FeeAfterChange, - /// Invalid recipient address - InvalidRecipient, - /// Multiple outputs are not supported - MultipleOutputs, - /// Missing output - MissingOutput, - /// Mixed Token Ids are not supported in these memos - MixedTokenIds, /// Other: {0} Other(String), } diff --git a/transaction/core/src/validation/validate.rs b/transaction/core/src/validation/validate.rs index 27b5586834..8d345c1a09 100644 --- a/transaction/core/src/validation/validate.rs +++ b/transaction/core/src/validation/validate.rs @@ -11,7 +11,7 @@ use crate::{ constants::*, membership_proofs::{derive_proof_at_index, is_membership_proof_valid}, tx::{Tx, TxOut, TxOutMembershipProof, TxPrefix}, - Amount, BlockVersion, CompressedCommitment, TokenId, + BlockVersion, CompressedCommitment, TokenId, }; use mc_common::HashSet; use mc_crypto_keys::CompressedRistrettoPublic; @@ -28,7 +28,7 @@ use rand_core::{CryptoRng, RngCore}; /// * `root_proofs` - Membership proofs for each input ring element contained in /// `tx`. /// * `minimum_fee` - The minimum fee for the token indicated by -/// tx.prefix.fee_token_id +/// tx.prefix.token_id /// * `csprng` - Cryptographically secure random number generator. pub fn validate( tx: &Tx, @@ -313,7 +313,8 @@ pub fn validate_signature( message, &rings, &output_commitments, - Amount::new(tx.prefix.fee, TokenId::from(tx.prefix.fee_token_id)), + tx.prefix.fee, + tx.prefix.token_id, rng, ) .map_err(TransactionValidationError::InvalidTransactionSignature) diff --git a/transaction/core/test-utils/src/lib.rs b/transaction/core/test-utils/src/lib.rs index 1193a6747d..f40f074e8f 100644 --- a/transaction/core/test-utils/src/lib.rs +++ b/transaction/core/test-utils/src/lib.rs @@ -142,7 +142,7 @@ pub fn create_transaction_with_amount_and_comparer< tx_out: &TxOut, sender: &AccountKey, recipient: &PublicAddress, - value: u64, + amount: u64, fee: u64, tombstone_block: BlockIndex, rng: &mut R, @@ -151,11 +151,10 @@ pub fn create_transaction_with_amount_and_comparer< let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(fee, sender_amount.token_id), + sender_amount.token_id, MockFogResolver::default(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); // The first transaction in the origin block should contain enough outputs to // use as mixins. @@ -194,11 +193,6 @@ pub fn create_transaction_with_amount_and_comparer< .unwrap(); transaction_builder.add_input(input_credentials); - let amount = Amount { - value, - token_id: sender_amount.token_id, - }; - // Output transaction_builder .add_output(amount, recipient, rng) @@ -207,6 +201,9 @@ pub fn create_transaction_with_amount_and_comparer< // Tombstone block transaction_builder.set_tombstone_block(tombstone_block); + // Fee + transaction_builder.set_fee(fee).unwrap(); + // Build and return the transaction transaction_builder.build_with_sorter::(rng).unwrap() } diff --git a/transaction/std/src/error.rs b/transaction/std/src/error.rs index b6a79175c7..112ab714ed 100644 --- a/transaction/std/src/error.rs +++ b/transaction/std/src/error.rs @@ -9,8 +9,8 @@ use mc_transaction_core::{ /// An error that can occur when using the TransactionBuilder #[derive(Debug, Display)] pub enum TxBuilderError { - /// Ring Signature construction failed: {0} - RingSignatureFailed(ring_signature::Error), + /// Ring Signature construction failed + RingSignatureFailed, /// Range proof construction failed RangeProofFailed, @@ -24,8 +24,8 @@ pub enum TxBuilderError { /// Bad Amount: {0} BadAmount(AmountError), - /// Mixed Transactions not allowed: Expected {0}, Found {1} - MixedTransactionsNotAllowed(TokenId, TokenId), + /// Input had wrong token id: Expected {0}, Found {1} + WrongTokenType(TokenId, TokenId), /// New Tx: {0} NewTx(NewTxError), @@ -89,8 +89,8 @@ impl From for TxBuilderError { } impl From for TxBuilderError { - fn from(src: Error) -> Self { - TxBuilderError::RingSignatureFailed(src) + fn from(_: Error) -> Self { + TxBuilderError::RingSignatureFailed } } diff --git a/transaction/std/src/memo_builder/mod.rs b/transaction/std/src/memo_builder/mod.rs index ce1e3e668e..6e2ce43132 100644 --- a/transaction/std/src/memo_builder/mod.rs +++ b/transaction/std/src/memo_builder/mod.rs @@ -7,7 +7,7 @@ use super::{memo, ChangeDestination}; use core::fmt::Debug; use mc_account_keys::PublicAddress; -use mc_transaction_core::{Amount, MemoContext, MemoPayload, NewMemoError}; +use mc_transaction_core::{MemoContext, MemoPayload, NewMemoError}; mod burn_redemption_memo_builder; mod rth_memo_builder; @@ -33,12 +33,12 @@ pub trait MemoBuilder: Debug { /// and gets a chance to report an error, if the fee is too large, or if it /// is being changed too late /// in the process, and memos that are already written would be invalid. - fn set_fee(&mut self, amount: Amount) -> Result<(), NewMemoError>; + fn set_fee(&mut self, value: u64) -> Result<(), NewMemoError>; /// Build a memo for a normal output (to another party). fn make_memo_for_output( &mut self, - amount: Amount, + value: u64, recipient: &PublicAddress, memo_context: MemoContext, ) -> Result; @@ -46,7 +46,7 @@ pub trait MemoBuilder: Debug { /// Build a memo for a change output (to ourselves). fn make_memo_for_change_output( &mut self, - amount: Amount, + value: u64, change_destination: &ChangeDestination, memo_context: MemoContext, ) -> Result; @@ -58,13 +58,13 @@ pub trait MemoBuilder: Debug { pub struct EmptyMemoBuilder; impl MemoBuilder for EmptyMemoBuilder { - fn set_fee(&mut self, _fee: Amount) -> Result<(), NewMemoError> { + fn set_fee(&mut self, _fee: u64) -> Result<(), NewMemoError> { Ok(()) } fn make_memo_for_output( &mut self, - _value: Amount, + _value: u64, _recipient: &PublicAddress, _memo_context: MemoContext, ) -> Result { @@ -73,7 +73,7 @@ impl MemoBuilder for EmptyMemoBuilder { fn make_memo_for_change_output( &mut self, - _value: Amount, + _value: u64, _change_destination: &ChangeDestination, _memo_context: MemoContext, ) -> Result { diff --git a/transaction/std/src/memo_builder/rth_memo_builder.rs b/transaction/std/src/memo_builder/rth_memo_builder.rs index 8b0798c341..df5a978bd7 100644 --- a/transaction/std/src/memo_builder/rth_memo_builder.rs +++ b/transaction/std/src/memo_builder/rth_memo_builder.rs @@ -14,9 +14,7 @@ use super::{ }; use crate::ChangeDestination; use mc_account_keys::{PublicAddress, ShortAddressHash}; -use mc_transaction_core::{ - tokens::Mob, Amount, MemoContext, MemoPayload, NewMemoError, Token, TokenId, -}; +use mc_transaction_core::{tokens::Mob, MemoContext, MemoPayload, NewMemoError, Token}; /// This memo builder attaches 0x0100 Authenticated Sender Memos to normal /// outputs, and 0x0200 Destination Memos to change outputs. @@ -66,12 +64,10 @@ pub struct RTHMemoBuilder { last_recipient: ShortAddressHash, // Tracks the total outlay so far total_outlay: u64, - // Tracks the total outlay token id - outlay_token_id: Option, // Tracks the number of recipients so far num_recipients: u8, // Tracks the fee - fee: Amount, + fee: u64, } impl Default for RTHMemoBuilder { @@ -83,9 +79,8 @@ impl Default for RTHMemoBuilder { wrote_destination_memo: false, last_recipient: Default::default(), total_outlay: 0, - outlay_token_id: None, num_recipients: 0, - fee: Amount::new(Mob::MINIMUM_FEE, Mob::ID), + fee: Mob::MINIMUM_FEE, } } } @@ -137,7 +132,7 @@ impl RTHMemoBuilder { impl MemoBuilder for RTHMemoBuilder { /// Set the fee - fn set_fee(&mut self, fee: Amount) -> Result<(), NewMemoError> { + fn set_fee(&mut self, fee: u64) -> Result<(), NewMemoError> { if self.wrote_destination_memo { return Err(NewMemoError::FeeAfterChange); } @@ -148,25 +143,16 @@ impl MemoBuilder for RTHMemoBuilder { /// Build a memo for a normal output (to another party). fn make_memo_for_output( &mut self, - amount: Amount, + value: u64, recipient: &PublicAddress, memo_context: MemoContext, ) -> Result { if self.wrote_destination_memo { return Err(NewMemoError::OutputsAfterChange); } - // Check if the outlay is mixing token ids - if let Some(prev_token_id) = self.outlay_token_id { - if prev_token_id != amount.token_id { - return Err(NewMemoError::MixedTokenIds); - } - } else { - // If this is the first outlay, then this is the token id for the whole outlay. - self.outlay_token_id = Some(amount.token_id); - } self.total_outlay = self .total_outlay - .checked_add(amount.value) + .checked_add(value) .ok_or(NewMemoError::LimitsExceeded("total_outlay"))?; self.num_recipients = self .num_recipients @@ -199,7 +185,7 @@ impl MemoBuilder for RTHMemoBuilder { /// Build a memo for a change output (to ourselves). fn make_memo_for_change_output( &mut self, - amount: Amount, + _value: u64, _change_destination: &ChangeDestination, _memo_context: MemoContext, ) -> Result { @@ -209,32 +195,11 @@ impl MemoBuilder for RTHMemoBuilder { if self.wrote_destination_memo { return Err(NewMemoError::MultipleChangeOutputs); } - // Check if the outlay is mixing token ids - if let Some(prev_token_id) = self.outlay_token_id { - if prev_token_id != amount.token_id { - return Err(NewMemoError::MixedTokenIds); - } - } else { - // If no outlays occurred yet, this should be the token id for the whole tx. - self.outlay_token_id = Some(amount.token_id); - } - - // If the fee is not the same token id as the outlay, then - // total_outlay.checked_add(self.fee.value) is wrong. - // We need to specify token-id aware RTH memos - if self.fee.token_id != amount.token_id { - return Err(NewMemoError::MixedTokenIds); - } - self.total_outlay = self .total_outlay - .checked_add(self.fee.value) + .checked_add(self.fee) .ok_or(NewMemoError::LimitsExceeded("total_outlay"))?; - match DestinationMemo::new( - self.last_recipient.clone(), - self.total_outlay, - self.fee.value, - ) { + match DestinationMemo::new(self.last_recipient.clone(), self.total_outlay, self.fee) { Ok(mut d_memo) => { self.wrote_destination_memo = true; d_memo.set_num_recipients(self.num_recipients); diff --git a/transaction/std/src/transaction_builder.rs b/transaction/std/src/transaction_builder.rs index 68cb805e22..e29b17bbd8 100644 --- a/transaction/std/src/transaction_builder.rs +++ b/transaction/std/src/transaction_builder.rs @@ -6,6 +6,7 @@ use crate::{ChangeDestination, InputCredentials, MemoBuilder, TxBuilderError}; use core::{cmp::min, fmt::Debug}; +use curve25519_dalek::scalar::Scalar; use mc_account_keys::PublicAddress; use mc_crypto_keys::{CompressedRistrettoPublic, RistrettoPrivate, RistrettoPublic}; use mc_fog_report_validation::FogPubkeyResolver; @@ -13,7 +14,7 @@ use mc_transaction_core::{ encrypted_fog_hint::EncryptedFogHint, fog_hint::FogHint, onetime_keys::create_shared_secret, - ring_signature::{InputSecret, OutputSecret, SignatureRctBulletproofs}, + ring_signature::SignatureRctBulletproofs, tokens::Mob, tx::{Tx, TxIn, TxOut, TxOutConfirmationNumber, TxPrefix}, Amount, BlockVersion, CompressedCommitment, MemoContext, MemoPayload, NewMemoError, Token, @@ -58,9 +59,9 @@ pub struct TransactionBuilder { /// expires, and can no longer be added to the blockchain tombstone_block: u64, /// The fee paid in connection to this transaction - /// If mixed transactions feature is off, then everything must be this token - /// id. - fee: Amount, + fee: u64, + /// The token id for this transaction + token_id: TokenId, /// The source of validated fog pubkeys used for this transaction fog_resolver: FPR, /// The limit on the tombstone block value imposed pubkey_expiry values in @@ -80,57 +81,49 @@ impl TransactionBuilder { /// Initializes a new TransactionBuilder. /// /// # Arguments - /// * `block_version` - The block version rules to use when building this - /// transaction - /// * `fee` - The fee (and token id) to use for this transaction. Note: The - /// fee token id cannot be changed later, and before mixed transactions - /// feature, every input and output must have this token id. /// * `fog_resolver` - Source of validated fog keys to use with this /// transaction /// * `memo_builder` - An object which creates memos for the TxOuts in this /// transaction pub fn new( block_version: BlockVersion, - fee: Amount, + token_id: TokenId, fog_resolver: FPR, memo_builder: MB, - ) -> Result { - TransactionBuilder::new_with_box(block_version, fee, fog_resolver, Box::new(memo_builder)) + ) -> Self { + TransactionBuilder::new_with_box( + block_version, + token_id, + fog_resolver, + Box::new(memo_builder), + ) } /// Initializes a new TransactionBuilder, using a Box /// instead of statically typed /// /// # Arguments - /// * `block_version` - The block version rules to use when building this - /// transaction - /// * `fee` - The fee (and token id) to use for this transaction. Note: The - /// fee token id cannot be changed later, and before mixed transactions - /// feature, every input and output must have the same token id as the - /// fee. /// * `fog_resolver` - Source of validated fog keys to use with this /// transaction /// * `memo_builder` - An object which creates memos for the TxOuts in this /// transaction pub fn new_with_box( block_version: BlockVersion, - fee: Amount, + token_id: TokenId, fog_resolver: FPR, - mut memo_builder: Box, - ) -> Result { - // make sure that the memo builder - // is initialized to the same fee as the transaction builder - memo_builder.set_fee(fee)?; - Ok(TransactionBuilder { + memo_builder: Box, + ) -> Self { + TransactionBuilder { block_version, input_credentials: Vec::new(), outputs_and_shared_secrets: Vec::new(), tombstone_block: u64::max_value(), - fee, + fee: Mob::MINIMUM_FEE, + token_id, fog_resolver, fog_tombstone_block_limit: u64::max_value(), memo_builder: Some(memo_builder), - }) + } } /// Add an Input to the transaction. @@ -149,12 +142,12 @@ impl TransactionBuilder { /// unused. /// /// # Arguments - /// * `amount` - The amount of this output + /// * `value` - The value of this output, in picoMOB. /// * `recipient` - The recipient's public address /// * `rng` - RNG used to generate blinding for commitment pub fn add_output( &mut self, - amount: Amount, + value: u64, recipient: &PublicAddress, rng: &mut RNG, ) -> Result<(TxOut, TxOutConfirmationNumber), TxBuilderError> { @@ -167,12 +160,12 @@ impl TransactionBuilder { .expect("memo builder is missing, this is a logic error"); let block_version = self.block_version; let result = self.add_output_with_fog_hint_address( - amount, + value, recipient, recipient, |memo_ctxt| { if block_version.e_memo_feature_is_supported() { - Some(mb.make_memo_for_output(amount, recipient, memo_ctxt)).transpose() + Some(mb.make_memo_for_output(value, recipient, memo_ctxt)).transpose() } else { Ok(None) } @@ -204,7 +197,7 @@ impl TransactionBuilder { /// unauthenticated. /// /// # Arguments - /// * `amount` - The amount of this change output. + /// * `value` - The value of this change output. /// * `change_destination` - An object including both a primary address and /// a change subaddress to use to create this change output. The primary /// address is used for the fog hint, the change subaddress owns the @@ -213,7 +206,7 @@ impl TransactionBuilder { /// * `rng` - RNG used to generate blinding for commitment pub fn add_change_output( &mut self, - amount: Amount, + value: u64, change_destination: &ChangeDestination, rng: &mut RNG, ) -> Result<(TxOut, TxOutConfirmationNumber), TxBuilderError> { @@ -226,12 +219,12 @@ impl TransactionBuilder { .expect("memo builder is missing, this is a logic error"); let block_version = self.block_version; let result = self.add_output_with_fog_hint_address( - amount, + value, &change_destination.change_subaddress, &change_destination.primary_address, |memo_ctxt| { if block_version.e_memo_feature_is_supported() { - Some(mb.make_memo_for_change_output(amount, change_destination, memo_ctxt)) + Some(mb.make_memo_for_change_output(value, change_destination, memo_ctxt)) .transpose() } else { Ok(None) @@ -257,30 +250,24 @@ impl TransactionBuilder { /// number of requests in half. /// /// # Arguments - /// * `amount` - The amount of this output + /// * `value` - The value of this output, in picoMOB. /// * `recipient` - The recipient's public address /// * `fog_hint_address` - The public address used to create the fog hint /// * `memo_fn` - The memo function to use (see TxOut::new_with_memo) /// * `rng` - RNG used to generate blinding for commitment fn add_output_with_fog_hint_address( &mut self, - amount: Amount, + value: u64, recipient: &PublicAddress, fog_hint_address: &PublicAddress, memo_fn: impl FnOnce(MemoContext) -> Result, NewMemoError>, rng: &mut RNG, ) -> Result<(TxOut, TxOutConfirmationNumber), TxBuilderError> { let (hint, pubkey_expiry) = create_fog_hint(fog_hint_address, &self.fog_resolver, rng)?; - - if !self.block_version.mixed_transactions_are_supported() - && self.fee.token_id != amount.token_id - { - return Err(TxBuilderError::MixedTransactionsNotAllowed( - self.fee.token_id, - amount.token_id, - )); - } - + let amount = Amount { + value, + token_id: self.token_id, + }; let (tx_out, shared_secret) = create_output_with_fog_hint(self.block_version, amount, recipient, hint, memo_fn, rng)?; @@ -315,28 +302,21 @@ impl TransactionBuilder { /// Sets the transaction fee. /// /// # Arguments - /// * `fee_value` - Transaction fee value, in smallest representable units. - pub fn set_fee(&mut self, fee_value: u64) -> Result<(), TxBuilderError> { + /// * `fee` - Transaction fee, in picoMOB. + pub fn set_fee(&mut self, fee: u64) -> Result<(), TxBuilderError> { // Set the fee in memo builder first, so that it can signal an error // before we set self.fee, and don't have to roll back. - let mut new_fee = self.fee; - new_fee.value = fee_value; self.memo_builder .as_mut() .expect("memo builder is missing, this is a logic error") - .set_fee(new_fee)?; - self.fee = new_fee; + .set_fee(fee)?; + self.fee = fee; Ok(()) } /// Gets the transaction fee. pub fn get_fee(&self) -> u64 { - self.fee.value - } - - /// Gets the fee token id - pub fn get_fee_token_id(&self) -> TokenId { - self.fee.token_id + self.fee } /// Consume the builder and return the transaction. @@ -379,9 +359,7 @@ impl TransactionBuilder { )); } - if !self.block_version.masked_token_id_feature_is_supported() - && self.fee.token_id != Mob::ID - { + if !self.block_version.masked_token_id_feature_is_supported() && self.token_id != Mob::ID { return Err(TxBuilderError::FeatureNotSupportedAtBlockVersion( *self.block_version, "nonzero token id", @@ -419,7 +397,7 @@ impl TransactionBuilder { self.outputs_and_shared_secrets .sort_by(|(a, _), (b, _)| O::cmp(&a.public_key, &b.public_key)); - let output_values_and_blindings: Vec = self + let output_values_and_blindings: Vec<(u64, Scalar)> = self .outputs_and_shared_secrets .iter() .map(|(tx_out, shared_secret)| { @@ -427,14 +405,20 @@ impl TransactionBuilder { let (amount, blinding) = masked_amount .get_value(shared_secret) .expect("TransactionBuilder created an invalid Amount"); - OutputSecret { amount, blinding } + (amount.value, blinding) }) .collect(); let (outputs, _shared_serets): (Vec, Vec<_>) = self.outputs_and_shared_secrets.into_iter().unzip(); - let tx_prefix = TxPrefix::new(inputs, outputs, self.fee, self.tombstone_block); + let tx_prefix = TxPrefix::new( + inputs, + outputs, + self.fee, + *self.token_id, + self.tombstone_block, + ); let mut rings: Vec> = Vec::new(); for input in &tx_prefix.inputs { @@ -453,27 +437,22 @@ impl TransactionBuilder { .collect(); // One-time private key, amount value, and amount blinding for each real input. - let mut input_secrets: Vec = Default::default(); + let mut input_secrets: Vec<(RistrettoPrivate, u64, Scalar)> = Vec::new(); for input_credential in &self.input_credentials { + let onetime_private_key = input_credential.onetime_private_key; let masked_amount = &input_credential.ring[input_credential.real_index].masked_amount; let shared_secret = create_shared_secret( &input_credential.real_output_public_key, &input_credential.view_private_key, ); let (amount, blinding) = masked_amount.get_value(&shared_secret)?; - if !self.block_version.mixed_transactions_are_supported() - && amount.token_id != self.fee.token_id - { - return Err(TxBuilderError::MixedTransactionsNotAllowed( - self.fee.token_id, + if amount.token_id != self.token_id { + return Err(TxBuilderError::WrongTokenType( + self.token_id, amount.token_id, )); } - input_secrets.push(InputSecret { - onetime_private_key: input_credential.onetime_private_key, - amount, - blinding, - }); + input_secrets.push((onetime_private_key, amount.value, blinding)); } let message = tx_prefix.hash().0; @@ -485,6 +464,7 @@ impl TransactionBuilder { &input_secrets, &output_values_and_blindings, self.fee, + *self.token_id, rng, )?; @@ -732,11 +712,10 @@ pub mod transaction_builder_tests { ) -> Result { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), + token_id, fog_resolver.clone(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); let input_value = 1000; let output_value = 10; @@ -758,11 +737,7 @@ pub mod transaction_builder_tests { // Outputs for _i in 0..num_outputs { transaction_builder - .add_output( - Amount::new(output_value, token_id), - &recipient.default_subaddress(), - rng, - ) + .add_output(output_value, &recipient.default_subaddress(), rng) .unwrap(); } @@ -804,18 +779,13 @@ pub mod transaction_builder_tests { let membership_proofs = input_credentials.membership_proofs.clone(); let key_image = KeyImage::from(&input_credentials.onetime_private_key); - let mut transaction_builder = TransactionBuilder::new( - block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), - fpr, - EmptyMemoBuilder::default(), - ) - .unwrap(); + let mut transaction_builder = + TransactionBuilder::new(block_version, token_id, fpr, EmptyMemoBuilder::default()); transaction_builder.add_input(input_credentials); let (_txout, confirmation) = transaction_builder .add_output( - Amount::new(value - Mob::MINIMUM_FEE, token_id), + value - Mob::MINIMUM_FEE, &recipient.default_subaddress(), &mut rng, ) @@ -888,16 +858,15 @@ pub mod transaction_builder_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), + token_id, fog_resolver, EmptyMemoBuilder::default(), - ) - .unwrap(); + ); transaction_builder.add_input(input_credentials); let (_txout, confirmation) = transaction_builder .add_output( - Amount::new(value - Mob::MINIMUM_FEE, token_id), + value - Mob::MINIMUM_FEE, &recipient.default_subaddress(), &mut rng, ) @@ -979,11 +948,10 @@ pub mod transaction_builder_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), + token_id, fog_resolver.clone(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); let input_credentials = get_input_credentials(block_version, amount, &sender, &fog_resolver, &mut rng); @@ -991,7 +959,7 @@ pub mod transaction_builder_tests { let (_txout, _confirmation) = transaction_builder .add_output_with_fog_hint_address( - Amount::new(value - Mob::MINIMUM_FEE, token_id), + value - Mob::MINIMUM_FEE, &recipient.default_subaddress(), &fog_hint_address, |_| Ok(Default::default()), @@ -1055,11 +1023,10 @@ pub mod transaction_builder_tests { { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), + token_id, fog_resolver.clone(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); transaction_builder.set_tombstone_block(2000); @@ -1068,11 +1035,7 @@ pub mod transaction_builder_tests { transaction_builder.add_input(input_credentials); let (_txout, _confirmation) = transaction_builder - .add_output( - Amount::new(value - Mob::MINIMUM_FEE, token_id), - &recipient_address, - &mut rng, - ) + .add_output(value - Mob::MINIMUM_FEE, &recipient_address, &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -1090,11 +1053,10 @@ pub mod transaction_builder_tests { { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), + token_id, fog_resolver.clone(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); transaction_builder.set_tombstone_block(500); @@ -1103,11 +1065,7 @@ pub mod transaction_builder_tests { transaction_builder.add_input(input_credentials); let (_txout, _confirmation) = transaction_builder - .add_output( - Amount::new(value - Mob::MINIMUM_FEE, token_id), - &recipient_address, - &mut rng, - ) + .add_output(value - Mob::MINIMUM_FEE, &recipient_address, &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -1154,11 +1112,10 @@ pub mod transaction_builder_tests { { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), + token_id, fog_resolver.clone(), EmptyMemoBuilder::default(), - ) - .unwrap(); + ); transaction_builder.set_tombstone_block(2000); @@ -1173,18 +1130,14 @@ pub mod transaction_builder_tests { let (_txout, _confirmation) = transaction_builder .add_output( - Amount::new(value - change_value - Mob::MINIMUM_FEE, token_id), + value - change_value - Mob::MINIMUM_FEE, &recipient_address, &mut rng, ) .unwrap(); transaction_builder - .add_change_output( - Amount::new(change_value, token_id), - &sender_change_dest, - &mut rng, - ) + .add_change_output(change_value, &sender_change_dest, &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -1335,11 +1288,10 @@ pub mod transaction_builder_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), + token_id, fog_resolver.clone(), memo_builder, - ) - .unwrap(); + ); transaction_builder.set_tombstone_block(2000); @@ -1354,18 +1306,14 @@ pub mod transaction_builder_tests { let (_txout, _confirmation) = transaction_builder .add_output( - Amount::new(value - change_value - Mob::MINIMUM_FEE, token_id), + value - change_value - Mob::MINIMUM_FEE, &recipient_address, &mut rng, ) .unwrap(); transaction_builder - .add_change_output( - Amount::new(change_value, token_id), - &sender_change_dest, - &mut rng, - ) + .add_change_output(change_value, &sender_change_dest, &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -1496,11 +1444,10 @@ pub mod transaction_builder_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), + token_id, fog_resolver.clone(), memo_builder, - ) - .unwrap(); + ); transaction_builder.set_tombstone_block(2000); transaction_builder.set_fee(Mob::MINIMUM_FEE * 4).unwrap(); @@ -1516,18 +1463,14 @@ pub mod transaction_builder_tests { let (_txout, _confirmation) = transaction_builder .add_output( - Amount::new(value - change_value - 4 * Mob::MINIMUM_FEE, token_id), + value - change_value - Mob::MINIMUM_FEE * 4, &recipient_address, &mut rng, ) .unwrap(); transaction_builder - .add_change_output( - Amount::new(change_value, token_id), - &sender_change_dest, - &mut rng, - ) + .add_change_output(change_value, &sender_change_dest, &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -1659,11 +1602,10 @@ pub mod transaction_builder_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), + token_id, fog_resolver.clone(), memo_builder, - ) - .unwrap(); + ); transaction_builder.set_tombstone_block(2000); @@ -1678,18 +1620,14 @@ pub mod transaction_builder_tests { let (_txout, _confirmation) = transaction_builder .add_output( - Amount::new(value - change_value - Mob::MINIMUM_FEE, token_id), + value - change_value - Mob::MINIMUM_FEE, &recipient_address, &mut rng, ) .unwrap(); transaction_builder - .add_change_output( - Amount::new(change_value, token_id), - &sender_change_dest, - &mut rng, - ) + .add_change_output(change_value, &sender_change_dest, &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -1821,11 +1759,10 @@ pub mod transaction_builder_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), + token_id, fog_resolver.clone(), memo_builder, - ) - .unwrap(); + ); transaction_builder.set_tombstone_block(2000); @@ -1840,18 +1777,14 @@ pub mod transaction_builder_tests { let (_txout, _confirmation) = transaction_builder .add_output( - Amount::new(value - change_value - Mob::MINIMUM_FEE, token_id), + value - change_value - Mob::MINIMUM_FEE, &recipient_address, &mut rng, ) .unwrap(); transaction_builder - .add_change_output( - Amount::new(change_value, token_id), - &sender_change_dest, - &mut rng, - ) + .add_change_output(change_value, &sender_change_dest, &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -1971,11 +1904,10 @@ pub mod transaction_builder_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), + token_id, fog_resolver.clone(), memo_builder, - ) - .unwrap(); + ); transaction_builder.set_tombstone_block(2000); @@ -1990,18 +1922,14 @@ pub mod transaction_builder_tests { let (_txout, _confirmation) = transaction_builder .add_output( - Amount::new(value - change_value - Mob::MINIMUM_FEE, token_id), + value - change_value - Mob::MINIMUM_FEE, &recipient_address, &mut rng, ) .unwrap(); transaction_builder - .add_change_output( - Amount::new(change_value, token_id), - &sender_change_dest, - &mut rng, - ) + .add_change_output(change_value, &sender_change_dest, &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -2146,11 +2074,10 @@ pub mod transaction_builder_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), + token_id, fog_resolver.clone(), memo_builder, - ) - .unwrap(); + ); transaction_builder.set_tombstone_block(2000); @@ -2165,18 +2092,14 @@ pub mod transaction_builder_tests { let (_txout, _confirmation) = transaction_builder .add_output( - Amount::new(value - change_value - Mob::MINIMUM_FEE, token_id), + value - change_value - Mob::MINIMUM_FEE, &bob_address, &mut rng, ) .unwrap(); transaction_builder - .add_change_output( - Amount::new(change_value, token_id), - &alice_change_dest, - &mut rng, - ) + .add_change_output(change_value, &alice_change_dest, &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -2338,11 +2261,10 @@ pub mod transaction_builder_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), + token_id, fog_resolver.clone(), memo_builder, - ) - .unwrap(); + ); transaction_builder.set_tombstone_block(2000); @@ -2357,18 +2279,14 @@ pub mod transaction_builder_tests { let (_txout, _confirmation) = transaction_builder .add_output( - Amount::new(value - change_value - Mob::MINIMUM_FEE, token_id), + value - change_value - Mob::MINIMUM_FEE, &recipient_address, &mut rng, ) .unwrap(); transaction_builder - .add_change_output( - Amount::new(change_value, token_id), - &sender_change_dest, - &mut rng, - ) + .add_change_output(change_value, &sender_change_dest, &mut rng) .unwrap(); assert!( @@ -2378,22 +2296,14 @@ pub mod transaction_builder_tests { assert!( transaction_builder - .add_output( - Amount::new(Mob::MINIMUM_FEE, token_id), - &recipient_address, - &mut rng - ) + .add_output(Mob::MINIMUM_FEE, &recipient_address, &mut rng) .is_err(), - "Adding another output after change output should be rejected" + "Adding another output after chnage output should be rejected" ); assert!( transaction_builder - .add_change_output( - Amount::new(change_value, token_id), - &sender_change_dest, - &mut rng - ) + .add_change_output(change_value, &sender_change_dest, &mut rng) .is_err(), "Adding a second change output should be rejected" ); @@ -2452,28 +2362,19 @@ pub mod transaction_builder_tests { ) .unwrap(); - let mut transaction_builder = TransactionBuilder::new( - block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), - fpr, - EmptyMemoBuilder::default(), - ) - .unwrap(); + let mut transaction_builder = + TransactionBuilder::new(block_version, token_id, fpr, EmptyMemoBuilder::default()); transaction_builder.add_input(input_credentials); let wrong_value = 999; transaction_builder - .add_output( - Amount::new(wrong_value, token_id), - &bob.default_subaddress(), - &mut rng, - ) + .add_output(wrong_value, &bob.default_subaddress(), &mut rng) .unwrap(); let result = transaction_builder.build(&mut rng); // Signing should fail if value is not conserved. match result { - Err(TxBuilderError::RingSignatureFailed(_)) => {} // Expected. + Err(TxBuilderError::RingSignatureFailed) => {} // Expected. _ => panic!("Unexpected result {:?}", result), } } @@ -2622,11 +2523,10 @@ pub mod transaction_builder_tests { let mut transaction_builder = TransactionBuilder::new( block_version, - Amount::new(Mob::MINIMUM_FEE, token_id), + token_id, fog_resolver.clone(), memo_builder, - ) - .unwrap(); + ); let input_credentials = get_input_credentials( block_version, @@ -2639,18 +2539,14 @@ pub mod transaction_builder_tests { let (burn_tx_out, _confirmation) = transaction_builder .add_output( - Amount::new(value - change_value - Mob::MINIMUM_FEE, token_id), + value - change_value - Mob::MINIMUM_FEE, &recipient, &mut rng, ) .unwrap(); transaction_builder - .add_change_output( - Amount::new(change_value, token_id), - &sender_change_dest, - &mut rng, - ) + .add_change_output(change_value, &sender_change_dest, &mut rng) .unwrap(); let tx = transaction_builder.build(&mut rng).unwrap(); @@ -3031,214 +2927,4 @@ pub mod transaction_builder_tests { } } } - - #[test] - // Test that sending mixed transactions works - // - // This test uses inputs of two different token IDs, paying the fee and creating - // change with TokenID1, and "passing through" the second token ID with its - // full amount as output. - fn test_mixed_transactions() { - let mut rng: StdRng = SeedableRng::from_seed([18u8; 32]); - - let fog_resolver = MockFogResolver::default(); - let sender = AccountKey::random(&mut rng); - let sender_change_dest = ChangeDestination::from(&sender); - let recipient = AccountKey::random(&mut rng); - let recipient_addr = recipient.default_subaddress(); - - let amount1 = Amount::new(1475 * MILLIMOB_TO_PICOMOB, Mob::ID); - let change_amount = Amount::new(128 * MILLIMOB_TO_PICOMOB, Mob::ID); - let amount2 = Amount::new(999999, 2.into()); - - let tx_out1_right_amount = Amount::new( - amount1.value - change_amount.value - Mob::MINIMUM_FEE, - Mob::ID, - ); - - for block_version in 3..=*BlockVersion::MAX { - let block_version = BlockVersion::try_from(block_version).unwrap(); - let memo_builder = EmptyMemoBuilder::default(); - - let mut transaction_builder = TransactionBuilder::new( - block_version, - Amount::new(Mob::MINIMUM_FEE, Mob::ID), - fog_resolver.clone(), - memo_builder, - ) - .unwrap(); - - let input_credentials = - get_input_credentials(block_version, amount1, &sender, &fog_resolver, &mut rng); - transaction_builder.add_input(input_credentials); - - let input_credentials = - get_input_credentials(block_version, amount2, &sender, &fog_resolver, &mut rng); - transaction_builder.add_input(input_credentials); - - let (tx_out1, _confirmation) = transaction_builder - .add_output(tx_out1_right_amount, &recipient_addr, &mut rng) - .unwrap(); - - let (tx_out2, _confirmation) = transaction_builder - .add_output(amount2, &recipient_addr, &mut rng) - .unwrap(); - - transaction_builder - .add_change_output(change_amount, &sender_change_dest, &mut rng) - .unwrap(); - - let tx = transaction_builder.build(&mut rng).unwrap(); - - assert_eq!(tx.prefix.outputs.len(), 3); - let idx1 = tx - .prefix - .outputs - .iter() - .position(|tx_out| tx_out.public_key == tx_out1.public_key) - .unwrap(); - let idx2 = tx - .prefix - .outputs - .iter() - .position(|tx_out| tx_out.public_key == tx_out2.public_key) - .unwrap(); - let change_idx = (0..3).find(|x| *x != idx1 && *x != idx2).unwrap(); - - let change_tx_out = &tx.prefix.outputs[change_idx]; - - // Test that sender's change subaddress owns the change, and not the other tx - // outs - assert!( - !subaddress_matches_tx_out(&sender, CHANGE_SUBADDRESS_INDEX, &tx_out1).unwrap() - ); - assert!( - !subaddress_matches_tx_out(&sender, CHANGE_SUBADDRESS_INDEX, &tx_out2).unwrap() - ); - assert!( - subaddress_matches_tx_out(&sender, CHANGE_SUBADDRESS_INDEX, change_tx_out).unwrap() - ); - - // Test that recipients's default subaddress owns the correct output, and not - // the other tx outs - assert!( - subaddress_matches_tx_out(&recipient, DEFAULT_SUBADDRESS_INDEX, &tx_out1).unwrap() - ); - assert!( - subaddress_matches_tx_out(&recipient, DEFAULT_SUBADDRESS_INDEX, &tx_out2).unwrap() - ); - assert!(!subaddress_matches_tx_out( - &recipient, - DEFAULT_SUBADDRESS_INDEX, - change_tx_out - ) - .unwrap()); - - // Test that view key matching works with the two tx outs - let (amount, _) = tx_out1 - .view_key_match(recipient.view_private_key()) - .unwrap(); - assert_eq!( - amount.value, - amount1.value - change_amount.value - Mob::MINIMUM_FEE - ); - assert_eq!(amount.token_id, Mob::ID); - - let (amount, _) = tx_out2 - .view_key_match(recipient.view_private_key()) - .unwrap(); - assert_eq!(amount, amount2); - - assert!(change_tx_out - .view_key_match(recipient.view_private_key()) - .is_err()); - - // Test that view key matching works with the change tx out with sender's view - // key - let (amount, _) = change_tx_out - .view_key_match(sender.view_private_key()) - .unwrap(); - assert_eq!(amount.value, change_amount.value); - - assert!(tx_out1.view_key_match(sender.view_private_key()).is_err()); - - assert!(tx_out2.view_key_match(sender.view_private_key()).is_err()); - } - } - - #[test] - // Test mixed transactions expected failures (imbalanced transactions) - fn test_mixed_transactions_expected_failure_imbalanced_transactions() { - let mut rng: StdRng = SeedableRng::from_seed([18u8; 32]); - - let fog_resolver = MockFogResolver::default(); - let sender = AccountKey::random(&mut rng); - let sender_change_dest = ChangeDestination::from(&sender); - let recipient = AccountKey::random(&mut rng); - let recipient_addr = recipient.default_subaddress(); - - let amount1 = Amount::new(1475 * MILLIMOB_TO_PICOMOB, Mob::ID); - let change_amount = Amount::new(128 * MILLIMOB_TO_PICOMOB, Mob::ID); - let amount2 = Amount::new(999999, 2.into()); - - let tx_out1_right_amount = Amount::new( - amount1.value - change_amount.value - Mob::MINIMUM_FEE, - Mob::ID, - ); - - // Builds a transaction using a particular amount in place of tx_out1, returning - // result of `.build()` - let mut test_fn = |block_version, tx_out1_amount| -> Result<_, _> { - let memo_builder = EmptyMemoBuilder::default(); - - let mut transaction_builder = TransactionBuilder::new( - block_version, - Amount::new(Mob::MINIMUM_FEE, Mob::ID), - fog_resolver.clone(), - memo_builder, - ) - .unwrap(); - - let input_credentials = - get_input_credentials(block_version, amount1, &sender, &fog_resolver, &mut rng); - transaction_builder.add_input(input_credentials); - - let input_credentials = - get_input_credentials(block_version, amount2, &sender, &fog_resolver, &mut rng); - transaction_builder.add_input(input_credentials); - - let (_tx_out1, _confirmation) = transaction_builder - .add_output(tx_out1_amount, &recipient_addr, &mut rng) - .unwrap(); - - let (_tx_out2, _confirmation) = transaction_builder - .add_output(amount2, &recipient_addr, &mut rng) - .unwrap(); - - transaction_builder - .add_change_output(change_amount, &sender_change_dest, &mut rng) - .unwrap(); - - transaction_builder.build(&mut rng) - }; - - for block_version in 3..=*BlockVersion::MAX { - let block_version = BlockVersion::try_from(block_version).unwrap(); - - assert!(test_fn(block_version, tx_out1_right_amount).is_ok()); - - let mut tx_out1_wrong_amount = tx_out1_right_amount; - tx_out1_wrong_amount.value -= 1; - assert!(test_fn(block_version, tx_out1_wrong_amount).is_err()); - - tx_out1_wrong_amount.value += 2; - assert!(test_fn(block_version, tx_out1_wrong_amount).is_err()); - - tx_out1_wrong_amount.token_id = 99.into(); - assert!(test_fn(block_version, tx_out1_wrong_amount).is_err()); - - tx_out1_wrong_amount.value -= 1; - assert!(test_fn(block_version, tx_out1_wrong_amount).is_err()); - } - } }