Merge pull request #755 from zcash/sapling-api-changes

Sapling API changes
This commit is contained in:
Daira Hopwood 2023-01-24 21:53:50 +00:00 committed by GitHub
commit 1b5b8d73e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 662 additions and 531 deletions

View File

@ -13,6 +13,11 @@ and this library adheres to Rust's notion of
takes a `shielding_threshold` argument that can be used to specify the
minimum value allowed as input to a shielding transaction. Previously
the shielding threshold was fixed at 100000 zatoshis.
- Note commitments now use
`zcash_primitives::sapling::note::ExtractedNoteCommitment` instead of
`bls12_381::Scalar` in the following places:
- The `cmu` field of `zcash_client_backend::wallet::WalletShieldedOutput`.
- `zcash_client_backend::proto::compact_formats::CompactSaplingOutput::cmu`.
## [0.6.1] - 2022-12-06
### Added

View File

@ -4,7 +4,7 @@ use zcash_primitives::{
consensus::{self, NetworkUpgrade},
memo::MemoBytes,
merkle_tree::MerklePath,
sapling::{self, prover::TxProver as SaplingProver},
sapling::{self, prover::TxProver as SaplingProver, Node},
transaction::{
builder::Builder,
components::amount::{Amount, BalanceError},
@ -341,9 +341,7 @@ where
RecipientAddress::Unified(ua) => {
builder.add_sapling_output(
ovk,
ua.sapling()
.expect("TODO: Add Orchard support to builder")
.clone(),
*ua.sapling().expect("TODO: Add Orchard support to builder"),
payment.amount,
payment.memo.clone().unwrap_or_else(MemoBytes::empty),
)?;
@ -351,7 +349,7 @@ where
RecipientAddress::Shielded(to) => {
builder.add_sapling_output(
ovk,
to.clone(),
*to,
payment.amount,
payment.memo.clone().unwrap_or_else(MemoBytes::empty),
)?;
@ -386,7 +384,7 @@ where
// Sapling outputs are shuffled, so we need to look up where the output ended up.
RecipientAddress::Shielded(addr) => {
let idx = tx_metadata.output_index(i).expect("An output should exist in the transaction for each shielded payment.");
(idx, Recipient::Sapling(addr.clone()))
(idx, Recipient::Sapling(*addr))
}
RecipientAddress::Unified(addr) => {
// TODO: When we add Orchard support, we will need to trial-decrypt to find them,
@ -549,12 +547,7 @@ where
for change_value in proposal.balance().proposed_change() {
match change_value {
ChangeValue::Sapling(amount) => {
builder.add_sapling_output(
Some(ovk),
shielding_address.clone(),
*amount,
memo.clone(),
)?;
builder.add_sapling_output(Some(ovk), shielding_address, *amount, memo.clone())?;
}
}
}
@ -610,18 +603,18 @@ fn select_key_for_note<N>(
// to spend the note that we've used the correct key.
let external_note = dfvk
.diversified_address(selected.diversifier)
.and_then(|addr| addr.create_note(selected.note_value.into(), selected.rseed));
.map(|addr| addr.create_note(selected.note_value.into(), selected.rseed));
let internal_note = dfvk
.diversified_change_address(selected.diversifier)
.and_then(|addr| addr.create_note(selected.note_value.into(), selected.rseed));
.map(|addr| addr.create_note(selected.note_value.into(), selected.rseed));
let expected_root = selected.witness.root();
external_note
.filter(|n| expected_root == merkle_path.root(n.commitment()))
.filter(|n| expected_root == merkle_path.root(Node::from_cmu(&n.cmu())))
.map(|n| (n, extsk.clone(), merkle_path.clone()))
.or_else(|| {
internal_note
.filter(|n| expected_root == merkle_path.root(n.commitment()))
.filter(|n| expected_root == merkle_path.root(Node::from_cmu(&n.cmu())))
.map(|n| (n, extsk.derive_internal(), merkle_path))
})
}

View File

@ -241,9 +241,6 @@ pub fn decode_extended_full_viewing_key(
///
/// ```
/// use group::Group;
/// use jubjub::SubgroupPoint;
/// use rand_core::SeedableRng;
/// use rand_xorshift::XorShiftRng;
/// use zcash_client_backend::{
/// encoding::encode_payment_address,
/// };
@ -252,15 +249,12 @@ pub fn decode_extended_full_viewing_key(
/// sapling::{Diversifier, PaymentAddress},
/// };
///
/// let rng = &mut XorShiftRng::from_seed([
/// 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
/// 0xbc, 0xe5,
/// ]);
///
/// let pa = PaymentAddress::from_parts(
/// Diversifier([0u8; 11]),
/// SubgroupPoint::random(rng),
/// )
/// let pa = PaymentAddress::from_bytes(&[
/// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x8e, 0x11,
/// 0x9d, 0x72, 0x99, 0x2b, 0x56, 0x0d, 0x26, 0x50, 0xff, 0xe0, 0xbe, 0x7f, 0x35, 0x42,
/// 0xfd, 0x97, 0x00, 0x3c, 0xb7, 0xcc, 0x3a, 0xbf, 0xf8, 0x1a, 0x7f, 0x90, 0x37, 0xf3,
/// 0xea,
/// ])
/// .unwrap();
///
/// assert_eq!(
@ -291,9 +285,6 @@ pub fn encode_payment_address_p<P: consensus::Parameters>(
///
/// ```
/// use group::Group;
/// use jubjub::SubgroupPoint;
/// use rand_core::SeedableRng;
/// use rand_xorshift::XorShiftRng;
/// use zcash_client_backend::{
/// encoding::decode_payment_address,
/// };
@ -302,15 +293,12 @@ pub fn encode_payment_address_p<P: consensus::Parameters>(
/// sapling::{Diversifier, PaymentAddress},
/// };
///
/// let rng = &mut XorShiftRng::from_seed([
/// 0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
/// 0xbc, 0xe5,
/// ]);
///
/// let pa = PaymentAddress::from_parts(
/// Diversifier([0u8; 11]),
/// SubgroupPoint::random(rng),
/// )
/// let pa = PaymentAddress::from_bytes(&[
/// 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x8e, 0x11,
/// 0x9d, 0x72, 0x99, 0x2b, 0x56, 0x0d, 0x26, 0x50, 0xff, 0xe0, 0xbe, 0x7f, 0x35, 0x42,
/// 0xfd, 0x97, 0x00, 0x3c, 0xb7, 0xcc, 0x3a, 0xbf, 0xf8, 0x1a, 0x7f, 0x90, 0x37, 0xf3,
/// 0xea,
/// ])
/// .unwrap();
///
/// assert_eq!(
@ -461,14 +449,7 @@ pub fn decode_transparent_address(
#[cfg(test)]
mod tests {
use group::Group;
use rand_core::SeedableRng;
use rand_xorshift::XorShiftRng;
use zcash_primitives::{
constants,
sapling::{Diversifier, PaymentAddress},
zip32::ExtendedSpendingKey,
};
use zcash_primitives::{constants, sapling::PaymentAddress, zip32::ExtendedSpendingKey};
use super::{
decode_extended_full_viewing_key, decode_extended_spending_key, decode_payment_address,
@ -559,14 +540,13 @@ mod tests {
#[test]
fn payment_address() {
let rng = &mut XorShiftRng::from_seed([
0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
0xbc, 0xe5,
]);
let addr =
PaymentAddress::from_parts(Diversifier([0u8; 11]), jubjub::SubgroupPoint::random(rng))
.unwrap();
let addr = PaymentAddress::from_bytes(&[
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x8e, 0x11,
0x9d, 0x72, 0x99, 0x2b, 0x56, 0x0d, 0x26, 0x50, 0xff, 0xe0, 0xbe, 0x7f, 0x35, 0x42,
0xfd, 0x97, 0x00, 0x3c, 0xb7, 0xcc, 0x3a, 0xbf, 0xf8, 0x1a, 0x7f, 0x90, 0x37, 0xf3,
0xea,
])
.unwrap();
let encoded_main =
"zs1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75c8v35z";
@ -602,22 +582,14 @@ mod tests {
#[test]
fn invalid_diversifier() {
let rng = &mut XorShiftRng::from_seed([
0x59, 0x62, 0xbe, 0x3d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06,
0xbc, 0xe5,
]);
let addr =
PaymentAddress::from_parts(Diversifier([1u8; 11]), jubjub::SubgroupPoint::random(rng))
.unwrap();
// Has a diversifier of `[1u8; 11]`.
let encoded_main =
encode_payment_address(constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS, &addr);
"zs1qyqszqgpqyqszqgpqycguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ugum9p";
assert_eq!(
decode_payment_address(
constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS,
&encoded_main
encoded_main,
),
Err(Bech32DecodeError::ReadError)
);

View File

@ -1,11 +1,9 @@
//! Generated code for handling light client protobuf structs.
use group::ff::PrimeField;
use zcash_primitives::{
block::{BlockHash, BlockHeader},
consensus::BlockHeight,
sapling::Nullifier,
sapling::{note::ExtractedNoteCommitment, Nullifier},
transaction::{components::sapling, TxId},
};
@ -96,10 +94,10 @@ impl compact_formats::CompactSaplingOutput {
/// A convenience method that parses [`CompactOutput.cmu`].
///
/// [`CompactOutput.cmu`]: #structfield.cmu
pub fn cmu(&self) -> Result<bls12_381::Scalar, ()> {
pub fn cmu(&self) -> Result<ExtractedNoteCommitment, ()> {
let mut repr = [0; 32];
repr.as_mut().copy_from_slice(&self.cmu[..]);
Option::from(bls12_381::Scalar::from_repr(repr)).ok_or(())
Option::from(ExtractedNoteCommitment::from_bytes(&repr)).ok_or(())
}
/// Returns the ephemeral public key for this output.
@ -120,7 +118,7 @@ impl<A: sapling::Authorization> From<sapling::OutputDescription<A>>
{
fn from(out: sapling::OutputDescription<A>) -> compact_formats::CompactSaplingOutput {
compact_formats::CompactSaplingOutput {
cmu: out.cmu().to_repr().to_vec(),
cmu: out.cmu().to_bytes().to_vec(),
ephemeral_key: out.ephemeral_key().as_ref().to_vec(),
ciphertext: out.enc_ciphertext()[..COMPACT_NOTE_SIZE].to_vec(),
}

View File

@ -7,7 +7,9 @@ use zcash_primitives::{
keys::OutgoingViewingKey,
legacy::TransparentAddress,
merkle_tree::IncrementalWitness,
sapling::{Diversifier, Node, Note, Nullifier, PaymentAddress, Rseed},
sapling::{
note::ExtractedNoteCommitment, Diversifier, Node, Note, Nullifier, PaymentAddress, Rseed,
},
transaction::{
components::{
sapling,
@ -99,7 +101,7 @@ pub struct WalletShieldedSpend {
/// [`OutputDescription`]: zcash_primitives::transaction::components::OutputDescription
pub struct WalletShieldedOutput<N> {
pub index: usize,
pub cmu: bls12_381::Scalar,
pub cmu: ExtractedNoteCommitment,
pub ephemeral_key: EphemeralKeyBytes,
pub account: AccountId,
pub note: Note,

View File

@ -334,7 +334,7 @@ pub(crate) fn scan_block_with_runner<
.collect();
// Increment tree and witnesses
let node = Node::from_scalar(output.cmu);
let node = Node::from_cmu(&output.cmu);
for witness in &mut *existing_witnesses {
witness.append(node).unwrap();
}
@ -394,14 +394,16 @@ mod tests {
GroupEncoding,
};
use rand_core::{OsRng, RngCore};
use zcash_note_encryption::Domain;
use zcash_primitives::{
consensus::{BlockHeight, Network},
constants::SPENDING_KEY_GENERATOR,
memo::MemoBytes,
merkle_tree::CommitmentTree,
sapling::{
note_encryption::{sapling_note_encryption, PreparedIncomingViewingKey},
note_encryption::{sapling_note_encryption, PreparedIncomingViewingKey, SaplingDomain},
util::generate_random_rseed,
value::NoteValue,
Note, Nullifier, SaplingIvk,
},
transaction::components::Amount,
@ -464,12 +466,7 @@ mod tests {
// Create a fake Note for the account
let mut rng = OsRng;
let rseed = generate_random_rseed(&Network::TestNetwork, height, &mut rng);
let note = Note {
g_d: to.diversifier().g_d().unwrap(),
pk_d: *to.pk_d(),
value: value.into(),
rseed,
};
let note = Note::from_parts(to, NoteValue::from_raw(value.into()), rseed);
let encryptor = sapling_note_encryption::<_, Network>(
Some(dfvk.fvk().ovk),
note.clone(),
@ -477,8 +474,10 @@ mod tests {
MemoBytes::empty(),
&mut rng,
);
let cmu = note.cmu().to_repr().as_ref().to_owned();
let ephemeral_key = encryptor.epk().to_bytes().to_vec();
let cmu = note.cmu().to_bytes().to_vec();
let ephemeral_key = SaplingDomain::<Network>::epk_bytes(encryptor.epk())
.0
.to_vec();
let enc_ciphertext = encryptor.encrypt_note_plaintext();
// Create a fake CompactBlock containing the note
@ -577,7 +576,7 @@ mod tests {
assert_eq!(tx.shielded_outputs.len(), 1);
assert_eq!(tx.shielded_outputs[0].index, 0);
assert_eq!(tx.shielded_outputs[0].account, account);
assert_eq!(tx.shielded_outputs[0].note.value, 5);
assert_eq!(tx.shielded_outputs[0].note.value().inner(), 5);
// Check that the witness root matches
assert_eq!(tx.shielded_outputs[0].witness.root(), tree.root());
@ -640,7 +639,7 @@ mod tests {
assert_eq!(tx.shielded_outputs.len(), 1);
assert_eq!(tx.shielded_outputs[0].index, 0);
assert_eq!(tx.shielded_outputs[0].account, AccountId::from(0));
assert_eq!(tx.shielded_outputs[0].note.value, 5);
assert_eq!(tx.shielded_outputs[0].note.value().inner(), 5);
// Check that the witness root matches
assert_eq!(tx.shielded_outputs[0].witness.root(), tree.root());

View File

@ -50,6 +50,7 @@ proptest = "1.0.0"
rand_core = "0.6"
regex = "1.4"
tempfile = "3"
zcash_note_encryption = { version = "0.2", path = "../components/zcash_note_encryption" }
zcash_proofs = { version = "0.9", path = "../zcash_proofs" }
zcash_primitives = { version = "0.9", path = "../zcash_primitives", features = ["test-dependencies"] }
zcash_address = { version = "0.2", path = "../components/zcash_address", features = ["test-dependencies"] }

View File

@ -584,7 +584,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
match output.transfer_type {
TransferType::Outgoing | TransferType::WalletInternal => {
let recipient = if output.transfer_type == TransferType::Outgoing {
Recipient::Sapling(output.to.clone())
Recipient::Sapling(output.to)
} else {
Recipient::InternalAccount(output.account, PoolType::Sapling)
};
@ -595,7 +595,7 @@ impl<'a, P: consensus::Parameters> WalletWrite for DataConnStmtCache<'a, P> {
tx_ref,
output.index,
&recipient,
Amount::from_u64(output.note.value).map_err(|_|
Amount::from_u64(output.note.value().inner()).map_err(|_|
SqliteClientError::CorruptedData("Note value is not a valid Zcash amount.".to_string()))?,
Some(&output.memo),
)?;
@ -1003,7 +1003,6 @@ extern crate assert_matches;
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use group::{ff::PrimeField, GroupEncoding};
use prost::Message;
use rand_core::{OsRng, RngCore};
use rusqlite::params;
@ -1012,14 +1011,17 @@ mod tests {
#[cfg(feature = "transparent-inputs")]
use zcash_primitives::{legacy, legacy::keys::IncomingViewingKey};
use zcash_note_encryption::Domain;
use zcash_primitives::{
block::BlockHash,
consensus::{BlockHeight, Network, NetworkUpgrade, Parameters},
legacy::TransparentAddress,
memo::MemoBytes,
sapling::{
note_encryption::sapling_note_encryption, util::generate_random_rseed, Note, Nullifier,
PaymentAddress,
note_encryption::{sapling_note_encryption, SaplingDomain},
util::generate_random_rseed,
value::NoteValue,
Note, Nullifier, PaymentAddress,
},
transaction::components::Amount,
zip32::{sapling::DiversifiableFullViewingKey, DiversifierIndex},
@ -1132,12 +1134,7 @@ mod tests {
// Create a fake Note for the account
let mut rng = OsRng;
let rseed = generate_random_rseed(&network(), height, &mut rng);
let note = Note {
g_d: to.diversifier().g_d().unwrap(),
pk_d: *to.pk_d(),
value: value.into(),
rseed,
};
let note = Note::from_parts(to, NoteValue::from_raw(value.into()), rseed);
let encryptor = sapling_note_encryption::<_, Network>(
Some(dfvk.fvk().ovk),
note.clone(),
@ -1145,8 +1142,10 @@ mod tests {
MemoBytes::empty(),
&mut rng,
);
let cmu = note.cmu().to_repr().as_ref().to_vec();
let ephemeral_key = encryptor.epk().to_bytes().to_vec();
let cmu = note.cmu().to_bytes().to_vec();
let ephemeral_key = SaplingDomain::<Network>::epk_bytes(encryptor.epk())
.0
.to_vec();
let enc_ciphertext = encryptor.encrypt_note_plaintext();
// Create a fake CompactBlock containing the note
@ -1197,12 +1196,7 @@ mod tests {
// Create a fake Note for the payment
ctx.outputs.push({
let note = Note {
g_d: to.diversifier().g_d().unwrap(),
pk_d: *to.pk_d(),
value: value.into(),
rseed,
};
let note = Note::from_parts(to, NoteValue::from_raw(value.into()), rseed);
let encryptor = sapling_note_encryption::<_, Network>(
Some(dfvk.fvk().ovk),
note.clone(),
@ -1210,8 +1204,10 @@ mod tests {
MemoBytes::empty(),
&mut rng,
);
let cmu = note.cmu().to_repr().as_ref().to_vec();
let ephemeral_key = encryptor.epk().to_bytes().to_vec();
let cmu = note.cmu().to_bytes().to_vec();
let ephemeral_key = SaplingDomain::<Network>::epk_bytes(encryptor.epk())
.0
.to_vec();
let enc_ciphertext = encryptor.encrypt_note_plaintext();
CompactSaplingOutput {
@ -1225,12 +1221,11 @@ mod tests {
ctx.outputs.push({
let change_addr = dfvk.default_address().1;
let rseed = generate_random_rseed(&network(), height, &mut rng);
let note = Note {
g_d: change_addr.diversifier().g_d().unwrap(),
pk_d: *change_addr.pk_d(),
value: (in_value - value).unwrap().into(),
let note = Note::from_parts(
change_addr,
NoteValue::from_raw((in_value - value).unwrap().into()),
rseed,
};
);
let encryptor = sapling_note_encryption::<_, Network>(
Some(dfvk.fvk().ovk),
note.clone(),
@ -1238,8 +1233,10 @@ mod tests {
MemoBytes::empty(),
&mut rng,
);
let cmu = note.cmu().to_repr().as_ref().to_vec();
let ephemeral_key = encryptor.epk().to_bytes().to_vec();
let cmu = note.cmu().to_bytes().to_vec();
let ephemeral_key = SaplingDomain::<Network>::epk_bytes(encryptor.epk())
.0
.to_vec();
let enc_ciphertext = encryptor.encrypt_note_plaintext();
CompactSaplingOutput {

View File

@ -1188,7 +1188,7 @@ pub fn put_received_note<'a, P, T: ShieldedOutput>(
let rcm = output.note().rcm().to_repr();
let account = output.account();
let diversifier = output.to().diversifier();
let value = output.note().value;
let value = output.note().value();
let memo = output.memo();
let is_change = output.is_change();
let output_index = output.index();
@ -1198,7 +1198,7 @@ pub fn put_received_note<'a, P, T: ShieldedOutput>(
if !stmts.stmt_update_received_note(
account,
diversifier,
value,
value.inner(),
rcm,
&nf,
memo,
@ -1212,7 +1212,7 @@ pub fn put_received_note<'a, P, T: ShieldedOutput>(
output_index,
account,
diversifier,
value,
value.inner(),
rcm,
&nf,
memo,

View File

@ -670,7 +670,7 @@ mod tests {
let extsk2 = ExtendedSpendingKey::master(&[]);
let addr2 = extsk2.default_address().1;
let to = addr2.clone().into();
let to = addr2.into();
let send_and_recover_with_policy = |db_write: &mut DataConnStmtCache<'_, _>, ovk_policy| {
let tx_row = create_spend_to_address(

View File

@ -488,7 +488,7 @@ mod tests {
extensions::transparent::{self as tze, Extension, FromPayload, ToPayload},
legacy::TransparentAddress,
merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::Rseed,
sapling::{Node, Rseed},
transaction::{
builder::Builder,
components::{
@ -814,10 +814,8 @@ mod tests {
// create some inputs to spend
let extsk = ExtendedSpendingKey::master(&[]);
let to = extsk.default_address().1;
let note1 = to
.create_note(101000, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)))
.unwrap();
let cm1 = note1.commitment();
let note1 = to.create_note(101000, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)));
let cm1 = Node::from_cmu(&note1.cmu());
let mut tree = CommitmentTree::empty();
// fake that the note appears in some previous
// shielded output

View File

@ -8,15 +8,51 @@ and this library adheres to Rust's notion of
## [Unreleased]
### Added
- `zcash_primitives::sapling`:
- `keys::DiversifiedTransmissionKey`
- `keys::{EphemeralSecretKey, EphemeralPublicKey, SharedSecret}`
- `keys::{PreparedIncomingViewingKey, PreparedEphemeralPublicKey}`
(re-exported from `note_encryption`).
- `note`, a module containing types related to Sapling notes. The existing
`Note` and `Rseed` types are re-exported here, and new types are added.
- `Node::from_cmu`
- `value`, containing types for handling Sapling note values and value
commitments.
- `Note::from_parts`
- `Note::{recipient, value, rseed}` getter methods.
- `impl Eq for Note`
- `impl Copy for PaymentAddress`
### Changed
- MSRV is now 1.60.0.
- `zcash_primitives::transaction::components::sapling::builder`:
- `SaplingBuilder::add_output` now takes a
`zcash_primitives::sapling::value::NoteValue`.
- `zcash_primitives::sapling`:
- `PaymentAddress::from_parts` now rejects invalid diversifiers.
- `PaymentAddress::create_note` is now infallible.
- `DiversifiedTransmissionKey` is now used instead of `jubjub::SubgroupPoint`
in the following places:
- `PaymentAddress::from_parts`
- `PaymentAddress::pk_d`
- `note_encryption::SaplingDomain::DiversifiedTransmissionKey`
- `EphemeralSecretKey` is now used instead of `jubjub::Scalar` in the
following places:
- `Note::generate_or_derive_esk`
- `note_encryption::SaplingDomain::EphemeralSecretKey`
- `note_encryption::SaplingDomain::EphemeralPublicKey` is now
`EphemeralPublicKey` instead of `jubjub::ExtendedPoint`.
- `note_encryption::SaplingDomain::SharedSecret` is now `SharedSecret` instead
of `jubjub::SubgroupPoint`.
- Note commitments now use
`zcash_primitives::sapling::note::ExtractedNoteCommitment` instead of
`bls12_381::Scalar` in the following places:
- `zcash_primitives::sapling`:
- `Note::cmu`
- `zcash_primitives::sapling::note_encryption`:
- `SaplingDomain::ExtractedCommitment`
- `zcash_primitives::transaction::components::sapling`:
- `OutputDescription::cmu`
- The `cmu` field of `CompactOutputDescription`.
- Value commitments now use `zcash_primitives::sapling::value::ValueCommitment`
instead of `jubjub::ExtendedPoint` in the following places:
- `zcash_primitives::sapling::note_encryption`:
@ -30,12 +66,22 @@ and this library adheres to Rust's notion of
- `zcash_primitives::transaction::components`:
- `sapling::{Bundle, SpendDescription, OutputDescription}` have had their
fields replaced by getter methods.
- The associated type `sapling::Authorization::Proof` has been replaced by
`Authorization::{SpendProof, OutputProof}`.
- `sapling::MapAuth::map_proof` has been replaced by
`MapAuth::{map_spend_proof, map_output_proof}`.
### Removed
- `zcash_primitives::sapling`:
- The fields of `Note` are now private (use the new getter methods instead).
- `Note::uncommitted` (use `Node::empty_leaf` instead).
- `Note::derive_esk` (use `SaplingDomain::derive_esk` instead).
- `Note::commitment` (use `Node::from_cmu` instead).
- `PaymentAddress::g_d`
- `NoteValue` (use `zcash_primitives::sapling::value::NoteValue` instead).
- `ValueCommitment` (use `zcash_primitives::sapling::value::ValueCommitment`
or `zcash_proofs::circuit::sapling::ValueCommitmentPreimage` instead).
- `note_encryption::sapling_ka_agree`
- `testing::{arb_note_value, arb_positive_note_value}` (use the methods in
`zcash_primitives::sapling::value::testing` instead).
- `zcash_primitives::transaction::components`:

View File

@ -14,7 +14,7 @@ use zcash_primitives::{
},
prover::mock::MockTxProver,
value::NoteValue,
Diversifier, PaymentAddress, SaplingIvk,
Diversifier, SaplingIvk,
},
transaction::components::sapling::{
builder::SaplingBuilder, CompactOutputDescription, GrothProofBytes, OutputDescription,
@ -34,8 +34,7 @@ fn bench_note_decryption(c: &mut Criterion) {
// Construct a Sapling output.
let output: OutputDescription<GrothProofBytes> = {
let diversifier = Diversifier([0; 11]);
let pk_d = diversifier.g_d().unwrap() * valid_ivk.0;
let pa = PaymentAddress::from_parts(diversifier, pk_d).unwrap();
let pa = valid_ivk.to_payment_address(diversifier).unwrap();
let mut builder = SaplingBuilder::new(TEST_NETWORK, height);
builder

View File

@ -3,7 +3,7 @@
mod address;
pub mod group_hash;
pub mod keys;
mod note;
pub mod note;
pub mod note_encryption;
pub mod pedersen_hash;
pub mod prover;

View File

@ -1,19 +1,19 @@
use group::{Group, GroupEncoding};
use super::{
keys::Diversifier,
keys::{DiversifiedTransmissionKey, Diversifier},
note::{Note, Rseed},
value::NoteValue,
};
/// A Sapling payment address.
///
/// # Invariants
///
/// `pk_d` is guaranteed to be prime-order (i.e. in the prime-order subgroup of Jubjub,
/// and not the identity).
#[derive(Clone, Debug)]
/// - `diversifier` is guaranteed to be valid for Sapling (only 50% of diversifiers are).
/// - `pk_d` is guaranteed to be prime-order (i.e. in the prime-order subgroup of Jubjub,
/// and not the identity).
#[derive(Clone, Copy, Debug)]
pub struct PaymentAddress {
pk_d: jubjub::SubgroupPoint,
pk_d: DiversifiedTransmissionKey,
diversifier: Diversifier,
}
@ -28,26 +28,21 @@ impl Eq for PaymentAddress {}
impl PaymentAddress {
/// Constructs a PaymentAddress from a diversifier and a Jubjub point.
///
/// Returns None if `pk_d` is the identity.
pub fn from_parts(diversifier: Diversifier, pk_d: jubjub::SubgroupPoint) -> Option<Self> {
if pk_d.is_identity().into() {
/// Returns None if `diversifier` is not valid for Sapling, or `pk_d` is the identity.
/// Note that we cannot verify in this constructor that `pk_d` is derived from
/// `diversifier`, so addresses for which these values have no known relationship
/// (and therefore no-one can receive funds at them) can still be constructed.
pub fn from_parts(diversifier: Diversifier, pk_d: DiversifiedTransmissionKey) -> Option<Self> {
// Check that the diversifier is valid
diversifier.g_d()?;
if pk_d.is_identity() {
None
} else {
Some(PaymentAddress { pk_d, diversifier })
}
}
/// Constructs a PaymentAddress from a diversifier and a Jubjub point.
///
/// Only for test code, as this explicitly bypasses the invariant.
#[cfg(test)]
pub(crate) fn from_parts_unchecked(
diversifier: Diversifier,
pk_d: jubjub::SubgroupPoint,
) -> Self {
PaymentAddress { pk_d, diversifier }
}
/// Parses a PaymentAddress from bytes.
pub fn from_bytes(bytes: &[u8; 43]) -> Option<Self> {
let diversifier = {
@ -55,11 +50,10 @@ impl PaymentAddress {
tmp.copy_from_slice(&bytes[0..11]);
Diversifier(tmp)
};
// Check that the diversifier is valid
diversifier.g_d()?;
let pk_d = jubjub::SubgroupPoint::from_bytes(bytes[11..43].try_into().unwrap());
let pk_d = DiversifiedTransmissionKey::from_bytes(bytes[11..43].try_into().unwrap());
if pk_d.is_some().into() {
// The remaining invariants are checked here.
PaymentAddress::from_parts(diversifier, pk_d.unwrap())
} else {
None
@ -80,21 +74,16 @@ impl PaymentAddress {
}
/// Returns `pk_d` for this `PaymentAddress`.
pub fn pk_d(&self) -> &jubjub::SubgroupPoint {
pub fn pk_d(&self) -> &DiversifiedTransmissionKey {
&self.pk_d
}
pub fn g_d(&self) -> Option<jubjub::SubgroupPoint> {
self.diversifier.g_d()
pub(crate) fn g_d(&self) -> jubjub::SubgroupPoint {
self.diversifier.g_d().expect("checked at construction")
}
pub fn create_note(&self, value: u64, rseed: Rseed) -> Option<Note> {
self.g_d().map(|g_d| Note {
value,
rseed,
g_d,
pk_d: self.pk_d,
})
pub fn create_note(&self, value: u64, rseed: Rseed) -> Note {
Note::from_parts(*self, NoteValue::from_raw(value), rseed)
}
}

View File

@ -8,12 +8,11 @@ use std::io::{self, Read, Write};
use super::{
address::PaymentAddress,
note_encryption::{
PreparedEphemeralPublicKey, PreparedIncomingViewingKey, KDF_SAPLING_PERSONALIZATION,
},
note_encryption::KDF_SAPLING_PERSONALIZATION,
spec::{
crh_ivk, diversify_hash, ka_sapling_agree, ka_sapling_agree_prepared,
ka_sapling_derive_public,
ka_sapling_derive_public, ka_sapling_derive_public_subgroup_prepared, PreparedBase,
PreparedBaseSubgroup, PreparedScalar,
},
};
use crate::{
@ -24,7 +23,7 @@ use crate::{
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
use ff::PrimeField;
use group::{Curve, Group, GroupEncoding};
use subtle::{ConstantTimeEq, CtOption};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
use zcash_note_encryption::EphemeralKeyBytes;
/// Errors that can occur in the decoding of Sapling spending keys.
@ -241,11 +240,9 @@ pub struct SaplingIvk(pub jubjub::Fr);
impl SaplingIvk {
pub fn to_payment_address(&self, diversifier: Diversifier) -> Option<PaymentAddress> {
diversifier.g_d().and_then(|g_d| {
let pk_d = g_d * self.0;
PaymentAddress::from_parts(diversifier, pk_d)
})
let prepared_ivk = PreparedIncomingViewingKey::new(self);
DiversifiedTransmissionKey::derive(&prepared_ivk, &diversifier)
.and_then(|pk_d| PaymentAddress::from_parts(diversifier, pk_d))
}
pub fn to_repr(&self) -> [u8; 32] {
@ -253,6 +250,27 @@ impl SaplingIvk {
}
}
/// A Sapling incoming viewing key that has been precomputed for trial decryption.
#[derive(Clone, Debug)]
pub struct PreparedIncomingViewingKey(PreparedScalar);
impl memuse::DynamicUsage for PreparedIncomingViewingKey {
fn dynamic_usage(&self) -> usize {
self.0.dynamic_usage()
}
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
self.0.dynamic_usage_bounds()
}
}
impl PreparedIncomingViewingKey {
/// Performs the necessary precomputations to use a `SaplingIvk` for note decryption.
pub fn new(ivk: &SaplingIvk) -> Self {
Self(PreparedScalar::new(&ivk.0))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Diversifier(pub [u8; 11]);
@ -262,6 +280,62 @@ impl Diversifier {
}
}
/// The diversified transmission key for a given payment address.
///
/// Defined in [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents].
///
/// Note that this type is allowed to be the identity in the protocol, but we reject this
/// in [`PaymentAddress::from_parts`].
///
/// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct DiversifiedTransmissionKey(jubjub::SubgroupPoint);
impl DiversifiedTransmissionKey {
/// Defined in [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents].
///
/// Returns `None` if `d` is an invalid diversifier.
///
/// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
pub(crate) fn derive(ivk: &PreparedIncomingViewingKey, d: &Diversifier) -> Option<Self> {
d.g_d()
.map(PreparedBaseSubgroup::new)
.map(|g_d| ka_sapling_derive_public_subgroup_prepared(&ivk.0, &g_d))
.map(DiversifiedTransmissionKey)
}
/// $abst_J(bytes)$
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
jubjub::SubgroupPoint::from_bytes(bytes).map(DiversifiedTransmissionKey)
}
/// $repr_J(self)$
pub(crate) fn to_bytes(self) -> [u8; 32] {
self.0.to_bytes()
}
/// Returns true if this is the identity.
pub(crate) fn is_identity(&self) -> bool {
self.0.is_identity().into()
}
/// Exposes the inner Jubjub point.
///
/// This API is exposed for `zcash_proof` usage, and will be removed when this type is
/// refactored into the `sapling-crypto` crate.
pub fn inner(&self) -> jubjub::SubgroupPoint {
self.0
}
}
impl ConditionallySelectable for DiversifiedTransmissionKey {
fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self {
DiversifiedTransmissionKey(jubjub::SubgroupPoint::conditional_select(
&a.0, &b.0, choice,
))
}
}
/// An ephemeral secret key used to encrypt an output note on-chain.
///
/// `esk` is "ephemeral" in the sense that each secret key is only used once. In
@ -290,8 +364,8 @@ impl EphemeralSecretKey {
EphemeralPublicKey(ka_sapling_derive_public(&self.0, &g_d))
}
pub(crate) fn agree(&self, pk_d: &jubjub::ExtendedPoint) -> SharedSecret {
SharedSecret(ka_sapling_agree(&self.0, pk_d))
pub(crate) fn agree(&self, pk_d: &DiversifiedTransmissionKey) -> SharedSecret {
SharedSecret(ka_sapling_agree(&self.0, &pk_d.0.into()))
}
}
@ -309,14 +383,8 @@ impl EphemeralSecretKey {
pub struct EphemeralPublicKey(jubjub::ExtendedPoint);
impl EphemeralPublicKey {
/// TODO: Remove once public API is changed.
pub(crate) fn from_inner(epk: jubjub::ExtendedPoint) -> Self {
EphemeralPublicKey(epk)
}
/// TODO: Remove once public API is changed.
pub(crate) fn into_inner(self) -> jubjub::ExtendedPoint {
self.0
pub(crate) fn from_affine(epk: jubjub::AffinePoint) -> Self {
EphemeralPublicKey(epk.into())
}
pub(crate) fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self> {
@ -328,9 +396,17 @@ impl EphemeralPublicKey {
}
}
/// A Sapling ephemeral public key that has been precomputed for trial decryption.
#[derive(Clone, Debug)]
pub struct PreparedEphemeralPublicKey(PreparedBase);
impl PreparedEphemeralPublicKey {
pub(crate) fn new(epk: EphemeralPublicKey) -> Self {
PreparedEphemeralPublicKey(PreparedBase::new(epk.0))
}
pub(crate) fn agree(&self, ivk: &PreparedIncomingViewingKey) -> SharedSecret {
SharedSecret(ka_sapling_agree_prepared(ivk.inner(), self.inner()))
SharedSecret(ka_sapling_agree_prepared(&ivk.0, &self.0))
}
}
@ -343,16 +419,6 @@ impl PreparedEphemeralPublicKey {
pub struct SharedSecret(jubjub::SubgroupPoint);
impl SharedSecret {
/// TODO: Remove once public API is changed.
pub(crate) fn from_inner(epk: jubjub::SubgroupPoint) -> Self {
SharedSecret(epk)
}
/// TODO: Remove once public API is changed.
pub(crate) fn into_inner(self) -> jubjub::SubgroupPoint {
self.0
}
/// For checking test vectors only.
#[cfg(test)]
pub(crate) fn to_bytes(&self) -> [u8; 32] {

View File

@ -1,10 +1,9 @@
use group::{
ff::{Field, PrimeField},
GroupEncoding,
};
use group::{ff::Field, GroupEncoding};
use rand_core::{CryptoRng, RngCore};
use super::{value::NoteValue, Node, Nullifier, NullifierDerivingKey};
use super::{
keys::EphemeralSecretKey, value::NoteValue, Nullifier, NullifierDerivingKey, PaymentAddress,
};
use crate::keys::prf_expand;
mod commitment;
@ -37,40 +36,68 @@ impl Rseed {
}
}
/// A discrete amount of funds received by an address.
#[derive(Clone, Debug)]
pub struct Note {
/// The value of the note
pub value: u64,
/// The diversified base of the address, GH(d)
pub g_d: jubjub::SubgroupPoint,
/// The public key of the address, g_d^ivk
pub pk_d: jubjub::SubgroupPoint,
/// rseed
pub rseed: Rseed,
/// The recipient of the funds.
recipient: PaymentAddress,
/// The value of this note.
value: NoteValue,
/// The seed randomness for various note components.
rseed: Rseed,
}
impl PartialEq for Note {
fn eq(&self, other: &Self) -> bool {
self.value == other.value
&& self.g_d == other.g_d
&& self.pk_d == other.pk_d
&& self.rcm() == other.rcm()
// Notes are canonically defined by their commitments.
self.cmu().eq(&other.cmu())
}
}
impl Eq for Note {}
impl Note {
pub fn uncommitted() -> bls12_381::Scalar {
// The smallest u-coordinate that is not on the curve
// is one.
bls12_381::Scalar::one()
/// Creates a note from its component parts.
///
/// # Caveats
///
/// This low-level constructor enforces that the provided arguments produce an
/// internally valid `Note`. However, it allows notes to be constructed in a way that
/// violates required security checks for note decryption, as specified in
/// [Section 4.19] of the Zcash Protocol Specification. Users of this constructor
/// should only call it with note components that have been fully validated by
/// decrypting a received note according to [Section 4.19].
///
/// [Section 4.19]: https://zips.z.cash/protocol/protocol.pdf#saplingandorchardinband
pub fn from_parts(recipient: PaymentAddress, value: NoteValue, rseed: Rseed) -> Self {
Note {
recipient,
value,
rseed,
}
}
/// Returns the recipient of this note.
pub fn recipient(&self) -> PaymentAddress {
self.recipient
}
/// Returns the value of this note.
pub fn value(&self) -> NoteValue {
self.value
}
/// Returns the rseed value of this note.
pub fn rseed(&self) -> &Rseed {
&self.rseed
}
/// Computes the note commitment, returning the full point.
fn cm_full_point(&self) -> NoteCommitment {
NoteCommitment::derive(
self.g_d.to_bytes(),
self.pk_d.to_bytes(),
NoteValue::from_raw(self.value),
self.recipient.g_d().to_bytes(),
self.recipient.pk_d().to_bytes(),
self.value,
self.rseed.rcm(),
)
}
@ -82,41 +109,43 @@ impl Note {
}
/// Computes the note commitment
pub fn cmu(&self) -> bls12_381::Scalar {
// TODO: Expose typed representation.
ExtractedNoteCommitment::from(self.cm_full_point()).inner()
pub fn cmu(&self) -> ExtractedNoteCommitment {
self.cm_full_point().into()
}
/// Defined in [Zcash Protocol Spec § 4.7.2: Sending Notes (Sapling)][saplingsend].
///
/// [saplingsend]: https://zips.z.cash/protocol/protocol.pdf#saplingsend
pub fn rcm(&self) -> jubjub::Fr {
self.rseed.rcm().0
}
pub fn generate_or_derive_esk<R: RngCore + CryptoRng>(&self, rng: &mut R) -> jubjub::Fr {
/// Derives `esk` from the internal `Rseed` value, or generates a random value if this
/// note was created with a v1 (i.e. pre-ZIP 212) note plaintext.
pub fn generate_or_derive_esk<R: RngCore + CryptoRng>(
&self,
rng: &mut R,
) -> EphemeralSecretKey {
self.generate_or_derive_esk_internal(rng)
}
pub(crate) fn generate_or_derive_esk_internal<R: RngCore>(&self, rng: &mut R) -> jubjub::Fr {
pub(crate) fn generate_or_derive_esk_internal<R: RngCore>(
&self,
rng: &mut R,
) -> EphemeralSecretKey {
match self.derive_esk() {
None => jubjub::Fr::random(rng),
None => EphemeralSecretKey(jubjub::Fr::random(rng)),
Some(esk) => esk,
}
}
/// Returns the derived `esk` if this note was created after ZIP 212 activated.
pub fn derive_esk(&self) -> Option<jubjub::Fr> {
pub(crate) fn derive_esk(&self) -> Option<EphemeralSecretKey> {
match self.rseed {
Rseed::BeforeZip212(_) => None,
Rseed::AfterZip212(rseed) => Some(jubjub::Fr::from_bytes_wide(
Rseed::AfterZip212(rseed) => Some(EphemeralSecretKey(jubjub::Fr::from_bytes_wide(
prf_expand(&rseed, &[0x05]).as_array(),
)),
}
}
/// Returns [`self.cmu`] in the correct representation for inclusion in the Sapling
/// note commitment tree.
pub fn commitment(&self) -> Node {
Node {
repr: self.cmu().to_repr(),
))),
}
}
}
@ -132,13 +161,12 @@ pub(super) mod testing {
prop_compose! {
pub fn arb_note(value: NoteValue)(
addr in arb_payment_address(),
recipient in arb_payment_address(),
rseed in prop::array::uniform32(prop::num::u8::ANY).prop_map(Rseed::AfterZip212)
) -> Note {
Note {
value: value.inner(),
g_d: addr.g_d().unwrap(), // this unwrap is safe because arb_payment_address always generates an address with a valid g_d
pk_d: *addr.pk_d(),
recipient,
value,
rseed
}
}

View File

@ -74,12 +74,6 @@ impl From<NoteCommitment> for ExtractedNoteCommitment {
}
}
impl ExtractedNoteCommitment {
pub(crate) fn inner(&self) -> bls12_381::Scalar {
self.0
}
}
impl From<&ExtractedNoteCommitment> for [u8; 32] {
fn from(cmu: &ExtractedNoteCommitment) -> Self {
cmu.to_bytes()

View File

@ -5,7 +5,6 @@
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
use byteorder::{LittleEndian, WriteBytesExt};
use ff::PrimeField;
use group::GroupEncoding;
use memuse::DynamicUsage;
use rand_core::RngCore;
@ -20,10 +19,12 @@ use crate::{
consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD},
memo::MemoBytes,
sapling::{
keys::{EphemeralPublicKey, EphemeralSecretKey, OutgoingViewingKey, SharedSecret},
spec::{PreparedBase, PreparedBaseSubgroup, PreparedScalar},
keys::{
DiversifiedTransmissionKey, EphemeralPublicKey, EphemeralSecretKey, OutgoingViewingKey,
SharedSecret,
},
value::ValueCommitment,
Diversifier, Note, PaymentAddress, Rseed, SaplingIvk,
Diversifier, Note, PaymentAddress, Rseed,
},
transaction::components::{
amount::Amount,
@ -31,53 +32,13 @@ use crate::{
},
};
use super::note::ExtractedNoteCommitment;
pub use crate::sapling::keys::{PreparedEphemeralPublicKey, PreparedIncomingViewingKey};
pub const KDF_SAPLING_PERSONALIZATION: &[u8; 16] = b"Zcash_SaplingKDF";
pub const PRF_OCK_PERSONALIZATION: &[u8; 16] = b"Zcash_Derive_ock";
/// A Sapling incoming viewing key that has been precomputed for trial decryption.
#[derive(Clone, Debug)]
pub struct PreparedIncomingViewingKey(PreparedScalar);
impl DynamicUsage for PreparedIncomingViewingKey {
fn dynamic_usage(&self) -> usize {
self.0.dynamic_usage()
}
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
self.0.dynamic_usage_bounds()
}
}
impl PreparedIncomingViewingKey {
/// Performs the necessary precomputations to use a `SaplingIvk` for note decryption.
pub fn new(ivk: &SaplingIvk) -> Self {
Self(PreparedScalar::new(&ivk.0))
}
/// TODO: Remove after type changes.
pub(crate) fn inner(&self) -> &PreparedScalar {
&self.0
}
}
/// A Sapling ephemeral public key that has been precomputed for trial decryption.
#[derive(Clone, Debug)]
pub struct PreparedEphemeralPublicKey(PreparedBase);
impl PreparedEphemeralPublicKey {
/// TODO: Remove after type changes.
pub(crate) fn inner(&self) -> &PreparedBase {
&self.0
}
}
/// Sapling key agreement for note encryption.
///
/// Implements section 5.4.4.3 of the Zcash Protocol Specification.
pub fn sapling_ka_agree(esk: &jubjub::Fr, pk_d: &jubjub::ExtendedPoint) -> jubjub::SubgroupPoint {
EphemeralSecretKey(*esk).agree(pk_d).into_inner()
}
/// Sapling PRF^ock.
///
/// Implemented per section 5.4.2 of the Zcash Protocol Specification.
@ -109,7 +70,7 @@ fn sapling_parse_note_plaintext_without_memo<F, P: consensus::Parameters>(
get_validated_pk_d: F,
) -> Option<(Note, PaymentAddress)>
where
F: FnOnce(&Diversifier) -> Option<jubjub::SubgroupPoint>,
F: FnOnce(&Diversifier) -> Option<DiversifiedTransmissionKey>,
{
assert!(plaintext.len() >= COMPACT_NOTE_SIZE);
@ -133,7 +94,7 @@ where
let pk_d = get_validated_pk_d(&diversifier)?;
let to = PaymentAddress::from_parts(diversifier, pk_d)?;
let note = to.create_note(value.into(), rseed)?;
let note = to.create_note(value.into(), rseed);
Some((note, to))
}
@ -164,21 +125,21 @@ impl<P: consensus::Parameters> SaplingDomain<P> {
}
impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
type EphemeralSecretKey = jubjub::Scalar;
// It is acceptable for this to be a point because we enforce by consensus that
// points must not be small-order, and all points with non-canonical serialization
// are small-order.
type EphemeralPublicKey = jubjub::ExtendedPoint;
type EphemeralSecretKey = EphemeralSecretKey;
// It is acceptable for this to be a point rather than a byte array, because we
// enforce by consensus that points must not be small-order, and all points with
// non-canonical serialization are small-order.
type EphemeralPublicKey = EphemeralPublicKey;
type PreparedEphemeralPublicKey = PreparedEphemeralPublicKey;
type SharedSecret = jubjub::SubgroupPoint;
type SharedSecret = SharedSecret;
type SymmetricKey = Blake2bHash;
type Note = Note;
type Recipient = PaymentAddress;
type DiversifiedTransmissionKey = jubjub::SubgroupPoint;
type DiversifiedTransmissionKey = DiversifiedTransmissionKey;
type IncomingViewingKey = PreparedIncomingViewingKey;
type OutgoingViewingKey = OutgoingViewingKey;
type ValueCommitment = ValueCommitment;
type ExtractedCommitment = bls12_381::Scalar;
type ExtractedCommitment = ExtractedNoteCommitment;
type ExtractedCommitmentBytes = [u8; 32];
type Memo = MemoBytes;
@ -187,41 +148,39 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
}
fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey {
note.pk_d
*note.recipient().pk_d()
}
fn prepare_epk(epk: Self::EphemeralPublicKey) -> Self::PreparedEphemeralPublicKey {
PreparedEphemeralPublicKey(PreparedBase::new(epk))
PreparedEphemeralPublicKey::new(epk)
}
fn ka_derive_public(
note: &Self::Note,
esk: &Self::EphemeralSecretKey,
) -> Self::EphemeralPublicKey {
EphemeralSecretKey(*esk)
.derive_public(note.g_d.into())
.into_inner()
esk.derive_public(note.recipient().g_d().into())
}
fn ka_agree_enc(
esk: &Self::EphemeralSecretKey,
pk_d: &Self::DiversifiedTransmissionKey,
) -> Self::SharedSecret {
EphemeralSecretKey(*esk).agree(pk_d.into()).into_inner()
esk.agree(pk_d)
}
fn ka_agree_dec(
ivk: &Self::IncomingViewingKey,
epk: &Self::PreparedEphemeralPublicKey,
) -> Self::SharedSecret {
epk.agree(ivk).into_inner()
epk.agree(ivk)
}
/// Sapling KDF for note encryption.
///
/// Implements section 5.4.4.4 of the Zcash Protocol Specification.
fn kdf(dhsecret: jubjub::SubgroupPoint, epk: &EphemeralKeyBytes) -> Blake2bHash {
SharedSecret::from_inner(dhsecret).kdf_sapling(epk)
fn kdf(dhsecret: SharedSecret, epk: &EphemeralKeyBytes) -> Blake2bHash {
dhsecret.kdf_sapling(epk)
}
fn note_plaintext_bytes(
@ -232,21 +191,21 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
// Note plaintext encoding is defined in section 5.5 of the Zcash Protocol
// Specification.
let mut input = [0; NOTE_PLAINTEXT_SIZE];
input[0] = match note.rseed {
input[0] = match note.rseed() {
Rseed::BeforeZip212(_) => 1,
Rseed::AfterZip212(_) => 2,
};
input[1..12].copy_from_slice(&to.diversifier().0);
(&mut input[12..20])
.write_u64::<LittleEndian>(note.value)
.write_u64::<LittleEndian>(note.value().inner())
.unwrap();
match note.rseed {
match note.rseed() {
Rseed::BeforeZip212(rcm) => {
input[20..COMPACT_NOTE_SIZE].copy_from_slice(rcm.to_repr().as_ref());
}
Rseed::AfterZip212(rseed) => {
input[20..COMPACT_NOTE_SIZE].copy_from_slice(&rseed);
input[20..COMPACT_NOTE_SIZE].copy_from_slice(rseed);
}
}
@ -269,23 +228,21 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
esk: &Self::EphemeralSecretKey,
) -> OutPlaintextBytes {
let mut input = [0u8; OUT_PLAINTEXT_SIZE];
input[0..32].copy_from_slice(&note.pk_d.to_bytes());
input[32..OUT_PLAINTEXT_SIZE].copy_from_slice(esk.to_repr().as_ref());
input[0..32].copy_from_slice(&note.recipient().pk_d().to_bytes());
input[32..OUT_PLAINTEXT_SIZE].copy_from_slice(esk.0.to_repr().as_ref());
OutPlaintextBytes(input)
}
fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes {
EphemeralPublicKey::from_inner(*epk).to_bytes()
epk.to_bytes()
}
fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option<Self::EphemeralPublicKey> {
// ZIP 216: We unconditionally reject non-canonical encodings, because these have
// always been rejected by consensus (due to small-order checks).
// https://zips.z.cash/zip-0216#specification
let res: Option<EphemeralPublicKey> =
EphemeralPublicKey::from_bytes(&ephemeral_key.0).into();
res.map(|epk| epk.into_inner())
EphemeralPublicKey::from_bytes(&ephemeral_key.0).into()
}
fn parse_note_plaintext_without_memo_ivk(
@ -294,7 +251,7 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
plaintext: &[u8],
) -> Option<(Self::Note, Self::Recipient)> {
sapling_parse_note_plaintext_without_memo(self, plaintext, |diversifier| {
Some(&PreparedBaseSubgroup::new(diversifier.g_d()?) * &ivk.0)
DiversifiedTransmissionKey::derive(ivk, diversifier)
})
}
@ -306,12 +263,7 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
plaintext: &NotePlaintextBytes,
) -> Option<(Self::Note, Self::Recipient)> {
sapling_parse_note_plaintext_without_memo(self, &plaintext.0, |diversifier| {
if EphemeralSecretKey(*esk)
.derive_public(diversifier.g_d()?.into())
.to_bytes()
.0
== ephemeral_key.0
{
if esk.derive_public(diversifier.g_d()?.into()).to_bytes().0 == ephemeral_key.0 {
Some(*pk_d)
} else {
None
@ -324,20 +276,19 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
}
fn extract_pk_d(op: &OutPlaintextBytes) -> Option<Self::DiversifiedTransmissionKey> {
jubjub::SubgroupPoint::from_bytes(
DiversifiedTransmissionKey::from_bytes(
op.0[0..32].try_into().expect("slice is the correct length"),
)
.into()
}
fn extract_esk(op: &OutPlaintextBytes) -> Option<Self::EphemeralSecretKey> {
let res: Option<EphemeralSecretKey> = EphemeralSecretKey::from_bytes(
EphemeralSecretKey::from_bytes(
op.0[32..OUT_PLAINTEXT_SIZE]
.try_into()
.expect("slice is the correct length"),
)
.into();
res.map(|esk| esk.0)
.into()
}
fn extract_memo(&self, plaintext: &NotePlaintextBytes) -> Self::Memo {
@ -349,11 +300,7 @@ impl<P: consensus::Parameters> BatchDomain for SaplingDomain<P> {
fn batch_kdf<'a>(
items: impl Iterator<Item = (Option<Self::SharedSecret>, &'a EphemeralKeyBytes)>,
) -> Vec<Option<Self::SymmetricKey>> {
let (shared_secrets, ephemeral_keys): (Vec<_>, Vec<_>) = items
.map(|(shared_secret, ephemeral_key)| {
(shared_secret.map(SharedSecret::from_inner), ephemeral_key)
})
.unzip();
let (shared_secrets, ephemeral_keys): (Vec<_>, Vec<_>) = items.unzip();
SharedSecret::batch_to_affine(shared_secrets)
.zip(ephemeral_keys.into_iter())
@ -372,9 +319,9 @@ impl<P: consensus::Parameters> BatchDomain for SaplingDomain<P> {
.zip(ephemeral_keys.into_iter())
.map(|(epk, ephemeral_key)| {
(
epk.map(jubjub::ExtendedPoint::from)
.map(Self::prepare_epk)
.into(),
Option::from(epk)
.map(EphemeralPublicKey::from_affine)
.map(Self::prepare_epk),
ephemeral_key,
)
})
@ -402,15 +349,15 @@ impl<P: consensus::Parameters> BatchDomain for SaplingDomain<P> {
/// note_encryption::sapling_note_encryption,
/// util::generate_random_rseed,
/// value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
/// Diversifier, PaymentAddress, Rseed,
/// Diversifier, PaymentAddress, Rseed, SaplingIvk,
/// },
/// };
///
/// let mut rng = OsRng;
///
/// let ivk = SaplingIvk(jubjub::Scalar::random(&mut rng));
/// let diversifier = Diversifier([0; 11]);
/// let pk_d = diversifier.g_d().unwrap();
/// let to = PaymentAddress::from_parts(diversifier, pk_d).unwrap();
/// let to = ivk.to_payment_address(diversifier).unwrap();
/// let ovk = Some(OutgoingViewingKey([0; 32]));
///
/// let value = NoteValue::from_raw(1000);
@ -418,7 +365,7 @@ impl<P: consensus::Parameters> BatchDomain for SaplingDomain<P> {
/// let cv = ValueCommitment::derive(value, rcv);
/// let height = TEST_NETWORK.activation_height(NetworkUpgrade::Canopy).unwrap();
/// let rseed = generate_random_rseed(&TEST_NETWORK, height, &mut rng);
/// let note = to.create_note(value.inner(), rseed).unwrap();
/// let note = to.create_note(value.inner(), rseed);
/// let cmu = note.cmu();
///
/// let mut enc = sapling_note_encryption::<_, TestNetwork>(ovk, note, to, MemoBytes::empty(), &mut rng);
@ -547,7 +494,7 @@ mod tests {
};
use ff::{Field, PrimeField};
use group::Group;
use group::{cofactor::CofactorGroup, GroupEncoding};
use group::GroupEncoding;
use rand_core::OsRng;
use rand_core::{CryptoRng, RngCore};
@ -571,7 +518,8 @@ mod tests {
keys::OutgoingViewingKey,
memo::MemoBytes,
sapling::{
keys::{EphemeralPublicKey, EphemeralSecretKey},
keys::{DiversifiedTransmissionKey, EphemeralSecretKey},
note::ExtractedNoteCommitment,
note_encryption::PreparedIncomingViewingKey,
util::generate_random_rseed,
value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
@ -629,8 +577,7 @@ mod tests {
OutputDescription<sapling::GrothProofBytes>,
) {
let diversifier = Diversifier([0; 11]);
let pk_d = diversifier.g_d().unwrap() * ivk.0;
let pa = PaymentAddress::from_parts_unchecked(diversifier, pk_d);
let pa = ivk.to_payment_address(diversifier).unwrap();
// Construct the value commitment for the proof instance
let value = NoteValue::from_raw(100);
@ -639,7 +586,7 @@ mod tests {
let rseed = generate_random_rseed(&TEST_NETWORK, height, &mut rng);
let note = pa.create_note(value.inner(), rseed).unwrap();
let note = pa.create_note(value.inner(), rseed);
let cmu = note.cmu();
let ovk = OutgoingViewingKey([0; 32]);
@ -650,14 +597,14 @@ mod tests {
MemoBytes::empty(),
&mut rng,
);
let epk = EphemeralPublicKey::from_inner(*ne.epk());
let ock = prf_ock(&ovk, &cv, &cmu.to_repr(), &epk.to_bytes());
let epk = ne.epk();
let ock = prf_ock(&ovk, &cv, &cmu.to_bytes(), &epk.to_bytes());
let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng);
let output = OutputDescription::from_parts(
cv,
cmu,
epk.to_bytes().into(),
epk.to_bytes(),
ne.encrypt_note_plaintext(),
out_ciphertext,
[0u8; GROTH_PROOF_SIZE],
@ -666,16 +613,15 @@ mod tests {
(ovk, ock, output)
}
fn reencrypt_enc_ciphertext(
fn reencrypt_out_ciphertext(
ovk: &OutgoingViewingKey,
cv: &ValueCommitment,
cmu: &bls12_381::Scalar,
cmu: &ExtractedNoteCommitment,
ephemeral_key: &EphemeralKeyBytes,
enc_ciphertext: &[u8; ENC_CIPHERTEXT_SIZE],
out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE],
modify_plaintext: impl Fn(&mut [u8; NOTE_PLAINTEXT_SIZE]),
) -> [u8; ENC_CIPHERTEXT_SIZE] {
let ock = prf_ock(ovk, cv, &cmu.to_repr(), ephemeral_key);
modify_plaintext: impl Fn(&mut [u8; OUT_PLAINTEXT_SIZE]),
) -> [u8; OUT_CIPHERTEXT_SIZE] {
let ock = prf_ock(ovk, cv, &cmu.to_bytes(), ephemeral_key);
let mut op = [0; OUT_PLAINTEXT_SIZE];
op.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]);
@ -689,11 +635,46 @@ mod tests {
)
.unwrap();
let pk_d = jubjub::SubgroupPoint::from_bytes(&op[0..32].try_into().unwrap()).unwrap();
modify_plaintext(&mut op);
let tag = ChaCha20Poly1305::new(ock.as_ref().into())
.encrypt_in_place_detached([0u8; 12][..].into(), &[], &mut op)
.unwrap();
let mut out_ciphertext = [0u8; OUT_CIPHERTEXT_SIZE];
out_ciphertext[..OUT_PLAINTEXT_SIZE].copy_from_slice(&op);
out_ciphertext[OUT_PLAINTEXT_SIZE..].copy_from_slice(&tag);
out_ciphertext
}
fn reencrypt_enc_ciphertext(
ovk: &OutgoingViewingKey,
cv: &ValueCommitment,
cmu: &ExtractedNoteCommitment,
ephemeral_key: &EphemeralKeyBytes,
enc_ciphertext: &[u8; ENC_CIPHERTEXT_SIZE],
out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE],
modify_plaintext: impl Fn(&mut [u8; NOTE_PLAINTEXT_SIZE]),
) -> [u8; ENC_CIPHERTEXT_SIZE] {
let ock = prf_ock(ovk, cv, &cmu.to_bytes(), ephemeral_key);
let mut op = [0; OUT_PLAINTEXT_SIZE];
op.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]);
ChaCha20Poly1305::new(ock.as_ref().into())
.decrypt_in_place_detached(
[0u8; 12][..].into(),
&[],
&mut op,
out_ciphertext[OUT_PLAINTEXT_SIZE..].into(),
)
.unwrap();
let pk_d = DiversifiedTransmissionKey::from_bytes(&op[0..32].try_into().unwrap()).unwrap();
let esk = jubjub::Fr::from_repr(op[32..OUT_PLAINTEXT_SIZE].try_into().unwrap()).unwrap();
let shared_secret = EphemeralSecretKey(esk).agree(&pk_d.into());
let shared_secret = EphemeralSecretKey(esk).agree(&pk_d);
let key = shared_secret.kdf_sapling(ephemeral_key);
let mut plaintext = [0; NOTE_PLAINTEXT_SIZE];
@ -807,7 +788,9 @@ mod tests {
for &height in heights.iter() {
let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng);
*output.cmu_mut() = bls12_381::Scalar::random(&mut rng);
*output.cmu_mut() =
ExtractedNoteCommitment::from_bytes(&bls12_381::Scalar::random(&mut rng).to_repr())
.unwrap();
assert_eq!(
try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output),
@ -977,7 +960,9 @@ mod tests {
for &height in heights.iter() {
let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng);
*output.cmu_mut() = bls12_381::Scalar::random(&mut rng);
*output.cmu_mut() =
ExtractedNoteCommitment::from_bytes(&bls12_381::Scalar::random(&mut rng).to_repr())
.unwrap();
assert_eq!(
try_sapling_compact_note_decryption(
@ -1164,7 +1149,9 @@ mod tests {
for &height in heights.iter() {
let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng);
*output.cmu_mut() = bls12_381::Scalar::random(&mut rng);
*output.cmu_mut() =
ExtractedNoteCommitment::from_bytes(&bls12_381::Scalar::random(&mut rng).to_repr())
.unwrap();
assert_eq!(
try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,),
@ -1353,9 +1340,16 @@ mod tests {
];
for &height in heights.iter() {
let ivk = SaplingIvk(jubjub::Fr::zero());
let (ovk, ock, output) = random_enc_ciphertext_with(height, &ivk, &mut rng);
let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng);
*output.out_ciphertext_mut() = reencrypt_out_ciphertext(
&ovk,
output.cv(),
output.cmu(),
output.ephemeral_key(),
output.out_ciphertext(),
|pt| pt[0..32].copy_from_slice(&jubjub::ExtendedPoint::random(rng).to_bytes()),
);
assert_eq!(
try_sapling_output_recovery(&TEST_NETWORK, height, &ovk, &output,),
None
@ -1371,9 +1365,9 @@ mod tests {
fn test_vectors() {
let test_vectors = crate::test_vectors::note_encryption::make_test_vectors();
macro_rules! read_bls12_381_scalar {
macro_rules! read_cmu {
($field:expr) => {{
bls12_381::Scalar::from_repr($field[..].try_into().unwrap()).unwrap()
ExtractedNoteCommitment::from_bytes($field[..].try_into().unwrap()).unwrap()
}};
}
@ -1383,9 +1377,9 @@ mod tests {
}};
}
macro_rules! read_point {
macro_rules! read_pk_d {
($field:expr) => {
jubjub::ExtendedPoint::from_bytes(&$field).unwrap()
DiversifiedTransmissionKey::from_bytes(&$field).unwrap()
};
}
@ -1403,29 +1397,29 @@ mod tests {
//
let ivk = PreparedIncomingViewingKey::new(&SaplingIvk(read_jubjub_scalar!(tv.ivk)));
let pk_d = read_point!(tv.default_pk_d).into_subgroup().unwrap();
let pk_d = read_pk_d!(tv.default_pk_d);
let rcm = read_jubjub_scalar!(tv.rcm);
let cv = read_cv!(tv.cv);
let cmu = read_bls12_381_scalar!(tv.cmu);
let esk = read_jubjub_scalar!(tv.esk);
let cmu = read_cmu!(tv.cmu);
let esk = EphemeralSecretKey(read_jubjub_scalar!(tv.esk));
let ephemeral_key = EphemeralKeyBytes(tv.epk);
//
// Test the individual components
//
let shared_secret = EphemeralSecretKey(esk).agree(&pk_d.into());
let shared_secret = esk.agree(&pk_d);
assert_eq!(shared_secret.to_bytes(), tv.shared_secret);
let k_enc = shared_secret.kdf_sapling(&ephemeral_key);
assert_eq!(k_enc.as_bytes(), tv.k_enc);
let ovk = OutgoingViewingKey(tv.ovk);
let ock = prf_ock(&ovk, &cv, &cmu.to_repr(), &ephemeral_key);
let ock = prf_ock(&ovk, &cv, &cmu.to_bytes(), &ephemeral_key);
assert_eq!(ock.as_ref(), tv.ock);
let to = PaymentAddress::from_parts(Diversifier(tv.default_d), pk_d).unwrap();
let note = to.create_note(tv.v, Rseed::BeforeZip212(rcm)).unwrap();
let note = to.create_note(tv.v, Rseed::BeforeZip212(rcm));
assert_eq!(note.cmu(), cmu);
let output = OutputDescription::from_parts(

View File

@ -98,6 +98,19 @@ pub(crate) fn ka_sapling_derive_public_prepared(
b * sk
}
/// This is defined implicitly by [Zcash Protocol Spec § 4.2.2: Sapling Key Components][saplingkeycomponents]
/// which uses $KA^\mathsf{Sapling}.\mathsf{DerivePublic}$ to produce a diversified
/// transmission key with type $KA^\mathsf{Sapling}.\mathsf{PublicPrimeSubgroup}$.
///
/// [saplingkeycomponents]: https://zips.z.cash/protocol/protocol.pdf#saplingkeycomponents
pub(crate) fn ka_sapling_derive_public_subgroup_prepared(
sk: &PreparedScalar,
b: &PreparedBaseSubgroup,
) -> jubjub::SubgroupPoint {
// [sk] b
b * sk
}
/// Defined in [Zcash Protocol Spec § 5.4.5.3: Sapling Key Agreement][concretesaplingkeyagreement].
///
/// [concretesaplingkeyagreement]: https://zips.z.cash/protocol/protocol.pdf#concretesaplingkeyagreement

View File

@ -5,7 +5,7 @@ use lazy_static::lazy_static;
use std::io::{self, Read, Write};
use super::{
note::Note,
note::ExtractedNoteCommitment,
pedersen_hash::{pedersen_hash, Personalization},
};
use crate::merkle_tree::{HashSer, Hashable};
@ -14,6 +14,7 @@ pub const SAPLING_COMMITMENT_TREE_DEPTH: usize = 32;
pub const SAPLING_COMMITMENT_TREE_DEPTH_U8: u8 = 32;
lazy_static! {
static ref UNCOMMITTED_SAPLING: bls12_381::Scalar = bls12_381::Scalar::one();
static ref EMPTY_ROOTS: Vec<Node> = {
let mut v = vec![Node::blank()];
for d in 0..SAPLING_COMMITMENT_TREE_DEPTH {
@ -70,6 +71,13 @@ impl Node {
Node { repr }
}
/// Creates a tree leaf from the given Sapling note commitment.
pub fn from_cmu(value: &ExtractedNoteCommitment) -> Self {
Node {
repr: value.to_bytes(),
}
}
/// Constructs a new note commitment tree node from a [`bls12_381::Scalar`]
pub fn from_scalar(cmu: bls12_381::Scalar) -> Self {
Self {
@ -81,7 +89,7 @@ impl Node {
impl incrementalmerkletree::Hashable for Node {
fn empty_leaf() -> Self {
Node {
repr: Note::uncommitted().to_repr(),
repr: UNCOMMITTED_SAPLING.to_repr(),
}
}

View File

@ -552,7 +552,7 @@ mod tests {
legacy::TransparentAddress,
memo::MemoBytes,
merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::Rseed,
sapling::{Node, Rseed},
transaction::components::{
amount::{Amount, DEFAULT_FEE},
sapling::builder::{self as build_s},
@ -670,10 +670,8 @@ mod tests {
let mut rng = OsRng;
let note1 = to
.create_note(50000, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)))
.unwrap();
let cmu1 = note1.commitment();
let note1 = to.create_note(50000, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)));
let cmu1 = Node::from_cmu(&note1.cmu());
let mut tree = CommitmentTree::empty();
tree.append(cmu1).unwrap();
let witness1 = IncrementalWitness::from_tree(&tree);
@ -749,7 +747,7 @@ mod tests {
builder
.add_sapling_output(
ovk,
to.clone(),
to,
Amount::from_u64(50000).unwrap(),
MemoBytes::empty(),
)
@ -780,10 +778,8 @@ mod tests {
);
}
let note1 = to
.create_note(50999, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)))
.unwrap();
let cmu1 = note1.commitment();
let note1 = to.create_note(50999, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)));
let cmu1 = Node::from_cmu(&note1.cmu());
let mut tree = CommitmentTree::empty();
tree.append(cmu1).unwrap();
let mut witness1 = IncrementalWitness::from_tree(&tree);
@ -803,7 +799,7 @@ mod tests {
builder
.add_sapling_output(
ovk,
to.clone(),
to,
Amount::from_u64(30000).unwrap(),
MemoBytes::empty(),
)
@ -820,10 +816,8 @@ mod tests {
);
}
let note2 = to
.create_note(1, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)))
.unwrap();
let cmu2 = note2.commitment();
let note2 = to.create_note(1, Rseed::BeforeZip212(jubjub::Fr::random(&mut rng)));
let cmu2 = Node::from_cmu(&note2.cmu());
tree.append(cmu2).unwrap();
witness1.append(cmu2).unwrap();
let witness2 = IncrementalWitness::from_tree(&tree);

View File

@ -12,6 +12,7 @@ use zcash_note_encryption::{
use crate::{
consensus,
sapling::{
note::ExtractedNoteCommitment,
note_encryption::SaplingDomain,
redjubjub::{self, PublicKey, Signature},
value::ValueCommitment,
@ -26,8 +27,10 @@ pub type GrothProofBytes = [u8; GROTH_PROOF_SIZE];
pub mod builder;
pub mod fees;
/// Defines the authorization type of a Sapling bundle.
pub trait Authorization: Debug {
type Proof: Clone + Debug;
type SpendProof: Clone + Debug;
type OutputProof: Clone + Debug;
type AuthSig: Clone + Debug;
}
@ -35,22 +38,27 @@ pub trait Authorization: Debug {
pub struct Unproven;
impl Authorization for Unproven {
type Proof = ();
type SpendProof = ();
type OutputProof = ();
type AuthSig = ();
}
/// Authorizing data for a bundle of Sapling spends and outputs, ready to be committed to
/// the ledger.
#[derive(Debug, Copy, Clone)]
pub struct Authorized {
pub binding_sig: redjubjub::Signature,
}
impl Authorization for Authorized {
type Proof = GrothProofBytes;
type SpendProof = GrothProofBytes;
type OutputProof = GrothProofBytes;
type AuthSig = redjubjub::Signature;
}
pub trait MapAuth<A: Authorization, B: Authorization> {
fn map_proof(&self, p: A::Proof) -> B::Proof;
fn map_spend_proof(&self, p: A::SpendProof) -> B::SpendProof;
fn map_output_proof(&self, p: A::OutputProof) -> B::OutputProof;
fn map_auth_sig(&self, s: A::AuthSig) -> B::AuthSig;
fn map_authorization(&self, a: A) -> B;
}
@ -62,10 +70,17 @@ pub trait MapAuth<A: Authorization, B: Authorization> {
///
/// [`TransactionData::map_authorization`]: crate::transaction::TransactionData::map_authorization
impl MapAuth<Authorized, Authorized> for () {
fn map_proof(
fn map_spend_proof(
&self,
p: <Authorized as Authorization>::Proof,
) -> <Authorized as Authorization>::Proof {
p: <Authorized as Authorization>::SpendProof,
) -> <Authorized as Authorization>::SpendProof {
p
}
fn map_output_proof(
&self,
p: <Authorized as Authorization>::OutputProof,
) -> <Authorized as Authorization>::OutputProof {
p
}
@ -84,7 +99,7 @@ impl MapAuth<Authorized, Authorized> for () {
#[derive(Debug, Clone)]
pub struct Bundle<A: Authorization> {
shielded_spends: Vec<SpendDescription<A>>,
shielded_outputs: Vec<OutputDescription<A::Proof>>,
shielded_outputs: Vec<OutputDescription<A::OutputProof>>,
value_balance: Amount,
authorization: A,
}
@ -93,7 +108,7 @@ impl<A: Authorization> Bundle<A> {
/// Constructs a `Bundle` from its constituent parts.
pub(crate) fn from_parts(
shielded_spends: Vec<SpendDescription<A>>,
shielded_outputs: Vec<OutputDescription<A::Proof>>,
shielded_outputs: Vec<OutputDescription<A::OutputProof>>,
value_balance: Amount,
authorization: A,
) -> Self {
@ -111,7 +126,7 @@ impl<A: Authorization> Bundle<A> {
}
/// Returns the list of outputs in this bundle.
pub fn shielded_outputs(&self) -> &[OutputDescription<A::Proof>] {
pub fn shielded_outputs(&self) -> &[OutputDescription<A::OutputProof>] {
&self.shielded_outputs
}
@ -139,7 +154,7 @@ impl<A: Authorization> Bundle<A> {
anchor: d.anchor,
nullifier: d.nullifier,
rk: d.rk,
zkproof: f.map_proof(d.zkproof),
zkproof: f.map_spend_proof(d.zkproof),
spend_auth_sig: f.map_auth_sig(d.spend_auth_sig),
})
.collect(),
@ -152,7 +167,7 @@ impl<A: Authorization> Bundle<A> {
ephemeral_key: o.ephemeral_key,
enc_ciphertext: o.enc_ciphertext,
out_ciphertext: o.out_ciphertext,
zkproof: f.map_proof(o.zkproof),
zkproof: f.map_output_proof(o.zkproof),
})
.collect(),
value_balance: self.value_balance,
@ -167,7 +182,7 @@ pub struct SpendDescription<A: Authorization> {
anchor: bls12_381::Scalar,
nullifier: Nullifier,
rk: PublicKey,
zkproof: A::Proof,
zkproof: A::SpendProof,
spend_auth_sig: A::AuthSig,
}
@ -203,7 +218,7 @@ impl<A: Authorization> SpendDescription<A> {
}
/// Returns the proof for this spend.
pub fn zkproof(&self) -> &A::Proof {
pub fn zkproof(&self) -> &A::SpendProof {
&self.zkproof
}
@ -228,6 +243,15 @@ fn read_value_commitment<R: Read>(mut reader: R) -> io::Result<ValueCommitment>
}
}
/// Consensus rules (§7.3) & (§7.4):
/// - Canonical encoding is enforced here
fn read_cmu<R: Read>(mut reader: R) -> io::Result<ExtractedNoteCommitment> {
let mut f = [0u8; 32];
reader.read_exact(&mut f)?;
Option::from(ExtractedNoteCommitment::from_bytes(&f))
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "cmu not in field"))
}
/// Consensus rules (§7.3) & (§7.4):
/// - Canonical encoding is enforced here
pub fn read_base<R: Read>(mut reader: R, field: &str) -> io::Result<bls12_381::Scalar> {
@ -350,7 +374,7 @@ impl SpendDescriptionV5 {
#[derive(Clone)]
pub struct OutputDescription<Proof> {
cv: ValueCommitment,
cmu: bls12_381::Scalar,
cmu: ExtractedNoteCommitment,
ephemeral_key: EphemeralKeyBytes,
enc_ciphertext: [u8; 580],
out_ciphertext: [u8; 80],
@ -364,7 +388,7 @@ impl<Proof> OutputDescription<Proof> {
}
/// Returns the commitment to the new note being created.
pub fn cmu(&self) -> &bls12_381::Scalar {
pub fn cmu(&self) -> &ExtractedNoteCommitment {
&self.cmu
}
@ -392,7 +416,7 @@ impl<Proof> OutputDescription<Proof> {
impl<Proof> OutputDescription<Proof> {
pub(crate) fn from_parts(
cv: ValueCommitment,
cmu: bls12_381::Scalar,
cmu: ExtractedNoteCommitment,
ephemeral_key: EphemeralKeyBytes,
enc_ciphertext: [u8; 580],
out_ciphertext: [u8; 80],
@ -410,7 +434,7 @@ impl<Proof> OutputDescription<Proof> {
pub(crate) fn cv_mut(&mut self) -> &mut ValueCommitment {
&mut self.cv
}
pub(crate) fn cmu_mut(&mut self) -> &mut bls12_381::Scalar {
pub(crate) fn cmu_mut(&mut self) -> &mut ExtractedNoteCommitment {
&mut self.cmu
}
pub(crate) fn ephemeral_key_mut(&mut self) -> &mut EphemeralKeyBytes {
@ -442,7 +466,7 @@ impl<P: consensus::Parameters, A> ShieldedOutput<SaplingDomain<P>, ENC_CIPHERTEX
}
fn cmstar_bytes(&self) -> [u8; 32] {
self.cmu.to_repr()
self.cmu.to_bytes()
}
fn enc_ciphertext(&self) -> &[u8; ENC_CIPHERTEXT_SIZE] {
@ -469,7 +493,7 @@ impl OutputDescription<GrothProofBytes> {
let cv = read_value_commitment(&mut reader)?;
// Consensus rule (§7.4): Canonical encoding is enforced here
let cmu = read_base(&mut reader, "cmu")?;
let cmu = read_cmu(&mut reader)?;
// Consensus rules (§4.5):
// - Canonical encoding is enforced in librustzcash_sapling_check_output by zcashd
@ -496,7 +520,7 @@ impl OutputDescription<GrothProofBytes> {
pub fn write_v4<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.cv.to_bytes())?;
writer.write_all(self.cmu.to_repr().as_ref())?;
writer.write_all(self.cmu.to_bytes().as_ref())?;
writer.write_all(self.ephemeral_key.as_ref())?;
writer.write_all(&self.enc_ciphertext)?;
writer.write_all(&self.out_ciphertext)?;
@ -505,7 +529,7 @@ impl OutputDescription<GrothProofBytes> {
pub fn write_v5_without_proof<W: Write>(&self, mut writer: W) -> io::Result<()> {
writer.write_all(&self.cv.to_bytes())?;
writer.write_all(self.cmu.to_repr().as_ref())?;
writer.write_all(self.cmu.to_bytes().as_ref())?;
writer.write_all(self.ephemeral_key.as_ref())?;
writer.write_all(&self.enc_ciphertext)?;
writer.write_all(&self.out_ciphertext)
@ -515,7 +539,7 @@ impl OutputDescription<GrothProofBytes> {
#[derive(Clone)]
pub struct OutputDescriptionV5 {
cv: ValueCommitment,
cmu: bls12_381::Scalar,
cmu: ExtractedNoteCommitment,
ephemeral_key: EphemeralKeyBytes,
enc_ciphertext: [u8; 580],
out_ciphertext: [u8; 80],
@ -526,7 +550,7 @@ memuse::impl_no_dynamic_usage!(OutputDescriptionV5);
impl OutputDescriptionV5 {
pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
let cv = read_value_commitment(&mut reader)?;
let cmu = read_base(&mut reader, "cmu")?;
let cmu = read_cmu(&mut reader)?;
// Consensus rules (§4.5):
// - Canonical encoding is enforced in librustzcash_sapling_check_output by zcashd
@ -566,7 +590,7 @@ impl OutputDescriptionV5 {
#[derive(Clone)]
pub struct CompactOutputDescription {
pub ephemeral_key: EphemeralKeyBytes,
pub cmu: bls12_381::Scalar,
pub cmu: ExtractedNoteCommitment,
pub enc_ciphertext: [u8; COMPACT_NOTE_SIZE],
}
@ -590,7 +614,7 @@ impl<P: consensus::Parameters> ShieldedOutput<SaplingDomain<P>, COMPACT_NOTE_SIZ
}
fn cmstar_bytes(&self) -> [u8; 32] {
self.cmu.to_repr()
self.cmu.to_bytes()
}
fn enc_ciphertext(&self) -> &[u8; COMPACT_NOTE_SIZE] {
@ -609,6 +633,7 @@ pub mod testing {
use crate::{
constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR},
sapling::{
note::ExtractedNoteCommitment,
redjubjub::{PrivateKey, PublicKey},
value::{
testing::{arb_note_value_bounded, arb_trapdoor},
@ -681,6 +706,7 @@ pub mod testing {
.prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()),
) -> OutputDescription<GrothProofBytes> {
let cv = ValueCommitment::derive(value, rcv);
let cmu = ExtractedNoteCommitment::from_bytes(&cmu.to_bytes()).unwrap();
OutputDescription {
cv,
cmu,

View File

@ -4,7 +4,6 @@ use core::fmt;
use std::sync::mpsc::Sender;
use ff::Field;
use group::GroupEncoding;
use rand::{seq::SliceRandom, RngCore};
use crate::{
@ -13,6 +12,7 @@ use crate::{
memo::MemoBytes,
merkle_tree::MerklePath,
sapling::{
keys::SaplingIvk,
note_encryption::sapling_note_encryption,
prover::TxProver,
redjubjub::{PrivateKey, Signature},
@ -78,7 +78,7 @@ impl fees::InputView<()> for SpendDescriptionInfo {
fn value(&self) -> Amount {
// An existing note to be spent must have a valid amount value.
Amount::from_u64(self.note.value).unwrap()
Amount::from_u64(self.note.value().inner()).unwrap()
}
}
@ -102,24 +102,17 @@ impl SaplingOutputInfo {
to: PaymentAddress,
value: NoteValue,
memo: MemoBytes,
) -> Result<Self, Error> {
let g_d = to.g_d().ok_or(Error::InvalidAddress)?;
) -> Self {
let rseed = generate_random_rseed_internal(params, target_height, rng);
let note = Note {
g_d,
pk_d: *to.pk_d(),
value: value.inner(),
rseed,
};
let note = Note::from_parts(to, value, rseed);
Ok(SaplingOutputInfo {
SaplingOutputInfo {
ovk,
to,
note,
memo,
})
}
}
fn build<P: consensus::Parameters, Pr: TxProver, R: RngCore>(
@ -128,20 +121,15 @@ impl SaplingOutputInfo {
ctx: &mut Pr::SaplingProvingContext,
rng: &mut R,
) -> OutputDescription<GrothProofBytes> {
let encryptor = sapling_note_encryption::<R, P>(
self.ovk,
self.note.clone(),
self.to.clone(),
self.memo,
rng,
);
let encryptor =
sapling_note_encryption::<R, P>(self.ovk, self.note.clone(), self.to, self.memo, rng);
let (zkproof, cv) = prover.output_proof(
ctx,
*encryptor.esk(),
encryptor.esk().0,
self.to,
self.note.rcm(),
self.note.value,
self.note.value().inner(),
);
let cmu = self.note.cmu();
@ -149,12 +137,12 @@ impl SaplingOutputInfo {
let enc_ciphertext = encryptor.encrypt_note_plaintext();
let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, rng);
let epk = *encryptor.epk();
let epk = encryptor.epk();
OutputDescription {
cv,
cmu,
ephemeral_key: epk.to_bytes().into(),
ephemeral_key: epk.to_bytes(),
enc_ciphertext,
out_ciphertext,
zkproof,
@ -164,7 +152,8 @@ impl SaplingOutputInfo {
impl fees::OutputView for SaplingOutputInfo {
fn value(&self) -> Amount {
Amount::from_u64(self.note.value).expect("Note values should be checked at construction.")
Amount::from_u64(self.note.value().inner())
.expect("Note values should be checked at construction.")
}
}
@ -227,7 +216,8 @@ impl std::fmt::Debug for Unauthorized {
}
impl Authorization for Unauthorized {
type Proof = GrothProofBytes;
type SpendProof = GrothProofBytes;
type OutputProof = GrothProofBytes;
type AuthSig = SpendDescriptionInfo;
}
@ -299,7 +289,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
merkle_path: MerklePath<Node>,
) -> Result<(), Error> {
// Consistency check: all anchors must equal the first one
let node = note.commitment();
let node = Node::from_cmu(&note.cmu());
if let Some(anchor) = self.anchor {
let path_root: bls12_381::Scalar = merkle_path.root(node).into();
if path_root != anchor {
@ -311,8 +301,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
let alpha = jubjub::Fr::random(&mut rng);
self.value_balance =
(self.value_balance + NoteValue::from_raw(note.value)).ok_or(Error::InvalidAddress)?;
self.value_balance = (self.value_balance + note.value()).ok_or(Error::InvalidAmount)?;
self.try_value_balance()?;
self.spends.push(SpendDescriptionInfo {
@ -344,7 +333,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
to,
value,
memo,
)?;
);
self.value_balance = (self.value_balance - value).ok_or(Error::InvalidAddress)?;
self.try_value_balance()?;
@ -417,9 +406,9 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
ctx,
proof_generation_key,
spend.diversifier,
spend.note.rseed,
*spend.note.rseed(),
spend.alpha,
spend.note.value,
spend.note.value().inner(),
anchor,
spend.merkle_path.clone(),
)
@ -463,48 +452,41 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
output.clone().build::<P, _, _>(prover, ctx, &mut rng)
} else {
// This is a dummy output
let (dummy_to, dummy_note) = {
let (diversifier, g_d) = {
let mut diversifier;
let g_d;
let dummy_note = {
let payment_address = {
let mut diversifier = Diversifier([0; 11]);
loop {
let mut d = [0; 11];
rng.fill_bytes(&mut d);
diversifier = Diversifier(d);
if let Some(val) = diversifier.g_d() {
g_d = val;
break;
rng.fill_bytes(&mut diversifier.0);
let dummy_ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
if let Some(addr) = dummy_ivk.to_payment_address(diversifier) {
break addr;
}
}
(diversifier, g_d)
};
let (pk_d, payment_address) = loop {
let dummy_ivk = jubjub::Fr::random(&mut rng);
let pk_d = g_d * dummy_ivk;
if let Some(addr) = PaymentAddress::from_parts(diversifier, pk_d) {
break (pk_d, addr);
}
};
let rseed =
generate_random_rseed_internal(&params, target_height, &mut rng);
(
payment_address,
Note {
g_d,
pk_d,
rseed,
value: 0,
},
)
Note::from_parts(payment_address, NoteValue::from_raw(0), rseed)
};
let esk = dummy_note.generate_or_derive_esk_internal(&mut rng);
let epk = dummy_note.g_d * esk;
let epk = esk.derive_public(
dummy_note
.recipient()
.diversifier()
.g_d()
.expect("checked at construction")
.into(),
);
let (zkproof, cv) =
prover.output_proof(ctx, esk, dummy_to, dummy_note.rcm(), dummy_note.value);
let (zkproof, cv) = prover.output_proof(
ctx,
esk.0,
dummy_note.recipient(),
dummy_note.rcm(),
dummy_note.value().inner(),
);
let cmu = dummy_note.cmu();
@ -516,7 +498,7 @@ impl<P: consensus::Parameters> SaplingBuilder<P> {
OutputDescription {
cv,
cmu,
ephemeral_key: epk.to_bytes().into(),
ephemeral_key: epk.to_bytes(),
enc_ciphertext,
out_ciphertext,
zkproof,

View File

@ -78,7 +78,7 @@ pub trait TransparentAuthorizingContext: transparent::Authorization {
pub fn signature_hash<
'a,
TA: TransparentAuthorizingContext,
SA: sapling::Authorization<Proof = GrothProofBytes>,
SA: sapling::Authorization<SpendProof = GrothProofBytes, OutputProof = GrothProofBytes>,
A: Authorization<SaplingAuth = SA, TransparentAuth = TA>,
>(
tx: &TransactionData<A>,

View File

@ -104,7 +104,9 @@ fn joinsplits_hash(
.hash(&data)
}
fn shielded_spends_hash<A: sapling::Authorization<Proof = GrothProofBytes>>(
fn shielded_spends_hash<
A: sapling::Authorization<SpendProof = GrothProofBytes, OutputProof = GrothProofBytes>,
>(
shielded_spends: &[SpendDescription<A>],
) -> Blake2bHash {
let mut data = Vec::with_capacity(shielded_spends.len() * 384);
@ -133,7 +135,7 @@ fn shielded_outputs_hash(shielded_outputs: &[OutputDescription<GrothProofBytes>]
}
pub fn v4_signature_hash<
SA: sapling::Authorization<Proof = GrothProofBytes>,
SA: sapling::Authorization<SpendProof = GrothProofBytes, OutputProof = GrothProofBytes>,
A: Authorization<SaplingAuth = SA>,
>(
tx: &TransactionData<A>,

View File

@ -175,7 +175,7 @@ pub(crate) fn hash_sapling_outputs<A>(shielded_outputs: &[OutputDescription<A>])
let mut mh = hasher(ZCASH_SAPLING_OUTPUTS_MEMOS_HASH_PERSONALIZATION);
let mut nh = hasher(ZCASH_SAPLING_OUTPUTS_NONCOMPACT_HASH_PERSONALIZATION);
for s_out in shielded_outputs {
ch.write_all(s_out.cmu().to_repr().as_ref()).unwrap();
ch.write_all(s_out.cmu().to_bytes().as_ref()).unwrap();
ch.write_all(s_out.ephemeral_key().as_ref()).unwrap();
ch.write_all(&s_out.enc_ciphertext()[..52]).unwrap();

View File

@ -13,6 +13,10 @@ and this library adheres to Rust's notion of
### Changed
- MSRV is now 1.60.0.
- Note commitments now use
`zcash_primitives::sapling::note::ExtractedNoteCommitment` instead of
`bls12_381::Scalar` in `zcash_proofs::sapling`:
- `SaplingVerificationContext::check_output`
- Value commitments now use `zcash_primitives::sapling::value::ValueCommitment`
instead of `jubjub::ExtendedPoint` in `zcash_proofs::sapling`:
- `SaplingProvingContext::{spend_proof, output_proof}`

View File

@ -74,7 +74,7 @@ fn criterion_benchmark(c: &mut Criterion) {
Spend {
value_commitment_opening: Some(value_commitment.clone()),
proof_generation_key: Some(proof_generation_key.clone()),
payment_address: Some(payment_address.clone()),
payment_address: Some(payment_address),
commitment_randomness: Some(commitment_randomness),
ar: Some(ar),
auth_path: auth_path.clone(),

View File

@ -26,6 +26,9 @@ use bellman::gadgets::Assignment;
#[cfg(test)]
use group::ff::PrimeFieldBits;
#[cfg(test)]
use zcash_primitives::sapling::value::NoteValue;
pub const TREE_DEPTH: usize = SAPLING_COMMITMENT_TREE_DEPTH;
/// The opening (value and randomness) of a Sapling value commitment.
@ -220,9 +223,12 @@ impl Circuit<bls12_381::Scalar> for Spend {
let g_d = {
ecc::EdwardsPoint::witness(
cs.namespace(|| "witness g_d"),
self.payment_address
.as_ref()
.and_then(|a| a.g_d().map(jubjub::ExtendedPoint::from)),
self.payment_address.as_ref().map(|a| {
a.diversifier()
.g_d()
.expect("checked at construction")
.into()
}),
)?
};
@ -429,9 +435,12 @@ impl Circuit<bls12_381::Scalar> for Output {
// curve.
let g_d = ecc::EdwardsPoint::witness(
cs.namespace(|| "witness g_d"),
self.payment_address
.as_ref()
.and_then(|a| a.g_d().map(jubjub::ExtendedPoint::from)),
self.payment_address.as_ref().map(|a| {
a.diversifier()
.g_d()
.expect("checked at construction")
.into()
}),
)?;
// g_d is ensured to be large order. The relationship
@ -467,7 +476,7 @@ impl Circuit<bls12_381::Scalar> for Output {
let pk_d = self
.payment_address
.as_ref()
.map(|e| jubjub::ExtendedPoint::from(*e.pk_d()).to_affine());
.map(|e| jubjub::ExtendedPoint::from(e.pk_d().inner()).to_affine());
// Witness the v-coordinate, encoded as little
// endian bits (to match the representation)
@ -572,7 +581,6 @@ fn test_input_circuit_with_bls12_381() {
}
}
let g_d = payment_address.diversifier().g_d().unwrap();
let commitment_randomness = jubjub::Fr::random(&mut rng);
let auth_path =
vec![Some((bls12_381::Scalar::random(&mut rng), rng.next_u32() % 2 != 0)); tree_depth];
@ -581,16 +589,15 @@ fn test_input_circuit_with_bls12_381() {
{
let rk = jubjub::ExtendedPoint::from(viewing_key.rk(ar)).to_affine();
let expected_value_commitment = value_commitment.commitment().to_affine();
let note = Note {
value: value_commitment.value,
g_d,
pk_d: *payment_address.pk_d(),
rseed: Rseed::BeforeZip212(commitment_randomness),
};
let note = Note::from_parts(
payment_address,
NoteValue::from_raw(value_commitment.value),
Rseed::BeforeZip212(commitment_randomness),
);
let mut position = 0u64;
let cmu = note.cmu();
let mut cur = cmu;
let mut cur = bls12_381::Scalar::from_bytes(&cmu.to_bytes()).unwrap();
for (i, val) in auth_path.clone().into_iter().enumerate() {
let (uncle, b) = val.unwrap();
@ -634,7 +641,7 @@ fn test_input_circuit_with_bls12_381() {
let instance = Spend {
value_commitment_opening: Some(value_commitment.clone()),
proof_generation_key: Some(proof_generation_key.clone()),
payment_address: Some(payment_address.clone()),
payment_address: Some(payment_address),
commitment_randomness: Some(commitment_randomness),
ar: Some(ar),
auth_path: auth_path.clone(),
@ -650,7 +657,10 @@ fn test_input_circuit_with_bls12_381() {
"d37c738e83df5d9b0bb6495ac96abf21bcb2697477e2c15c2c7916ff7a3b6a89"
);
assert_eq!(cs.get("randomization of note commitment/u3/num"), cmu);
assert_eq!(
cs.get("randomization of note commitment/u3/num").to_repr(),
cmu.to_bytes()
);
assert_eq!(cs.num_inputs(), 8);
assert_eq!(cs.get_input(0, "ONE"), bls12_381::Scalar::one());
@ -664,7 +674,10 @@ fn test_input_circuit_with_bls12_381() {
cs.get_input(4, "value commitment/commitment point/v/input variable"),
expected_value_commitment.get_v()
);
assert_eq!(cs.get_input(5, "anchor/input variable"), cur);
assert_eq!(
cs.get_input(5, "anchor/input variable").to_repr(),
cur.to_bytes()
);
assert_eq!(cs.get_input(6, "pack nullifier/input 0"), expected_nf[0]);
assert_eq!(cs.get_input(7, "pack nullifier/input 1"), expected_nf[1]);
}
@ -740,7 +753,6 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() {
}
}
let g_d = payment_address.diversifier().g_d().unwrap();
let commitment_randomness = jubjub::Fr::random(&mut rng);
let auth_path =
vec![Some((bls12_381::Scalar::random(&mut rng), rng.next_u32() % 2 != 0)); tree_depth];
@ -757,16 +769,15 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() {
expected_value_commitment.get_v(),
bls12_381::Scalar::from_str_vartime(expected_commitment_vs[i as usize]).unwrap()
);
let note = Note {
value: value_commitment.value,
g_d,
pk_d: *payment_address.pk_d(),
rseed: Rseed::BeforeZip212(commitment_randomness),
};
let note = Note::from_parts(
payment_address,
NoteValue::from_raw(value_commitment.value),
Rseed::BeforeZip212(commitment_randomness),
);
let mut position = 0u64;
let cmu = note.cmu();
let mut cur = cmu;
let mut cur = bls12_381::Scalar::from_bytes(&cmu.to_bytes()).unwrap();
for (i, val) in auth_path.clone().into_iter().enumerate() {
let (uncle, b) = val.unwrap();
@ -810,7 +821,7 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() {
let instance = Spend {
value_commitment_opening: Some(value_commitment.clone()),
proof_generation_key: Some(proof_generation_key.clone()),
payment_address: Some(payment_address.clone()),
payment_address: Some(payment_address),
commitment_randomness: Some(commitment_randomness),
ar: Some(ar),
auth_path: auth_path.clone(),
@ -826,7 +837,10 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() {
"d37c738e83df5d9b0bb6495ac96abf21bcb2697477e2c15c2c7916ff7a3b6a89"
);
assert_eq!(cs.get("randomization of note commitment/u3/num"), cmu);
assert_eq!(
cs.get("randomization of note commitment/u3/num").to_repr(),
cmu.to_bytes()
);
assert_eq!(cs.num_inputs(), 8);
assert_eq!(cs.get_input(0, "ONE"), bls12_381::Scalar::one());
@ -840,7 +854,10 @@ fn test_input_circuit_with_bls12_381_external_test_vectors() {
cs.get_input(4, "value commitment/commitment point/v/input variable"),
expected_value_commitment.get_v()
);
assert_eq!(cs.get_input(5, "anchor/input variable"), cur);
assert_eq!(
cs.get_input(5, "anchor/input variable").to_repr(),
cur.to_bytes()
);
assert_eq!(cs.get_input(6, "pack nullifier/input 0"), expected_nf[0]);
assert_eq!(cs.get_input(7, "pack nullifier/input 1"), expected_nf[1]);
}
@ -896,7 +913,7 @@ fn test_output_circuit_with_bls12_381() {
let instance = Output {
value_commitment_opening: Some(value_commitment.clone()),
payment_address: Some(payment_address.clone()),
payment_address: Some(payment_address),
commitment_randomness: Some(commitment_randomness),
esk: Some(esk),
};
@ -915,14 +932,18 @@ fn test_output_circuit_with_bls12_381() {
value_commitment.value,
Rseed::BeforeZip212(commitment_randomness),
)
.expect("should be valid")
.cmu();
let expected_value_commitment = value_commitment.commitment().to_affine();
let expected_epk =
jubjub::ExtendedPoint::from(payment_address.g_d().expect("should be valid") * esk)
.to_affine();
let expected_epk = jubjub::ExtendedPoint::from(
payment_address
.diversifier()
.g_d()
.expect("should be valid")
* esk,
)
.to_affine();
assert_eq!(cs.num_inputs(), 6);
assert_eq!(cs.get_input(0, "ONE"), bls12_381::Scalar::one());
@ -942,7 +963,10 @@ fn test_output_circuit_with_bls12_381() {
cs.get_input(4, "epk/v/input variable"),
expected_epk.get_v()
);
assert_eq!(cs.get_input(5, "commitment/input variable"), expected_cmu);
assert_eq!(
cs.get_input(5, "commitment/input variable").to_repr(),
expected_cmu.to_bytes()
);
}
}
}

View File

@ -82,12 +82,7 @@ impl SaplingProvingContext {
let rk = PublicKey(proof_generation_key.ak.into()).randomize(ar, SPENDING_KEY_GENERATOR);
// Let's compute the nullifier while we have the position
let note = Note {
value,
g_d: diversifier.g_d().expect("was a valid diversifier before"),
pk_d: *payment_address.pk_d(),
rseed,
};
let note = Note::from_parts(payment_address, NoteValue::from_raw(value), rseed);
let nullifier = note.nf(&viewing_key.nk, merkle_path.position);

View File

@ -1,8 +1,9 @@
use bellman::{gadgets::multipack, groth16::Proof};
use bls12_381::Bls12;
use group::{Curve, GroupEncoding};
use group::{ff::PrimeField, Curve, GroupEncoding};
use zcash_primitives::{
sapling::{
note::ExtractedNoteCommitment,
redjubjub::{PublicKey, Signature},
value::{CommitmentSum, ValueCommitment},
},
@ -105,7 +106,7 @@ impl SaplingVerificationContextInner {
fn check_output(
&mut self,
cv: &ValueCommitment,
cmu: bls12_381::Scalar,
cmu: ExtractedNoteCommitment,
epk: jubjub::ExtendedPoint,
zkproof: Proof<Bls12>,
proof_verifier: impl FnOnce(Proof<Bls12>, [bls12_381::Scalar; 5]) -> bool,
@ -134,7 +135,7 @@ impl SaplingVerificationContextInner {
public_input[2] = u;
public_input[3] = v;
}
public_input[4] = cmu;
public_input[4] = bls12_381::Scalar::from_repr(cmu.to_bytes()).unwrap();
// Verify the proof
proof_verifier(zkproof, public_input)

View File

@ -3,6 +3,7 @@ use bls12_381::Bls12;
use zcash_primitives::{
constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR},
sapling::{
note::ExtractedNoteCommitment,
redjubjub::{PublicKey, Signature},
value::ValueCommitment,
},
@ -64,7 +65,7 @@ impl SaplingVerificationContext {
pub fn check_output(
&mut self,
cv: &ValueCommitment,
cmu: bls12_381::Scalar,
cmu: ExtractedNoteCommitment,
epk: jubjub::ExtendedPoint,
zkproof: Proof<Bls12>,
verifying_key: &PreparedVerifyingKey<Bls12>,