Skip to content

Commit

Permalink
signed contingent input builder, and make tx builder able to use thes…
Browse files Browse the repository at this point in the history
…e inputs
  • Loading branch information
cbeck88 committed Apr 27, 2022
1 parent 480e320 commit 892f742
Show file tree
Hide file tree
Showing 20 changed files with 3,158 additions and 232 deletions.
4 changes: 3 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,15 @@ cargo-emit = "0.2.1"
[dev-dependencies]
mc-crypto-x509-test-vectors = { path = "../crypto/x509/test-vectors" }
mc-test-vectors-b58-encodings = { path = "../test-vectors/b58-encodings" }
mc-transaction-core-test-utils = { path = "../transaction/core/test-utils" }
mc-transaction-std = { path = "../transaction/std" }
mc-fog-report-validation-test-utils = { path = "../fog/report/validation/test-utils" }
mc-transaction-std = { path = "../transaction/std", features = ["test-only"] }
mc-util-from-random = { path = "../util/from-random" }
mc-util-test-helper = { path = "../util/test-helper" }
mc-util-test-vector = { path = "../util/test-vector" }
mc-util-test-with-data = { path = "../util/test-with-data" }

generic-array = "0.14"
maplit = "1.0"
pem = "1.0"
prost = { version = "0.10", default-features = false }
rand = "0.8"
Expand Down
32 changes: 32 additions & 0 deletions api/proto/external.proto
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ message TxIn {
InputRules input_rules = 3;
}

