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

View file

@ -14,6 +14,7 @@
//! Transactions
use byteorder::{ByteOrder, BigEndian};
use secp::{self, Secp256k1, Message, Signature};
use secp::key::SecretKey;
use secp::pedersen::{RangeProof, Commitment};
@ -24,34 +25,54 @@ use core::MerkleRow;
use core::hash::{Hash, Hashed};
use ser::{self, Reader, Writer, Readable, Writeable};
/// A proof that a transaction did not create (or remove) funds. Includes both
/// the transaction's Pedersen commitment and the signature that guarantees
/// that the commitment amounts to zero.
/// 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 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,
/// temporarily public
/// The signature proving the remainder is a valid public key, which signs
/// the transaction fee.
pub sig: Vec<u8>,
/// Fee originally included in the transaction this proof is for.
pub fee: u64,
}
impl Writeable for TxProof {
fn write(&self, writer: &mut Writer) -> Result<(), ser::Error> {
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 {
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 {
remainder: Commitment::from_vec(remainder),
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
#[derive(Debug)]
pub struct Transaction {
@ -172,7 +193,7 @@ impl Transaction {
// 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
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));
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
// verify the transaction sig with it
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));
try!(secp.verify(&msg, &sig, &pubk));
Ok(TxProof {
remainder: rsum,
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()
}
fn u64_to_32bytes(n: u64) -> [u8; 32] {
let mut bytes = [0; 32];
BigEndian::write_u64(&mut bytes[24..32], n);
bytes
}
#[cfg(test)]
mod test {
use super::*;

View file

@ -31,7 +31,7 @@ pub fn genesis() -> core::Block {
core::Block {
header: core::BlockHeader {
height: 0,
previous: core::hash::ZERO_HASH,
previous: core::hash::Hash([0xff; 32]),
timestamp: time::Tm {
tm_year: 1997,
tm_mon: 7,
@ -41,7 +41,6 @@ pub fn genesis() -> core::Block {
td: 0,
utxo_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,
pow: core::Proof::zero(), // TODO get actual PoW solution
},

View file

@ -47,7 +47,6 @@ struct PowHeader {
pub timestamp: time::Tm,
pub utxo_merkle: Hash,
pub tx_merkle: Hash,
pub total_fees: u64,
pub n_in: u64,
pub n_out: 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_fixed_bytes(&self.utxo_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_out));
writer.write_u64(self.n_proofs)
@ -81,7 +79,6 @@ impl PowHeader {
timestamp: h.timestamp,
utxo_merkle: h.utxo_merkle,
tx_merkle: h.tx_merkle,
total_fees: h.total_fees,
n_in: b.inputs.len() as u64,
n_out: b.outputs.len() as u64,
n_proofs: b.proofs.len() as u64,