diff --git a/Cargo.lock b/Cargo.lock index 670c63a28..9100a1d83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2363,9 +2363,9 @@ dependencies = [ [[package]] name = "zcash_primitives" -version = "0.9.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9a45953c4ddd81d68f45920955707f45c8926800671f354dd13b97507edf28" +checksum = "ec8aed1d098e9f1b2bcd957ceab4188bf343cea30e7d0327fa49cea6ec44b167" dependencies = [ "aes", "bip0039", @@ -2399,9 +2399,9 @@ dependencies = [ [[package]] name = "zcash_proofs" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77381adc72286874e563ee36ba99953946abcbd195ada45440a2754ca823d407" +checksum = "28ca180a8138ae6e2de2b88573ed19dd57798f42a79a00d992b4d727132c7081" dependencies = [ "bellman", "blake2b_simd", diff --git a/Cargo.toml b/Cargo.toml index 4165686c5..bcc6558aa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,8 +61,8 @@ zcash_address = "0.2" zcash_encoding = "0.2" zcash_history = "0.3" zcash_note_encryption = "0.2" -zcash_primitives = { version = "0.9", features = ["transparent-inputs"] } -zcash_proofs = { version = "0.9", features = ["directories"] } +zcash_primitives = { version = "0.10.2", features = ["temporary-zcashd", "transparent-inputs"] } +zcash_proofs = { version = "0.10", features = ["directories"] } ed25519-zebra = "3" zeroize = "1.4.2" @@ -112,4 +112,3 @@ features = ["ansi", "env-filter", "fmt", "time"] lto = 'thin' panic = 'abort' codegen-units = 1 - diff --git a/qa/supply-chain/audits.toml b/qa/supply-chain/audits.toml index 6d9ad95a6..b5b846f2c 100644 --- a/qa/supply-chain/audits.toml +++ b/qa/supply-chain/audits.toml @@ -966,6 +966,30 @@ criteria = "safe-to-deploy" delta = "0.8.1 -> 0.9.1" notes = "The ECC core team maintains this crate, and we have reviewed every line." +[[audits.zcash_primitives]] +who = "Jack Grigg " +criteria = ["safe-to-deploy", "crypto-reviewed"] +delta = "0.9.1 -> 0.10.0" +notes = "The ECC core team maintains this crate, and we have reviewed every line." + +[[audits.zcash_primitives]] +who = "Jack Grigg " +criteria = ["safe-to-deploy", "crypto-reviewed"] +delta = "0.10.0 -> 0.10.1" +notes = """ +The ECC core team maintains this crate, and we have reviewed every line. +This point release temporarily re-exposes some constructors. +""" + +[[audits.zcash_primitives]] +who = "Jack Grigg " +criteria = ["safe-to-deploy", "crypto-reviewed"] +delta = "0.10.1 -> 0.10.2" +notes = """ +The ECC core team maintains this crate, and we have reviewed every line. +This point release temporarily re-exposes a constructor. +""" + [[audits.zcash_proofs]] who = "Jack Grigg " criteria = ["crypto-reviewed", "safe-to-deploy"] @@ -994,6 +1018,12 @@ who = "Jack Grigg " criteria = "safe-to-deploy" delta = "0.8.0 -> 0.9.0" +[[audits.zcash_proofs]] +who = "Jack Grigg " +criteria = ["safe-to-deploy", "crypto-reviewed"] +delta = "0.9.0 -> 0.10.0" +notes = "The ECC core team maintains this crate, and we have reviewed every line." + [[audits.zeroize]] who = "Daira Hopwood " criteria = "safe-to-deploy" diff --git a/src/gtest/test_checktransaction.cpp b/src/gtest/test_checktransaction.cpp index 928080fa6..ffb6d537a 100644 --- a/src/gtest/test_checktransaction.cpp +++ b/src/gtest/test_checktransaction.cpp @@ -6,6 +6,7 @@ #include "consensus/validation.h" #include "transaction_builder.h" #include "gtest/utils.h" +#include "test/test_util.h" #include "util/test.h" #include "zcash/JoinSplit.hpp" @@ -1150,7 +1151,7 @@ TEST(ChecktransactionTests, InvalidSaplingShieldedCoinbase) { // Make it an invalid shielded coinbase (no ciphertexts or commitments). mtx.vin.resize(1); mtx.vin[0].prevout.SetNull(); - mtx.vShieldedOutput.resize(1); + mtx.vShieldedOutput.push_back(RandomInvalidOutputDescription()); mtx.vJoinSplit.resize(0); CTransaction tx(mtx); diff --git a/src/main.cpp b/src/main.cpp index 430c75bd4..aa1a6890f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -981,6 +981,37 @@ bool ContextualCheckTransaction( // https://zips.z.cash/zip-0213#specification uint256 ovk; for (const OutputDescription &output : tx.vShieldedOutput) { + bool zip_212_enabled; + libzcash::SaplingPaymentAddress zaddr; + CAmount value; + + // EoS height for 5.3.3 and 5.4.2 is 2121024 (mainnet). + // On testnet this height will be in the past, as of the 5.5.0 release. + if (nHeight >= 2121200) { + try { + auto decrypted = wallet::try_sapling_output_recovery( + *chainparams.RustNetwork(), + nHeight, + ovk.GetRawBytes(), + { + output.cv.GetRawBytes(), + output.cmu.GetRawBytes(), + output.ephemeralKey.GetRawBytes(), + output.encCiphertext, + output.outCiphertext, + }); + zip_212_enabled = decrypted->zip_212_enabled(); + + libzcash::SaplingNotePlaintext notePt; + std::tie(notePt, zaddr) = SaplingNotePlaintext::from_rust(std::move(decrypted)); + value = notePt.value(); + } catch (const rust::Error &e) { + return state.DoS( + DOS_LEVEL_BLOCK, + error("ContextualCheckTransaction(): failed to recover plaintext of coinbase output description"), + REJECT_INVALID, "bad-cb-output-desc-invalid-outct"); + } + } else { auto outPlaintext = SaplingOutgoingPlaintext::decrypt( output.outCiphertext, ovk, output.cv, output.cmu, output.ephemeralKey); if (!outPlaintext) { @@ -1007,12 +1038,20 @@ bool ContextualCheckTransaction( REJECT_INVALID, "bad-cb-output-desc-invalid-encct"); } + auto leadByte = encPlaintext->get_leadbyte(); + assert(leadByte == 0x01 || leadByte == 0x02); + zip_212_enabled = (leadByte == 0x02); + + zaddr = libzcash::SaplingPaymentAddress(encPlaintext->d, outPlaintext->pk_d); + value = encPlaintext->value(); + } + + { // ZIP 207: detect shielded funding stream elements if (canopyActive) { - libzcash::SaplingPaymentAddress zaddr(encPlaintext->d, outPlaintext->pk_d); for (auto it = fundingStreamElements.begin(); it != fundingStreamElements.end(); ++it) { const libzcash::SaplingPaymentAddress* streamAddr = std::get_if(&(it->first)); - if (streamAddr && zaddr == *streamAddr && encPlaintext->value() == it->second) { + if (streamAddr && zaddr == *streamAddr && value == it->second) { fundingStreamElements.erase(it); break; } @@ -1024,15 +1063,14 @@ bool ContextualCheckTransaction( // to 0x02. This applies even during the grace period, and also applies to // funding stream outputs sent to shielded payment addresses, if any. // https://zips.z.cash/zip-0212#consensus-rule-change-for-coinbase-transactions - auto leadByte = encPlaintext->get_leadbyte(); - assert(leadByte == 0x01 || leadByte == 0x02); - if (canopyActive != (leadByte == 0x02)) { + if (canopyActive != zip_212_enabled) { return state.DoS( DOS_LEVEL_BLOCK, error("ContextualCheckTransaction(): coinbase output description has invalid note plaintext version"), REJECT_INVALID, "bad-cb-output-desc-invalid-note-plaintext-version"); } + } } } } else { diff --git a/src/rust/bin/inspect/transaction.rs b/src/rust/bin/inspect/transaction.rs index 4de4df25f..56af769cd 100644 --- a/src/rust/bin/inspect/transaction.rs +++ b/src/rust/bin/inspect/transaction.rs @@ -19,7 +19,7 @@ use zcash_primitives::{ memo::{Memo, MemoBytes}, sapling::note_encryption::SaplingDomain, transaction::{ - components::{orchard as orchard_serialization, sapling, transparent, Amount}, + components::{sapling, transparent, Amount}, sighash::{signature_hash, SignableInput, TransparentAuthorizingContext}, txid::TxIdDigester, Authorization, Transaction, TransactionData, TxId, TxVersion, @@ -139,44 +139,6 @@ impl transparent::MapAuth for MapTrans } } -// TODO: Move these trait impls into `zcash_primitives` so they can be on `()`. -struct IdentityMap; - -impl sapling::MapAuth for IdentityMap { - fn map_proof( - &self, - p: ::Proof, - ) -> ::Proof { - p - } - - fn map_auth_sig( - &self, - s: ::AuthSig, - ) -> ::AuthSig { - s - } - - fn map_authorization(&self, a: sapling::Authorized) -> sapling::Authorized { - a - } -} - -impl orchard_serialization::MapAuth - for IdentityMap -{ - fn map_spend_auth( - &self, - s: ::SpendAuth, - ) -> ::SpendAuth { - s - } - - fn map_authorization(&self, a: orchard::bundle::Authorized) -> orchard::bundle::Authorized { - a - } -} - pub(crate) struct PrecomputedAuth; impl Authorization for PrecomputedAuth { @@ -230,8 +192,7 @@ pub(crate) fn inspect(tx: Transaction, context: Option) { let tx = Transaction::read(&buf[..], tx.consensus_branch_id()).unwrap(); let tx: TransactionData = - tx.into_data() - .map_authorization(f_transparent, IdentityMap, IdentityMap); + tx.into_data().map_authorization(f_transparent, (), ()); let txid_parts = tx.digest(TxIdDigester); (tx, txid_parts) }); @@ -412,23 +373,23 @@ pub(crate) fn inspect(tx: Transaction, context: Option) { } if let Some(bundle) = tx.sapling_bundle() { - assert!(!(bundle.shielded_spends.is_empty() && bundle.shielded_outputs.is_empty())); + assert!(!(bundle.shielded_spends().is_empty() && bundle.shielded_outputs().is_empty())); // TODO: Separate into checking proofs, signatures, and other structural details. let mut ctx = SaplingVerificationContext::new(true); - if !bundle.shielded_spends.is_empty() { - eprintln!(" - {} Sapling Spend(s)", bundle.shielded_spends.len()); + if !bundle.shielded_spends().is_empty() { + eprintln!(" - {} Sapling Spend(s)", bundle.shielded_spends().len()); if let Some(sighash) = &common_sighash { - for (i, spend) in bundle.shielded_spends.iter().enumerate() { + for (i, spend) in bundle.shielded_spends().iter().enumerate() { if !ctx.check_spend( - spend.cv, - spend.anchor, - &spend.nullifier.0, - spend.rk.clone(), + spend.cv(), + *spend.anchor(), + &spend.nullifier().0, + spend.rk().clone(), sighash.as_ref(), - spend.spend_auth_sig, - groth16::Proof::read(&spend.zkproof[..]).unwrap(), + *spend.spend_auth_sig(), + groth16::Proof::read(&spend.zkproof()[..]).unwrap(), &GROTH16_PARAMS.spend_vk, ) { eprintln!(" ⚠️ Spend {} is invalid", i); @@ -441,9 +402,9 @@ pub(crate) fn inspect(tx: Transaction, context: Option) { } } - if !bundle.shielded_outputs.is_empty() { - eprintln!(" - {} Sapling Output(s)", bundle.shielded_outputs.len()); - for (i, output) in bundle.shielded_outputs.iter().enumerate() { + if !bundle.shielded_outputs().is_empty() { + eprintln!(" - {} Sapling Output(s)", bundle.shielded_outputs().len()); + for (i, output) in bundle.shielded_outputs().iter().enumerate() { if is_coinbase { if let Some((params, addr_net)) = context .as_ref() @@ -453,17 +414,17 @@ pub(crate) fn inspect(tx: Transaction, context: Option) { &SaplingDomain::for_height(params, height.unwrap()), &zcash_primitives::keys::OutgoingViewingKey([0; 32]), output, - &output.cv, - &output.out_ciphertext, + output.cv(), + output.out_ciphertext(), ) { - if note.value == 0 { + if note.value().inner() == 0 { eprintln!(" - Output {} (dummy output):", i); } else { let zaddr = ZcashAddress::from_sapling(addr_net, addr.to_bytes()); eprintln!(" - Output {}:", i); eprintln!(" - {}", zaddr); - eprintln!(" - {}", render_value(note.value)); + eprintln!(" - {}", render_value(note.value().inner())); } eprintln!(" - {}", render_memo(memo)); } else { @@ -478,10 +439,10 @@ pub(crate) fn inspect(tx: Transaction, context: Option) { } if !ctx.check_output( - output.cv, - output.cmu, - jubjub::ExtendedPoint::from_bytes(&output.ephemeral_key.0).unwrap(), - groth16::Proof::read(&output.zkproof[..]).unwrap(), + output.cv(), + *output.cmu(), + jubjub::ExtendedPoint::from_bytes(&output.ephemeral_key().0).unwrap(), + groth16::Proof::read(&output.zkproof()[..]).unwrap(), &GROTH16_PARAMS.output_vk, ) { eprintln!(" ⚠️ Output {} is invalid", i); @@ -491,9 +452,9 @@ pub(crate) fn inspect(tx: Transaction, context: Option) { if let Some(sighash) = &common_sighash { if !ctx.final_check( - bundle.value_balance, + *bundle.value_balance(), sighash.as_ref(), - bundle.authorization.binding_sig, + bundle.authorization().binding_sig, ) { eprintln!("⚠️ Sapling bindingSig is invalid"); } diff --git a/src/rust/include/librustzcash.h b/src/rust/include/librustzcash.h index 24333ba23..5ff1d5f2e 100644 --- a/src/rust/include/librustzcash.h +++ b/src/rust/include/librustzcash.h @@ -104,14 +104,17 @@ extern "C" { unsigned char *result ); - /// Compute [sk] [8] P for some 32-byte - /// point P, and 32-byte Fs. If P or sk - /// are invalid, returns false. Otherwise, - /// the result is written to the 32-byte - /// `result` buffer. - bool librustzcash_sapling_ka_agree( + /// Compute KDF^Sapling(KA^Agree(sk, P), ephemeral_key). + /// + /// P and sk must point to 32-byte buffers. If P does not + /// represent a Jubjub point or sk does not represent a + /// canonical Jubjub scalar, this function returns false. + /// Otherwise, it writes the result to the 32-byte `result` + /// buffer and returns true. + bool librustzcash_sapling_ka_derive_symmetric_key( const unsigned char *p, const unsigned char *sk, + const unsigned char *ephemeral_key, unsigned char *result ); diff --git a/src/rust/src/note_encryption.rs b/src/rust/src/note_encryption.rs index 71aa08d7c..19486ab4f 100644 --- a/src/rust/src/note_encryption.rs +++ b/src/rust/src/note_encryption.rs @@ -1,4 +1,5 @@ -use group::GroupEncoding; +use std::convert::TryInto; + use zcash_note_encryption::{ try_output_recovery_with_ovk, Domain, EphemeralKeyBytes, ShieldedOutput, ENC_CIPHERTEXT_SIZE, }; @@ -9,6 +10,7 @@ use zcash_primitives::{ sapling::{ self, note_encryption::{PreparedIncomingViewingKey, SaplingDomain}, + value::ValueCommitment, SaplingIvk, }, }; @@ -64,7 +66,7 @@ pub(crate) fn try_sapling_output_recovery( ) -> Result, &'static str> { let domain = SaplingDomain::for_height(*network, BlockHeight::from_u32(height)); - let cv = Option::from(jubjub::ExtendedPoint::from_bytes(&output.cv)) + let cv = Option::from(ValueCommitment::from_bytes_not_small_order(&output.cv)) .ok_or("Invalid output.cv passed to wallet::try_sapling_note_decryption()")?; let (note, recipient, memo) = try_output_recovery_with_ovk( @@ -115,18 +117,18 @@ pub(crate) struct DecryptedSaplingOutput { impl DecryptedSaplingOutput { pub(crate) fn note_value(&self) -> u64 { - self.note.value + self.note.value().inner() } pub(crate) fn note_rseed(&self) -> [u8; 32] { - match self.note.rseed { + match self.note.rseed() { sapling::Rseed::BeforeZip212(rcm) => rcm.to_bytes(), - sapling::Rseed::AfterZip212(rseed) => rseed, + sapling::Rseed::AfterZip212(rseed) => *rseed, } } pub(crate) fn zip_212_enabled(&self) -> bool { - matches!(self.note.rseed, sapling::Rseed::AfterZip212(_)) + matches!(self.note.rseed(), sapling::Rseed::AfterZip212(_)) } pub(crate) fn recipient_d(&self) -> [u8; 11] { @@ -134,7 +136,7 @@ impl DecryptedSaplingOutput { } pub(crate) fn recipient_pk_d(&self) -> [u8; 32] { - self.recipient.pk_d().to_bytes() + self.recipient.to_bytes()[11..].try_into().unwrap() } pub(crate) fn memo(&self) -> [u8; 512] { diff --git a/src/rust/src/rustzcash.rs b/src/rust/src/rustzcash.rs index ce43a63ea..8a64018e6 100644 --- a/src/rust/src/rustzcash.rs +++ b/src/rust/src/rustzcash.rs @@ -23,6 +23,7 @@ use bellman::groth16::{self, Parameters, PreparedVerifyingKey}; use blake2s_simd::Params as Blake2sParams; use bls12_381::Bls12; use group::{cofactor::CofactorGroup, GroupEncoding}; +use incrementalmerkletree::Hashable; use libc::{c_uchar, size_t}; use rand_core::{OsRng, RngCore}; use std::fs::File; @@ -43,11 +44,19 @@ use std::ffi::OsString; #[cfg(target_os = "windows")] use std::os::windows::ffi::OsStringExt; +use zcash_note_encryption::{Domain, EphemeralKeyBytes}; use zcash_primitives::{ + consensus::Network, constants::{CRH_IVK_PERSONALIZATION, PROOF_GENERATION_KEY_GENERATOR, SPENDING_KEY_GENERATOR}, + merkle_tree::HashSer, sapling::{ - keys::FullViewingKey, merkle_hash, note_encryption::sapling_ka_agree, redjubjub, spend_sig, - Diversifier, Note, NullifierDerivingKey, Rseed, + keys::FullViewingKey, + merkle_hash, + note::{ExtractedNoteCommitment, NoteCommitment}, + note_encryption::{PreparedIncomingViewingKey, SaplingDomain}, + redjubjub, spend_sig, + value::NoteValue, + Diversifier, Node, Note, NullifierDerivingKey, PaymentAddress, Rseed, SaplingIvk, }, zip32::{self, sapling_address, sapling_derive_internal_fvk, sapling_find_address}, }; @@ -210,12 +219,12 @@ pub extern "C" fn librustzcash_init_zksnark_params( /// `result` must be a valid pointer to 32 bytes which will be written. #[no_mangle] pub extern "C" fn librustzcash_tree_uncommitted(result: *mut [c_uchar; 32]) { - let tmp = Note::uncommitted().to_bytes(); - // Should be okay, caller is responsible for ensuring the pointer // is a valid pointer to 32 bytes that can be mutated. let result = unsafe { &mut *result }; - *result = tmp; + Node::empty_leaf() + .write(&mut result[..]) + .expect("Sapling leaves are 32 bytes"); } /// Computes a merkle tree hash for a given depth. The `depth` parameter should @@ -363,26 +372,24 @@ fn priv_get_note( value: u64, rcm: *const [c_uchar; 32], ) -> Result { - let diversifier = Diversifier(unsafe { *diversifier }); - let g_d = diversifier.g_d().ok_or(())?; - - let pk_d = de_ct(jubjub::ExtendedPoint::from_bytes(unsafe { &*pk_d })).ok_or(())?; - - let pk_d = de_ct(pk_d.into_subgroup()).ok_or(())?; + let recipient_bytes = { + let mut tmp = [0; 43]; + tmp[..11].copy_from_slice(unsafe { &*diversifier }); + tmp[11..].copy_from_slice(unsafe { &*pk_d }); + tmp + }; + let recipient = PaymentAddress::from_bytes(&recipient_bytes).ok_or(())?; // Deserialize randomness // If this is after ZIP 212, the caller has calculated rcm, and we don't need to call // Note::derive_esk, so we just pretend the note was using this rcm all along. let rseed = Rseed::BeforeZip212(de_ct(jubjub::Scalar::from_bytes(unsafe { &*rcm })).ok_or(())?); - let note = Note { - value, - g_d, - pk_d, + Ok(Note::from_parts( + recipient, + NoteValue::from_raw(value), rseed, - }; - - Ok(note) + )) } /// Compute a Sapling nullifier. @@ -440,45 +447,68 @@ pub extern "C" fn librustzcash_sapling_compute_cmu( rcm: *const [c_uchar; 32], result: *mut [c_uchar; 32], ) -> bool { - let note = match priv_get_note(diversifier, pk_d, value, rcm) { - Ok(p) => p, - Err(_) => return false, + let get_cm = || -> Result { + let diversifier = Diversifier(unsafe { *diversifier }); + let g_d = diversifier.g_d().ok_or(())?; + + let pk_d = de_ct(jubjub::ExtendedPoint::from_bytes(unsafe { &*pk_d })).ok_or(())?; + let pk_d = de_ct(pk_d.into_subgroup()).ok_or(())?; + + let rcm = de_ct(jubjub::Scalar::from_bytes(unsafe { &*rcm })).ok_or(())?; + + Ok(NoteCommitment::temporary_zcashd_derive( + g_d.to_bytes(), + pk_d.to_bytes(), + NoteValue::from_raw(value), + rcm, + )) + }; + + let cmu = match get_cm() { + Ok(cm) => ExtractedNoteCommitment::from(cm), + Err(()) => return false, }; let result = unsafe { &mut *result }; - *result = note.cmu().to_bytes(); + *result = cmu.to_bytes(); true } -/// Computes \[sk\] \[8\] P for some 32-byte point P, and 32-byte Fs. +/// Computes KDF^Sapling(KA^Agree(sk, P), ephemeral_key). /// -/// If P or sk are invalid, returns false. Otherwise, the result is written to -/// the 32-byte `result` buffer. +/// `p` and `sk` must point to 32-byte buffers. If `p` does not represent a compressed +/// Jubjub point or `sk` does not represent a canonical Jubjub scalar, this function +/// returns `false`. Otherwise, it writes the result to the 32-byte `result` buffer and +/// returns `true`. #[no_mangle] -pub extern "C" fn librustzcash_sapling_ka_agree( +pub extern "C" fn librustzcash_sapling_ka_derive_symmetric_key( p: *const [c_uchar; 32], sk: *const [c_uchar; 32], + ephemeral_key: *const [c_uchar; 32], result: *mut [c_uchar; 32], ) -> bool { - // Deserialize p - let p = match de_ct(jubjub::ExtendedPoint::from_bytes(unsafe { &*p })) { - Some(p) => p, + // Deserialize p (representing either epk or pk_d; we can handle them identically). + let epk = match SaplingDomain::::epk(&EphemeralKeyBytes(unsafe { *p })) { + Some(p) => SaplingDomain::::prepare_epk(p), None => return false, }; - // Deserialize sk - let sk = match de_ct(jubjub::Scalar::from_bytes(unsafe { &*sk })) { - Some(p) => p, + // Deserialize sk (either ivk or esk; we can handle them identically). + let ivk = match de_ct(jubjub::Scalar::from_bytes(unsafe { &*sk })) { + Some(p) => PreparedIncomingViewingKey::new(&SaplingIvk(p)), None => return false, }; // Compute key agreement - let ka = sapling_ka_agree(&sk, &p); + let secret = SaplingDomain::::ka_agree_dec(&ivk, &epk); // Produce result let result = unsafe { &mut *result }; - *result = ka.to_bytes(); + result.clone_from_slice( + SaplingDomain::::kdf(secret, &EphemeralKeyBytes(unsafe { *ephemeral_key })) + .as_bytes(), + ); true } diff --git a/src/rust/src/sapling.rs b/src/rust/src/sapling.rs index 255232d33..81bbbf156 100644 --- a/src/rust/src/sapling.rs +++ b/src/rust/src/sapling.rs @@ -12,7 +12,9 @@ use zcash_note_encryption::EphemeralKeyBytes; use zcash_primitives::{ merkle_tree::MerklePath, sapling::{ + note::ExtractedNoteCommitment, redjubjub::{self, Signature}, + value::ValueCommitment, Diversifier, Nullifier, PaymentAddress, ProofGenerationKey, Rseed, }, transaction::{ @@ -164,7 +166,7 @@ impl BundleAssembler { spend_auth_sig: &[u8; 64], ) -> bool { // Deserialize the value commitment - let cv = match de_ct(jubjub::ExtendedPoint::from_bytes(cv)) { + let cv = match Option::from(ValueCommitment::from_bytes_not_small_order(cv)) { Some(p) => p, None => return false, }; @@ -188,14 +190,15 @@ impl BundleAssembler { Err(_) => return false, }; - self.shielded_spends.push(sapling::SpendDescription { - cv, - anchor, - nullifier: Nullifier(nullifier), - rk, - zkproof, - spend_auth_sig, - }); + self.shielded_spends + .push(sapling::SpendDescription::temporary_zcashd_from_parts( + cv, + anchor, + Nullifier(nullifier), + rk, + zkproof, + spend_auth_sig, + )); true } @@ -210,26 +213,26 @@ impl BundleAssembler { zkproof: [u8; 192], // GROTH_PROOF_SIZE ) -> bool { // Deserialize the value commitment - let cv = match de_ct(jubjub::ExtendedPoint::from_bytes(cv)) { + let cv = match Option::from(ValueCommitment::from_bytes_not_small_order(cv)) { Some(p) => p, None => return false, }; - // Deserialize the commitment, which should be an element - // of Fr. - let cmu = match de_ct(bls12_381::Scalar::from_bytes(cm)) { + // Deserialize the extracted note commitment. + let cmu = match Option::from(ExtractedNoteCommitment::from_bytes(cm)) { Some(a) => a, None => return false, }; - self.shielded_outputs.push(sapling::OutputDescription { - cv, - cmu, - ephemeral_key: EphemeralKeyBytes(ephemeral_key), - enc_ciphertext, - out_ciphertext, - zkproof, - }); + self.shielded_outputs + .push(sapling::OutputDescription::temporary_zcashd_from_parts( + cv, + cmu, + EphemeralKeyBytes(ephemeral_key), + enc_ciphertext, + out_ciphertext, + zkproof, + )); true } @@ -244,12 +247,12 @@ fn finish_bundle_assembly( let value_balance = Amount::from_i64(value_balance).expect("parsed elsewhere"); let binding_sig = redjubjub::Signature::read(&binding_sig[..]).expect("parsed elsewhere"); - Box::new(Bundle(sapling::Bundle { - shielded_spends: assembler.shielded_spends, - shielded_outputs: assembler.shielded_outputs, + Box::new(Bundle(sapling::Bundle::temporary_zcashd_from_parts( + assembler.shielded_spends, + assembler.shielded_outputs, value_balance, - authorization: sapling::Authorized { binding_sig }, - })) + sapling::Authorized { binding_sig }, + ))) } struct Prover(SaplingProvingContext); @@ -453,7 +456,7 @@ impl Verifier { sighash_value: &[u8; 32], ) -> bool { // Deserialize the value commitment - let cv = match de_ct(jubjub::ExtendedPoint::from_bytes(cv)) { + let cv = match Option::from(ValueCommitment::from_bytes_not_small_order(cv)) { Some(p) => p, None => return false, }; @@ -484,7 +487,7 @@ impl Verifier { }; self.0.check_spend( - cv, + &cv, anchor, nullifier, rk, @@ -505,14 +508,13 @@ impl Verifier { zkproof: &[u8; GROTH_PROOF_SIZE], ) -> bool { // Deserialize the value commitment - let cv = match de_ct(jubjub::ExtendedPoint::from_bytes(cv)) { + let cv = match Option::from(ValueCommitment::from_bytes_not_small_order(cv)) { Some(p) => p, None => return false, }; - // Deserialize the commitment, which should be an element - // of Fr. - let cm = match de_ct(bls12_381::Scalar::from_bytes(cm)) { + // Deserialize the extracted note commitment. + let cmu = match Option::from(ExtractedNoteCommitment::from_bytes(cm)) { Some(a) => a, None => return false, }; @@ -530,8 +532,8 @@ impl Verifier { }; self.0.check_output( - cv, - cm, + &cv, + cmu, epk, zkproof, &prepare_verifying_key( diff --git a/src/rust/src/tests/key_agreement.rs b/src/rust/src/tests/key_agreement.rs index c999156ed..7185b8a7e 100644 --- a/src/rust/src/tests/key_agreement.rs +++ b/src/rust/src/tests/key_agreement.rs @@ -1,9 +1,11 @@ -use group::{Group, GroupEncoding}; +use std::convert::TryInto; + +use group::Group; use rand_core::{OsRng, RngCore}; use zcash_primitives::sapling::{Diversifier, NullifierDerivingKey, ViewingKey}; use crate::{ - librustzcash_sapling_generate_r, librustzcash_sapling_ka_agree, + librustzcash_sapling_generate_r, librustzcash_sapling_ka_derive_symmetric_key, librustzcash_sapling_ka_derivepublic, }; @@ -39,14 +41,8 @@ fn test_key_agreement() { // we randomly generated let mut shared_secret_sender = [0u8; 32]; - // Serialize pk_d for the call to librustzcash_sapling_ka_agree - let addr_pk_d = addr.pk_d().to_bytes(); - - assert!(librustzcash_sapling_ka_agree( - &addr_pk_d, - &esk, - &mut shared_secret_sender - )); + // Serialize pk_d for the call to librustzcash_sapling_ka_derive_symmetric_key + let addr_pk_d = addr.to_bytes()[11..].try_into().unwrap(); // Create epk for the recipient, placed in the transaction. Computed // using the diversifier and esk. @@ -57,11 +53,19 @@ fn test_key_agreement() { &mut epk )); + assert!(librustzcash_sapling_ka_derive_symmetric_key( + &addr_pk_d, + &esk, + &epk, + &mut shared_secret_sender + )); + // Create sharedSecret with ephemeral key let mut shared_secret_recipient = [0u8; 32]; - assert!(librustzcash_sapling_ka_agree( + assert!(librustzcash_sapling_ka_derive_symmetric_key( &epk, &ivk_serialized, + &epk, &mut shared_secret_recipient )); diff --git a/src/rust/src/tests/key_components.rs b/src/rust/src/tests/key_components.rs index df43b2611..d3dc23aeb 100644 --- a/src/rust/src/tests/key_components.rs +++ b/src/rust/src/tests/key_components.rs @@ -685,7 +685,7 @@ fn key_components() { assert!(librustzcash_check_diversifier(&tv.default_d)); let addr = fvk.to_payment_address(diversifier).unwrap(); - assert_eq!(&addr.pk_d().to_bytes(), &tv.default_pk_d); + assert_eq!(&addr.to_bytes()[11..], &tv.default_pk_d); { let mut default_pk_d = [0u8; 32]; librustzcash_ivk_to_pkd(&tv.ivk, &tv.default_d, &mut default_pk_d); @@ -693,9 +693,7 @@ fn key_components() { } let note_r = jubjub::Scalar::from_bytes(&tv.note_r).unwrap(); - let note = addr - .create_note(tv.note_v, Rseed::BeforeZip212(note_r)) - .unwrap(); + let note = addr.create_note(tv.note_v, Rseed::BeforeZip212(note_r)); assert_eq!(¬e.cmu().to_bytes(), &tv.note_cm); assert_eq!(note.nf(&fvk.nk, tv.note_pos), Nullifier(tv.note_nf)); diff --git a/src/rust/src/transaction_ffi.rs b/src/rust/src/transaction_ffi.rs index 517567a73..bd8c92efb 100644 --- a/src/rust/src/transaction_ffi.rs +++ b/src/rust/src/transaction_ffi.rs @@ -10,7 +10,7 @@ use zcash_primitives::{ consensus::BranchId, legacy::Script, transaction::{ - components::{orchard as orchard_serialization, sapling, transparent, Amount}, + components::{sapling, transparent, Amount}, sighash::{SignableInput, TransparentAuthorizingContext}, sighash_v5::v5_signature_hash, txid::TxIdDigester, @@ -102,44 +102,6 @@ impl transparent::MapAuth for MapTrans } } -// TODO: Move these trait impls into `zcash_primitives` so they can be on `()`. -struct IdentityMap; - -impl sapling::MapAuth for IdentityMap { - fn map_proof( - &self, - p: ::Proof, - ) -> ::Proof { - p - } - - fn map_auth_sig( - &self, - s: ::AuthSig, - ) -> ::AuthSig { - s - } - - fn map_authorization(&self, a: sapling::Authorized) -> sapling::Authorized { - a - } -} - -impl orchard_serialization::MapAuth - for IdentityMap -{ - fn map_spend_auth( - &self, - s: ::SpendAuth, - ) -> ::SpendAuth { - s - } - - fn map_authorization(&self, a: orchard::bundle::Authorized) -> orchard::bundle::Authorized { - a - } -} - pub(crate) struct PrecomputedAuth; impl Authorization for PrecomputedAuth { @@ -240,9 +202,7 @@ pub extern "C" fn zcash_transaction_precomputed_init( }, }; - let tx = tx - .into_data() - .map_authorization(f_transparent, IdentityMap, IdentityMap); + let tx = tx.into_data().map_authorization(f_transparent, (), ()); let txid_parts = tx.digest(TxIdDigester); Box::into_raw(Box::new(PrecomputedTxParts { tx, txid_parts })) diff --git a/src/rust/src/wallet_scanner.rs b/src/rust/src/wallet_scanner.rs index 2d9cfdcd1..3aa0adf7a 100644 --- a/src/rust/src/wallet_scanner.rs +++ b/src/rust/src/wallet_scanner.rs @@ -1,5 +1,6 @@ use core::fmt; use std::collections::HashMap; +use std::convert::TryInto; use std::io; use std::mem; use std::sync::{ @@ -8,7 +9,6 @@ use std::sync::{ }; use crossbeam_channel as channel; -use group::GroupEncoding; use memuse::DynamicUsage; use zcash_note_encryption::{batch, BatchDomain, Domain, ShieldedOutput, ENC_CIPHERTEXT_SIZE}; use zcash_primitives::{ @@ -711,7 +711,7 @@ impl BatchScanner { block_tag, txid, || SaplingDomain::for_height(params, height), - &bundle.shielded_outputs, + bundle.shielded_outputs(), ); } @@ -768,7 +768,9 @@ impl BatchResult { output: *output as u32, ivk: decrypted_note.ivk_tag, diversifier: decrypted_note.recipient.diversifier().0, - pk_d: decrypted_note.recipient.pk_d().to_bytes(), + pk_d: decrypted_note.recipient.to_bytes()[11..] + .try_into() + .unwrap(), }, ) .collect() diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp index 0455ffa78..ce79dfd25 100644 --- a/src/test/sighash_tests.cpp +++ b/src/test/sighash_tests.cpp @@ -13,6 +13,7 @@ #include "test/test_bitcoin.h" #include "test/test_util.h" #include "util/system.h" +#include "util/test.h" #include "version.h" #include @@ -149,23 +150,10 @@ void static RandomTransaction(CMutableTransaction &tx, bool fSingle, uint32_t co if (tx.nVersionGroupId == SAPLING_VERSION_GROUP_ID) { tx.valueBalanceSapling = InsecureRandRange(100000000); for (int spend = 0; spend < shielded_spends; spend++) { - SpendDescription sdesc; - zcash_test_harness_random_jubjub_point(sdesc.cv.begin()); - zcash_test_harness_random_jubjub_base(sdesc.anchor.begin()); - sdesc.nullifier = InsecureRand256(); - zcash_test_harness_random_jubjub_point(sdesc.rk.begin()); - GetRandBytes(sdesc.zkproof.begin(), sdesc.zkproof.size()); - tx.vShieldedSpend.push_back(sdesc); + tx.vShieldedSpend.push_back(RandomInvalidSpendDescription()); } for (int out = 0; out < shielded_outs; out++) { - OutputDescription odesc; - zcash_test_harness_random_jubjub_point(odesc.cv.begin()); - zcash_test_harness_random_jubjub_base(odesc.cmu.begin()); - zcash_test_harness_random_jubjub_point(odesc.ephemeralKey.begin()); - GetRandBytes(odesc.encCiphertext.begin(), odesc.encCiphertext.size()); - GetRandBytes(odesc.outCiphertext.begin(), odesc.outCiphertext.size()); - GetRandBytes(odesc.zkproof.begin(), odesc.zkproof.size()); - tx.vShieldedOutput.push_back(odesc); + tx.vShieldedOutput.push_back(RandomInvalidOutputDescription()); } } // We have removed pre-Sapling Sprout support. diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 84a13927f..48d098976 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -309,8 +309,7 @@ void test_simple_sapling_invalidity(uint32_t consensusBranchId, CMutableTransact CMutableTransaction newTx(tx); CValidationState state; - newTx.vShieldedSpend.push_back(SpendDescription()); - newTx.vShieldedSpend[0].nullifier = InsecureRand256(); + newTx.vShieldedSpend.push_back(RandomInvalidSpendDescription()); BOOST_CHECK(!CheckTransactionWithoutProofVerification(newTx, state)); BOOST_CHECK(state.GetRejectReason() == "bad-txns-no-sink-of-funds"); @@ -320,12 +319,11 @@ void test_simple_sapling_invalidity(uint32_t consensusBranchId, CMutableTransact CMutableTransaction newTx(tx); CValidationState state; - newTx.vShieldedSpend.push_back(SpendDescription()); - newTx.vShieldedSpend[0].nullifier = InsecureRand256(); + newTx.vShieldedSpend.push_back(RandomInvalidSpendDescription()); - newTx.vShieldedOutput.push_back(OutputDescription()); + newTx.vShieldedOutput.push_back(RandomInvalidOutputDescription()); - newTx.vShieldedSpend.push_back(SpendDescription()); + newTx.vShieldedSpend.push_back(RandomInvalidSpendDescription()); newTx.vShieldedSpend[1].nullifier = newTx.vShieldedSpend[0].nullifier; BOOST_CHECK(!CheckTransactionWithoutProofVerification(newTx, state)); @@ -347,7 +345,7 @@ void test_simple_sapling_invalidity(uint32_t consensusBranchId, CMutableTransact vout.nValue = 1; newTx.vout.push_back(vout); - newTx.vShieldedSpend.push_back(SpendDescription()); + newTx.vShieldedSpend.push_back(RandomInvalidSpendDescription()); BOOST_CHECK(!CheckTransactionWithoutProofVerification(newTx, state)); BOOST_CHECK(state.GetRejectReason() == "bad-cb-has-spend-description"); diff --git a/src/util/test.cpp b/src/util/test.cpp index 9d14748b5..c5db5675f 100644 --- a/src/util/test.cpp +++ b/src/util/test.cpp @@ -11,6 +11,7 @@ #include #include +#include // Sprout CMutableTransaction GetValidSproutReceiveTransaction( @@ -64,8 +65,7 @@ CMutableTransaction GetValidSproutReceiveTransaction( // depend on this happening. if (version >= 4) { // Shielded Output - OutputDescription od; - mtx.vShieldedOutput.push_back(od); + mtx.vShieldedOutput.push_back(RandomInvalidOutputDescription()); } // Empty output script. @@ -334,6 +334,27 @@ CKey AddTestCKeyToKeyStore(CBasicKeyStore& keyStore) { return tsk; } +SpendDescription RandomInvalidSpendDescription() { + SpendDescription sdesc; + zcash_test_harness_random_jubjub_point(sdesc.cv.begin()); + zcash_test_harness_random_jubjub_base(sdesc.anchor.begin()); + sdesc.nullifier = GetRandHash(); + zcash_test_harness_random_jubjub_point(sdesc.rk.begin()); + GetRandBytes(sdesc.zkproof.begin(), sdesc.zkproof.size()); + return sdesc; +} + +OutputDescription RandomInvalidOutputDescription() { + OutputDescription odesc; + zcash_test_harness_random_jubjub_point(odesc.cv.begin()); + zcash_test_harness_random_jubjub_base(odesc.cmu.begin()); + zcash_test_harness_random_jubjub_point(odesc.ephemeralKey.begin()); + GetRandBytes(odesc.encCiphertext.begin(), odesc.encCiphertext.size()); + GetRandBytes(odesc.outCiphertext.begin(), odesc.outCiphertext.size()); + GetRandBytes(odesc.zkproof.begin(), odesc.zkproof.size()); + return odesc; +} + TestSaplingNote GetTestSaplingNote(const libzcash::SaplingPaymentAddress& pa, CAmount value) { // Generate dummy Sapling note libzcash::SaplingNote note(pa, value, libzcash::Zip212Enabled::BeforeZip212); diff --git a/src/util/test.h b/src/util/test.h index 407a430a5..94b16b642 100644 --- a/src/util/test.h +++ b/src/util/test.h @@ -122,6 +122,9 @@ libzcash::SaplingExtendedSpendingKey GetTestMasterSaplingSpendingKey(); CKey AddTestCKeyToKeyStore(CBasicKeyStore& keyStore); +SpendDescription RandomInvalidSpendDescription(); +OutputDescription RandomInvalidOutputDescription(); + /** * Generate a dummy SaplingNote and a SaplingMerkleTree with that note's commitment. */ diff --git a/src/zcash/NoteEncryption.cpp b/src/zcash/NoteEncryption.cpp index 6c427cfae..15c943a01 100644 --- a/src/zcash/NoteEncryption.cpp +++ b/src/zcash/NoteEncryption.cpp @@ -112,19 +112,20 @@ std::optional SaplingNoteEncryption::encrypt_to_recipient( throw std::logic_error("already encrypted to the recipient using this key"); } - uint256 dhsecret; - + // Construct the symmetric key // The new consensus rules from ZIP 216 (https://zips.z.cash/zip-0216#specification) // on pk_d were enabled unconditionally, even before we started to apply them // retroactively. - if (!librustzcash_sapling_ka_agree(pk_d.begin(), esk.begin(), dhsecret.begin())) { + unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE]; + if (!librustzcash_sapling_ka_derive_symmetric_key( + pk_d.begin(), + esk.begin(), + epk.begin(), + K)) + { return std::nullopt; } - // Construct the symmetric key - unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE]; - KDF_Sapling(K, dhsecret, epk); - // The nonce is zero because we never reuse keys unsigned char cipher_nonce[crypto_aead_chacha20poly1305_IETF_NPUBBYTES] = {}; @@ -148,19 +149,19 @@ std::optional AttemptSaplingEncDecryption( const uint256 &epk ) { - uint256 dhsecret; - + // Construct the symmetric key. // We consider ZIP 216 active all of the time because blocks prior to NU5 // activation (on mainnet and testnet) did not contain Sapling transactions // that violated its canonicity rule. - if (!librustzcash_sapling_ka_agree(epk.begin(), ivk.begin(), dhsecret.begin())) { + unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE]; + if (!librustzcash_sapling_ka_derive_symmetric_key( + epk.begin(), + ivk.begin(), + epk.begin(), K)) + { return std::nullopt; } - // Construct the symmetric key - unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE]; - KDF_Sapling(K, dhsecret, epk); - // The nonce is zero because we never reuse keys unsigned char cipher_nonce[crypto_aead_chacha20poly1305_IETF_NPUBBYTES] = {}; @@ -187,19 +188,20 @@ std::optional AttemptSaplingEncDecryption ( const uint256 &pk_d ) { - uint256 dhsecret; - + // Construct the symmetric key. // We consider ZIP 216 active all of the time because blocks prior to NU5 // activation (on mainnet and testnet) did not contain Sapling transactions // that violated its canonicity rule. - if (!librustzcash_sapling_ka_agree(pk_d.begin(), esk.begin(), dhsecret.begin())) { + unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE]; + if (!librustzcash_sapling_ka_derive_symmetric_key( + pk_d.begin(), + esk.begin(), + epk.begin(), + K)) + { return std::nullopt; } - // Construct the symmetric key - unsigned char K[NOTEENCRYPTION_CIPHER_KEYSIZE]; - KDF_Sapling(K, dhsecret, epk); - // The nonce is zero because we never reuse keys unsigned char cipher_nonce[crypto_aead_chacha20poly1305_IETF_NPUBBYTES] = {};