Add features bitmask to Block, Output, Kernel; coinbase validation

Block, Output and Kernel now have bitmasks to hold supported
features and eventually versioning. Will make adding features and
updates easier and open the possibility of soft forks.

First added feature for Output and Kernel is the marking of coinbase
related ones. Allows the validation of the coinbase part of a block.
This commit is contained in:
Ignotus Peverell 2017-03-23 17:06:00 -07:00
parent a57c4e8f04
commit 38d5d67e79
No known key found for this signature in database
GPG key ID: 99CD25F39F8F8211
10 changed files with 191 additions and 105 deletions

View file

@ -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

View file

@ -4,6 +4,7 @@ version = "0.1.0"
authors = ["Ignotus Peverell <igno.peverell@protonmail.com>"]
[dependencies]
bitflags = "~0.7.0"
byteorder = "^0.5"
num-bigint = "^0.1.35"
rust-crypto = "^0.2"

View file

@ -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<BlockHeader> 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<BlockHeader> 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::<Vec<_>>();
let cb_kerns = self.kernels
.iter()
.filter(|k| k.features.intersects(COINBASE_KERNEL))
.map(|k| k.clone())
.collect::<Vec<_>>();
// 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,
@ -430,7 +482,9 @@ impl Block {
// 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()
build::transaction(vec![input(v, b), output_rand(3), with_fee(1)])
.map(|(tx, _)| tx)
.unwrap()
}
#[test]
@ -441,7 +495,8 @@ impl Block {
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);
@ -449,7 +504,7 @@ impl Block {
// block should have been automatically compacted (including reward
// output) and should still be valid
b.verify(&secp).unwrap();
b.validate(&secp).unwrap();
assert_eq!(b.inputs.len(), 3);
assert_eq!(b.outputs.len(), 3);
}
@ -463,15 +518,16 @@ impl Block {
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);
let b1 = new_block(vec![&mut btx1, &mut btx2], secp);
b1.verify(&secp).unwrap();
b1.validate(&secp).unwrap();
let b2 = new_block(vec![&mut btx3], secp);
b2.verify(&secp).unwrap();
b2.validate(&secp).unwrap();
// block should have been automatically compacted and should still be valid
let b3 = b1.merge(b2);

View file

@ -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<Append> {
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<Append> {
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,
}),

View file

@ -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

View file

@ -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<TxKernel> for TxKernel {
fn read(reader: &mut Reader) -> Result<TxKernel, ser::Error> {
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<Output> for Output {
fn read(reader: &mut Reader) -> Result<Output, ser::Error> {
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)?,
})
}
}

View file

@ -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
},

View file

@ -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;

View file

@ -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

View file

@ -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,