split send_funds function into bindgen and pure rust impl

This commit is contained in:
Francisco Gindre 2020-12-29 09:35:39 -03:00
parent d1a099b26f
commit 228183af23
3 changed files with 215 additions and 212 deletions

26
Cargo.lock generated
View File

@ -429,7 +429,7 @@ checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
[[package]]
name = "equihash"
version = "0.1.0"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#d930860b2115de4e8415429498900d9abeb4ab4e"
dependencies = [
"blake2b_simd",
"byteorder",
@ -512,9 +512,9 @@ dependencies = [
[[package]]
name = "funty"
version = "1.0.1"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ba62103ce691c2fd80fbae2213dfdda9ce60804973ac6b6e97de818ea7f52c8"
checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7"
[[package]]
name = "futures"
@ -626,9 +626,9 @@ checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
[[package]]
name = "itoa"
version = "0.4.6"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "js-sys"
@ -1092,9 +1092,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.60"
version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1500e84d27fe482ed1dc791a56eddc2f230046a040fa908c08bda1d9fb615779"
checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
dependencies = [
"itoa",
"ryu",
@ -1222,9 +1222,9 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]]
name = "syn"
version = "1.0.55"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a571a711dddd09019ccc628e1b17fe87c59b09d513c06c026877aa708334f37a"
checksum = "a9802ddde94170d186eeee5005b798d9c159fa970403f1be19976d0cfb939b72"
dependencies = [
"proc-macro2",
"quote",
@ -1462,7 +1462,7 @@ checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214"
[[package]]
name = "zcash_client_backend"
version = "0.4.0"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#d930860b2115de4e8415429498900d9abeb4ab4e"
dependencies = [
"base64 0.12.3",
"bech32",
@ -1485,7 +1485,7 @@ dependencies = [
[[package]]
name = "zcash_client_sqlite"
version = "0.2.1"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#d930860b2115de4e8415429498900d9abeb4ab4e"
dependencies = [
"bech32",
"bs58",
@ -1503,7 +1503,7 @@ dependencies = [
[[package]]
name = "zcash_primitives"
version = "0.4.0"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#d930860b2115de4e8415429498900d9abeb4ab4e"
dependencies = [
"aes",
"bitvec",
@ -1531,7 +1531,7 @@ dependencies = [
[[package]]
name = "zcash_proofs"
version = "0.4.0"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#dc99e1e130e859e5f17dbdbb55cb0f505ffb37e9"
source = "git+https://github.com/pacu/librustzcash?branch=autoshield-poc#d930860b2115de4e8415429498900d9abeb4ab4e"
dependencies = [
"bellman",
"blake2b_simd",

View File

@ -237,18 +237,6 @@ int32_t zcashlc_scan_blocks(const uint8_t *db_cache,
const uint8_t *db_data,
uintptr_t db_data_len);
/**
*
* psst, hey I have a Major in Social Sciences. Consider using something else
* Creates a transaction paying the specified address from the given account.
*
* Returns the row index of the newly-created transaction in the `transactions` table
* within the data database. The caller can read the raw transaction bytes from the `raw`
* column in order to broadcast the transaction to the network.
*
* Do not call this multiple times in parallel, or you will generate transactions that
* double-spend the same notes.
*/
int64_t zcashlc_shield_funds(const uint8_t *db_data,
uintptr_t db_data_len,
const uint8_t *db_cache,

View File

@ -1042,6 +1042,206 @@ pub fn double_sha256(payload: &[u8]) -> Vec<u8> {
///
/// Do not call this multiple times in parallel, or you will generate transactions that
/// double-spend the same notes.
///
///
fn shield_funds(
db_cache: &BlockDB,
db_data: &WalletDB,
account: u32,
tsk: &str,
extsk: &str,
memo: &str,
spend_params: &Path,
output_params: &Path,
) -> Result<i64,failure::Error> {
let anchor_and_height = match (&db_data).get_target_and_anchor_heights() {
Ok(Some(h)) => h,
Ok(None) => {
return Err(format_err!("No anchor and target heights found"));
},
Err(e) => {
return Err(format_err!("Error fetching anchor and target heights: {}", e));
},
};
// grab secret private key for t-funds
let sk = match secp256k1::key::SecretKey::from_str(&tsk) {
Ok(sk) => sk,
Err(e) => {
return Err(format_err!("Invalid Transparent Secret key: {}", e));
},
};
// derive the corresponding t-address
let t_addr_str = derive_transparent_address_from_secret_key(sk);
let t_addr = match RecipientAddress::decode(&NETWORK, &t_addr_str) {
Some(to) => to,
None => {
return Err(format_err!("PaymentAddress is for the wrong network"));
},
};
let extsk =
match decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &extsk)
{
Ok(Some(extsk)) => extsk,
Ok(None) => {
return Err(format_err!("ExtendedSpendingKey is for the wrong network"));
},
Err(e) => {
return Err(format_err!("Invalid ExtendedSpendingKey: {}", e));
},
};
// derive own shielded address from the provided extended spending key
let z_address = extsk.default_address().unwrap().1;
let exfvk = ExtendedFullViewingKey::from(&extsk);
let ovk = exfvk.fvk.ovk;
let memo = match Memo::from_str(&memo) {
Ok(memo) => memo,
Err(_) => {
return Err(format_err!("Invalid memo input"));
}
};
// get UTXOs from DB
let utxos = match get_utxos(&NETWORK, &db_cache) {
Ok(u) => u,
Err(e) => {
return Err(format_err!("Error getting UTXOs {}",e));
},
};
// verify that the addresses of the UTXOs are correspond to the given t-address
let distinct_addresses: Vec<&UnspentTransactionOutput> = utxos.iter().filter(|utxo| utxo.address != t_addr).collect::<Vec<_>>();
if distinct_addresses.len() > 0 {
return Err(format_err!("one or more UTXOs correspond to other addresses that don't match the provided SecretKey"));
}
// check that the utxos are confirmed
let latest_scanned_height = anchor_and_height.1;
let latest_anchor = anchor_and_height.0;
let unconfirmed_funds: Vec<BlockHeight> = utxos.iter().map(|u| u.height).filter(|h| h > &latest_anchor).collect();
if unconfirmed_funds.len() > 0 {
return Err(format_err!("one or more UTXOs are unconfirmed "));
}
let total_amount = match Amount::from_i64(utxos.iter().map(|u| i64::from(u.value)).sum::<i64>()) {
Ok(a) => a,
_ => {
return Err(format_err!("error collecting total amount from UTXOs"));
},
};
let fee = DEFAULT_FEE;
let target_value = fee + total_amount;
if fee >= total_amount {
return Err(format_err!("Insufficient verified funds (have {}, need {:?}). NOTE: funds need {} confirmations before they can be spent.",
u64::from(total_amount), target_value, anchor_and_height.0 + 1));
}
let amount_to_shield = total_amount - fee;
let prover = LocalTxProver::new(spend_params, output_params);
let mut builder = Builder::new(NETWORK, latest_scanned_height);
for utxo in utxos.iter() {
let outpoint = OutPoint::new(
utxo.txid.0,
utxo.index as u32
);
let coin = TxOut {
value: utxo.value.clone(),
script_pubkey: utxo.script.clone(),
};
match builder.add_transparent_input(sk.clone(), outpoint.clone(), coin.clone()) {
Ok(_) => (),
Err(e) => {
return Err(format_err!("error adding transparent input {}",e));
},
}
}
// there are no sapling notes so we set the change manually
builder.send_change_to(ovk, z_address.clone());
// add the sapling output to shield the funds
match builder.add_sapling_output(Some(ovk), z_address.clone(), amount_to_shield, Some(memo.clone())) {
Ok(_) =>(),
Err(e) => {
return Err(format_err!("Failed to add sapling output {}", e));
}
};
let consensus_branch_id = BranchId::for_height(&NETWORK, anchor_and_height.1);
let (tx, tx_metadata) = match builder
.build(consensus_branch_id, &prover) {
Ok(t) => t,
Err(e) => {
return Err(format_err!("Error building transaction {}",e));
},
};
// We only called add_sapling_output() once.
let output_index = match tx_metadata.output_index(0) {
Some(idx) => idx as i64,
None => {
return Err(format_err!("Output 0 should exist in the transaction"));
},
};
// Update the database atomically, to ensure the result is internally consistent.
let mut db_update = match (&db_data).get_update_ops() {
Ok(d) => d,
Err(e) => {
return Err(format_err!("error updating database with created tx {}",e));
},
};
db_update
.transactionally(|up| {
let created = time::OffsetDateTime::now_utc();
let tx_ref = up.put_tx_data(&tx, Some(created))?;
// Mark notes as spent.
//
// This locks the notes so they aren't selected again by a subsequent call to
// create_spend_to_address() before this transaction has been mined (at which point the notes
// get re-marked as spent).
//
// Assumes that create_spend_to_address() will never be called in parallel, which is a
// reasonable assumption for a light client such as a mobile phone.
for spend in &tx.shielded_spends {
up.mark_spent(tx_ref, &spend.nullifier)?;
}
up.insert_sent_note(
&NETWORK,
tx_ref,
output_index as usize,
AccountId(account),
&RecipientAddress::from(z_address),
amount_to_shield,
Some(memo),
)?;
// Return the row number of the transaction, so the caller can fetch it for sending.
Ok(tx_ref)
})
.map_err(|e| format_err!("error building updating data_db {}",e))
}
#[no_mangle]
pub extern "C" fn zcashlc_shield_funds(
db_data: *const u8,
@ -1076,192 +1276,7 @@ pub extern "C" fn zcashlc_shield_funds(
slice::from_raw_parts(output_params, output_params_len)
}));
let anchor_and_height = match (&db_data).get_target_and_anchor_heights() {
Ok(Some(h)) => h,
Ok(None) => {
return Err(format_err!("No anchor and target heights found"));
},
Err(e) => {
return Err(format_err!("Error fetching anchor and target heights: {}", e));
},
};
// grab secret private key for t-funds
let sk = match secp256k1::key::SecretKey::from_str(&tsk) {
Ok(sk) => sk,
Err(e) => {
return Err(format_err!("Invalid Transparent Secret key: {}", e));
},
};
// derive the corresponding t-address
let t_addr_str = derive_transparent_address_from_secret_key(sk);
let t_addr = match RecipientAddress::decode(&NETWORK, &t_addr_str) {
Some(to) => to,
None => {
return Err(format_err!("PaymentAddress is for the wrong network"));
},
};
let extsk =
match decode_extended_spending_key(NETWORK.hrp_sapling_extended_spending_key(), &extsk)
{
Ok(Some(extsk)) => extsk,
Ok(None) => {
return Err(format_err!("ExtendedSpendingKey is for the wrong network"));
},
Err(e) => {
return Err(format_err!("Invalid ExtendedSpendingKey: {}", e));
},
};
// derive own shielded address from the provided extended spending key
let z_address = extsk.default_address().unwrap().1;
let exfvk = ExtendedFullViewingKey::from(&extsk);
let ovk = exfvk.fvk.ovk;
let memo = match Memo::from_str(&memo) {
Ok(memo) => memo,
Err(_) => {
return Err(format_err!("Invalid memo input"));
}
};
// get UTXOs from DB
let utxos = match get_utxos(&NETWORK, &db_cache) {
Ok(u) => u,
Err(e) => {
return Err(format_err!("Error getting UTXOs {}",e));
},
};
// verify that the addresses of the UTXOs are correspond to the given t-address
let distinct_addresses: Vec<&UnspentTransactionOutput> = utxos.iter().filter(|utxo| utxo.address != t_addr).collect::<Vec<_>>();
if distinct_addresses.len() > 0 {
return Err(format_err!("one or more UTXOs correspond to other addresses that don't match the provided SecretKey"));
}
// check that the utxos are confirmed
let latest_scanned_height = anchor_and_height.1;
let latest_anchor = anchor_and_height.0;
let unconfirmed_funds: Vec<BlockHeight> = utxos.iter().map(|u| u.height).filter(|h| h > &latest_anchor).collect();
if unconfirmed_funds.len() > 0 {
return Err(format_err!("one or more UTXOs are unconfirmed "));
}
let total_amount = match Amount::from_i64(utxos.iter().map(|u| i64::from(u.value)).sum::<i64>()) {
Ok(a) => a,
_ => {
return Err(format_err!("error collecting total amount from UTXOs"));
},
};
let fee = DEFAULT_FEE;
let target_value = fee + total_amount;
if fee >= total_amount {
return Err(format_err!("Insufficient verified funds (have {}, need {:?}). NOTE: funds need {} confirmations before they can be spent.",
u64::from(total_amount), target_value, anchor_and_height.0 + 1));
}
let amount_to_shield = total_amount - fee;
let prover = LocalTxProver::new(spend_params, output_params);
let mut builder = Builder::new(NETWORK, latest_scanned_height);
for utxo in utxos.iter() {
let outpoint = OutPoint::new(
utxo.txid.0,
utxo.index as u32
);
let coin = TxOut {
value: utxo.value.clone(),
script_pubkey: utxo.script.clone(),
};
match builder.add_transparent_input(sk.clone(), outpoint.clone(), coin.clone()) {
Ok(_) => (),
Err(e) => {
return Err(format_err!("error adding transparent input {}",e));
},
}
}
// there are no sapling notes so we set the change manually
builder.send_change_to(ovk, z_address.clone());
// add the sapling output to shield the funds
match builder.add_sapling_output(Some(ovk), z_address.clone(), amount_to_shield, Some(memo.clone())) {
Ok(_) =>(),
Err(e) => {
return Err(format_err!("Failed to add sapling output {}", e));
}
};
let consensus_branch_id = BranchId::for_height(&NETWORK, anchor_and_height.1);
let (tx, tx_metadata) = match builder
.build(consensus_branch_id, &prover) {
Ok(t) => t,
Err(e) => {
return Err(format_err!("Error building transaction {}",e));
},
};
// We only called add_sapling_output() once.
let output_index = match tx_metadata.output_index(0) {
Some(idx) => idx as i64,
None => {
return Err(format_err!("Output 0 should exist in the transaction"));
},
};
// Update the database atomically, to ensure the result is internally consistent.
let mut db_update = match (&db_data).get_update_ops() {
Ok(d) => d,
Err(e) => {
return Err(format_err!("error updating database with created tx {}",e));
},
};
db_update
.transactionally(|up| {
let created = time::OffsetDateTime::now_utc();
let tx_ref = up.put_tx_data(&tx, Some(created))?;
// Mark notes as spent.
//
// This locks the notes so they aren't selected again by a subsequent call to
// create_spend_to_address() before this transaction has been mined (at which point the notes
// get re-marked as spent).
//
// Assumes that create_spend_to_address() will never be called in parallel, which is a
// reasonable assumption for a light client such as a mobile phone.
for spend in &tx.shielded_spends {
up.mark_spent(tx_ref, &spend.nullifier)?;
}
up.insert_sent_note(
&NETWORK,
tx_ref,
output_index as usize,
AccountId(account),
&RecipientAddress::from(z_address),
amount_to_shield,
Some(memo),
)?;
// Return the row number of the transaction, so the caller can fetch it for sending.
Ok(tx_ref)
})
.map_err(|e| format_err!("error building updating data_db {}",e))
shield_funds(&db_cache, &db_data, account, &tsk, &extsk, &memo, &spend_params, &output_params)
});
unwrap_exc_or(res, -1)
}
}