Include index as part of pmmr element hash (#748)

* storing index as part of pmmr element hash

* factor out hash_with_index into generic trait impl

* tighten up trait bounds for PMMRable
This commit is contained in:
Yeastplume 2018-03-05 15:05:42 +00:00 committed by GitHub
parent 63c5795eb9
commit 90f844d382
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 48 additions and 24 deletions

View file

@ -30,7 +30,7 @@ use core::core::{Block, BlockHeader, Input, Output, OutputFeatures, OutputIdenti
OutputStoreable, TxKernel};
use core::core::pmmr::{self, MerkleProof, PMMR};
use core::core::hash::{Hash, Hashed};
use core::ser::{self, PMMRable};
use core::ser::{self, PMMRable, PMMRIndexHashable};
use grin_store;
use grin_store::pmmr::{PMMRBackend, PMMRFileMetadata};
@ -141,7 +141,7 @@ impl SumTrees {
let output_pmmr: PMMR<OutputStoreable, _> =
PMMR::at(&mut self.utxo_pmmr_h.backend, self.utxo_pmmr_h.last_pos);
if let Some((hash, _)) = output_pmmr.get(pos, false) {
if hash == output_id.hash() {
if hash == output_id.hash_with_index(pos) {
Ok(hash)
} else {
Err(Error::SumTreeErr(format!("sumtree hash mismatch")))
@ -348,14 +348,14 @@ impl<'a> Extension<'a> {
fn apply_input(&mut self, input: &Input, height: u64) -> Result<(), Error> {
let commit = input.commitment();
let pos_res = self.get_output_pos(&commit);
let output_id_hash = OutputIdentifier::from_input(input).hash();
if let Ok(pos) = pos_res {
let output_id_hash = OutputIdentifier::from_input(input).hash_with_index(pos);
if let Some((read_hash, read_elem)) = self.utxo_pmmr.get(pos, true) {
// check hash from pmmr matches hash from input (or corresponding output)
// if not then the input is not being honest about
// what it is attempting to spend...
if output_id_hash != read_hash
|| output_id_hash != read_elem.expect("no output at position").hash()
|| output_id_hash != read_elem.expect("no output at position").hash_with_index(pos)
{
return Err(Error::SumTreeErr(format!("output pmmr hash mismatch")));
}

View file

@ -40,7 +40,7 @@ use std::marker::PhantomData;
use core::hash::{Hash, Hashed};
use ser;
use ser::{Readable, Reader, Writeable, Writer};
use ser::PMMRable;
use ser::{PMMRable, PMMRIndexHashable};
use util;
use util::LOGGER;
@ -360,7 +360,7 @@ where
/// the same time if applicable.
pub fn push(&mut self, elmt: T) -> Result<u64, String> {
let elmt_pos = self.last_pos + 1;
let mut current_hash = elmt.hash();
let mut current_hash = elmt.hash_with_index(elmt_pos);
let mut to_append = vec![(current_hash, Some(elmt))];
let mut height = 0;
let mut pos = elmt_pos;
@ -917,6 +917,7 @@ mod test {
use ser::{Error, Readable, Writeable};
use core::{Reader, Writer};
use core::hash::Hash;
use ser::{PMMRable, PMMRIndexHashable};
/// Simple MMR backend implementation based on a Vector. Pruning does not
/// compact the Vec itself.
@ -1169,7 +1170,7 @@ mod test {
fn len() -> usize {
16
}
}
}
impl Writeable for TestElem {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), Error> {
@ -1305,28 +1306,28 @@ mod test {
// one element
pmmr.push(elems[0]).unwrap();
let node_hash = elems[0].hash();
assert_eq!(pmmr.root(), node_hash,);
let node_hash = elems[0].hash_with_index(1);
assert_eq!(pmmr.root(), node_hash);
assert_eq!(pmmr.unpruned_size(), 1);
pmmr.dump(false);
// two elements
pmmr.push(elems[1]).unwrap();
let sum2 = elems[0].hash() + elems[1].hash();
let sum2 = elems[0].hash_with_index(1) + elems[1].hash_with_index(2);
pmmr.dump(false);
assert_eq!(pmmr.root(), sum2);
assert_eq!(pmmr.unpruned_size(), 3);
// three elements
pmmr.push(elems[2]).unwrap();
let sum3 = sum2 + elems[2].hash();
let sum3 = sum2 + elems[2].hash_with_index(4);
pmmr.dump(false);
assert_eq!(pmmr.root(), sum3);
assert_eq!(pmmr.unpruned_size(), 4);
// four elements
pmmr.push(elems[3]).unwrap();
let sum_one = elems[2].hash() + elems[3].hash();
let sum_one = elems[2].hash_with_index(4) + elems[3].hash_with_index(5);
let sum4 = sum2 + sum_one;
pmmr.dump(false);
assert_eq!(pmmr.root(), sum4);
@ -1334,33 +1335,34 @@ mod test {
// five elements
pmmr.push(elems[4]).unwrap();
let sum3 = sum4 + elems[4].hash();
let sum3 = sum4 + elems[4].hash_with_index(8);
pmmr.dump(false);
assert_eq!(pmmr.root(), sum3);
assert_eq!(pmmr.unpruned_size(), 8);
// six elements
pmmr.push(elems[5]).unwrap();
let sum6 = sum4 + (elems[4].hash() + elems[5].hash());
let sum6 = sum4 + (elems[4].hash_with_index(8) + elems[5].hash_with_index(9));
assert_eq!(pmmr.root(), sum6.clone());
assert_eq!(pmmr.unpruned_size(), 10);
// seven elements
pmmr.push(elems[6]).unwrap();
let sum7 = sum6 + elems[6].hash();
let sum7 = sum6 + elems[6].hash_with_index(11);
assert_eq!(pmmr.root(), sum7);
assert_eq!(pmmr.unpruned_size(), 11);
// eight elements
pmmr.push(elems[7]).unwrap();
let sum8 =
sum4 + ((elems[4].hash() + elems[5].hash()) + (elems[6].hash() + elems[7].hash()));
sum4 + ((elems[4].hash_with_index(8) + elems[5].hash_with_index(9))
+ (elems[6].hash_with_index(11) + elems[7].hash_with_index(12)));
assert_eq!(pmmr.root(), sum8);
assert_eq!(pmmr.unpruned_size(), 15);
// nine elements
pmmr.push(elems[8]).unwrap();
let sum9 = sum8 + elems[8].hash();
let sum9 = sum8 + elems[8].hash_with_index(16);
assert_eq!(pmmr.root(), sum9);
assert_eq!(pmmr.unpruned_size(), 16);
}

View file

@ -938,6 +938,8 @@ pub struct OutputIdentifier {
pub commit: Commitment,
}
impl OutputIdentifier {
/// Build a new output_identifier.
pub fn new(features: OutputFeatures, commit: &Commitment) -> OutputIdentifier {
@ -973,6 +975,13 @@ impl OutputIdentifier {
}
}
/// Ensure this is implemented to centralize hashing with indexes
impl PMMRable for OutputIdentifier {
fn len() -> usize {
1 + secp::constants::PEDERSEN_COMMITMENT_SIZE + SWITCH_COMMIT_HASH_SIZE
}
}
impl Writeable for OutputIdentifier {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u8(self.features.bits())?;

View file

@ -25,7 +25,7 @@ use byteorder::{BigEndian, ByteOrder, ReadBytesExt};
use keychain::{BlindingFactor, Identifier, IDENTIFIER_SIZE};
use consensus;
use consensus::VerifySortOrder;
use core::hash::Hashed;
use core::hash::{Hashed, Hash};
use core::transaction::{SwitchCommitHash, SWITCH_COMMIT_HASH_SIZE};
use util::secp::pedersen::Commitment;
use util::secp::pedersen::RangeProof;
@ -549,11 +549,23 @@ impl Writeable for [u8; 4] {
}
/// Trait for types that can serialize and report their size
pub trait PMMRable: Readable + Writeable + Hashed + Clone {
pub trait PMMRable: Readable + Writeable + Clone {
/// Length in bytes
fn len() -> usize;
}
/// Generic trait to ensure PMMR elements can be hashed with an index
pub trait PMMRIndexHashable {
/// Hash with a given index
fn hash_with_index(&self, index: u64) -> Hash;
}
impl<T: PMMRable> PMMRIndexHashable for T {
fn hash_with_index(&self, index: u64) -> Hash {
(index, self).hash()
}
}
/// Useful marker trait on types that can be sized byte slices
pub trait AsFixedBytes: Sized + AsRef<[u8]> {
/// The length in bytes

View file

@ -37,13 +37,14 @@ fn pmmr_append() {
backend.sync().unwrap();
// check the resulting backend store and the computation of the root
let node_hash = elems[0].hash();
let node_hash = elems[0].hash_with_index(1);
assert_eq!(backend.get(1, false).expect("").0, node_hash);
let sum2 = elems[0].hash() + elems[1].hash();
let sum4 = sum2 + (elems[2].hash() + elems[3].hash());
let sum8 = sum4 + ((elems[4].hash() + elems[5].hash()) + (elems[6].hash() + elems[7].hash()));
let sum9 = sum8 + elems[8].hash();
let sum2 = elems[0].hash_with_index(1) + elems[1].hash_with_index(2);
let sum4 = sum2 + (elems[2].hash_with_index(4) + elems[3].hash_with_index(5));
let sum8 = sum4 + ((elems[4].hash_with_index(8) + elems[5].hash_with_index(9))
+ (elems[6].hash_with_index(11) + elems[7].hash_with_index(12)));
let sum9 = sum8 + elems[8].hash_with_index(16);
{
let pmmr: PMMR<TestElem, _> = PMMR::at(&mut backend, mmr_size);