Merge pull request #755 from zcash/sapling-api-changes
Sapling API changes
This commit is contained in:
commit
1b5b8d73e6
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(¬e1.cmu());
|
||||
let mut tree = CommitmentTree::empty();
|
||||
// fake that the note appears in some previous
|
||||
// shielded output
|
||||
|
|
|
@ -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`:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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] {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(¬e.pk_d.to_bytes());
|
||||
input[32..OUT_PLAINTEXT_SIZE].copy_from_slice(esk.to_repr().as_ref());
|
||||
input[0..32].copy_from_slice(¬e.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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(¬e1.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(¬e1.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(¬e2.cmu());
|
||||
tree.append(cmu2).unwrap();
|
||||
witness1.append(cmu2).unwrap();
|
||||
let witness2 = IncrementalWitness::from_tree(&tree);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(¬e.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(¶ms, 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,
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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}`
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>,
|
||||
|
|
Loading…
Reference in New Issue