Merge branch 'master' of github.com:paritytech/parity-zcash

This commit is contained in:
NikVolf 2018-12-24 13:31:49 +03:00
commit d789957924
18 changed files with 743 additions and 139 deletions

26
Cargo.lock generated
View File

@ -40,6 +40,11 @@ dependencies = [
"nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "assert_matches"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "atty"
version = "0.2.3"
@ -116,11 +121,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "bitcrypto"
version = "0.1.0"
dependencies = [
"blake2-rfc 0.2.18 (git+https://github.com/gtank/blake2-rfc.git?branch=persona)",
"bellman 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"blake2-rfc 0.2.18 (git+https://github.com/gtank/blake2-rfc?rev=7a5b5fc99ae483a0043db7547fb79a6fa44b88a9)",
"bn 0.4.4 (git+https://github.com/paritytech/bn)",
"pairing 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)",
"primitives 0.1.0",
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-hex 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sapling-crypto 0.0.1 (git+https://github.com/zcash-hackworks/sapling-crypto.git?rev=21084bde2019c04bd34208e63c3560fe2c02fb0e)",
"serde 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.21 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
@ -142,16 +150,6 @@ name = "bitflags"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "blake2-rfc"
version = "0.2.18"
source = "git+https://github.com/gtank/blake2-rfc.git?branch=persona#c7c458429c429b81fea845421f5ab859710fa8af"
dependencies = [
"arrayvec 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "blake2-rfc"
version = "0.2.18"
@ -1582,7 +1580,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
name = "verification"
version = "0.1.0"
dependencies = [
"bellman 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"bitcrypto 0.1.0",
"byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"chain 0.1.0",
@ -1590,13 +1588,11 @@ dependencies = [
"lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"network 0.1.0",
"pairing 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
"primitives 0.1.0",
"rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"rayon 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc-hex 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"sapling-crypto 0.0.1 (git+https://github.com/zcash-hackworks/sapling-crypto.git?rev=21084bde2019c04bd34208e63c3560fe2c02fb0e)",
"script 0.1.0",
"serialization 0.1.0",
"storage 0.1.0",
@ -1685,6 +1681,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
"checksum app_dirs 1.2.1 (git+https://github.com/paritytech/app-dirs-rs)" = "<none>"
"checksum arrayvec 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2f0ef4a9820019a0c91d918918c93dc71d469f581a49b47ddc1d285d4270bbe2"
"checksum assert_matches 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7deb0a829ca7bcfaf5da70b073a8d128619259a7be8216a355e23f00763059e5"
"checksum atty 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "21e50800ec991574876040fff8ee46b136a53e985286fbe6a3bdfe6421b78860"
"checksum base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83"
"checksum base64 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96434f987501f0ed4eb336a411e0631ecd1afa11574fe148587adc4ff96143c9"
@ -1694,7 +1691,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
"checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
"checksum blake2-rfc 0.2.18 (git+https://github.com/gtank/blake2-rfc.git?branch=persona)" = "<none>"
"checksum blake2-rfc 0.2.18 (git+https://github.com/gtank/blake2-rfc?rev=7a5b5fc99ae483a0043db7547fb79a6fa44b88a9)" = "<none>"
"checksum bn 0.4.4 (git+https://github.com/paritytech/bn)" = "<none>"
"checksum byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "74c0b906e9446b0a2e4f760cdb3fa4b2c48cdc6db8766a845c54b6ff063fd2e9"

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4,14 +4,15 @@ version = "0.1.0"
authors = ["debris <marek.kotewicz@gmail.com>"]
[dependencies]
blake2-rfc = { git = "https://github.com/gtank/blake2-rfc.git", branch = "persona" }
bellman = "0.1"
blake2-rfc = { git = "https://github.com/gtank/blake2-rfc.git", rev = "7a5b5fc99ae483a0043db7547fb79a6fa44b88a9" }
pairing = "0.14.2"
rust-crypto = "0.2.36"
sapling-crypto = { git = "https://github.com/zcash-hackworks/sapling-crypto.git", rev = "21084bde2019c04bd34208e63c3560fe2c02fb0e" }
serde_json = "1.0"
siphasher = "0.1.1"
primitives = { path = "../primitives" }
bn = { git = "https://github.com/paritytech/bn" }
serde = "1.0"
serde_derive = "1.0"
rustc-hex = "2"
[dev-dependencies]
serde_json = "1.0"

155
crypto/src/json/groth16.rs Normal file
View File

@ -0,0 +1,155 @@
use std::fmt;
use hex::FromHex;
use bellman::groth16::{prepare_verifying_key, VerifyingKey as BellmanVerifyingKey};
use pairing::{EncodedPoint, bls12_381::{Bls12, G1Uncompressed, G2Uncompressed}};
use serde::de::{self, Visitor, Deserialize, Deserializer};
use json::pghr13::clean_0x;
use Groth16VerifyingKey;
/// Load Sapling spend verification key.
pub fn load_sapling_spend_verifying_key() -> Result<Groth16VerifyingKey, String> {
let spend_vk_json = include_bytes!("../../../res/sapling-spend-verifying-key.json");
let spend_vk = serde_json::from_slice::<VerifyingKey>(&spend_vk_json[..]).unwrap();
Ok(Groth16VerifyingKey(prepare_verifying_key(&spend_vk.into())))
}
/// Load Sapling output verification key.
pub fn load_sapling_output_verifying_key() -> Result<Groth16VerifyingKey, String> {
let output_vk_json = include_bytes!("../../../res/sapling-output-verifying-key.json");
let output_vk = serde_json::from_slice::<VerifyingKey>(&output_vk_json[..]).unwrap();
Ok(Groth16VerifyingKey(prepare_verifying_key(&output_vk.into())))
}
type G1 = Point<G1Uncompressed>;
type G2 = Point<G2Uncompressed>;
#[derive(Clone, Deserialize)]
struct VerifyingKey {
#[serde(rename = "alphaG1")]
pub alpha_g1: G1,
#[serde(rename = "betaG1")]
pub beta_g1: G1,
#[serde(rename = "betaG2")]
pub beta_g2: G2,
#[serde(rename = "gammaG2")]
pub gamma_g2: G2,
#[serde(rename = "deltaG1")]
pub delta_g1: G1,
#[serde(rename = "deltaG2")]
pub delta_g2: G2,
#[serde(rename = "ic")]
pub ic: Vec<G1>,
}
impl From<VerifyingKey> for BellmanVerifyingKey<Bls12> {
fn from(vk: VerifyingKey) -> BellmanVerifyingKey<Bls12> {
BellmanVerifyingKey {
alpha_g1: vk.alpha_g1.0,
beta_g1: vk.beta_g1.0,
beta_g2: vk.beta_g2.0,
gamma_g2: vk.gamma_g2.0,
delta_g1: vk.delta_g1.0,
delta_g2: vk.delta_g2.0,
ic: vk.ic.into_iter().map(|p| p.0).collect(),
}
}
}
#[derive(Debug, Clone)]
struct Point<EP: EncodedPoint>(EP::Affine);
impl<'de, EP: EncodedPoint> Deserialize<'de> for Point<EP> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct EncodedPointVisitor<EP: EncodedPoint>(::std::marker::PhantomData<EP>);
impl<'de, EP: EncodedPoint> Visitor<'de> for EncodedPointVisitor<EP> {
type Value = Point<EP>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a hex string")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
let mut point = EP::empty();
let point_raw = clean_0x(value).from_hex::<Vec<_>>()
.map_err(|e| de::Error::custom(format!("Expected hex string: {}", e)))?;
if point.as_ref().len() != point_raw.len() {
return Err(de::Error::custom(format!("Expected hex string of length {}", point.as_ref().len())));
}
point.as_mut().copy_from_slice(&point_raw);
point.into_affine()
.map_err(|e| de::Error::custom(format!("Invalid curve point: {}", e)))
.map(Point)
}
}
deserializer.deserialize_str(EncodedPointVisitor::<EP>(Default::default()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn g1() {
let valid = r#""0x0db882cf5db3e8567f16b4db1772d4d1f5a3fe8d62f0df2eb8a5cfa50806702afde8fc25335eb5ec859c2818b2610b2e19ab445dac720bb1f2b0cd3336f7a1acc62bf1b3a321826264dc7e469281e23b218394d598689da04e136878ff9a7897""#;
serde_json::from_str::<G1>(valid).unwrap();
}
#[test]
fn g1_messed() {
// too few chars
let invalid = r#""0xb882cf5db3e8567f16b4db1772d4d1f5a3fe8d62f0df2eb8a5cfa50806702afde8fc25335eb5ec859c2818b2610b2e19ab445dac720bb1f2b0cd3336f7a1acc62bf1b3a321826264dc7e469281e23b218394d598689da04e136878ff9a7897""#;
serde_json::from_str::<G1>(invalid).unwrap_err();
// too much chars
let invalid = r#""0xFF0db882cf5db3e8567f16b4db1772d4d1f5a3fe8d62f0df2eb8a5cfa50806702afde8fc25335eb5ec859c2818b2610b2e19ab445dac720bb1f2b0cd3336f7a1acc62bf1b3a321826264dc7e469281e23b218394d598689da04e136878ff9a7897""#;
serde_json::from_str::<G1>(invalid).unwrap_err();
// invalid curve point
let invalid = r#""0x19ab445dac720bb1f2b0cd3336f7a1acc62bf1b3a321826264dc7e469281e23b218394d598689da04e136878ff9a78970db882cf5db3e8567f16b4db1772d4d1f5a3fe8d62f0df2eb8a5cfa50806702afde8fc25335eb5ec859c2818b2610b2e""#;
serde_json::from_str::<G1>(invalid).unwrap_err();
}
#[test]
fn g2() {
let valid = r#""0x050302fecf4d86671c66ed1ee097efccd2a2add6fd42c9d0a809bb6a3e0f8348bfac6cfa4427c83d5ed1ff844a5b1b1209c069a8a1ccd8c7c22b2a84fede0e53b536cabd7d4c7f0ddc53bec42eeda2b09190d43bcbaece88f7a2a1fc686076d20f2acbc06f28f913a2a77a731d96133aeb5282461cd452a3f3f1d3b63907840dc79b1066e898a335c3a676de9c97507c0c4824b4c9ac0dfc2b1b017e1ebe1b96920a80a7f7e61e39d2f275c51ea8c0b4a6aa86643ee4696af6611d027c58401c""#;
serde_json::from_str::<G2>(valid).unwrap();
}
#[test]
fn g2_messed() {
// too few chars
let invalid = r#""0x0302fecf4d86671c66ed1ee097efccd2a2add6fd42c9d0a809bb6a3e0f8348bfac6cfa4427c83d5ed1ff844a5b1b1209c069a8a1ccd8c7c22b2a84fede0e53b536cabd7d4c7f0ddc53bec42eeda2b09190d43bcbaece88f7a2a1fc686076d20f2acbc06f28f913a2a77a731d96133aeb5282461cd452a3f3f1d3b63907840dc79b1066e898a335c3a676de9c97507c0c4824b4c9ac0dfc2b1b017e1ebe1b96920a80a7f7e61e39d2f275c51ea8c0b4a6aa86643ee4696af6611d027c58401c""#;
serde_json::from_str::<G2>(invalid).unwrap_err();
// too much chars
let invalid = r#""0xFF050302fecf4d86671c66ed1ee097efccd2a2add6fd42c9d0a809bb6a3e0f8348bfac6cfa4427c83d5ed1ff844a5b1b1209c069a8a1ccd8c7c22b2a84fede0e53b536cabd7d4c7f0ddc53bec42eeda2b09190d43bcbaece88f7a2a1fc686076d20f2acbc06f28f913a2a77a731d96133aeb5282461cd452a3f3f1d3b63907840dc79b1066e898a335c3a676de9c97507c0c4824b4c9ac0dfc2b1b017e1ebe1b96920a80a7f7e61e39d2f275c51ea8c0b4a6aa86643ee4696af6611d027c58401c""#;
serde_json::from_str::<G2>(invalid).unwrap_err();
// invalid curve point
let invalid = r#""0x09c069a8a1ccd8c7c22b2a84fede0e53b536cabd7d4c7f0ddc53bec42eeda2b09190d43bcbaece88f7a2a1fc686076d2050302fecf4d86671c66ed1ee097efccd2a2add6fd42c9d0a809bb6a3e0f8348bfac6cfa4427c83d5ed1ff844a5b1b120c4824b4c9ac0dfc2b1b017e1ebe1b96920a80a7f7e61e39d2f275c51ea8c0b4a6aa86643ee4696af6611d027c58401c0f2acbc06f28f913a2a77a731d96133aeb5282461cd452a3f3f1d3b63907840dc79b1066e898a335c3a676de9c97507c""#;
serde_json::from_str::<G2>(invalid).unwrap_err();
}
#[test]
fn output_key() {
let output_vk_json = include_bytes!("../../../res/sapling-output-verifying-key.json");
serde_json::from_slice::<VerifyingKey>(&output_vk_json[..]).unwrap();
}
#[test]
fn spend_key() {
let spend_vk_json = include_bytes!("../../../res/sapling-spend-verifying-key.json");
serde_json::from_slice::<VerifyingKey>(&spend_vk_json[..]).unwrap();
}
}

2
crypto/src/json/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod groth16;
pub mod pghr13;

View File

@ -10,7 +10,7 @@ pub struct G1(bn::G1);
struct G1Visitor;
fn clean_0x(s: &str) -> &str {
pub(crate) fn clean_0x(s: &str) -> &str {
if s.starts_with("0x") {
&s[2..]
} else {
@ -120,8 +120,6 @@ pub struct VerifyingKey {
#[cfg(test)]
mod tests {
extern crate serde_json;
use super::*;
#[test]

View File

@ -1,15 +1,20 @@
extern crate blake2_rfc;
extern crate crypto as rcrypto;
extern crate primitives;
extern crate serde_json;
extern crate siphasher;
extern crate bn;
extern crate serde;
extern crate rustc_hex as hex;
pub extern crate bellman;
pub extern crate pairing;
pub extern crate sapling_crypto;
#[macro_use] extern crate serde_derive;
mod pghr13;
mod json;
mod pghr13;
pub use rcrypto::digest::Digest;
pub use blake2_rfc::blake2b::Blake2b;
@ -21,11 +26,17 @@ use rcrypto::ripemd160::Ripemd160;
use siphasher::sip::SipHasher24;
use primitives::hash::{H32, H160, H256};
pub use json::groth16::{
load_sapling_spend_verifying_key, load_sapling_output_verifying_key,
};
pub use pghr13::{
VerifyingKey as Pghr13VerifyingKey, Proof as Pghr13Proof, verify as pghr13_verify,
G1, G2, Fr, Group,
};
pub struct Groth16VerifyingKey(pub bellman::groth16::PreparedVerifyingKey<pairing::bls12_381::Bls12>);
pub struct DHash160 {
sha256: Sha256,
ripemd: Ripemd160,
@ -195,6 +206,12 @@ pub fn checksum(data: &[u8]) -> H32 {
result
}
impl ::std::fmt::Debug for Groth16VerifyingKey {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
f.write_str("Groth16VerifyingKey")
}
}
#[cfg(test)]
mod tests {
use primitives::bytes::Bytes;

View File

@ -7,7 +7,7 @@ use bytes::Bytes;
use primitives::compact::Compact;
use chain::{
IndexedBlock, IndexedBlockHeader, IndexedTransaction, BlockHeader, Block, Transaction,
OutPoint, TransactionOutput, SAPLING_TX_VERSION_GROUP_ID,
OutPoint, TransactionOutput,
};
use ser::{
deserialize, serialize, List
@ -287,15 +287,13 @@ impl<T> BlockChainDatabase<T> where T: KeyValueDatabase {
}
for tx in block.transactions.iter().skip(1) {
let is_sapling_group = tx.raw.version_group_id == SAPLING_TX_VERSION_GROUP_ID;
modified_meta.insert(tx.hash.clone(), TransactionMeta::new(new_best_block.number, tx.raw.outputs.len()));
if let Some(ref js) = tx.raw.join_split {
for js_descriptor in js.descriptions.iter() {
for nullifier in &js_descriptor.nullifiers[..] {
let nullifier_key = Nullifier::new(
if is_sapling_group { NullifierTag::Sapling } else { NullifierTag::Sprout },
NullifierTag::Sprout,
H256::from(&nullifier[..])
);
if self.contains_nullifier(nullifier_key) {
@ -307,6 +305,20 @@ impl<T> BlockChainDatabase<T> where T: KeyValueDatabase {
}
}
if let Some(ref sapling) = tx.raw.sapling {
for spend in &sapling.spends {
let nullifier_key = Nullifier::new(
NullifierTag::Sapling,
H256::from(&spend.nullifier[..])
);
if self.contains_nullifier(nullifier_key) {
trace!(target: "db", "Duplicate nullifer during canonization: {:?}", nullifier_key);
return Err(Error::CannotCanonize);
}
update.insert(KeyValue::Nullifier(nullifier_key));
}
}
for input in &tx.raw.inputs {
use std::collections::hash_map::Entry;
@ -363,12 +375,11 @@ impl<T> BlockChainDatabase<T> where T: KeyValueDatabase {
let mut modified_meta: HashMap<H256, TransactionMeta> = HashMap::new();
for tx in block.transactions.iter().skip(1) {
let is_sapling_group = tx.raw.version_group_id == SAPLING_TX_VERSION_GROUP_ID;
if let Some(ref js) = tx.raw.join_split {
for js_descriptor in js.descriptions.iter() {
for nullifier in &js_descriptor.nullifiers[..] {
let nullifier_key = Nullifier::new(
if is_sapling_group { NullifierTag::Sapling } else { NullifierTag::Sprout },
NullifierTag::Sprout,
H256::from(&nullifier[..])
);
if !self.contains_nullifier(nullifier_key) {
@ -380,6 +391,20 @@ impl<T> BlockChainDatabase<T> where T: KeyValueDatabase {
}
}
if let Some(ref sapling) = tx.raw.sapling {
for spend in &sapling.spends {
let nullifier_key = Nullifier::new(
NullifierTag::Sapling,
H256::from(&spend.nullifier[..])
);
if !self.contains_nullifier(nullifier_key) {
warn!(target: "db", "cannot decanonize, no nullifier: {:?}", nullifier_key);
return Err(Error::CannotDecanonize);
}
update.delete(Key::Nullifier(nullifier_key));
}
}
for input in &tx.raw.inputs {
use std::collections::hash_map::Entry;

View File

@ -115,10 +115,10 @@ mod tests {
#[test]
fn test_inventory_type_conversion() {
assert_eq!(0u32, InventoryType::Error.into());
assert_eq!(1u32, InventoryType::MessageTx.into());
assert_eq!(2u32, InventoryType::MessageBlock.into());
assert_eq!(3u32, InventoryType::MessageFilteredBlock.into());
assert_eq!(0u32, u32::from(InventoryType::Error));
assert_eq!(1u32, u32::from(InventoryType::MessageTx));
assert_eq!(2u32, u32::from(InventoryType::MessageBlock));
assert_eq!(3u32, u32::from(InventoryType::MessageFilteredBlock));
assert_eq!(InventoryType::from_u32(0).unwrap(), InventoryType::Error);
assert_eq!(InventoryType::from_u32(1).unwrap(), InventoryType::MessageTx);

View File

@ -1,5 +1,12 @@
use {Network, Magic, Deployment, crypto};
lazy_static! {
static ref SAPLING_SPEND_VK: crypto::Groth16VerifyingKey = crypto::load_sapling_spend_verifying_key()
.expect("hardcoded value should load without errors");
static ref SAPLING_OUTPUT_VK: crypto::Groth16VerifyingKey = crypto::load_sapling_output_verifying_key()
.expect("hardcoded value should load without errors");
}
#[derive(Debug, Clone)]
/// Parameters that influence chain consensus.
pub struct ConsensusParams {
@ -45,6 +52,11 @@ pub struct ConsensusParams {
/// Active key for pghr13 joinsplit verification
pub joinsplit_verification_key: crypto::Pghr13VerifyingKey,
/// Sapling spend verification key.
pub sapling_spend_verifying_key: &'static crypto::Groth16VerifyingKey,
/// Sapling output verification key.
pub sapling_output_verifying_key: &'static crypto::Groth16VerifyingKey,
}
fn mainnet_pghr_verification_key() -> crypto::Pghr13VerifyingKey {
@ -135,6 +147,9 @@ impl ConsensusParams {
equihash_params: Some((200, 9)),
joinsplit_verification_key: mainnet_pghr_verification_key(),
sapling_spend_verifying_key: &SAPLING_SPEND_VK,
sapling_output_verifying_key: &SAPLING_OUTPUT_VK,
},
Network::Testnet => ConsensusParams {
network: network,
@ -157,6 +172,9 @@ impl ConsensusParams {
equihash_params: Some((200, 9)),
joinsplit_verification_key: testnet_pghr_verification_key(),
sapling_spend_verifying_key: &SAPLING_SPEND_VK,
sapling_output_verifying_key: &SAPLING_OUTPUT_VK,
},
Network::Regtest => ConsensusParams {
network: network,
@ -179,6 +197,9 @@ impl ConsensusParams {
equihash_params: Some((200, 9)),
joinsplit_verification_key: regtest_pghr_verification_key(),
sapling_spend_verifying_key: &SAPLING_SPEND_VK,
sapling_output_verifying_key: &SAPLING_OUTPUT_VK,
},
Network::Unitest => ConsensusParams {
network: network,
@ -201,6 +222,9 @@ impl ConsensusParams {
equihash_params: None,
joinsplit_verification_key: unitest_pghr_verification_key(),
sapling_spend_verifying_key: &SAPLING_SPEND_VK,
sapling_output_verifying_key: &SAPLING_OUTPUT_VK,
},
}
}

View File

@ -0,0 +1,16 @@
{
"alphaG1": "0x0db882cf5db3e8567f16b4db1772d4d1f5a3fe8d62f0df2eb8a5cfa50806702afde8fc25335eb5ec859c2818b2610b2e19ab445dac720bb1f2b0cd3336f7a1acc62bf1b3a321826264dc7e469281e23b218394d598689da04e136878ff9a7897",
"betaG1": "0x014a78a8d17180a37c4ca8fb231f264ab89bd14863777fc1ffe901fd92444365d18f78237612ac38e39f419c32f0824515219ec45c26c1fad530514ed891a0d0043acedf348922102e95b3e6d07e0afa94c58aa41480631fc1ca36e55aae51fd",
"betaG2": "0x0a416b8187450b28f025c421e3ff14d38f9abd9af2f1046b914b53ab37e9aebba683cb25284e5c22fa341129985250a103547de5d005df48265f7cb258162253d56fbc682d106a1ecb07666ebf7524a364e512c37aa62f82d6e7dd4ed8838478104376a98072766c29959358e9cde6a4985618f65ea257e8f288974f4aedde52e5dac2fb7ae5d30eab7cd828a2c8b15f15b16f139f2c33ef33d63befe404e696c97077d17ea42f4ff9d82ec456aaf43914a3d07968111a3a348f157e64c0278a",
"gammaG2": "0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801",
"deltaG1": "0x0f61208393fe783b523d23c911eda2b295231684a8c3b3898d8fce83fa949fd6a25aee7fe4d0d37f7a892bf7c2639d5b1571bb257e41dab703d78f54a71dadec6f37e40d21bb29f5eca47c91078a016cf95fb7e6aa31c1321450202037c69ee7",
"deltaG2": "0x050302fecf4d86671c66ed1ee097efccd2a2add6fd42c9d0a809bb6a3e0f8348bfac6cfa4427c83d5ed1ff844a5b1b1209c069a8a1ccd8c7c22b2a84fede0e53b536cabd7d4c7f0ddc53bec42eeda2b09190d43bcbaece88f7a2a1fc686076d20f2acbc06f28f913a2a77a731d96133aeb5282461cd452a3f3f1d3b63907840dc79b1066e898a335c3a676de9c97507c0c4824b4c9ac0dfc2b1b017e1ebe1b96920a80a7f7e61e39d2f275c51ea8c0b4a6aa86643ee4696af6611d027c58401c",
"ic": [
"0x04fd67184c37c5240183cff73f52544ecd6b629e775ad6648696f99a431a63ca8b386bdcb94cb1e4c53c4ec0276d3dc50c2bd5d31bf904b30b9d0508d59c915bfaea64830425366257fe599f2eaf2f6650bea1abe4bab1955da88a6860c31b4d",
"0x073143b431c7e8e9323f3693c470fd9a3adf199a31b6e3006c64189bde7a07a316f9f7252d042147ac4c68be3a62a367012534e285c0ac0e01c7c8f9d013f13aa1da0c06777564d76b27ab788fd46ca5a9a9854424c325eab85298044b258da3",
"0x13cca7cdbcfb681f36c0a3d9ab957960b57dd73002f042f7222eec23d5d60a30bf93bd8d0564113c0be8f1baf577ca570088e381355d3f7e8dab653e7a2e758724b39a9d63e4905fbc2927d5246d2e6072ad142f81498e6d5aecdf36200de579",
"0x0ffb9b423f4631fbbeb423d81fe9b244953a2b701eb2f5d96bf97981eb50227705128a401c10789582bbc8648150270f0d77cb44de2f73bdea9e4405174ef3034ce88bc4b02d4e0886b3fa5fb5e6b0b1f404bfa18ad308aadddd2634c9765d5b",
"0x0f91ffe602148dd9a6404f1701af31a48481ef23bf05beed476428178c2f972082d533348904348831ea61ce33bf6ccc0b04fae453962cb4c2634bc75eaecaaf9a034cb30d82968bb08a46403b308ac0be82f5964c0a9631032c363147097ad2",
"0x0936d64376b8cff46bf7a9c3db65b47ad83c1cdf2cea84f2671278d597f8867a3a4a955aaab958169bf96839b923b27a08e7d52742571a73eb842f9c31b8972505079760a63c909f0033ba1331c8131c3eafb8945f2112c405869b54721b014e"
]
}

View File

@ -0,0 +1,18 @@
{
"alphaG1": "0x0db882cf5db3e8567f16b4db1772d4d1f5a3fe8d62f0df2eb8a5cfa50806702afde8fc25335eb5ec859c2818b2610b2e19ab445dac720bb1f2b0cd3336f7a1acc62bf1b3a321826264dc7e469281e23b218394d598689da04e136878ff9a7897",
"betaG1": "0x014a78a8d17180a37c4ca8fb231f264ab89bd14863777fc1ffe901fd92444365d18f78237612ac38e39f419c32f0824515219ec45c26c1fad530514ed891a0d0043acedf348922102e95b3e6d07e0afa94c58aa41480631fc1ca36e55aae51fd",
"betaG2": "0x0a416b8187450b28f025c421e3ff14d38f9abd9af2f1046b914b53ab37e9aebba683cb25284e5c22fa341129985250a103547de5d005df48265f7cb258162253d56fbc682d106a1ecb07666ebf7524a364e512c37aa62f82d6e7dd4ed8838478104376a98072766c29959358e9cde6a4985618f65ea257e8f288974f4aedde52e5dac2fb7ae5d30eab7cd828a2c8b15f15b16f139f2c33ef33d63befe404e696c97077d17ea42f4ff9d82ec456aaf43914a3d07968111a3a348f157e64c0278a",
"gammaG2": "0x13e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb80606c4a02ea734cc32acd2b02bc28b99cb3e287e85a763af267492ab572e99ab3f370d275cec1da1aaa9075ff05f79be0ce5d527727d6e118cc9cdc6da2e351aadfd9baa8cbdd3a76d429a695160d12c923ac9cc3baca289e193548608b82801",
"deltaG1": "0x0b9d259333f82bb97fd52701bc1e2bf04755c376a19aaadef0ebe8466a4f30862accb63e627e73fab29b929d6106795f0fd0296b4a6945694850071bf5ca887cd39fd803f34c00cf2965cf5218cf58d66fb7eecaf1d17055eb465bf3584fa638",
"deltaG2": "0x077284b5eb4a16c08a9b4810bcb80336367adbc7425bb37cb5dfb41977a78e152125fad6cb858a38edbe25ad2a34f7f019841965c9831072efe2b9739766edd008283f835d09206e51bfc1f97d9b2eb1c8b726195b2ffb5e5506fccb8f93e6060bdfb00b4aa6237784e645e0cda032243c65b73634f508403ae8297e4b894d313bce5b96db0a3311c28182d3a798a02f185e77ca7a24fe2a1a9447c93de31bd3e481fa9f623ae8d27baf78e6547e83b3d99a6eda5b455b09469c0d6ecd442ba2",
"ic": [
"0x12daedb4014a51305d017ac2b895d7dae97d813b243a7d33ed0135ad58a7fbc80b8ebc25ee1ff1af7c9c21b4e4fdf5b213567233a168b9ec9b81ee411aa9937f9f89e847c26810bd5a177dfb9ad3a6a3ac1b46ddffcc32ce3b8277200d9bfb08",
"0x175e64129e1d2394bca57349b2845248b7e92f41acdbb23ca1532898ca29fe08ce2a0ff8795c35fe711d6959ab1ba1051617085c03e25b57a689f5ecfa2ecda5ab0f5eff00079ea957283a482cf7de40839ed0683603af30768a3206ea816fd0",
"0x02198d34ecd17c02740fed470c026a5e4e5fe2ad22fb5ba7b68eac3fc82ef37dd921f7ae8e1773f4c164b447f550760101a1e6108959ce1c98a64b96bd76c474360c630cc5977267855288c0f9fca4a205604a77aa655507169929d7fb52eb95",
"0x071151c8ae74d6bcb6e24b466c59d242009deaf65cd82fb9b662c867a7c88377c115e90f8bb9bda528b9f1541ac15d850befd35dd79811c12a7065a075bb2a412e28592cdd307e75c4231c8ce3e3f8b6ac1a5a87666c55f4f8bc2d770ea5259b",
"0x0f008453cef037d48e211c6eea1610304c9e1aedc28b8208b9f16ba471156a9ab870bbba871f9b26c3a31a67268df26b11c9e64194deb7b9b095903271d42753f1d66fe66d11a82f8e12096333b06d6f35e675ff650189cbb9a010776fd73a95",
"0x0f51fec047929e2802c2840269daa7a7f082db690e6a49c16a749df8ab82a02eb7356f70dc8c7e68682695dfc815491e0172a3966c1e2effc8bc7d0fb462f033cdb93065c9e09d2865c99b494b5fabbddc86194a4b0109a4da58152990e68b93",
"0x183d15a395bd62e4844440f3e9612db6315fd16f0b395155205464ecd9546477d797d353bc9c045acd18c5d9498071c416637dd87e22f4e26bfbf7b94d6cc073ab5cda9423f9f8a30cc024529ff8fea2c7338d34fec14a5f390b4fee213d4e41",
"0x02d64a3c00693baa032b101608c51fc37aa1e99bf255bd4f293592ffd6bf2ffadfc842fc1dcf3b7142eeb3f8edce90e61885b1f5779fc3c818df56d05a48f96f9710652a8b43833a35fd947b9cf3081bc245bf6cf4c380c9b702e7b0b8338d86"
]
}

View File

@ -8,11 +8,8 @@ time = "0.1"
log = "0.4"
rayon = "1.0"
parking_lot = "0.4"
bellman = "0.1"
byteorder = "1.2"
lazy_static = "1.2.0"
pairing = "0.14.2"
sapling-crypto = { git = "https://github.com/zcash-hackworks/sapling-crypto.git", rev = "21084bde2019c04bd34208e63c3560fe2c02fb0e" }
primitives = { path = "../primitives" }
chain = { path = "../chain" }
serialization = { path = "../serialization" }
@ -26,3 +23,4 @@ rustc-hex = "2"
rand = "0.4"
test-data = { path = "../test-data" }
db = { path = "../db" }
assert_matches = "1.3.0"

View File

@ -1,4 +1,5 @@
use ser::Serializable;
use crypto::Groth16VerifyingKey;
use storage::{TransactionMetaProvider, TransactionOutputProvider, Nullifier, NullifierTag, NullifierTracker};
use network::{ConsensusParams};
use script::{Script, verify_script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner, SighashBase};
@ -10,7 +11,6 @@ use canon::CanonTransaction;
use constants::COINBASE_MATURITY;
use error::TransactionError;
use primitives::hash::H256;
use chain::SAPLING_TX_VERSION_GROUP_ID;
use VerificationLevel;
pub struct TransactionAcceptor<'a> {
@ -21,8 +21,8 @@ pub struct TransactionAcceptor<'a> {
pub overspent: TransactionOverspent<'a>,
pub double_spent: TransactionDoubleSpend<'a>,
pub eval: TransactionEval<'a>,
pub join_split: Option<JoinSplitVerification<'a>>,
pub sapling_valid: TransactionSaplingValid<'a>,
pub join_split: JoinSplitVerification<'a>,
pub sapling: SaplingVerification<'a>,
}
impl<'a> TransactionAcceptor<'a> {
@ -47,13 +47,16 @@ impl<'a> TransactionAcceptor<'a> {
bip30: TransactionBip30::new_for_sync(transaction, meta_store),
missing_inputs: TransactionMissingInputs::new(transaction, output_store, transaction_index),
maturity: TransactionMaturity::new(transaction, meta_store, height),
sapling_valid: TransactionSaplingValid::new(transaction),
overspent: TransactionOverspent::new(transaction, output_store),
double_spent: TransactionDoubleSpend::new(transaction, output_store),
eval: TransactionEval::new(transaction, output_store, consensus, verification_level, height, time, deployments),
join_split: transaction.join_split().map(|js| {
JoinSplitVerification::new(transaction.raw.version_group_id, js, nullifier_tracker)
}),
join_split: JoinSplitVerification::new(transaction, nullifier_tracker),
sapling: SaplingVerification::new(
nullifier_tracker,
consensus.sapling_spend_verifying_key,
consensus.sapling_output_verifying_key,
transaction,
),
}
}
@ -68,7 +71,8 @@ impl<'a> TransactionAcceptor<'a> {
// to make sure we're using the sighash-cache, let's make all sighash-related
// calls from single checker && pass sighash to other checkers
let sighash = self.eval.check()?;
self.sapling_valid.check(sighash)?;
self.join_split.check()?;
self.sapling.check(sighash)?;
Ok(())
}
@ -472,11 +476,11 @@ impl<'a> TransactionSize<'a> {
/// Check the joinsplit proof of the transaction
pub struct JoinSplitProof<'a> {
_join_split: &'a chain::JoinSplit,
_transaction: CanonTransaction<'a>,
}
impl<'a> JoinSplitProof<'a> {
fn new(join_split: &'a chain::JoinSplit) -> Self { JoinSplitProof { _join_split: join_split }}
fn new(transaction: CanonTransaction<'a>) -> Self { JoinSplitProof { _transaction: transaction }}
fn check(&self) -> Result<(), TransactionError> {
// TODO: Zero-knowledge proof
@ -484,49 +488,28 @@ impl<'a> JoinSplitProof<'a> {
}
}
/// Check if nullifiers are unique
pub struct Nullifiers<'a> {
tag: NullifierTag,
/// Check if join split nullifiers are unique
pub struct JoinSplitNullifiers<'a> {
tracker: &'a NullifierTracker,
join_split: &'a chain::JoinSplit,
}
impl<'a> Nullifiers<'a> {
fn new(tag: NullifierTag, tracker: &'a NullifierTracker, join_split: &'a chain::JoinSplit) -> Self {
Nullifiers { tag: tag, tracker: tracker, join_split: join_split }
}
fn check(&self) -> Result<(), TransactionError> {
for description in self.join_split.descriptions.iter() {
for nullifier in &description.nullifiers[..] {
let check = Nullifier::new(self.tag, H256::from(&nullifier[..]));
if self.tracker.contains_nullifier(check) {
return Err(TransactionError::JoinSplitDeclared(*check.hash()))
}
}
}
Ok(())
}
}
/// Checks that sapling signatures/proofs are valid.
pub struct TransactionSaplingValid<'a> {
transaction: CanonTransaction<'a>,
}
impl<'a> TransactionSaplingValid<'a> {
fn new(transaction: CanonTransaction<'a>) -> Self {
TransactionSaplingValid {
transaction: transaction,
}
impl<'a> JoinSplitNullifiers<'a> {
fn new(tracker: &'a NullifierTracker, transaction: CanonTransaction<'a>) -> Self {
JoinSplitNullifiers { tracker: tracker, transaction: transaction }
}
fn check(&self, sighash: H256) -> Result<(), TransactionError> {
if let Some(sapling) = self.transaction.raw.sapling.as_ref() {
accept_sapling(&sighash, sapling)
.map_err(|_| TransactionError::InvalidSapling)?;
fn check(&self) -> Result<(), TransactionError> {
if let Some(ref join_split) = self.transaction.raw.join_split {
for description in join_split.descriptions.iter() {
for nullifier in &description.nullifiers[..] {
let check = Nullifier::new(NullifierTag::Sprout, H256::from(&nullifier[..]));
if self.tracker.contains_nullifier(check) {
return Err(TransactionError::JoinSplitDeclared(*check.hash()))
}
}
}
}
Ok(())
@ -536,19 +519,16 @@ impl<'a> TransactionSaplingValid<'a> {
/// Join split verification
pub struct JoinSplitVerification<'a> {
proof: JoinSplitProof<'a>,
nullifiers: Nullifiers<'a>,
nullifiers: JoinSplitNullifiers<'a>,
}
impl<'a> JoinSplitVerification<'a> {
pub fn new(tx_version_group: u32, join_split: &'a chain::JoinSplit, tracker: &'a NullifierTracker)
pub fn new(transaction: CanonTransaction<'a>, tracker: &'a NullifierTracker)
-> Self
{
let tag = if tx_version_group == SAPLING_TX_VERSION_GROUP_ID
{ NullifierTag::Sapling } else { NullifierTag::Sprout };
JoinSplitVerification {
proof: JoinSplitProof::new(join_split),
nullifiers: Nullifiers::new(tag, tracker, join_split),
proof: JoinSplitProof::new(transaction),
nullifiers: JoinSplitNullifiers::new(tracker, transaction),
}
}
@ -558,11 +538,96 @@ impl<'a> JoinSplitVerification<'a> {
}
}
/// Check if Sapling nullifiers are unique
pub struct SaplingNullifiers<'a> {
tracker: &'a NullifierTracker,
transaction: CanonTransaction<'a>,
}
impl<'a> SaplingNullifiers<'a> {
fn new(tracker: &'a NullifierTracker, transaction: CanonTransaction<'a>) -> Self {
SaplingNullifiers { tracker: tracker, transaction: transaction }
}
fn check(&self) -> Result<(), TransactionError> {
if let Some(ref sapling) = self.transaction.raw.sapling {
for spend in &sapling.spends {
let check = Nullifier::new(NullifierTag::Sapling, H256::from(&spend.nullifier[..]));
if self.tracker.contains_nullifier(check) {
return Err(TransactionError::SaplingDeclared(*check.hash()))
}
}
}
Ok(())
}
}
/// Checks that sapling signatures/proofs are valid.
pub struct SaplingProof<'a> {
spend_vk: &'a Groth16VerifyingKey,
output_vk: &'a Groth16VerifyingKey,
transaction: CanonTransaction<'a>,
}
impl<'a> SaplingProof<'a> {
fn new(
spend_vk: &'a Groth16VerifyingKey,
output_vk: &'a Groth16VerifyingKey,
transaction: CanonTransaction<'a>,
) -> Self {
SaplingProof {
spend_vk,
output_vk,
transaction: transaction,
}
}
fn check(&self, sighash: H256) -> Result<(), TransactionError> {
if let Some(sapling) = self.transaction.raw.sapling.as_ref() {
accept_sapling(self.spend_vk, self.output_vk, &sighash, sapling)
.map_err(|_| TransactionError::InvalidSapling)?;
}
Ok(())
}
}
/// Sapling verification
pub struct SaplingVerification<'a> {
proof: SaplingProof<'a>,
nullifiers: SaplingNullifiers<'a>,
}
impl<'a> SaplingVerification<'a> {
pub fn new(
tracker: &'a NullifierTracker,
spend_vk: &'a Groth16VerifyingKey,
output_vk: &'a Groth16VerifyingKey,
transaction: CanonTransaction<'a>
) -> Self
{
SaplingVerification {
proof: SaplingProof::new(spend_vk, output_vk, transaction),
nullifiers: SaplingNullifiers::new(tracker, transaction),
}
}
pub fn check(&self, sighash: H256) -> Result<(), TransactionError> {
self.proof.check(sighash)?;
self.nullifiers.check()
}
}
#[cfg(test)]
mod tests {
use chain::Transaction;
use chain::{Transaction, Sapling};
use db::BlockChainDatabase;
use script::{Script, VerificationFlags, TransactionSignatureChecker, TransactionInputSigner, verify_script};
use super::*;
#[test]
fn join_split() {
@ -593,4 +658,34 @@ mod tests {
.verify_p2sh(true);
assert_eq!(verify_script(&input_script, &output_script, &flags, &mut checker), Ok(()));
}
#[test]
fn sapling_nullifiers_works() {
let storage = BlockChainDatabase::init_test_chain(vec![test_data::genesis().into()]);
let tx: Transaction = test_data::TransactionBuilder::with_sapling(Sapling {
spends: vec![Default::default()],
..Default::default()
}).into();
let block = test_data::block_builder()
.header().parent(test_data::genesis().hash()).build()
.transaction().coinbase().build()
.with_transaction(tx.clone())
.build();
let tx = tx.into();
let block_hash = block.hash();
// when nullifier is not in the db
assert_eq!(SaplingNullifiers::new(&storage, CanonTransaction::new(&tx)).check(), Ok(()));
// insert nullifier into db
storage.insert(block.into()).unwrap();
storage.canonize(&block_hash).unwrap();
// when nullifier is in the db
assert_eq!(
SaplingNullifiers::new(&storage, CanonTransaction::new(&tx)).check(),
Err(TransactionError::SaplingDeclared(Default::default()))
);
}
}

View File

@ -136,5 +136,7 @@ pub enum TransactionError {
JoinSplitVersionInvalid,
/// Transaction sapling verification has failed.
InvalidSapling,
/// Sapling nullifier already revealed earlier in the chain.
SaplingDeclared(H256),
}

View File

@ -56,15 +56,12 @@ extern crate time;
extern crate log;
extern crate parking_lot;
extern crate rayon;
extern crate bellman;
extern crate byteorder;
#[macro_use]
extern crate lazy_static;
#[cfg(test)]
extern crate rand;
extern crate rustc_hex as hex;
extern crate pairing;
extern crate sapling_crypto;
extern crate storage;
extern crate chain;
@ -76,6 +73,10 @@ extern crate bitcrypto as crypto;
#[cfg(test)]
extern crate db;
#[cfg(test)]
#[macro_use]
extern crate assert_matches;
pub mod constants;
mod canon;
mod deployments;

View File

@ -1,20 +1,21 @@
use std::io::Error as IoError;
use chain::{Sapling, SaplingSpendDescription, SaplingOutputDescription};
use pairing::{bls12_381::{Bls12, Fr, FrRepr}, PrimeField, PrimeFieldRepr, PrimeFieldDecodingError};
use bellman::{SynthesisError, groth16::{verify_proof, PreparedVerifyingKey, Proof,}};
use sapling_crypto::{circuit::multipack, redjubjub::{self, Signature}};
use sapling_crypto::jubjub::{edwards,fs::FsRepr, FixedGenerators, JubjubBls12, JubjubParams, Unknown};
use crypto::{
Groth16VerifyingKey,
pairing::{bls12_381::{Bls12, Fr, FrRepr}, PrimeField, PrimeFieldRepr, PrimeFieldDecodingError},
bellman::{SynthesisError, groth16::{verify_proof, Proof}},
sapling_crypto::{circuit::multipack, redjubjub::{self, Signature}},
sapling_crypto::jubjub::{edwards,fs::FsRepr, FixedGenerators, JubjubBls12, JubjubParams, Unknown}
};
type Point = edwards::Point<Bls12, Unknown>;
lazy_static! {
static ref JUBJUB: JubjubBls12 = { JubjubBls12::new() };
static ref SAPLING_SPEND_VK: Option<PreparedVerifyingKey<Bls12>> = None;
static ref SAPLING_OUTPUT_VK: Option<PreparedVerifyingKey<Bls12>> = None;
}
/// Errors that could occur during sapling verification.
#[derive(Debug)]
pub enum Error {
/// Spend description verification error.
Spend(usize, SpendError),
@ -22,25 +23,27 @@ pub enum Error {
Output(usize, OutputError),
/// Invalid balance value.
InvalidBalanceValue,
/// Error deserializing/verifying binding_sig.
BindingSig(SignatureError),
/// Error verifying binding_sig.
BadBindingSignature,
}
/// Errors that can occur during spend description verification.
#[derive(Debug)]
pub enum SpendError {
/// Error deserializing value commitment.
ValueCommitment(PointError),
/// Error deserializing anchor.
Anchor(PrimeFieldDecodingError),
/// Error deserializing randomized key.
RandomizedKey(PublicKeyError),
/// Error deserializing/verifying spend_auth_sig.
SpendAuthSig(SignatureError),
RandomizedKey(PointError),
/// Error verifying spend_auth_sig.
BadSpendAuthSig,
/// Error deserializing/verifying zk-proof.
Proof(ProofError),
}
/// Errors that can occur during output description verification.
#[derive(Debug)]
pub enum OutputError {
/// Error deserializing value commitment.
ValueCommitment(PointError),
@ -53,6 +56,7 @@ pub enum OutputError {
}
/// Errors that can occur during point deserialization.
#[derive(Debug)]
pub enum PointError {
/// The point is invalid.
Invalid(IoError),
@ -60,23 +64,8 @@ pub enum PointError {
SmallOrder,
}
/// Errors that can occur during public key deserialization.
pub enum PublicKeyError {
/// The public key is invalid.
Invalid(IoError),
/// The point corresponding to the public key MUST NOT be small order.
SmallOrder,
}
/// Error that can occur during signature deserialization/verification.
pub enum SignatureError {
/// The signature is invalid.
Invalid(IoError),
/// The signature verifciation has failed.
Failed,
}
/// Proof verification error.
#[derive(Debug)]
pub enum ProofError {
/// The proof is invalid.
Invalid(IoError),
@ -87,19 +76,24 @@ pub enum ProofError {
}
/// Verify sapling proofs/signatures validity.
pub fn accept_sapling(sighash: &[u8; 32], sapling: &Sapling) -> Result<(), Error> {
pub fn accept_sapling(
spend_vk: &Groth16VerifyingKey,
output_vk: &Groth16VerifyingKey,
sighash: &[u8; 32],
sapling: &Sapling,
) -> Result<(), Error> {
// binding verification key is not encoded explicitly in transaction and must be recalculated
let mut total = edwards::Point::zero();
// verify each spend description
for (idx, spend) in sapling.spends.iter().enumerate() {
accept_spend(sighash, &mut total, spend)
accept_spend(spend_vk, sighash, &mut total, spend)
.map_err(|err| Error::Spend(idx, err))?;
}
// verify each output description
for (idx, output) in sapling.outputs.iter().enumerate() {
accept_output(&mut total, output)
accept_output(output_vk, &mut total, output)
.map_err(|err| Error::Output(idx, err))?;
}
@ -108,7 +102,12 @@ pub fn accept_sapling(sighash: &[u8; 32], sapling: &Sapling) -> Result<(), Error
}
/// Verify sapling spend description.
fn accept_spend(sighash: &[u8; 32], total: &mut Point, spend: &SaplingSpendDescription) -> Result<(), SpendError> {
fn accept_spend(
spend_vk: &Groth16VerifyingKey,
sighash: &[u8; 32],
total: &mut Point,
spend: &SaplingSpendDescription,
) -> Result<(), SpendError> {
// deserialize and check value commitment
let value_commitment = require_non_small_order_point(&spend.value_commitment)
.map_err(SpendError::ValueCommitment)?;
@ -127,18 +126,18 @@ fn accept_spend(sighash: &[u8; 32], total: &mut Point, spend: &SaplingSpendDescr
// deserialize and check randomized key
let randomized_key = redjubjub::PublicKey::<Bls12>::read(&spend.randomized_key[..], &JUBJUB)
.map_err(|err| SpendError::RandomizedKey(PublicKeyError::Invalid(err)))?;
.map_err(|err| SpendError::RandomizedKey(PointError::Invalid(err)))?;
if is_small_order(&randomized_key.0) {
return Err(SpendError::RandomizedKey(PublicKeyError::SmallOrder));
return Err(SpendError::RandomizedKey(PointError::SmallOrder));
}
// deserialize the signature
let spend_auth_sig = Signature::read(&spend.spend_auth_sig[..])
.map_err(|err| SpendError::SpendAuthSig(SignatureError::Invalid(err)))?;
.expect("only could fail if length of passed buffer != 64; qed");
// verify the spend_auth_sig
if !randomized_key.verify(&data_to_be_signed, &spend_auth_sig, FixedGenerators::SpendingKeyGenerator, &JUBJUB) {
return Err(SpendError::SpendAuthSig(SignatureError::Failed));
return Err(SpendError::BadSpendAuthSig);
}
// Add the nullifier through multiscalar packing
@ -164,8 +163,7 @@ fn accept_spend(sighash: &[u8; 32], total: &mut Point, spend: &SaplingSpendDescr
.map_err(|err| SpendError::Proof(ProofError::Invalid(err)))?;
// check the proof
let verification_key = SAPLING_SPEND_VK.as_ref().expect("TODO");
let is_verification_ok = verify_proof(verification_key, &zkproof, &public_input[..])
let is_verification_ok = verify_proof(&spend_vk.0, &zkproof, &public_input[..])
.map_err(|err| SpendError::Proof(ProofError::Synthesis(err)))?;
if !is_verification_ok {
return Err(SpendError::Proof(ProofError::Failed));
@ -174,7 +172,11 @@ fn accept_spend(sighash: &[u8; 32], total: &mut Point, spend: &SaplingSpendDescr
Ok(())
}
fn accept_output(total: &mut Point, output: &SaplingOutputDescription) -> Result<(), OutputError> {
fn accept_output(
output_vk: &Groth16VerifyingKey,
total: &mut Point,
output: &SaplingOutputDescription,
) -> Result<(), OutputError> {
// deserialize and check value commitment
let value_commitment = require_non_small_order_point(&output.value_commitment)
.map_err(OutputError::ValueCommitment)?;
@ -206,8 +208,7 @@ fn accept_output(total: &mut Point, output: &SaplingOutputDescription) -> Result
.map_err(|err| OutputError::Proof(ProofError::Invalid(err)))?;
// check the proof
let verification_key = SAPLING_OUTPUT_VK.as_ref().expect("TODO");
let is_verification_ok = verify_proof(verification_key, &zkproof, &public_input[..])
let is_verification_ok = verify_proof(&output_vk.0, &zkproof, &public_input[..])
.map_err(|err| OutputError::Proof(ProofError::Synthesis(err)))?;
if !is_verification_ok {
return Err(OutputError::Proof(ProofError::Failed));
@ -234,13 +235,13 @@ fn accept_sapling_final(sighash: &[u8; 32], total: Point, sapling: &Sapling) ->
// deserialize the binding signature
let binding_sig = Signature::read(&sapling.binding_sig[..])
.map_err(|err| Error::BindingSig(SignatureError::Invalid(err)))?;
.expect("only could fail if length of passed buffer != 64; qed");
// check the binding signature
let is_verification_ok = binding_verification_key
.verify(&data_to_be_signed, &binding_sig, FixedGenerators::ValueCommitmentRandomness, &JUBJUB);
if !is_verification_ok {
return Err(Error::BindingSig(SignatureError::Failed));
return Err(Error::BadBindingSignature);
}
Ok(())
@ -296,5 +297,239 @@ fn is_small_order(point: &Point) -> bool {
#[cfg(test)]
mod tests {
// TODO: detailed tests when sighash + verification keys are available
extern crate test_data;
use chain::Transaction;
use script::{TransactionInputSigner, SighashBase};
use super::*;
// tx: https://zcash.blockexplorer.com/tx/bd4fe81c15cfbd125f5ca6fe51fb5ac4ef340e64a36f576a6a09f7528eb2e176
fn test_tx() -> Transaction {
"0400008085202f8900000000000072da060010270000000000000148b1c0668fce604361fbb1b89bbd76f8fee09b51a9dc0fdfcf6c6720cd596083d970234fcc0e9a70fdfed82d32fbb9ca92c9c5c3bad5daad9ac62b5bf4255817ee5bc95a9af453bb9cc7e2c544aa29efa20011a65b624998369c849aa8f0bc83d60e7902a3cfe6eeaeb8d583a491de5982c5ded29e64cd8f8fac594a5bb4f2838e6c30876e36a18d8d935238815c8d9205a4f1f523ff76b51f614bff1064d1c5fa0a27ec0c43c8a6c2714e7234d32e9a8934a3e9c0f74f1fdac2ddf6be3b13bc933b0478cae556a2d387cc23b05e8b0bd53d9e838ad2d2cb31daccefe256087511b044dfae665f0af0fa968edeea4cbb437a8099724159471adf7946eec434cccc1129f4d1e31d7f3f8be524226c65f28897d3604c14efb64bea6a889b2705617432927229dfa382e78c0ace31cc158fbf3ec1597242955e45af1ee5cfaffd789cc80dc53d6b18d42033ec2c327170e2811fe8ec00feadeb1033eb48ab24a6dce2480ad428be57c4619466fc3181ece69b914fed30566ff853250ef19ef7370601f4c24b0125e4059eec61f63ccbe277363172f2bdee384412ea073c5aca06b94e402ba3a43e15bd9c65bbfb194c561c24a031dec43be95c59eb6b568c176b1038d5b7b057dc032488335284adebfb6607e6a995b7fa418f13c8a61b343e5df44faa1050d9d76550748d9efebe01da97ade5937afd5f007ed26e0af03f283611655e91bc6a4857f66a57a1584ff687c4baf725f4a1b32fae53a3e6e8b98bca319bb1badb704c9c1a04f401f33d813d605eef6943c2c52dbc85ab7081d1f8f69d3202aae281bf42336a949a12a7dbbd22abdd6e92996282ebd69033c22cb0539d97f83636d6a8232209a7411e8b03bef180d83e608563ea2d0becff56dc996c2049df054961bfb21b7cbef5049a7dacc18f2c977aa1b2d48291abc19c3c8ea25d2e61901048354b17ce952f6f2248cf3a0eb54c19b507b41d7281c3d227e2b142ff695d8b925a4bb942ed9492a73a17468a8332a367fd16295420bdca6c04d380271f40440709998fce3a3af3e1e505f5402e5dd464dd179cb0eede3d494a95b84d2fb2eb5abb425cf2c712af999c65259c4782a5ec97388324c67738908a5ba43b6db62a10f50cddf9b5039123437c74165921ac8cf4f13292a216baef9d00bd544106b52755986c98a462ade1149f69367e926d88eb92798c0e56cd19a1bcf264fd93293033b758da65c7901eb5b4a17ee265a3312dbc477868da0057e1b3cbf47726dead6ecfcc8e1044c6f311ff0fc83192dc2f75a89626ba33364dac747b63ff3c8337e00332c8783ba9c8dc13cdf0750d7adc3926fbe1279017d50adba35c38c5b810f73abe5d759cd7fb650f6b0a1f78dc1f62fd017090ff4de4cf54c883752ddda68083d4617ed2c38bab8da313965dd3f7b755aec23a2d9e2965d08d2134827a72ffb3bd65b1fd5410da105bfba7a74ddff0928a654aca1ee211ac9dce8019ddcbb52263ce44b2544a314355c1e8c8543f3ed3e883e7a7a8f9e3c7c11f41ab9069854fb21e9b3660a860df19d289d54b29d82522b32d187cde6261eb0a429c3994dff6f37b9ab9102281223e3cd584790a909e05ba0ea1a2d9aef8e571986e98e09312dccaf8e739d718a1edd217dc4c8a5c8a650015405b592a7c674a451d7d1686c7ea6d93e74a8fe4ade12b679ac780457f08a79bfbf96dcf7eefe9a39b99f1ae39d2c5f86aadf156b7d5ce4b2733f307cfe1e1ff6de0ff2006d9cba535b0c40dfb7a98399cdff8e681fc38c7b9aa94ee5eb89432e28d94ee27f238776ba964a87caf58eddbb64771e64de094305a8eb848d2d9ad6373903687d22170f48f1ae8d714514034ee2733857af4747312bb006e6ce3918ede8c730bacc7821b81c1b93bb50b219e79e8e0d74531ed18c1145632d9847d38783b49141ac5353aaa7d125fb2934e681467e16b28090978e74e0b".into()
}
fn compute_sighash(tx: Transaction) -> [u8; 32] {
let signer: TransactionInputSigner = tx.into();
signer.signature_hash(&mut None, None, 0, &From::from(vec![]), SighashBase::All.into(), 0x76b809bb).into()
}
fn run_accept_sapling(tx: Transaction) -> Result<(), Error> {
let spend_vk = crypto::load_sapling_spend_verifying_key().unwrap();
let output_vk = crypto::load_sapling_output_verifying_key().unwrap();
let sighash = compute_sighash(tx.clone());
let sapling = tx.sapling.unwrap();
accept_sapling(&spend_vk, &output_vk, &sighash, &sapling)
}
fn swap_xy(point: [u8; 32]) -> [u8; 32] {
let mut new_point = [0; 32];
new_point[..16].copy_from_slice(&point[16..]);
new_point[16..].copy_from_slice(&point[..16]);
new_point
}
fn small_order_point() -> [u8; 32] {
[0; 32]
}
fn not_in_field_number() -> [u8; 32] {
[0xFF; 32]
}
fn bad_signature() -> [u8; 64] {
[0; 64]
}
fn bad_proof() -> [u8; 192] {
[0; 192]
}
fn bad_verifying_key() -> Groth16VerifyingKey {
use crypto::pairing::{CurveAffine, bls12_381::{G1Affine, G2Affine}};
use crypto::bellman::groth16::{VerifyingKey, prepare_verifying_key};
Groth16VerifyingKey(prepare_verifying_key(&VerifyingKey {
alpha_g1: G1Affine::zero(),
beta_g1: G1Affine::zero(),
beta_g2: G2Affine::zero(),
gamma_g2: G2Affine::zero(),
delta_g1: G1Affine::zero(),
delta_g2: G2Affine::zero(),
ic: vec![],
}))
}
#[test]
fn accept_sapling_works() {
run_accept_sapling(test_tx()).unwrap();
}
#[test]
fn accept_spend_fails() {
let spend_vk = crypto::load_sapling_spend_verifying_key().unwrap();
let sighash = compute_sighash(test_tx());
let sapling = test_tx().sapling.unwrap();
let mut total = edwards::Point::zero();
// when value commitment isn't an on-curve point
let mut spend = sapling.spends[0].clone();
spend.value_commitment = swap_xy(spend.value_commitment);
assert_matches!(
accept_spend(&spend_vk, &sighash, &mut total, &spend),
Err(SpendError::ValueCommitment(PointError::Invalid(_)))
);
// when value commitment is a small order point
let mut spend = sapling.spends[0].clone();
spend.value_commitment = small_order_point();
assert_matches!(
accept_spend(&spend_vk, &sighash, &mut total, &spend),
Err(SpendError::ValueCommitment(PointError::SmallOrder))
);
// when anchor is not in field
let mut spend = sapling.spends[0].clone();
spend.anchor = not_in_field_number();
assert_matches!(
accept_spend(&spend_vk, &sighash, &mut total, &spend),
Err(SpendError::Anchor(_))
);
// when randomized key isn't represented by an on-curve point
let mut spend = sapling.spends[0].clone();
spend.randomized_key = swap_xy(spend.randomized_key);
assert_matches!(
accept_spend(&spend_vk, &sighash, &mut total, &spend),
Err(SpendError::RandomizedKey(PointError::Invalid(_)))
);
// when randomized key is represented by a small order point
let mut spend = sapling.spends[0].clone();
spend.randomized_key = small_order_point();
assert_matches!(
accept_spend(&spend_vk, &sighash, &mut total, &spend),
Err(SpendError::RandomizedKey(PointError::SmallOrder))
);
// when spend auth signature verification fails
let mut spend = sapling.spends[0].clone();
spend.spend_auth_sig = bad_signature();
assert_matches!(
accept_spend(&spend_vk, &sighash, &mut total, &spend),
Err(SpendError::BadSpendAuthSig)
);
// when proof is failed to deserialize
let mut spend = sapling.spends[0].clone();
spend.zkproof = bad_proof();
assert_matches!(
accept_spend(&spend_vk, &sighash, &mut total, &spend),
Err(SpendError::Proof(ProofError::Invalid(_)))
);
// when proof isn't compatible with verifying key
assert_matches!(
accept_spend(&bad_verifying_key(), &sighash, &mut total, &sapling.spends[0]),
Err(SpendError::Proof(ProofError::Synthesis(_)))
);
// when proof verification has failed
let mut spend = sapling.spends[0].clone();
spend.nullifier = [0; 32];
assert_matches!(
accept_spend(&spend_vk, &sighash, &mut total, &spend),
Err(SpendError::Proof(ProofError::Failed))
);
}
#[test]
fn accept_output_fails() {
let output_vk = crypto::load_sapling_output_verifying_key().unwrap();
let sapling = test_tx().sapling.unwrap();
let mut total = edwards::Point::zero();
// when value commitment isn't an on-curve point
let mut output = sapling.outputs[0].clone();
output.value_commitment = swap_xy(sapling.spends[0].value_commitment);
assert_matches!(
accept_output(&output_vk, &mut total, &output),
Err(OutputError::ValueCommitment(PointError::Invalid(_)))
);
// when value commitment is a small order point
let mut output = sapling.outputs[0].clone();
output.value_commitment = small_order_point();
assert_matches!(
accept_output(&output_vk, &mut total, &output),
Err(OutputError::ValueCommitment(PointError::SmallOrder))
);
// when note commitment is not in field
let mut output = sapling.outputs[0].clone();
output.note_commitment = not_in_field_number();
assert_matches!(
accept_output(&output_vk, &mut total, &output),
Err(OutputError::NoteCommitment(_))
);
// when empeheral key isn't represented by an on-curve point
let mut output = sapling.outputs[0].clone();
output.ephemeral_key = swap_xy(output.ephemeral_key);
assert_matches!(
accept_output(&output_vk, &mut total, &output),
Err(OutputError::EphemeralKey(PointError::Invalid(_)))
);
// when empeheral key is represented by a small order point
let mut output = sapling.outputs[0].clone();
output.ephemeral_key = small_order_point();
assert_matches!(
accept_output(&output_vk, &mut total, &output),
Err(OutputError::EphemeralKey(PointError::SmallOrder))
);
// when proof is failed to deserialize
let mut output = sapling.outputs[0].clone();
output.zkproof = bad_proof();
assert_matches!(
accept_output(&output_vk, &mut total, &output),
Err(OutputError::Proof(ProofError::Invalid(_)))
);
// when proof isn't compatible with verifying key
assert_matches!(
accept_output(&bad_verifying_key(), &mut total, &sapling.outputs[0]),
Err(OutputError::Proof(ProofError::Synthesis(_)))
);
// when proof verification has failed
let mut output = sapling.outputs[0].clone();
output.note_commitment = output.value_commitment.clone();
assert_matches!(
accept_output(&output_vk, &mut total, &output),
Err(OutputError::Proof(ProofError::Failed))
);
}
#[test]
fn accept_sapling_final_fails() {
let sighash = compute_sighash(test_tx().clone());
let sapling = test_tx().sapling.unwrap();
// when total value is -i64::MAX
let mut bad_sapling = sapling.clone();
bad_sapling.balancing_value = ::std::i64::MIN;
assert_matches!(
accept_sapling_final(&sighash, Point::zero(), &bad_sapling),
Err(Error::InvalidBalanceValue)
);
// when proof verification has failed
assert_matches!(
accept_sapling_final(&sighash, Point::zero(), &sapling),
Err(Error::BadBindingSignature)
);
}
}