diff --git a/chain/src/pipe.rs b/chain/src/pipe.rs index e2346e214..ed880628b 100644 --- a/chain/src/pipe.rs +++ b/chain/src/pipe.rs @@ -213,7 +213,7 @@ fn validate_header(header: &BlockHeader, ctx: &mut BlockContext) -> Result<(), E /// Fully validate the block content. fn validate_block(b: &Block, ctx: &mut BlockContext) -> Result<(), Error> { let curve = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); - try!(b.verify(&curve).map_err(&Error::InvalidBlockProof)); + try!(b.validate(&curve).map_err(&Error::InvalidBlockProof)); if !ctx.opts.intersects(SYNC) { // TODO check every input exists as a UTXO using the UXTO index diff --git a/core/Cargo.toml b/core/Cargo.toml index bb989ac15..378da1e72 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" authors = ["Ignotus Peverell "] [dependencies] +bitflags = "~0.7.0" byteorder = "^0.5" num-bigint = "^0.1.35" rust-crypto = "^0.2" diff --git a/core/src/core/block.rs b/core/src/core/block.rs index 4953c3125..2912f2d19 100644 --- a/core/src/core/block.rs +++ b/core/src/core/block.rs @@ -21,13 +21,20 @@ use secp::key::SecretKey; use std::collections::HashSet; use core::Committed; -use core::{Input, Output, Proof, TxKernel, Transaction}; +use core::{Input, Output, Proof, TxKernel, Transaction, COINBASE_KERNEL, COINBASE_OUTPUT}; use core::transaction::merkle_inputs_outputs; use consensus::{REWARD, DEFAULT_SIZESHIFT}; use core::hash::{Hash, Hashed, ZERO_HASH}; use core::target::Difficulty; use ser::{self, Readable, Reader, Writeable, Writer}; +bitflags! { + /// Options for block validation + pub flags BlockFeatures: u8 { + const DEFAULT_BLOCK = 0b00000000, + } +} + /// Block header, fairly standard compared to other blockchains. pub struct BlockHeader { /// Height of this block since the genesis block (height 0) @@ -39,7 +46,10 @@ pub struct BlockHeader { /// Length of the cuckoo cycle used to mine this block. pub cuckoo_len: u8, pub utxo_merkle: Hash, + /// Merkle tree of hashes for all inputs, outputs and kernels in the block pub tx_merkle: Hash, + /// Features specific to this block, allowing possible future extensions + pub features: BlockFeatures, /// Nonce increment used to mine this block. pub nonce: u64, /// Proof of work data. @@ -61,6 +71,7 @@ impl Default for BlockHeader { total_difficulty: Difficulty::one(), utxo_merkle: ZERO_HASH, tx_merkle: ZERO_HASH, + features: DEFAULT_BLOCK, nonce: 0, pow: Proof::zero(), } @@ -76,7 +87,8 @@ impl Writeable for BlockHeader { [write_i64, self.timestamp.to_timespec().sec], [write_u8, self.cuckoo_len], [write_fixed_bytes, &self.utxo_merkle], - [write_fixed_bytes, &self.tx_merkle]); + [write_fixed_bytes, &self.tx_merkle], + [write_u8, self.features.bits()]); try!(writer.write_u64(self.nonce)); try!(self.difficulty.write(writer)); @@ -97,7 +109,7 @@ impl Readable for BlockHeader { let (timestamp, cuckoo_len) = ser_multiread!(reader, read_i64, read_u8); let utxo_merkle = try!(Hash::read(reader)); let tx_merkle = try!(Hash::read(reader)); - let nonce = try!(reader.read_u64()); + let (features, nonce) = ser_multiread!(reader, read_u8, read_u64); let difficulty = try!(Difficulty::read(reader)); let total_difficulty = try!(Difficulty::read(reader)); let pow = try!(Proof::read(reader)); @@ -112,6 +124,7 @@ impl Readable for BlockHeader { cuckoo_len: cuckoo_len, utxo_merkle: utxo_merkle, tx_merkle: tx_merkle, + features: BlockFeatures::from_bits(features).ok_or(ser::Error::CorruptedData)?, pow: pow, nonce: nonce, difficulty: difficulty, @@ -344,15 +357,32 @@ impl Block { .compact() } - /// Checks the block is valid by verifying the overall commitments sums and - /// kernels. - pub fn verify(&self, secp: &Secp256k1) -> Result<(), secp::Error> { + /// Validates all the elements in a block that can be checked without + /// additional + /// data. Includes commitment sums and kernels, Merkle trees, reward, etc. + pub fn validate(&self, secp: &Secp256k1) -> Result<(), secp::Error> { + self.verify_coinbase(secp)?; + self.verify_kernels(secp)?; + + // verify the transaction Merkle root + let tx_merkle = merkle_inputs_outputs(&self.inputs, &self.outputs); + if tx_merkle != self.header.tx_merkle { + // TODO more specific error + return Err(secp::Error::IncorrectCommitSum); + } + Ok(()) + } + + /// Validate the sum of input/output commitments match the sum in kernels + /// and + /// that all kernel signatures are valid. + pub fn verify_kernels(&self, secp: &Secp256k1) -> Result<(), secp::Error> { // sum all inputs and outs commitments - let io_sum = try!(self.sum_commitments(secp)); + let io_sum = self.sum_commitments(secp)?; // sum all kernels commitments let proof_commits = map_vec!(self.kernels, |proof| proof.excess); - let proof_sum = try!(secp.commit_sum(proof_commits, vec![])); + let proof_sum = secp.commit_sum(proof_commits, vec![])?; // both should be the same if proof_sum != io_sum { @@ -362,19 +392,39 @@ impl Block { // verify all signatures with the commitment as pk for proof in &self.kernels { - try!(proof.verify(secp)); + proof.verify(secp)?; } - - // verify the transaction Merkle root - let tx_merkle = merkle_inputs_outputs(&self.inputs, &self.outputs); - if tx_merkle != self.header.tx_merkle { - // TODO more specific error - return Err(secp::Error::IncorrectCommitSum); - } - Ok(()) } + // Validate the coinbase outputs generated by miners. Entails 2 main checks: + // + // * That the sum of all coinbase-marked outputs equal the supply. + // * That the sum of blinding factors for all coinbase-marked outputs match + // the coinbase-marked kernels. + fn verify_coinbase(&self, secp: &Secp256k1) -> Result<(), secp::Error> { + let cb_outs = self.outputs + .iter() + .filter(|out| out.features.intersects(COINBASE_OUTPUT)) + .map(|o| o.clone()) + .collect::>(); + let cb_kerns = self.kernels + .iter() + .filter(|k| k.features.intersects(COINBASE_KERNEL)) + .map(|k| k.clone()) + .collect::>(); + + // verifying the kernels on a block composed of just the coinbase outputs + // and kernels checks all we need + Block { + header: BlockHeader::default(), + inputs: vec![], + outputs: cb_outs, + kernels: cb_kerns, + } + .verify_kernels(secp) + } + // Builds the blinded output and related signature proof for the block reward. fn reward_output(skey: secp::key::SecretKey, secp: &Secp256k1) @@ -385,6 +435,7 @@ impl Block { let rproof = secp.range_proof(0, REWARD, skey, commit); let output = Output { + features: COINBASE_OUTPUT, commit: commit, proof: rproof, }; @@ -394,6 +445,7 @@ impl Block { let excess = try!(secp.commit_sum(vec![over_commit], vec![out_commit])); let proof = TxKernel { + features: COINBASE_KERNEL, excess: excess, excess_sig: sig.serialize_der(&secp), fee: 0, @@ -402,80 +454,84 @@ impl Block { } } - #[cfg(test)] - mod test { - use super::*; - use core::{Input, Output, Transaction}; +#[cfg(test)] +mod test { + use super::*; + use core::{Input, Output, Transaction}; use core::build::{self, input, output, input_rand, output_rand, with_fee}; - use core::hash::{Hash, Hashed}; - use core::test::{tx1i1o, tx2i1o}; + use core::hash::{Hash, Hashed}; + use core::test::{tx1i1o, tx2i1o}; - use secp::{self, Secp256k1}; - use secp::key::SecretKey; - use rand::Rng; - use rand::os::OsRng; + use secp::{self, Secp256k1}; + use secp::key::SecretKey; + use rand::Rng; + use rand::os::OsRng; - fn new_secp() -> Secp256k1 { - secp::Secp256k1::with_caps(secp::ContextFlag::Commit) - } + fn new_secp() -> Secp256k1 { + secp::Secp256k1::with_caps(secp::ContextFlag::Commit) + } - // utility to create a block without worrying about the key or previous - //header - fn new_block(txs: Vec<&mut Transaction>, secp: &Secp256k1) -> Block { - let mut rng = OsRng::new().unwrap(); - let skey = SecretKey::new(secp, &mut rng); - Block::new(&BlockHeader::default(), txs, skey).unwrap() - } - - // utility producing a transaction that spends an output with the provided - // value and blinding key - fn txspend1i1o(v: u64, b: SecretKey) -> Transaction { - build::transaction(vec![input(v, b), output_rand(3), with_fee(1)]).map(|(tx, _)| tx).unwrap() - } - - #[test] - // builds a block with a tx spending another and check if merging occurred - fn compactable_block() { - let mut rng = OsRng::new().unwrap(); - let ref secp = new_secp(); - - let mut btx1 = tx2i1o(); + // utility to create a block without worrying about the key or previous + // header + fn new_block(txs: Vec<&mut Transaction>, secp: &Secp256k1) -> Block { + let mut rng = OsRng::new().unwrap(); let skey = SecretKey::new(secp, &mut rng); - let (mut btx2, _) = build::transaction(vec![input_rand(5), output(4, skey), with_fee(1)]).unwrap(); + Block::new(&BlockHeader::default(), txs, skey).unwrap() + } - // spending tx2 - let mut btx3 = txspend1i1o(4, skey); - let b = new_block(vec![&mut btx1, &mut btx2, &mut btx3], secp); + // utility producing a transaction that spends an output with the provided + // value and blinding key + fn txspend1i1o(v: u64, b: SecretKey) -> Transaction { + build::transaction(vec![input(v, b), output_rand(3), with_fee(1)]) + .map(|(tx, _)| tx) + .unwrap() + } - // block should have been automatically compacted (including reward - // output) and should still be valid - b.verify(&secp).unwrap(); - assert_eq!(b.inputs.len(), 3); - assert_eq!(b.outputs.len(), 3); - } + #[test] + // builds a block with a tx spending another and check if merging occurred + fn compactable_block() { + let mut rng = OsRng::new().unwrap(); + let ref secp = new_secp(); - #[test] - // builds 2 different blocks with a tx spending another and check if merging - // occurs - fn mergeable_blocks() { - let mut rng = OsRng::new().unwrap(); - let ref secp = new_secp(); - - let mut btx1 = tx2i1o(); + let mut btx1 = tx2i1o(); let skey = SecretKey::new(secp, &mut rng); - let (mut btx2, _) = build::transaction(vec![input_rand(5), output(4, skey), with_fee(1)]).unwrap(); + let (mut btx2, _) = build::transaction(vec![input_rand(5), output(4, skey), with_fee(1)]) + .unwrap(); - // spending tx2 - let mut btx3 = txspend1i1o(4, skey); + // spending tx2 + let mut btx3 = txspend1i1o(4, skey); + let b = new_block(vec![&mut btx1, &mut btx2, &mut btx3], secp); - let b1 = new_block(vec![&mut btx1, &mut btx2], secp); - b1.verify(&secp).unwrap(); - let b2 = new_block(vec![&mut btx3], secp); - b2.verify(&secp).unwrap(); + // block should have been automatically compacted (including reward + // output) and should still be valid + b.validate(&secp).unwrap(); + assert_eq!(b.inputs.len(), 3); + assert_eq!(b.outputs.len(), 3); + } - // block should have been automatically compacted and should still be valid - let b3 = b1.merge(b2); - assert_eq!(b3.inputs.len(), 3); - assert_eq!(b3.outputs.len(), 4); - } + #[test] + // builds 2 different blocks with a tx spending another and check if merging + // occurs + fn mergeable_blocks() { + let mut rng = OsRng::new().unwrap(); + let ref secp = new_secp(); + + let mut btx1 = tx2i1o(); + let skey = SecretKey::new(secp, &mut rng); + let (mut btx2, _) = build::transaction(vec![input_rand(5), output(4, skey), with_fee(1)]) + .unwrap(); + + // spending tx2 + let mut btx3 = txspend1i1o(4, skey); + + let b1 = new_block(vec![&mut btx1, &mut btx2], secp); + b1.validate(&secp).unwrap(); + let b2 = new_block(vec![&mut btx3], secp); + b2.validate(&secp).unwrap(); + + // block should have been automatically compacted and should still be valid + let b3 = b1.merge(b2); + assert_eq!(b3.inputs.len(), 3); + assert_eq!(b3.outputs.len(), 4); + } } diff --git a/core/src/core/build.rs b/core/src/core/build.rs index 9847f6586..6f7391ae4 100644 --- a/core/src/core/build.rs +++ b/core/src/core/build.rs @@ -31,7 +31,7 @@ use secp::key::SecretKey; use secp::pedersen::*; use rand::os::OsRng; -use core::{Transaction, Input, Output}; +use core::{Transaction, Input, Output, DEFAULT_OUTPUT}; /// Context information available to transaction combinators. pub struct Context { @@ -113,6 +113,7 @@ pub fn output(value: u64, blinding: SecretKey) -> Box { let commit = build.secp.commit(value, blinding).unwrap(); let rproof = build.secp.range_proof(0, value, blinding, commit); (tx.with_output(Output { + features: DEFAULT_OUTPUT, commit: commit, proof: rproof, }), @@ -129,6 +130,7 @@ pub fn output_rand(value: u64) -> Box { let commit = build.secp.commit(value, blinding).unwrap(); let rproof = build.secp.range_proof(0, value, blinding, commit); (tx.with_output(Output { + features: DEFAULT_OUTPUT, commit: commit, proof: rproof, }), diff --git a/core/src/core/mod.rs b/core/src/core/mod.rs index 0c06f7c04..05d843fd1 100644 --- a/core/src/core/mod.rs +++ b/core/src/core/mod.rs @@ -28,8 +28,9 @@ use secp::{self, Secp256k1}; use secp::pedersen::*; use consensus::PROOFSIZE; -pub use self::block::{Block, BlockHeader}; -pub use self::transaction::{Transaction, Input, Output, TxKernel}; +pub use self::block::{Block, BlockHeader, DEFAULT_BLOCK}; +pub use self::transaction::{Transaction, Input, Output, TxKernel, COINBASE_KERNEL, + COINBASE_OUTPUT, DEFAULT_OUTPUT}; use self::hash::{Hash, Hashed, HashWriter, ZERO_HASH}; use ser::{Writeable, Writer, Reader, Readable, Error}; @@ -344,7 +345,7 @@ mod test { let skey = SecretKey::new(secp, &mut rng); let b = Block::new(&BlockHeader::default(), vec![], skey).unwrap(); - b.compact().verify(&secp).unwrap(); + b.compact().validate(&secp).unwrap(); } #[test] @@ -357,7 +358,7 @@ mod test { tx1.verify_sig(&secp).unwrap(); let b = Block::new(&BlockHeader::default(), vec![&mut tx1], skey).unwrap(); - b.compact().verify(&secp).unwrap(); + b.compact().validate(&secp).unwrap(); } #[test] @@ -373,7 +374,7 @@ mod test { tx2.verify_sig(&secp).unwrap(); let b = Block::new(&BlockHeader::default(), vec![&mut tx1, &mut tx2], skey).unwrap(); - b.verify(&secp).unwrap(); + b.validate(&secp).unwrap(); } // utility producing a transaction with 2 inputs and a single outputs diff --git a/core/src/core/transaction.rs b/core/src/core/transaction.rs index c243d79bd..90b184b6b 100644 --- a/core/src/core/transaction.rs +++ b/core/src/core/transaction.rs @@ -24,12 +24,22 @@ use core::MerkleRow; use core::hash::{Hash, Hashed}; use ser::{self, Reader, Writer, Readable, Writeable}; +bitflags! { + /// Options for a kernel's structure or use + pub flags KernelFeatures: u8 { + const DEFAULT_KERNEL = 0b00000000, + const COINBASE_KERNEL = 0b00000001, + } +} + /// 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. The signature signs the fee, which is retained for /// signature validation. #[derive(Debug, Clone)] pub struct TxKernel { + /// Options for a kernel's structure or use + pub features: KernelFeatures, /// Remainder of the sum of all transaction commitments. If the transaction /// is well formed, amounts components should sum to zero and the excess /// is hence a valid public key. @@ -43,20 +53,23 @@ pub struct TxKernel { impl Writeable for TxKernel { fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> { - try!(writer.write_fixed_bytes(&self.excess)); - try!(writer.write_bytes(&self.excess_sig)); - writer.write_u64(self.fee) + ser_multiwrite!(writer, + [write_u8, self.features.bits()], + [write_fixed_bytes, &self.excess], + [write_bytes, &self.excess_sig], + [write_u64, self.fee]); + Ok(()) } } impl Readable for TxKernel { fn read(reader: &mut Reader) -> Result { - let excess = try!(Commitment::read(reader)); - let (sig, fee) = ser_multiread!(reader, read_vec, read_u64); Ok(TxKernel { - excess: excess, - excess_sig: sig, - fee: fee, + features: + KernelFeatures::from_bits(reader.read_u8()?).ok_or(ser::Error::CorruptedData)?, + excess: Commitment::read(reader)?, + excess_sig: reader.read_vec()?, + fee: reader.read_u64()?, }) } } @@ -212,6 +225,7 @@ impl Transaction { try!(secp.verify(&msg, &sig, &pubk)); Ok(TxKernel { + features: DEFAULT_KERNEL, excess: rsum, excess_sig: self.excess_sig.clone(), fee: self.fee, @@ -258,6 +272,14 @@ impl Input { } } +bitflags! { + /// Options for block validation + pub flags OutputFeatures: u8 { + const DEFAULT_OUTPUT = 0b00000000, + const COINBASE_OUTPUT = 0b00000001, + } +} + /// Output for a transaction, defining the new ownership of coins that are being /// transferred. The commitment is a blinded value for the output while the /// range @@ -265,6 +287,7 @@ impl Input { /// and the ownership of the private key. #[derive(Debug, Copy, Clone)] pub struct Output { + pub features: OutputFeatures, pub commit: Commitment, pub proof: RangeProof, } @@ -273,10 +296,10 @@ pub struct Output { /// an Output as binary. impl Writeable for Output { fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> { - // The hash of an output is only the hash of its commitment. - try!(writer.write_fixed_bytes(&self.commit)); + ser_multiwrite!(writer, [write_u8, self.features.bits()], [write_fixed_bytes, &self.commit]); + // The hash of an output doesn't include the range proof if writer.serialization_mode() == ser::SerializationMode::Full { - try!(writer.write_bytes(&self.proof.bytes())) + writer.write_bytes(&self.proof.bytes())? } Ok(()) } @@ -286,11 +309,11 @@ impl Writeable for Output { /// an Output from a binary stream. impl Readable for Output { fn read(reader: &mut Reader) -> Result { - let commit = try!(Commitment::read(reader)); - let proof = try!(RangeProof::read(reader)); Ok(Output { - commit: commit, - proof: proof, + features: + OutputFeatures::from_bits(reader.read_u8()?).ok_or(ser::Error::CorruptedData)?, + commit: Commitment::read(reader)?, + proof: RangeProof::read(reader)?, }) } } diff --git a/core/src/genesis.rs b/core/src/genesis.rs index f4729b499..595acd104 100644 --- a/core/src/genesis.rs +++ b/core/src/genesis.rs @@ -39,6 +39,7 @@ pub fn genesis() -> core::Block { total_difficulty: Difficulty::one(), utxo_merkle: [].hash(), tx_merkle: [].hash(), + features: core::DEFAULT_BLOCK, nonce: 0, pow: core::Proof::zero(), // TODO get actual PoW solution }, diff --git a/core/src/lib.rs b/core/src/lib.rs index 31da509ee..e2b1bc50b 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -21,6 +21,8 @@ #![deny(unused_mut)] #![warn(missing_docs)] +#[macro_use] +extern crate bitflags; extern crate byteorder; extern crate crypto; extern crate num_bigint as bigint; diff --git a/doc/intro.md b/doc/intro.md index a37dd1f54..b6e2685e5 100644 --- a/doc/intro.md +++ b/doc/intro.md @@ -13,7 +13,7 @@ cryptocurrency deployment. The main goal and characteristics of the Grin project are: * Privacy by default. This enables complete fungibility without precluding - the possibility to ability to selectively disclose information as needed. + the ability to selectively disclose information as needed. * Scales with the number of users and not the number of transactions, with very large space savings compared to other blockchains. * Strong and proven cryptography. MimbleWimble only relies on Elliptic Curve diff --git a/p2p/src/types.rs b/p2p/src/types.rs index 5513adc71..971bc9a4b 100644 --- a/p2p/src/types.rs +++ b/p2p/src/types.rs @@ -81,7 +81,7 @@ impl Default for P2PConfig { } bitflags! { - /// Options for block validation + /// Options for what type of interaction a peer supports pub flags Capabilities: u32 { /// We don't know (yet) what the peer can do. const UNKNOWN = 0b00000000,