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. /// Fully validate the block content.
fn validate_block(b: &Block, ctx: &mut BlockContext) -> Result<(), Error> { fn validate_block(b: &Block, ctx: &mut BlockContext) -> Result<(), Error> {
let curve = secp::Secp256k1::with_caps(secp::ContextFlag::Commit); 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) { if !ctx.opts.intersects(SYNC) {
// TODO check every input exists as a UTXO using the UXTO index // 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>"] authors = ["Ignotus Peverell <igno.peverell@protonmail.com>"]
[dependencies] [dependencies]
bitflags = "~0.7.0"
byteorder = "^0.5" byteorder = "^0.5"
num-bigint = "^0.1.35" num-bigint = "^0.1.35"
rust-crypto = "^0.2" rust-crypto = "^0.2"

View file

@ -21,13 +21,20 @@ use secp::key::SecretKey;
use std::collections::HashSet; use std::collections::HashSet;
use core::Committed; 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 core::transaction::merkle_inputs_outputs;
use consensus::{REWARD, DEFAULT_SIZESHIFT}; use consensus::{REWARD, DEFAULT_SIZESHIFT};
use core::hash::{Hash, Hashed, ZERO_HASH}; use core::hash::{Hash, Hashed, ZERO_HASH};
use core::target::Difficulty; use core::target::Difficulty;
use ser::{self, Readable, Reader, Writeable, Writer}; 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. /// Block header, fairly standard compared to other blockchains.
pub struct BlockHeader { pub struct BlockHeader {
/// Height of this block since the genesis block (height 0) /// 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. /// Length of the cuckoo cycle used to mine this block.
pub cuckoo_len: u8, pub cuckoo_len: u8,
pub utxo_merkle: Hash, pub utxo_merkle: Hash,
/// Merkle tree of hashes for all inputs, outputs and kernels in the block
pub tx_merkle: Hash, pub tx_merkle: Hash,
/// Features specific to this block, allowing possible future extensions
pub features: BlockFeatures,
/// Nonce increment used to mine this block. /// Nonce increment used to mine this block.
pub nonce: u64, pub nonce: u64,
/// Proof of work data. /// Proof of work data.
@ -61,6 +71,7 @@ impl Default for BlockHeader {
total_difficulty: Difficulty::one(), total_difficulty: Difficulty::one(),
utxo_merkle: ZERO_HASH, utxo_merkle: ZERO_HASH,
tx_merkle: ZERO_HASH, tx_merkle: ZERO_HASH,
features: DEFAULT_BLOCK,
nonce: 0, nonce: 0,
pow: Proof::zero(), pow: Proof::zero(),
} }
@ -76,7 +87,8 @@ impl Writeable for BlockHeader {
[write_i64, self.timestamp.to_timespec().sec], [write_i64, self.timestamp.to_timespec().sec],
[write_u8, self.cuckoo_len], [write_u8, self.cuckoo_len],
[write_fixed_bytes, &self.utxo_merkle], [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!(writer.write_u64(self.nonce));
try!(self.difficulty.write(writer)); 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 (timestamp, cuckoo_len) = ser_multiread!(reader, read_i64, read_u8);
let utxo_merkle = try!(Hash::read(reader)); let utxo_merkle = try!(Hash::read(reader));
let tx_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 difficulty = try!(Difficulty::read(reader));
let total_difficulty = try!(Difficulty::read(reader)); let total_difficulty = try!(Difficulty::read(reader));
let pow = try!(Proof::read(reader)); let pow = try!(Proof::read(reader));
@ -112,6 +124,7 @@ impl Readable<BlockHeader> for BlockHeader {
cuckoo_len: cuckoo_len, cuckoo_len: cuckoo_len,
utxo_merkle: utxo_merkle, utxo_merkle: utxo_merkle,
tx_merkle: tx_merkle, tx_merkle: tx_merkle,
features: BlockFeatures::from_bits(features).ok_or(ser::Error::CorruptedData)?,
pow: pow, pow: pow,
nonce: nonce, nonce: nonce,
difficulty: difficulty, difficulty: difficulty,
@ -344,15 +357,32 @@ impl Block {
.compact() .compact()
} }
/// Checks the block is valid by verifying the overall commitments sums and /// Validates all the elements in a block that can be checked without
/// kernels. /// additional
pub fn verify(&self, secp: &Secp256k1) -> Result<(), secp::Error> { /// 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 // 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 // sum all kernels commitments
let proof_commits = map_vec!(self.kernels, |proof| proof.excess); 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 // both should be the same
if proof_sum != io_sum { if proof_sum != io_sum {
@ -362,19 +392,39 @@ impl Block {
// verify all signatures with the commitment as pk // verify all signatures with the commitment as pk
for proof in &self.kernels { 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(()) 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. // Builds the blinded output and related signature proof for the block reward.
fn reward_output(skey: secp::key::SecretKey, fn reward_output(skey: secp::key::SecretKey,
secp: &Secp256k1) secp: &Secp256k1)
@ -385,6 +435,7 @@ impl Block {
let rproof = secp.range_proof(0, REWARD, skey, commit); let rproof = secp.range_proof(0, REWARD, skey, commit);
let output = Output { let output = Output {
features: COINBASE_OUTPUT,
commit: commit, commit: commit,
proof: rproof, proof: rproof,
}; };
@ -394,6 +445,7 @@ impl Block {
let excess = try!(secp.commit_sum(vec![over_commit], vec![out_commit])); let excess = try!(secp.commit_sum(vec![over_commit], vec![out_commit]));
let proof = TxKernel { let proof = TxKernel {
features: COINBASE_KERNEL,
excess: excess, excess: excess,
excess_sig: sig.serialize_der(&secp), excess_sig: sig.serialize_der(&secp),
fee: 0, fee: 0,
@ -402,8 +454,8 @@ impl Block {
} }
} }
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
use core::{Input, Output, Transaction}; use core::{Input, Output, Transaction};
use core::build::{self, input, output, input_rand, output_rand, with_fee}; use core::build::{self, input, output, input_rand, output_rand, with_fee};
@ -420,7 +472,7 @@ impl Block {
} }
// utility to create a block without worrying about the key or previous // utility to create a block without worrying about the key or previous
//header // header
fn new_block(txs: Vec<&mut Transaction>, secp: &Secp256k1) -> Block { fn new_block(txs: Vec<&mut Transaction>, secp: &Secp256k1) -> Block {
let mut rng = OsRng::new().unwrap(); let mut rng = OsRng::new().unwrap();
let skey = SecretKey::new(secp, &mut rng); let skey = SecretKey::new(secp, &mut rng);
@ -430,7 +482,9 @@ impl Block {
// utility producing a transaction that spends an output with the provided // utility producing a transaction that spends an output with the provided
// value and blinding key // value and blinding key
fn txspend1i1o(v: u64, b: SecretKey) -> Transaction { 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] #[test]
@ -441,7 +495,8 @@ impl Block {
let mut btx1 = tx2i1o(); let mut btx1 = tx2i1o();
let skey = SecretKey::new(secp, &mut rng); 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 // spending tx2
let mut btx3 = txspend1i1o(4, skey); let mut btx3 = txspend1i1o(4, skey);
@ -449,7 +504,7 @@ impl Block {
// block should have been automatically compacted (including reward // block should have been automatically compacted (including reward
// output) and should still be valid // output) and should still be valid
b.verify(&secp).unwrap(); b.validate(&secp).unwrap();
assert_eq!(b.inputs.len(), 3); assert_eq!(b.inputs.len(), 3);
assert_eq!(b.outputs.len(), 3); assert_eq!(b.outputs.len(), 3);
} }
@ -463,15 +518,16 @@ impl Block {
let mut btx1 = tx2i1o(); let mut btx1 = tx2i1o();
let skey = SecretKey::new(secp, &mut rng); 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 // spending tx2
let mut btx3 = txspend1i1o(4, skey); let mut btx3 = txspend1i1o(4, skey);
let b1 = new_block(vec![&mut btx1, &mut btx2], secp); 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); 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 // block should have been automatically compacted and should still be valid
let b3 = b1.merge(b2); let b3 = b1.merge(b2);

View file

@ -31,7 +31,7 @@ use secp::key::SecretKey;
use secp::pedersen::*; use secp::pedersen::*;
use rand::os::OsRng; use rand::os::OsRng;
use core::{Transaction, Input, Output}; use core::{Transaction, Input, Output, DEFAULT_OUTPUT};
/// Context information available to transaction combinators. /// Context information available to transaction combinators.
pub struct Context { 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 commit = build.secp.commit(value, blinding).unwrap();
let rproof = build.secp.range_proof(0, value, blinding, commit); let rproof = build.secp.range_proof(0, value, blinding, commit);
(tx.with_output(Output { (tx.with_output(Output {
features: DEFAULT_OUTPUT,
commit: commit, commit: commit,
proof: rproof, proof: rproof,
}), }),
@ -129,6 +130,7 @@ pub fn output_rand(value: u64) -> Box<Append> {
let commit = build.secp.commit(value, blinding).unwrap(); let commit = build.secp.commit(value, blinding).unwrap();
let rproof = build.secp.range_proof(0, value, blinding, commit); let rproof = build.secp.range_proof(0, value, blinding, commit);
(tx.with_output(Output { (tx.with_output(Output {
features: DEFAULT_OUTPUT,
commit: commit, commit: commit,
proof: rproof, proof: rproof,
}), }),

View file

@ -28,8 +28,9 @@ use secp::{self, Secp256k1};
use secp::pedersen::*; use secp::pedersen::*;
use consensus::PROOFSIZE; use consensus::PROOFSIZE;
pub use self::block::{Block, BlockHeader}; pub use self::block::{Block, BlockHeader, DEFAULT_BLOCK};
pub use self::transaction::{Transaction, Input, Output, TxKernel}; pub use self::transaction::{Transaction, Input, Output, TxKernel, COINBASE_KERNEL,
COINBASE_OUTPUT, DEFAULT_OUTPUT};
use self::hash::{Hash, Hashed, HashWriter, ZERO_HASH}; use self::hash::{Hash, Hashed, HashWriter, ZERO_HASH};
use ser::{Writeable, Writer, Reader, Readable, Error}; use ser::{Writeable, Writer, Reader, Readable, Error};
@ -344,7 +345,7 @@ mod test {
let skey = SecretKey::new(secp, &mut rng); let skey = SecretKey::new(secp, &mut rng);
let b = Block::new(&BlockHeader::default(), vec![], skey).unwrap(); let b = Block::new(&BlockHeader::default(), vec![], skey).unwrap();
b.compact().verify(&secp).unwrap(); b.compact().validate(&secp).unwrap();
} }
#[test] #[test]
@ -357,7 +358,7 @@ mod test {
tx1.verify_sig(&secp).unwrap(); tx1.verify_sig(&secp).unwrap();
let b = Block::new(&BlockHeader::default(), vec![&mut tx1], skey).unwrap(); let b = Block::new(&BlockHeader::default(), vec![&mut tx1], skey).unwrap();
b.compact().verify(&secp).unwrap(); b.compact().validate(&secp).unwrap();
} }
#[test] #[test]
@ -373,7 +374,7 @@ mod test {
tx2.verify_sig(&secp).unwrap(); tx2.verify_sig(&secp).unwrap();
let b = Block::new(&BlockHeader::default(), vec![&mut tx1, &mut tx2], skey).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 // 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 core::hash::{Hash, Hashed};
use ser::{self, Reader, Writer, Readable, Writeable}; 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 /// A proof that a transaction sums to zero. Includes both the transaction's
/// Pedersen commitment and the signature, that guarantees that the commitments /// Pedersen commitment and the signature, that guarantees that the commitments
/// amount to zero. The signature signs the fee, which is retained for /// amount to zero. The signature signs the fee, which is retained for
/// signature validation. /// signature validation.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TxKernel { 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 /// Remainder of the sum of all transaction commitments. If the transaction
/// is well formed, amounts components should sum to zero and the excess /// is well formed, amounts components should sum to zero and the excess
/// is hence a valid public key. /// is hence a valid public key.
@ -43,20 +53,23 @@ pub struct TxKernel {
impl Writeable for TxKernel { impl Writeable for TxKernel {
fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> { fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> {
try!(writer.write_fixed_bytes(&self.excess)); ser_multiwrite!(writer,
try!(writer.write_bytes(&self.excess_sig)); [write_u8, self.features.bits()],
writer.write_u64(self.fee) [write_fixed_bytes, &self.excess],
[write_bytes, &self.excess_sig],
[write_u64, self.fee]);
Ok(())
} }
} }
impl Readable<TxKernel> for TxKernel { impl Readable<TxKernel> for TxKernel {
fn read(reader: &mut Reader) -> Result<TxKernel, ser::Error> { 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 { Ok(TxKernel {
excess: excess, features:
excess_sig: sig, KernelFeatures::from_bits(reader.read_u8()?).ok_or(ser::Error::CorruptedData)?,
fee: fee, 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)); try!(secp.verify(&msg, &sig, &pubk));
Ok(TxKernel { Ok(TxKernel {
features: DEFAULT_KERNEL,
excess: rsum, excess: rsum,
excess_sig: self.excess_sig.clone(), excess_sig: self.excess_sig.clone(),
fee: self.fee, 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 /// 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 /// transferred. The commitment is a blinded value for the output while the
/// range /// range
@ -265,6 +287,7 @@ impl Input {
/// and the ownership of the private key. /// and the ownership of the private key.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Output { pub struct Output {
pub features: OutputFeatures,
pub commit: Commitment, pub commit: Commitment,
pub proof: RangeProof, pub proof: RangeProof,
} }
@ -273,10 +296,10 @@ pub struct Output {
/// an Output as binary. /// an Output as binary.
impl Writeable for Output { impl Writeable for Output {
fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> { fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> {
// The hash of an output is only the hash of its commitment. ser_multiwrite!(writer, [write_u8, self.features.bits()], [write_fixed_bytes, &self.commit]);
try!(writer.write_fixed_bytes(&self.commit)); // The hash of an output doesn't include the range proof
if writer.serialization_mode() == ser::SerializationMode::Full { if writer.serialization_mode() == ser::SerializationMode::Full {
try!(writer.write_bytes(&self.proof.bytes())) writer.write_bytes(&self.proof.bytes())?
} }
Ok(()) Ok(())
} }
@ -286,11 +309,11 @@ impl Writeable for Output {
/// an Output from a binary stream. /// an Output from a binary stream.
impl Readable<Output> for Output { impl Readable<Output> for Output {
fn read(reader: &mut Reader) -> Result<Output, ser::Error> { fn read(reader: &mut Reader) -> Result<Output, ser::Error> {
let commit = try!(Commitment::read(reader));
let proof = try!(RangeProof::read(reader));
Ok(Output { Ok(Output {
commit: commit, features:
proof: proof, 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(), total_difficulty: Difficulty::one(),
utxo_merkle: [].hash(), utxo_merkle: [].hash(),
tx_merkle: [].hash(), tx_merkle: [].hash(),
features: core::DEFAULT_BLOCK,
nonce: 0, nonce: 0,
pow: core::Proof::zero(), // TODO get actual PoW solution pow: core::Proof::zero(), // TODO get actual PoW solution
}, },

View file

@ -21,6 +21,8 @@
#![deny(unused_mut)] #![deny(unused_mut)]
#![warn(missing_docs)] #![warn(missing_docs)]
#[macro_use]
extern crate bitflags;
extern crate byteorder; extern crate byteorder;
extern crate crypto; extern crate crypto;
extern crate num_bigint as bigint; extern crate num_bigint as bigint;

View file

@ -13,7 +13,7 @@ cryptocurrency deployment.
The main goal and characteristics of the Grin project are: The main goal and characteristics of the Grin project are:
* Privacy by default. This enables complete fungibility without precluding * 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 * Scales with the number of users and not the number of transactions, with very
large space savings compared to other blockchains. large space savings compared to other blockchains.
* Strong and proven cryptography. MimbleWimble only relies on Elliptic Curve * Strong and proven cryptography. MimbleWimble only relies on Elliptic Curve

View file

@ -81,7 +81,7 @@ impl Default for P2PConfig {
} }
bitflags! { bitflags! {
/// Options for block validation /// Options for what type of interaction a peer supports
pub flags Capabilities: u32 { pub flags Capabilities: u32 {
/// We don't know (yet) what the peer can do. /// We don't know (yet) what the peer can do.
const UNKNOWN = 0b00000000, const UNKNOWN = 0b00000000,