move verify_kernel_sums into committed trait (#1131)

This commit is contained in:
Antioch Peverell 2018-06-02 19:00:44 +01:00 committed by GitHub
parent 6fd2afccb4
commit 0ecadd3486
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 302 additions and 210 deletions

View file

@ -23,6 +23,7 @@ use std::time::{Duration, Instant};
use core::core::hash::{Hash, Hashed};
use core::core::pmmr::MerkleProof;
use core::core::target::Difficulty;
use core::core::Committed;
use core::core::{Block, BlockHeader, Output, OutputIdentifier, Transaction, TxKernel};
use core::global;
use grin_store::Error::NotFoundErr;
@ -200,7 +201,14 @@ impl Chain {
header.height,
header.hash()
);
let (output_sum, kernel_sum) = extension.validate_sums(&header)?;
let (output_sum, kernel_sum) = extension.verify_kernel_sums(
header.total_overage(),
header.total_kernel_offset(),
None,
None,
)?;
store.save_block_sums(
&header.hash(),
&BlockSums {

View file

@ -24,11 +24,11 @@ use std::time::Instant;
use util::secp::pedersen::{Commitment, RangeProof};
use core::consensus::REWARD;
use core::core::committed::Committed;
use core::core::hash::{Hash, Hashed};
use core::core::pmmr::{self, MerkleProof, PMMR};
use core::core::{Block, BlockHeader, Committed, Input, Output, OutputFeatures, OutputIdentifier,
Transaction, TxKernel};
use core::core::{Block, BlockHeader, Input, Output, OutputFeatures, OutputIdentifier, Transaction,
TxKernel};
use core::global;
use core::ser::{PMMRIndexHashable, PMMRable};
@ -773,35 +773,6 @@ impl<'a> Extension<'a> {
Ok(())
}
/// The real magicking: the sum of all kernel excess should equal the sum
/// of all output commitments, minus the total supply.
pub fn validate_sums(&self, header: &BlockHeader) -> Result<((Commitment, Commitment)), Error> {
let now = Instant::now();
// Treat the total "supply" as one huge overage that needs to be accounted for.
// If we have a supply of 6,000 grin then we should
// have a corresponding 6,000 grin in unspent outputs.
let supply = ((header.height * REWARD) as i64).checked_neg().unwrap_or(0);
let output_sum = self.sum_commitments(supply, None)?;
let (kernel_sum, kernel_sum_plus_offset) =
self.sum_kernel_excesses(&header.total_kernel_offset, None)?;
if output_sum != kernel_sum_plus_offset {
return Err(Error::InvalidTxHashSet(
"Differing Output commitment and kernel excess sums.".to_owned(),
));
}
debug!(
LOGGER,
"txhashset: validated sums, took (total) {}s",
now.elapsed().as_secs(),
);
Ok((output_sum, kernel_sum))
}
/// Validate the txhashset state against the provided block header.
pub fn validate(
&mut self,
@ -816,13 +787,21 @@ impl<'a> Extension<'a> {
return Ok((zero_commit.clone(), zero_commit.clone()));
}
let (output_sum, kernel_sum) = self.validate_sums(header)?;
// The real magicking happens here.
// Sum of kernel excesses should equal sum of
// unspent outputs minus total supply.
let (output_sum, kernel_sum) = self.verify_kernel_sums(
header.total_overage(),
header.total_kernel_offset(),
None,
None,
)?;
// this is a relatively expensive verification step
// This is an expensive verification step.
self.verify_kernel_signatures()?;
// verify the rangeproof for each output in the sum above
// this is an expensive operation (only verified if requested)
// Verify the rangeproof for each output in the sum above.
// This is an expensive verification step (skip for faster verification).
if !skip_rproofs {
self.verify_rangeproofs()?;
}

View file

@ -20,6 +20,7 @@ use util::secp;
use util::secp::pedersen::Commitment;
use util::secp_static;
use core::core::committed;
use core::core::hash::{Hash, Hashed};
use core::core::target::Difficulty;
use core::core::{block, transaction, Block, BlockHeader};
@ -107,6 +108,8 @@ pub enum Error {
Transaction(transaction::Error),
/// Anything else
Other(String),
/// Error from summing and verifying kernel sums via committed trait.
Committed(committed::Error),
}
impl error::Error for Error {
@ -155,6 +158,12 @@ impl From<secp::Error> for Error {
}
}
impl From<committed::Error> for Error {
fn from(e: committed::Error) -> Error {
Error::Committed(e)
}
}
impl Error {
/// Whether the error is due to a block that was intrinsically wrong
pub fn is_bad_data(&self) -> bool {

View file

@ -21,12 +21,14 @@ use time;
use consensus;
use consensus::{exceeds_weight, reward, VerifySortOrder, REWARD};
use core::committed;
use core::committed::Committed;
use core::hash::{Hash, HashWriter, Hashed, ZERO_HASH};
use core::id::ShortIdentifiable;
use core::target::Difficulty;
use core::transaction;
use core::{Commitment, Committed, Input, KernelFeatures, Output, OutputFeatures, Proof, ShortId,
Transaction, TxKernel};
use core::{Commitment, Input, KernelFeatures, Output, OutputFeatures, Proof, ShortId, Transaction,
TxKernel};
use global;
use keychain;
use keychain::BlindingFactor;
@ -66,10 +68,18 @@ pub enum Error {
},
/// Underlying Merkle proof error
MerkleProof,
/// Error when verifying kernel sums via committed trait.
Committed(committed::Error),
/// Other unspecified error condition
Other(String),
}
impl From<committed::Error> for Error {
fn from(e: committed::Error) -> Error {
Error::Committed(e)
}
}
impl From<transaction::Error> for Error {
fn from(e: transaction::Error) -> Error {
Error::Transaction(e)
@ -219,6 +229,23 @@ impl BlockHeader {
hasher.finalize(&mut ret);
Hash(ret)
}
/// The "overage" to use when verifying the kernel sums.
/// For a block header the overage is 0 - reward.
pub fn overage(&self) -> i64 {
(REWARD as i64).checked_neg().unwrap_or(0)
}
/// The "total overage" to use when verifying the kernel sums for a full
/// chain state. For a full chain state this is 0 - (height * reward).
pub fn total_overage(&self) -> i64 {
((self.height * REWARD) as i64).checked_neg().unwrap_or(0)
}
/// Total kernel offset for the chain state up to and including this block.
pub fn total_kernel_offset(&self) -> BlindingFactor {
self.total_kernel_offset
}
}
/// Compact representation of a full block.
@ -658,7 +685,17 @@ impl Block {
self.verify_coinbase()?;
self.verify_inputs()?;
self.verify_kernel_lock_heights()?;
self.verify_sums(prev_output_sum, prev_kernel_sum)
let sums = self.verify_kernel_sums(
self.header.overage(),
self.header.total_kernel_offset(),
Some(prev_output_sum),
Some(prev_kernel_sum),
)?;
self.verify_rangeproofs()?;
self.verify_kernel_signatures()?;
Ok(sums)
}
fn verify_weight(&self) -> Result<(), Error> {
@ -707,37 +744,22 @@ impl Block {
Ok(())
}
/// Verify sums
pub fn verify_sums(
&self,
prev_output_sum: &Commitment,
prev_kernel_sum: &Commitment,
) -> Result<((Commitment, Commitment)), Error> {
// Verify the output rangeproofs.
// Note: this is expensive.
for x in &self.outputs {
x.verify_proof()?;
}
// Verify the kernel signatures.
// Note: this is expensive.
/// Verify the kernel signatures.
/// Note: this is expensive.
fn verify_kernel_signatures(&self) -> Result<(), Error> {
for x in &self.kernels {
x.verify()?;
}
// Sum all input|output|overage commitments.
let overage = (REWARD as i64).checked_neg().unwrap_or(0);
let io_sum = self.sum_commitments(overage, Some(prev_output_sum))?;
// Sum the kernel excesses accounting for the kernel offset.
let (kernel_sum, kernel_sum_plus_offset) =
self.sum_kernel_excesses(&self.header.total_kernel_offset, Some(prev_kernel_sum))?;
if io_sum != kernel_sum_plus_offset {
return Err(Error::KernelSumMismatch);
Ok(())
}
Ok((io_sum, kernel_sum))
/// Verify all the output rangeproofs.
/// Note: this is expensive.
fn verify_rangeproofs(&self) -> Result<(), Error> {
for x in &self.outputs {
x.verify_proof()?;
}
Ok(())
}
/// Validate the coinbase outputs generated by miners. Entails 2 main

169
core/src/core/committed.rs Normal file
View file

@ -0,0 +1,169 @@
// Copyright 2018 The Grin Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! The Committed trait and associated errors.
use keychain;
use keychain::BlindingFactor;
use util::secp::pedersen::*;
use util::{secp, secp_static, static_secp_instance};
/// Errors from summing and verifying kernel excesses via committed trait.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
/// Keychain related error.
Keychain(keychain::Error),
/// Secp related error.
Secp(secp::Error),
/// Kernel sums do not equal output sums.
KernelSumMismatch,
}
impl From<secp::Error> for Error {
fn from(e: secp::Error) -> Error {
Error::Secp(e)
}
}
impl From<keychain::Error> for Error {
fn from(e: keychain::Error) -> Error {
Error::Keychain(e)
}
}
/// Implemented by types that hold inputs and outputs (and kernels)
/// containing Pedersen commitments.
/// Handles the collection of the commitments as well as their
/// summing, taking potential explicit overages of fees into account.
pub trait Committed {
/// Gather the kernel excesses and sum them.
fn sum_kernel_excesses(
&self,
offset: &BlindingFactor,
extra_excess: Option<&Commitment>,
) -> Result<(Commitment, Commitment), Error> {
let zero_commit = secp_static::commit_to_zero_value();
// then gather the kernel excess commitments
let mut kernel_commits = self.kernels_committed();
if let Some(extra) = extra_excess {
kernel_commits.push(*extra);
}
// handle "zero commit" values by filtering them out here
kernel_commits.retain(|x| *x != zero_commit);
// sum the commitments
let kernel_sum = {
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
secp.commit_sum(kernel_commits, vec![])?
};
// sum the commitments along with the
// commit to zero built from the offset
let kernel_sum_plus_offset = {
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
let mut commits = vec![kernel_sum];
if *offset != BlindingFactor::zero() {
let key = offset.secret_key(&secp)?;
let offset_commit = secp.commit(0, key)?;
commits.push(offset_commit);
}
secp.commit_sum(commits, vec![])?
};
Ok((kernel_sum, kernel_sum_plus_offset))
}
/// Gathers commitments and sum them.
fn sum_commitments(
&self,
overage: i64,
extra_commit: Option<&Commitment>,
) -> Result<Commitment, Error> {
let zero_commit = secp_static::commit_to_zero_value();
// then gather the commitments
let mut input_commits = self.inputs_committed();
let mut output_commits = self.outputs_committed();
// add the overage as output commitment if positive,
// or as an input commitment if negative
if overage != 0 {
let over_commit = {
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
secp.commit_value(overage.abs() as u64).unwrap()
};
if overage < 0 {
input_commits.push(over_commit);
} else {
output_commits.push(over_commit);
}
}
if let Some(extra) = extra_commit {
output_commits.push(*extra);
}
// handle "zero commit" values by filtering them out here
output_commits.retain(|x| *x != zero_commit);
input_commits.retain(|x| *x != zero_commit);
// sum all that stuff
{
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
let res = secp.commit_sum(output_commits, input_commits)?;
Ok(res)
}
}
/// Vector of input commitments to verify.
fn inputs_committed(&self) -> Vec<Commitment>;
/// Vector of output commitments to verify.
fn outputs_committed(&self) -> Vec<Commitment>;
/// Vector of kernel excesses to verify.
fn kernels_committed(&self) -> Vec<Commitment>;
/// Verify the sum of the kernel excesses equals the
/// sum of the outputs, taking into account both
/// the kernel_offset and overage.
fn verify_kernel_sums(
&self,
overage: i64,
kernel_offset: BlindingFactor,
prev_output_sum: Option<&Commitment>,
prev_kernel_sum: Option<&Commitment>,
) -> Result<((Commitment, Commitment)), Error> {
// Sum all input|output|overage commitments.
let utxo_sum = self.sum_commitments(overage, prev_output_sum)?;
// Sum the kernel excesses accounting for the kernel offset.
let (kernel_sum, kernel_sum_plus_offset) =
self.sum_kernel_excesses(&kernel_offset, prev_kernel_sum)?;
if utxo_sum != kernel_sum_plus_offset {
return Err(Error::KernelSumMismatch);
}
Ok((utxo_sum, kernel_sum))
}
}

View file

@ -15,6 +15,7 @@
//! Core types
pub mod block;
pub mod committed;
pub mod hash;
pub mod id;
pub mod pmmr;
@ -23,122 +24,19 @@ pub mod transaction;
use consensus::GRIN_BASE;
#[allow(dead_code)]
use rand::{thread_rng, Rng};
use std::cmp::Ordering;
use std::num::ParseFloatError;
use std::{fmt, iter};
use util::secp::pedersen::*;
use util::{secp, secp_static, static_secp_instance};
pub use self::block::*;
pub use self::committed::Committed;
pub use self::id::ShortId;
pub use self::transaction::*;
use core::hash::Hashed;
use global;
use keychain;
use keychain::BlindingFactor;
use ser::{Error, Readable, Reader, Writeable, Writer};
/// Implemented by types that hold inputs and outputs (and kernels)
/// containing Pedersen commitments.
/// Handles the collection of the commitments as well as their
/// summing, taking potential explicit overages of fees into account.
pub trait Committed {
/// Gather the kernel excesses and sum them.
fn sum_kernel_excesses(
&self,
offset: &BlindingFactor,
extra_excess: Option<&Commitment>,
) -> Result<(Commitment, Commitment), keychain::Error> {
let zero_commit = secp_static::commit_to_zero_value();
// then gather the kernel excess commitments
let mut kernel_commits = self.kernels_committed();
if let Some(extra) = extra_excess {
kernel_commits.push(*extra);
}
// handle "zero commit" values by filtering them out here
kernel_commits.retain(|x| *x != zero_commit);
// sum the commitments
let kernel_sum = {
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
secp.commit_sum(kernel_commits, vec![])?
};
// sum the commitments along with the
// commit to zero built from the offset
let kernel_sum_plus_offset = {
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
let mut commits = vec![kernel_sum];
if *offset != BlindingFactor::zero() {
let key = offset.secret_key(&secp)?;
let offset_commit = secp.commit(0, key)?;
commits.push(offset_commit);
}
secp.commit_sum(commits, vec![])?
};
Ok((kernel_sum, kernel_sum_plus_offset))
}
/// Gathers commitments and sum them.
fn sum_commitments(
&self,
overage: i64,
extra_commit: Option<&Commitment>,
) -> Result<Commitment, secp::Error> {
let zero_commit = secp_static::commit_to_zero_value();
// then gather the commitments
let mut input_commits = self.inputs_committed();
let mut output_commits = self.outputs_committed();
// add the overage as output commitment if positive,
// or as an input commitment if negative
if overage != 0 {
let over_commit = {
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
secp.commit_value(overage.abs() as u64).unwrap()
};
if overage < 0 {
input_commits.push(over_commit);
} else {
output_commits.push(over_commit);
}
}
if let Some(extra) = extra_commit {
output_commits.push(*extra);
}
// handle "zero commit" values by filtering them out here
output_commits.retain(|x| *x != zero_commit);
input_commits.retain(|x| *x != zero_commit);
// sum all that stuff
{
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
secp.commit_sum(output_commits, input_commits)
}
}
/// Vector of input commitments to verify.
fn inputs_committed(&self) -> Vec<Commitment>;
/// Vector of output commitments to verify.
fn outputs_committed(&self) -> Vec<Commitment>;
/// Vector of kernel excesses to verify.
fn kernels_committed(&self) -> Vec<Commitment>;
}
/// Proof of work
#[derive(Clone, PartialOrd, PartialEq)]
pub struct Proof {

View file

@ -14,8 +14,8 @@
//! Transactions
use std::cmp::Ordering;
use std::cmp::max;
use std::cmp::Ordering;
use std::collections::HashSet;
use std::io::Cursor;
use std::{error, fmt};
@ -26,11 +26,12 @@ use util::{kernel_sig_msg, static_secp_instance};
use consensus;
use consensus::VerifySortOrder;
use core::BlockHeader;
use core::Committed;
use core::committed;
use core::committed::Committed;
use core::global;
use core::hash::{Hash, Hashed, ZERO_HASH};
use core::pmmr::MerkleProof;
use core::BlockHeader;
use keychain;
use keychain::BlindingFactor;
use ser::{self, read_and_verify_sorted, ser_vec, PMMRable, Readable, Reader, Writeable,
@ -75,9 +76,8 @@ pub enum Error {
/// Returns if the value hidden within the a RangeProof message isn't
/// repeated 3 times, indicating it's incorrect
InvalidProofMessage,
/// Error when sums do not verify correctly during tx aggregation.
/// Likely a "double spend" across two unconfirmed txs.
AggregationError,
/// Error when verifying kernel sums via committed trait.
Committed(committed::Error),
}
impl error::Error for Error {
@ -114,6 +114,12 @@ impl From<keychain::Error> for Error {
}
}
impl From<committed::Error> for Error {
fn from(e: committed::Error) -> Error {
Error::Committed(e)
}
}
/// A proof that a transaction sums to zero. Includes both the transaction's
/// Pedersen commitment and the signature, that guarantees that the commitments
/// amount to zero.
@ -387,6 +393,10 @@ impl Transaction {
self.kernels.iter().fold(0, |acc, ref x| acc + x.fee)
}
fn overage(&self) -> i64 {
self.fee() as i64
}
/// Lock height of a transaction is the max lock height of the kernels.
pub fn lock_height(&self) -> u64 {
self.kernels
@ -394,46 +404,24 @@ impl Transaction {
.fold(0, |acc, ref x| max(acc, x.lock_height))
}
/// Verify the kernel signatures.
/// Note: this is expensive.
fn verify_kernel_signatures(&self) -> Result<(), Error> {
// Verify the kernel signatures.
// Note: this is expensive.
for x in &self.kernels {
x.verify()?;
}
Ok(())
}
/// Verify all the output rangeproofs.
/// Note: this is expensive.
fn verify_rangeproofs(&self) -> Result<(), Error> {
// Verify all the output rangeproofs.
// Note: this is expensive.
for x in &self.outputs {
x.verify_proof()?;
}
Ok(())
}
/// To verify transaction kernels we check that -
/// * all kernels have an even fee
/// * sum of input/output commitments matches sum of kernel commitments
/// after applying offset * each kernel sig is valid (i.e. tx commitments
/// sum to zero, given above is true)
fn verify_kernel_sums(&self) -> Result<(), Error> {
// Sum all input|output|overage commitments.
let overage = self.fee() as i64;
let io_sum = self.sum_commitments(overage, None)?;
// Sum the kernel excesses accounting for the kernel offset.
let (_, kernel_sum) = self.sum_kernel_excesses(&self.offset, None)?;
// sum of kernel commitments (including the offset) must match
// the sum of input/output commitments (minus fee)
if io_sum != kernel_sum {
return Err(Error::KernelSumMismatch);
}
Ok(())
}
/// Validates all relevant parts of a fully built transaction. Checks the
/// excess value against the signature as well as range proofs for each
/// output.
@ -442,7 +430,7 @@ impl Transaction {
return Err(Error::TooManyInputs);
}
self.verify_sorted()?;
self.verify_kernel_sums()?;
self.verify_kernel_sums(self.overage(), self.offset, None, None)?;
self.verify_rangeproofs()?;
self.verify_kernel_signatures()?;
@ -553,10 +541,9 @@ pub fn aggregate(transactions: Vec<Transaction>) -> Result<Transaction, Error> {
let tx = Transaction::new(new_inputs, new_outputs, kernels).with_offset(total_kernel_offset);
// We need to check sums here as aggregation/cut-through may have created an
// invalid tx.
tx.verify_kernel_sums()
.map_err(|_| Error::AggregationError)?;
// We need to check sums here as aggregation/cut-through
// may have created an invalid tx.
tx.verify_kernel_sums(tx.overage(), tx.offset, None, None)?;
Ok(tx)
}

View file

@ -24,6 +24,7 @@ use grin_core::consensus::{BLOCK_OUTPUT_WEIGHT, MAX_BLOCK_WEIGHT};
use grin_core::core::block::Error;
use grin_core::core::hash::Hashed;
use grin_core::core::id::{ShortId, ShortIdentifiable};
use grin_core::core::Committed;
use grin_core::core::{Block, BlockHeader, CompactBlock, KernelFeatures, OutputFeatures};
use grin_core::global;
use grin_core::ser;
@ -167,7 +168,12 @@ fn remove_coinbase_output_flag() {
.remove(OutputFeatures::COINBASE_OUTPUT);
assert_eq!(b.verify_coinbase(), Err(Error::CoinbaseSumMismatch));
assert!(b.verify_sums(&zero_commit, &zero_commit).is_ok());
assert!(b.verify_kernel_sums(
b.header.overage(),
b.header.total_kernel_offset(),
None,
None
).is_ok());
assert_eq!(
b.validate(&zero_commit, &zero_commit),
Err(Error::CoinbaseSumMismatch)

View file

@ -16,6 +16,7 @@
use failure::{Backtrace, Context, Fail};
use std::fmt::{self, Display};
use core::core::committed;
use core::core::transaction;
use keychain::{self, extkey};
use util::secp;
@ -50,6 +51,9 @@ pub enum ErrorKind {
/// Fee error
#[fail(display = "Fee Error")]
Fee(String),
/// Error from summing commitments via committed trait.
#[fail(display = "Committed Error")]
Committed(committed::Error),
}
impl Fail for Error {
@ -97,6 +101,14 @@ impl From<secp::Error> for Error {
}
}
impl From<committed::Error> for Error {
fn from(error: committed::Error) -> Error {
Error {
inner: Context::new(ErrorKind::Committed(error)),
}
}
}
impl From<keychain::Error> for Error {
fn from(error: keychain::Error) -> Error {
Error {

View file

@ -17,13 +17,15 @@
use rand::thread_rng;
use uuid::Uuid;
use core::core::{amount_to_hr_string, Committed, Transaction};
use core::core::committed;
use core::core::committed::Committed;
use core::core::{amount_to_hr_string, Transaction};
use keychain::{BlindSum, BlindingFactor, Keychain};
use libtx::error::{Error, ErrorKind};
use libtx::{aggsig, build, tx_fee};
use util::secp::Signature;
use util::secp::key::{PublicKey, SecretKey};
use util::secp::Signature;
use util::{secp, LOGGER};
/// Public data for each participant in the slate