Merge pull request #6459 from str4d/zcash_primitives-0.10

Migrate to `zcash_primitives 0.10`
This commit is contained in:
str4d 2023-03-17 20:03:37 +00:00 committed by GitHub
commit 6ebf01fa83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 305 additions and 263 deletions

8
Cargo.lock generated
View File

@ -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",

View File

@ -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

View File

@ -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 <jack@electriccoin.co>"
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 <jack@electriccoin.co>"
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 <jack@electriccoin.co>"
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 <jack@z.cash>"
criteria = ["crypto-reviewed", "safe-to-deploy"]
@ -994,6 +1018,12 @@ who = "Jack Grigg <jack@z.cash>"
criteria = "safe-to-deploy"
delta = "0.8.0 -> 0.9.0"
[[audits.zcash_proofs]]
who = "Jack Grigg <jack@electriccoin.co>"
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 <daira@jacaranda.org>"
criteria = "safe-to-deploy"

View File

@ -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);

View File

@ -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<libzcash::SaplingPaymentAddress>(&(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 {

View File

@ -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<transparent::Authorized, TransparentAuth> for MapTrans
}
}
// TODO: Move these trait impls into `zcash_primitives` so they can be on `()`.
struct IdentityMap;
impl sapling::MapAuth<sapling::Authorized, sapling::Authorized> for IdentityMap {
fn map_proof(
&self,
p: <sapling::Authorized as sapling::Authorization>::Proof,
) -> <sapling::Authorized as sapling::Authorization>::Proof {
p
}
fn map_auth_sig(
&self,
s: <sapling::Authorized as sapling::Authorization>::AuthSig,
) -> <sapling::Authorized as sapling::Authorization>::AuthSig {
s
}
fn map_authorization(&self, a: sapling::Authorized) -> sapling::Authorized {
a
}
}
impl orchard_serialization::MapAuth<orchard::bundle::Authorized, orchard::bundle::Authorized>
for IdentityMap
{
fn map_spend_auth(
&self,
s: <orchard::bundle::Authorized as orchard::bundle::Authorization>::SpendAuth,
) -> <orchard::bundle::Authorized as orchard::bundle::Authorization>::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<Context>) {
let tx = Transaction::read(&buf[..], tx.consensus_branch_id()).unwrap();
let tx: TransactionData<PrecomputedAuth> =
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<Context>) {
}
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<Context>) {
}
}
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<Context>) {
&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<Context>) {
}
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<Context>) {
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");
}

View File

@ -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
);

View File

@ -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<Box<DecryptedSaplingOutput>, &'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] {

View File

@ -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<Note, ()> {
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<NoteCommitment, ()> {
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::<Network>::epk(&EphemeralKeyBytes(unsafe { *p })) {
Some(p) => SaplingDomain::<Network>::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::<Network>::ka_agree_dec(&ivk, &epk);
// Produce result
let result = unsafe { &mut *result };
*result = ka.to_bytes();
result.clone_from_slice(
SaplingDomain::<Network>::kdf(secret, &EphemeralKeyBytes(unsafe { *ephemeral_key }))
.as_bytes(),
);
true
}

View File

@ -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(

View File

@ -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
));

View File

@ -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!(&note.cmu().to_bytes(), &tv.note_cm);
assert_eq!(note.nf(&fvk.nk, tv.note_pos), Nullifier(tv.note_nf));

View File

@ -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<transparent::Authorized, TransparentAuth> for MapTrans
}
}
// TODO: Move these trait impls into `zcash_primitives` so they can be on `()`.
struct IdentityMap;
impl sapling::MapAuth<sapling::Authorized, sapling::Authorized> for IdentityMap {
fn map_proof(
&self,
p: <sapling::Authorized as sapling::Authorization>::Proof,
) -> <sapling::Authorized as sapling::Authorization>::Proof {
p
}
fn map_auth_sig(
&self,
s: <sapling::Authorized as sapling::Authorization>::AuthSig,
) -> <sapling::Authorized as sapling::Authorization>::AuthSig {
s
}
fn map_authorization(&self, a: sapling::Authorized) -> sapling::Authorized {
a
}
}
impl orchard_serialization::MapAuth<orchard::bundle::Authorized, orchard::bundle::Authorized>
for IdentityMap
{
fn map_spend_auth(
&self,
s: <orchard::bundle::Authorized as orchard::bundle::Authorization>::SpendAuth,
) -> <orchard::bundle::Authorized as orchard::bundle::Authorization>::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 }))

View File

@ -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()

View File

@ -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 <iostream>
@ -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.

View File

@ -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");

View File

@ -11,6 +11,7 @@
#include <optional>
#include <rust/ed25519.h>
#include <rust/test_harness.h>
// 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);

View File

@ -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.
*/

View File

@ -112,19 +112,20 @@ std::optional<SaplingEncCiphertext> 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<SaplingEncPlaintext> 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<SaplingEncPlaintext> 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] = {};