// Rules enforced on a transaction by a signed input within it (MCIP #31)
message InputRules {
// Outputs required to appear in the TxPrefix for the Tx to be valid
repeated TxOut required_outputs = 1;
Expand Down Expand Up @@ -403,3 +404,34 @@ message ValidatedMintConfigTx {
MintConfigTx mint_config_tx = 1;
Ed25519SignerSet signer_set = 2;
}

// The amount and blinding factor of a TxOut
message UnmaskedAmount {
// The value of the amount commitment
fixed64 value = 1;

// The token_id of the amount commitment
fixed64 token_id = 2;

// The blinding factor of the amount commitment
CurveScalar blinding = 3;
}

// A pre-signed transaction input with associated rules, as described in MCIP #31
message SignedContingentInput {
// The tx_in which was signed
TxIn tx_in = 1;

// The Ring MLSAG signature, conferring spending authority
RingMLSAG mlsag = 2;

// The amount and blinding of the pseudo-output of the MLSAG
UnmaskedAmount pseudo_output_amount = 3;

/// The amount and blinding of any TxOut required by the input rules
repeated UnmaskedAmount required_output_amounts = 4;

/// The tx_out global index of each ring member
/// This helps the recipient of this payload construct proofs of membership for the ring
repeated fixed64 tx_out_global_indices = 5;
}
176 changes: 125 additions & 51 deletions api/src/convert/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,20 @@ impl TryFrom<&external::Tx> for tx::Tx {
#[cfg(test)]
mod tests {
use super::*;
use mc_account_keys::{AccountKey, PublicAddress};
use mc_crypto_keys::RistrettoPublic;
use mc_account_keys::AccountKey;
use mc_fog_report_validation_test_utils::MockFogResolver;
use mc_transaction_core::{
onetime_keys::recover_onetime_private_key,
tokens::Mob,
tx::{Tx, TxOut, TxOutMembershipProof},
Amount, BlockVersion, Token,
constants::MILLIMOB_TO_PICOMOB, tokens::Mob, tx::Tx, Amount, BlockVersion, Token, TokenId,
};
use mc_transaction_std::{
test_utils::get_input_credentials, ChangeDestination, EmptyMemoBuilder,
SignedContingentInputBuilder, TransactionBuilder,
};
use mc_transaction_core_test_utils::MockFogResolver;
use mc_transaction_std::{EmptyMemoBuilder, InputCredentials, TransactionBuilder};
use protobuf::Message;
use rand::{rngs::StdRng, SeedableRng};

#[test]
/// Tx --> externalTx --> Tx should be the identity function.
/// Tx --> externalTx --> Tx should be the identity function, for simple tx
fn test_convert_tx() {
// Generate a Tx to test with. This is copied from
// transaction_builder.rs::test_simple_transaction
Expand All @@ -51,69 +50,144 @@ mod tests {
for block_version in BlockVersion::iterator() {
let alice = AccountKey::random(&mut rng);
let bob = AccountKey::random(&mut rng);
let charlie = AccountKey::random(&mut rng);

let minted_outputs: Vec<TxOut> = {
// Mint an initial collection of outputs, including one belonging to
// `sender_account`.
let recipient_and_amounts: Vec<(PublicAddress, u64)> = vec![
(alice.default_subaddress(), 65536),
// Some outputs belonging to this account will be used as mix-ins.
(charlie.default_subaddress(), 65536),
(charlie.default_subaddress(), 65536),
];

mc_transaction_core_test_utils::get_outputs(
block_version,
&recipient_and_amounts,
&mut rng,
)
};
let fpr = MockFogResolver::default();

let mut transaction_builder = TransactionBuilder::new(
block_version,
Amount::new(Mob::MINIMUM_FEE, Mob::ID),
MockFogResolver::default(),
fpr.clone(),
EmptyMemoBuilder::default(),
)
.unwrap();

let ring: Vec<TxOut> = minted_outputs.clone();
let public_key = RistrettoPublic::try_from(&minted_outputs[0].public_key).unwrap();
let onetime_private_key = recover_onetime_private_key(
&public_key,
alice.view_private_key(),
&alice.default_subaddress_spend_private(),
transaction_builder.add_input(get_input_credentials(
block_version,
Amount::new(65536 + Mob::MINIMUM_FEE, Mob::ID),
&alice,
&fpr,
&mut rng,
));
transaction_builder
.add_output(
Amount::new(65536, Mob::ID),
&bob.default_subaddress(),
&mut rng,
)
.unwrap();

let tx = transaction_builder.build(&mut rng).unwrap();

// decode(encode(tx)) should be the identity function.
{
let bytes = mc_util_serial::encode(&tx);
let recovered_tx = mc_util_serial::decode(&bytes).unwrap();
assert_eq!(tx, recovered_tx);
}

// Converting mc_transaction_core::Tx -> external::Tx -> mc_transaction_core::Tx
// should be the identity function.
{
let external_tx: external::Tx = external::Tx::from(&tx);
let recovered_tx: Tx = Tx::try_from(&external_tx).unwrap();
assert_eq!(tx, recovered_tx);
}

// Encoding with prost, decoding with protobuf should be the identity function.
{
let bytes = mc_util_serial::encode(&tx);
let recovered_tx = external::Tx::parse_from_bytes(&bytes).unwrap();
assert_eq!(recovered_tx, external::Tx::from(&tx));
}

// Encoding with protobuf, decoding with prost should be the identity function.
{
let external_tx: external::Tx = external::Tx::from(&tx);
let bytes = external_tx.write_to_bytes().unwrap();
let recovered_tx: Tx = mc_util_serial::decode(&bytes).unwrap();
assert_eq!(tx, recovered_tx);
}
}
}

#[test]
/// Tx --> externalTx --> Tx should be the identity function, for tx with
/// input rules
fn test_convert_tx_with_input_rules() {
// Generate a Tx to test with. This is copied from
// transaction_builder.rs::test_simple_transaction
let mut rng: StdRng = SeedableRng::from_seed([1u8; 32]);

for block_version in BlockVersion::iterator().skip(3) {
let alice = AccountKey::random(&mut rng);
let bob = AccountKey::random(&mut rng);
let charlie = AccountKey::random(&mut rng);

let token2 = TokenId::from(2);

let fpr = MockFogResolver::default();

// Charlie makes a signed contingent input, offering 1000 token2's for 1 MOB
let input_credentials = get_input_credentials(
block_version,
Amount::new(1000, token2),
&charlie,
&fpr,
&mut rng,
);
let mut sci_builder = SignedContingentInputBuilder::new(
block_version,
input_credentials,
vec![59],
fpr.clone(),
EmptyMemoBuilder::default(),
);

let membership_proofs: Vec<TxOutMembershipProof> = ring
.iter()
.map(|_tx_out| {
// TransactionBuilder does not validate membership proofs, but does require one
// for each ring member.
TxOutMembershipProof::new(0, 0, Default::default())
})
.collect();

let input_credentials = InputCredentials::new(
ring.clone(),
membership_proofs,
0,
onetime_private_key,
*alice.view_private_key(),
sci_builder
.add_output(
Amount::new(1000 * MILLIMOB_TO_PICOMOB, Mob::ID),
&charlie.default_subaddress(),
&mut rng,
)
.unwrap();

let sci = sci_builder.build(&mut rng).unwrap();

// Alice sends this token2 amount to Bob from Charlie, paying Charlie 1000 MOB
// as he desires.
let mut transaction_builder = TransactionBuilder::new(
block_version,
Amount::new(Mob::MINIMUM_FEE, Mob::ID),
fpr.clone(),
EmptyMemoBuilder::default(),
)
.unwrap();

transaction_builder.add_input(input_credentials);
transaction_builder.set_fee(0).unwrap();
transaction_builder.add_input(get_input_credentials(
block_version,
Amount::new(1475 * MILLIMOB_TO_PICOMOB, Mob::ID),
&alice,
&fpr,
&mut rng,
));
transaction_builder.add_presigned_input(sci).unwrap();

transaction_builder
.add_output(
Amount::new(65536, Mob::ID),
Amount::new(1000, token2),
&bob.default_subaddress(),
&mut rng,
)
.unwrap();

transaction_builder
.add_change_output(
Amount::new(475 * MILLIMOB_TO_PICOMOB - Mob::MINIMUM_FEE, Mob::ID),
&ChangeDestination::from(&alice),
&mut rng,
)
.unwrap();

let tx = transaction_builder.build(&mut rng).unwrap();

// decode(encode(tx)) should be the identity function.
Expand Down
Loading

0 comments on commit 892f742

Please sign in to comment.