wallet: Use `zcash_note_encryption` in `CWalletTx::RecoverSaplingNote`

This commit is contained in:
Jack Grigg 2023-03-14 21:22:13 +00:00
parent 2fd287e73b
commit f5ed454f87
5 changed files with 73 additions and 28 deletions

View File

@ -1,7 +1,10 @@
use group::GroupEncoding;
use zcash_note_encryption::{Domain, EphemeralKeyBytes, ShieldedOutput, ENC_CIPHERTEXT_SIZE};
use zcash_note_encryption::{
try_output_recovery_with_ovk, Domain, EphemeralKeyBytes, ShieldedOutput, ENC_CIPHERTEXT_SIZE,
};
use zcash_primitives::{
consensus::BlockHeight,
keys::OutgoingViewingKey,
memo::MemoBytes,
sapling::{
self,
@ -44,6 +47,42 @@ pub(crate) fn try_sapling_note_decryption(
}))
}
/// Recovery of the full note plaintext by the sender.
///
/// Attempts to decrypt and validate the given shielded output using the given `ovk`.
/// If successful, the corresponding note and memo are returned, along with the address to
/// which the note was sent.
///
/// Implements [Zcash Protocol Specification section 4.19.3][decryptovk].
///
/// [decryptovk]: https://zips.z.cash/protocol/protocol.pdf#decryptovk
pub(crate) fn try_sapling_output_recovery(
network: &Network,
height: u32,
ovk: [u8; 32],
output: SaplingShieldedOutput,
) -> Result<Box<DecryptedSaplingOutput>, &'static str> {
let domain = SaplingDomain::for_height(*network, BlockHeight::from_u32(height));
let cv = Option::from(jubjub::ExtendedPoint::from_bytes(&output.cv))
.ok_or("Invalid output.cv passed to wallet::try_sapling_note_decryption()")?;
let (note, recipient, memo) = try_output_recovery_with_ovk(
&domain,
&OutgoingViewingKey(ovk),
&output,
&cv,
&output.out_ciphertext,
)
.ok_or("Decryption failed")?;
Ok(Box::new(DecryptedSaplingOutput {
note,
recipient,
memo,
}))
}
/// Parses and validates a Sapling incoming viewing key, and prepares it for decryption.
pub(crate) fn parse_and_prepare_sapling_ivk(
raw_ivk: &[u8; 32],

View File

@ -23,7 +23,8 @@ use zcash_primitives::{
use crate::{
note_encryption::{
parse_and_prepare_sapling_ivk, try_sapling_note_decryption, DecryptedSaplingOutput,
parse_and_prepare_sapling_ivk, try_sapling_note_decryption, try_sapling_output_recovery,
DecryptedSaplingOutput,
},
params::{network, Network},
};
@ -32,9 +33,11 @@ use crate::{
pub(crate) mod ffi {
#[namespace = "wallet"]
pub(crate) struct SaplingShieldedOutput {
cv: [u8; 32],
cmu: [u8; 32],
ephemeral_key: [u8; 32],
enc_ciphertext: [u8; 580],
out_ciphertext: [u8; 80],
}
#[namespace = "wallet"]
@ -69,6 +72,12 @@ pub(crate) mod ffi {
raw_ivk: &[u8; 32],
output: SaplingShieldedOutput,
) -> Result<Box<DecryptedSaplingOutput>>;
fn try_sapling_output_recovery(
network: &Network,
height: u32,
ovk: [u8; 32],
output: SaplingShieldedOutput,
) -> Result<Box<DecryptedSaplingOutput>>;
type DecryptedSaplingOutput;
fn note_value(self: &DecryptedSaplingOutput) -> u64;

View File

@ -4253,7 +4253,6 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
}
};
auto& consensusParams = Params().GetConsensus();
KeyIO keyIO(Params());
// Sprout spends
for (size_t i = 0; i < wtx.vJoinSplit.size(); ++i) {
@ -4422,7 +4421,7 @@ UniValue z_viewtransaction(const UniValue& params, bool fHelp)
isOutgoing = false;
} else {
// Try recovering the output
auto recovered = wtx.RecoverSaplingNote(consensusParams, op, ovks);
auto recovered = wtx.RecoverSaplingNote(Params(), op, ovks);
if (recovered) {
notePt = recovered->first;
pa = recovered->second;

View File

@ -3787,9 +3787,11 @@ std::pair<mapSaplingNoteData_t, SaplingIncomingViewingKeyMap> CWallet::FindMySap
height,
ivk.GetRawBytes(),
{
output.cv.GetRawBytes(),
output.cmu.GetRawBytes(),
output.ephemeralKey.GetRawBytes(),
output.encCiphertext,
output.outCiphertext,
});
SaplingPaymentAddress address(
@ -4319,9 +4321,11 @@ std::optional<std::pair<
params.GetConsensus().vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight,
nd.ivk.GetRawBytes(),
{
output.cv.GetRawBytes(),
output.cmu.GetRawBytes(),
output.ephemeralKey.GetRawBytes(),
output.encCiphertext,
output.outCiphertext,
});
return SaplingNotePlaintext::from_rust(std::move(decrypted));
@ -4332,35 +4336,29 @@ std::optional<std::pair<
std::optional<std::pair<
SaplingNotePlaintext,
SaplingPaymentAddress>> CWalletTx::RecoverSaplingNote(const Consensus::Params& params, SaplingOutPoint op, std::set<uint256>& ovks) const
SaplingPaymentAddress>> CWalletTx::RecoverSaplingNote(const CChainParams& params, SaplingOutPoint op, std::set<uint256>& ovks) const
{
auto output = this->vShieldedOutput[op.n];
for (auto ovk : ovks) {
auto outPt = SaplingOutgoingPlaintext::decrypt(
output.outCiphertext,
ovk,
output.cv,
output.cmu,
output.ephemeralKey);
if (!outPt) {
try {
auto decrypted = wallet::try_sapling_output_recovery(
*params.RustNetwork(),
// Canopy activation is inside the ZIP 212 grace period.
params.GetConsensus().vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight,
ovk.GetRawBytes(),
{
output.cv.GetRawBytes(),
output.cmu.GetRawBytes(),
output.ephemeralKey.GetRawBytes(),
output.encCiphertext,
output.outCiphertext,
});
return SaplingNotePlaintext::from_rust(std::move(decrypted));
} catch (const rust::Error &e) {
// Try decrypting with the next ovk
continue;
}
auto maybe_pt = SaplingNotePlaintext::decrypt(
params,
// Canopy activation is inside the ZIP 212 grace period.
params.vUpgrades[Consensus::UPGRADE_CANOPY].nActivationHeight,
output.encCiphertext,
output.ephemeralKey,
outPt->esk,
outPt->pk_d,
output.cmu);
assert(static_cast<bool>(maybe_pt));
auto notePt = maybe_pt.value();
return std::make_pair(notePt, SaplingPaymentAddress(notePt.d, outPt->pk_d));
}
// Couldn't recover with any of the provided OutgoingViewingKeys

View File

@ -686,7 +686,7 @@ public:
*/
std::optional<std::pair<
libzcash::SaplingNotePlaintext,
libzcash::SaplingPaymentAddress>> RecoverSaplingNote(const Consensus::Params& params,
libzcash::SaplingPaymentAddress>> RecoverSaplingNote(const CChainParams& params,
SaplingOutPoint op, std::set<uint256>& ovks) const;
OrchardActions RecoverOrchardActions(const std::vector<uint256>& ovks) const;