Merge pull request #14 from ignopeverell/topic/fees-in-sig

Slight changes to transaction signature. The transaction fee gets signed (instead of the empty string) and the fee amount is attached to the transaction proof in blocks.
This commit is contained in:
Ignotus Peverell 2016-11-10 15:55:49 -08:00 committed by GitHub
commit 712cf78e8a
4 changed files with 48 additions and 34 deletions

View file

@ -35,7 +35,6 @@ pub struct BlockHeader {
pub td: u64, // total difficulty up to this block pub td: u64, // total difficulty up to this block
pub utxo_merkle: Hash, pub utxo_merkle: Hash,
pub tx_merkle: Hash, pub tx_merkle: Hash,
pub total_fees: u64,
pub nonce: u64, pub nonce: u64,
pub pow: Proof, pub pow: Proof,
} }
@ -49,7 +48,6 @@ impl Default for BlockHeader {
td: 0, td: 0,
utxo_merkle: ZERO_HASH, utxo_merkle: ZERO_HASH,
tx_merkle: ZERO_HASH, tx_merkle: ZERO_HASH,
total_fees: 0,
nonce: 0, nonce: 0,
pow: Proof::zero(), pow: Proof::zero(),
} }
@ -65,8 +63,7 @@ impl Writeable for BlockHeader {
[write_fixed_bytes, &self.previous], [write_fixed_bytes, &self.previous],
[write_i64, self.timestamp.to_timespec().sec], [write_i64, self.timestamp.to_timespec().sec],
[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_u64, self.total_fees]);
// make sure to not introduce any variable length data before the nonce to // make sure to not introduce any variable length data before the nonce to
// avoid complicating PoW // avoid complicating PoW
try!(writer.write_u64(self.nonce)); try!(writer.write_u64(self.nonce));
@ -117,14 +114,12 @@ impl Writeable for Block {
/// from a binary stream. /// from a binary stream.
impl Readable<Block> for Block { impl Readable<Block> for Block {
fn read(reader: &mut Reader) -> Result<Block, ser::Error> { fn read(reader: &mut Reader) -> Result<Block, ser::Error> {
let (height, previous, timestamp, utxo_merkle, tx_merkle, total_fees, nonce) = let (height, previous, timestamp, utxo_merkle, tx_merkle, nonce) = ser_multiread!(reader,
ser_multiread!(reader,
read_u64, read_u64,
read_32_bytes, read_32_bytes,
read_i64, read_i64,
read_32_bytes, read_32_bytes,
read_32_bytes, read_32_bytes,
read_u64,
read_u64); read_u64);
// cuckoo cycle of 42 nodes // cuckoo cycle of 42 nodes
@ -156,7 +151,6 @@ impl Readable<Block> for Block {
td: td, td: td,
utxo_merkle: Hash::from_vec(utxo_merkle), utxo_merkle: Hash::from_vec(utxo_merkle),
tx_merkle: Hash::from_vec(tx_merkle), tx_merkle: Hash::from_vec(tx_merkle),
total_fees: total_fees,
pow: Proof(pow), pow: Proof(pow),
nonce: nonce, nonce: nonce,
}, },
@ -178,7 +172,7 @@ impl Committed for Block {
&self.outputs &self.outputs
} }
fn overage(&self) -> i64 { fn overage(&self) -> i64 {
(REWARD as i64) - (self.header.total_fees as i64) (REWARD as i64) - (self.total_fees() as i64)
} }
} }
@ -234,12 +228,10 @@ impl Block {
outputs.sort_by_key(|out| out.hash()); outputs.sort_by_key(|out| out.hash());
// calculate the overall Merkle tree and fees // calculate the overall Merkle tree and fees
let fees = txs.iter().map(|tx| tx.fee).sum();
Ok(Block { Ok(Block {
header: BlockHeader { header: BlockHeader {
height: prev.height + 1, height: prev.height + 1,
total_fees: fees,
timestamp: time::now(), timestamp: time::now(),
..Default::default() ..Default::default()
}, },
@ -254,6 +246,10 @@ impl Block {
self.header.hash() self.header.hash()
} }
pub fn total_fees(&self) -> u64 {
self.proofs.iter().map(|p| p.fee).sum()
}
/// Matches any output with a potential spending input, eliminating them /// Matches any output with a potential spending input, eliminating them
/// from the block. Provides a simple way to compact the block. The /// from the block. Provides a simple way to compact the block. The
/// elimination is stable with respect to inputs and outputs order. /// elimination is stable with respect to inputs and outputs order.
@ -311,11 +307,7 @@ impl Block {
Block { Block {
// compact will fix the merkle tree // compact will fix the merkle tree
header: BlockHeader { header: BlockHeader { pow: self.header.pow.clone(), ..self.header },
total_fees: self.header.total_fees + other.header.total_fees,
pow: self.header.pow.clone(),
..self.header
},
inputs: all_inputs, inputs: all_inputs,
outputs: all_outputs, outputs: all_outputs,
proofs: all_proofs, proofs: all_proofs,
@ -339,11 +331,8 @@ impl Block {
} }
// verify all signatures with the commitment as pk // verify all signatures with the commitment as pk
let msg = try!(Message::from_slice(&[0; 32]));
for proof in &self.proofs { for proof in &self.proofs {
let pubk = try!(proof.remainder.to_pubkey(secp)); try!(proof.verify(secp));
let sig = try!(Signature::from_der(secp, &proof.sig));
try!(secp.verify(&msg, &sig, &pubk));
} }
Ok(()) Ok(())
} }
@ -367,6 +356,7 @@ impl Block {
let proof = TxProof { let proof = TxProof {
remainder: remainder, remainder: remainder,
sig: sig.serialize_der(&secp), sig: sig.serialize_der(&secp),
fee: 0,
}; };
Ok((output, proof)) Ok((output, proof))
} }

View file

@ -14,6 +14,7 @@
//! Transactions //! Transactions
use byteorder::{ByteOrder, BigEndian};
use secp::{self, Secp256k1, Message, Signature}; use secp::{self, Secp256k1, Message, Signature};
use secp::key::SecretKey; use secp::key::SecretKey;
use secp::pedersen::{RangeProof, Commitment}; use secp::pedersen::{RangeProof, Commitment};
@ -24,34 +25,54 @@ 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};
/// A proof that a transaction did not create (or remove) funds. Includes both /// A proof that a transaction sums to zero. Includes both the transaction's
/// the transaction's Pedersen commitment and the signature that guarantees /// Pedersen commitment and the signature, that guarantees that the commitments
/// that the commitment amounts to zero. /// amount to zero. The signature signs the fee, which is retained for
/// signature validation.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct TxProof { pub struct TxProof {
/// temporarily public /// Remainder of the sum of all transaction commitments. If the transaction
/// is well formed, amounts components should sum to zero and the remainder
/// is hence a valid public key.
pub remainder: Commitment, pub remainder: Commitment,
/// temporarily public /// The signature proving the remainder is a valid public key, which signs
/// the transaction fee.
pub sig: Vec<u8>, pub sig: Vec<u8>,
/// Fee originally included in the transaction this proof is for.
pub fee: u64,
} }
impl Writeable for TxProof { impl Writeable for TxProof {
fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> { fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> {
try!(writer.write_fixed_bytes(&self.remainder)); try!(writer.write_fixed_bytes(&self.remainder));
writer.write_bytes(&self.sig) try!(writer.write_bytes(&self.sig));
writer.write_u64(self.fee)
} }
} }
impl Readable<TxProof> for TxProof { impl Readable<TxProof> for TxProof {
fn read(reader: &mut Reader) -> Result<TxProof, ser::Error> { fn read(reader: &mut Reader) -> Result<TxProof, ser::Error> {
let (remainder, sig) = ser_multiread!(reader, read_33_bytes, read_vec); let (remainder, sig, fee) = ser_multiread!(reader, read_33_bytes, read_vec, read_u64);
Ok(TxProof { Ok(TxProof {
remainder: Commitment::from_vec(remainder), remainder: Commitment::from_vec(remainder),
sig: sig, sig: sig,
fee: fee,
}) })
} }
} }
impl TxProof {
/// Verify the transaction proof validity. Entails handling the commitment
/// as a public key and checking the signature verifies with the fee as
/// message.
pub fn verify(&self, secp: &Secp256k1) -> Result<(), secp::Error> {
let msg = try!(Message::from_slice(&u64_to_32bytes(self.fee)));
let pubk = try!(self.remainder.to_pubkey(secp));
let sig = try!(Signature::from_der(secp, &self.sig));
secp.verify(&msg, &sig, &pubk)
}
}
/// A transaction /// A transaction
#[derive(Debug)] #[derive(Debug)]
pub struct Transaction { pub struct Transaction {
@ -172,7 +193,7 @@ impl Transaction {
// and sign with the remainder so the signature can be checked to match with // and sign with the remainder so the signature can be checked to match with
// the k.G commitment leftover, that should also be the pubkey // the k.G commitment leftover, that should also be the pubkey
let msg = try!(Message::from_slice(&[0; 32])); let msg = try!(Message::from_slice(&u64_to_32bytes(self.fee)));
let sig = try!(secp.sign(&msg, &remainder)); let sig = try!(secp.sign(&msg, &remainder));
Ok(Transaction { Ok(Transaction {
@ -205,13 +226,14 @@ impl Transaction {
// pretend the sum is a public key (which it is, being of the form r.G) and // pretend the sum is a public key (which it is, being of the form r.G) and
// verify the transaction sig with it // verify the transaction sig with it
let pubk = try!(rsum.to_pubkey(secp)); let pubk = try!(rsum.to_pubkey(secp));
let msg = try!(Message::from_slice(&[0; 32])); let msg = try!(Message::from_slice(&u64_to_32bytes(self.fee)));
let sig = try!(Signature::from_der(secp, &self.zerosig)); let sig = try!(Signature::from_der(secp, &self.zerosig));
try!(secp.verify(&msg, &sig, &pubk)); try!(secp.verify(&msg, &sig, &pubk));
Ok(TxProof { Ok(TxProof {
remainder: rsum, remainder: rsum,
sig: self.zerosig.clone(), sig: self.zerosig.clone(),
fee: self.fee,
}) })
} }
} }
@ -365,6 +387,12 @@ pub fn merkle_inputs_outputs(inputs: &Vec<Input>, outputs: &Vec<Output>) -> Hash
MerkleRow::new(all_hs).root() MerkleRow::new(all_hs).root()
} }
fn u64_to_32bytes(n: u64) -> [u8; 32] {
let mut bytes = [0; 32];
BigEndian::write_u64(&mut bytes[24..32], n);
bytes
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;

View file

@ -31,7 +31,7 @@ pub fn genesis() -> core::Block {
core::Block { core::Block {
header: core::BlockHeader { header: core::BlockHeader {
height: 0, height: 0,
previous: core::hash::ZERO_HASH, previous: core::hash::Hash([0xff; 32]),
timestamp: time::Tm { timestamp: time::Tm {
tm_year: 1997, tm_year: 1997,
tm_mon: 7, tm_mon: 7,
@ -41,7 +41,6 @@ pub fn genesis() -> core::Block {
td: 0, td: 0,
utxo_merkle: core::hash::Hash::from_vec(empty_h.to_vec()), utxo_merkle: core::hash::Hash::from_vec(empty_h.to_vec()),
tx_merkle: core::hash::Hash::from_vec(empty_h.to_vec()), tx_merkle: core::hash::Hash::from_vec(empty_h.to_vec()),
total_fees: 0,
nonce: 0, nonce: 0,
pow: core::Proof::zero(), // TODO get actual PoW solution pow: core::Proof::zero(), // TODO get actual PoW solution
}, },

View file

@ -47,7 +47,6 @@ struct PowHeader {
pub timestamp: time::Tm, pub timestamp: time::Tm,
pub utxo_merkle: Hash, pub utxo_merkle: Hash,
pub tx_merkle: Hash, pub tx_merkle: Hash,
pub total_fees: u64,
pub n_in: u64, pub n_in: u64,
pub n_out: u64, pub n_out: u64,
pub n_proofs: u64, pub n_proofs: u64,
@ -64,7 +63,6 @@ impl Writeable for PowHeader {
try!(writer.write_i64(self.timestamp.to_timespec().sec)); try!(writer.write_i64(self.timestamp.to_timespec().sec));
try!(writer.write_fixed_bytes(&self.utxo_merkle)); try!(writer.write_fixed_bytes(&self.utxo_merkle));
try!(writer.write_fixed_bytes(&self.tx_merkle)); try!(writer.write_fixed_bytes(&self.tx_merkle));
try!(writer.write_u64(self.total_fees));
try!(writer.write_u64(self.n_in)); try!(writer.write_u64(self.n_in));
try!(writer.write_u64(self.n_out)); try!(writer.write_u64(self.n_out));
writer.write_u64(self.n_proofs) writer.write_u64(self.n_proofs)
@ -81,7 +79,6 @@ impl PowHeader {
timestamp: h.timestamp, timestamp: h.timestamp,
utxo_merkle: h.utxo_merkle, utxo_merkle: h.utxo_merkle,
tx_merkle: h.tx_merkle, tx_merkle: h.tx_merkle,
total_fees: h.total_fees,
n_in: b.inputs.len() as u64, n_in: b.inputs.len() as u64,
n_out: b.outputs.len() as u64, n_out: b.outputs.len() as u64,
n_proofs: b.proofs.len() as u64, n_proofs: b.proofs.len() as u64,