Bulletproof messages (#730)

* beginning to add bullet proof messages

* Updated core transaction creation to embed the output's value and switch commit hash as part of the rangeproof message

* formatting issue

* more formatting issues

* Removing conditional feature compliation.. just bulletproofs from now on

* ensure MAX_PROOF_SIZE uses bulletproof sizing instead of earlier version

* updated with switch commit committed to in extra data

* accidentally commented out bullet-proof-size feature
This commit is contained in:
Yeastplume 2018-02-27 21:11:55 +00:00 committed by GitHub
parent d116a434bf
commit 5d1f1af892
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 350 additions and 301 deletions

View file

@ -639,7 +639,7 @@ impl<'a> Extension<'a> {
let commit = out.commit.clone();
match self.rproof_pmmr.get(n, true) {
Some((_, Some(rp))) => out.to_output(rp).verify_proof()?,
res => {
_res => {
return Err(Error::OutputNotFound);
}
}

View file

@ -26,6 +26,7 @@ use core::{
ShortId,
SwitchCommitHash,
Proof,
ProofMessageElements,
TxKernel,
Transaction,
OutputFeatures,
@ -41,7 +42,6 @@ use ser::{self, Readable, Reader, Writeable, Writer, WriteableSorted, read_and_v
use global;
use keychain;
use keychain::BlindingFactor;
use util;
use util::kernel_sig_msg;
use util::LOGGER;
use util::{secp, static_secp_instance};
@ -818,8 +818,13 @@ impl Block {
"Block reward - Switch Commit Hash is: {:?}",
switch_commit_hash
);
let msg = util::secp::pedersen::ProofMessage::empty();
let rproof = keychain.range_proof(reward(fees), key_id, commit, msg)?;
let value = reward(fees);
let msg = (ProofMessageElements {
value: value
}).to_proof_message();
let rproof = keychain.range_proof(value, key_id, commit, Some(switch_commit_hash.as_ref().to_vec()), msg)?;
let output = Output {
features: OutputFeatures::COINBASE_OUTPUT,
@ -1059,10 +1064,7 @@ mod test {
let b = new_block(vec![], &keychain);
let mut vec = Vec::new();
ser::serialize(&mut vec, &b).expect("serialization failed");
let target_len = match Keychain::is_using_bullet_proofs() {
true => 1_256,
false => 5_708,
};
let target_len = 1_256;
assert_eq!(
vec.len(),
target_len,
@ -1076,10 +1078,7 @@ mod test {
let b = new_block(vec![&tx1], &keychain);
let mut vec = Vec::new();
ser::serialize(&mut vec, &b).expect("serialization failed");
let target_len = match Keychain::is_using_bullet_proofs() {
true => 2_900,
false => 16_256,
};
let target_len = 2_900;
assert_eq!(
vec.len(),
target_len,
@ -1092,10 +1091,7 @@ mod test {
let b = new_block(vec![], &keychain);
let mut vec = Vec::new();
ser::serialize(&mut vec, &b.as_compact_block()).expect("serialization failed");
let target_len = match Keychain::is_using_bullet_proofs() {
true => 1_264,
false => 5_716,
};
let target_len = 1_264;
assert_eq!(
vec.len(),
target_len,
@ -1109,10 +1105,7 @@ mod test {
let b = new_block(vec![&tx1], &keychain);
let mut vec = Vec::new();
ser::serialize(&mut vec, &b.as_compact_block()).expect("serialization failed");
let target_len = match Keychain::is_using_bullet_proofs() {
true => 1_270,
false => 5_722,
};
let target_len = 1_270;
assert_eq!(
vec.len(),
target_len,
@ -1135,10 +1128,7 @@ mod test {
);
let mut vec = Vec::new();
ser::serialize(&mut vec, &b).expect("serialization failed");
let target_len = match Keychain::is_using_bullet_proofs() {
true => 17696,
false => 111188,
};
let target_len = 17_696;
assert_eq!(
vec.len(),
target_len,
@ -1161,10 +1151,7 @@ mod test {
);
let mut vec = Vec::new();
ser::serialize(&mut vec, &b.as_compact_block()).expect("serialization failed");
let target_len = match Keychain::is_using_bullet_proofs() {
true => 1_324,
false => 5_776,
};
let target_len = 1_324;
assert_eq!(
vec.len(),
target_len,

View file

@ -27,7 +27,7 @@
use util::{secp, kernel_sig_msg};
use core::{Transaction, TxKernel, Input, Output, OutputFeatures, SwitchCommitHash};
use core::{Transaction, TxKernel, Input, Output, OutputFeatures, ProofMessageElements, SwitchCommitHash};
use core::hash::Hash;
use keychain;
use keychain::{Keychain, BlindSum, BlindingFactor, Identifier};
@ -112,10 +112,14 @@ pub fn output(value: u64, key_id: Identifier) -> Box<Append> {
"Builder - Switch Commit Hash is: {:?}",
switch_commit_hash
);
let msg = secp::pedersen::ProofMessage::empty();
let msg = (ProofMessageElements {
value: value,
}).to_proof_message();
let rproof = build
.keychain
.range_proof(value, &key_id, commit, msg)
.range_proof(value, &key_id, commit, Some(switch_commit_hash.as_ref().to_vec()), msg)
.unwrap();
(

View file

@ -263,10 +263,7 @@ mod test {
let tx = tx2i1o();
let mut vec = Vec::new();
ser::serialize(&mut vec, &tx).expect("serialization failed");
let target_len = match Keychain::is_using_bullet_proofs() {
true => 986,
false => 5_438,
};
let target_len = 986;
assert_eq!(
vec.len(),
target_len,
@ -389,16 +386,14 @@ mod test {
assert!(h != h2);
}
#[ignore]
#[test]
fn blind_tx() {
let btx = tx2i1o();
assert!(btx.validate().is_ok());
// Ignored for bullet proofs, info doesn't exist yet and calling range_proof_info
// Ignored for bullet proofs, because calling range_proof_info
// with a bullet proof causes painful errors
if Keychain::is_using_bullet_proofs() {
return;
}
// checks that the range proof on our blind output is sufficiently hiding
let Output { proof, .. } = btx.outputs[0];

View file

@ -16,7 +16,7 @@
use blake2::blake2b::blake2b;
use util::secp::{self, Message, Signature};
use util::{static_secp_instance, kernel_sig_msg};
use util::secp::pedersen::{Commitment, RangeProof};
use util::secp::pedersen::{Commitment, RangeProof, ProofMessage};
use std::cmp::{min, max};
use std::cmp::Ordering;
@ -26,7 +26,8 @@ use core::Committed;
use core::hash::{Hash, Hashed, ZERO_HASH};
use keychain::{Identifier, Keychain, BlindingFactor};
use keychain;
use ser::{self, read_and_verify_sorted, PMMRable, Readable, Reader, Writeable, WriteableSorted, Writer};
use ser::{self, read_and_verify_sorted, PMMRable, Readable, Reader, Writeable, WriteableSorted, Writer, ser_vec};
use std::io::Cursor;
use util;
/// The size of the blake2 hash of a switch commitment (256 bits)
@ -750,7 +751,7 @@ impl Output {
pub fn verify_proof(&self) -> Result<(), secp::Error> {
let secp = static_secp_instance();
let secp = secp.lock().unwrap();
match Keychain::verify_range_proof(&secp, self.commit, self.proof){
match Keychain::verify_range_proof(&secp, self.commit, self.proof, Some(self.switch_commit_hash.as_ref().to_vec())){
Ok(_) => Ok(()),
Err(e) => Err(e),
}
@ -759,10 +760,11 @@ impl Output {
/// Given the original blinding factor we can recover the
/// value from the range proof and the commitment
pub fn recover_value(&self, keychain: &Keychain, key_id: &Identifier) -> Option<u64> {
match keychain.rewind_range_proof(key_id, self.commit, self.proof) {
match keychain.rewind_range_proof(key_id, self.commit, Some(self.switch_commit_hash.as_ref().to_vec()), self.proof) {
Ok(proof_info) => {
if proof_info.success {
Some(proof_info.value)
let elements = ProofMessageElements::from_proof_message(proof_info.message).unwrap();
Some(elements.value)
} else {
None
}
@ -770,6 +772,7 @@ impl Output {
Err(_) => None,
}
}
}
/// An output_identifier can be build from either an input _or_ and output and
@ -903,6 +906,47 @@ impl Readable for OutputStoreable {
}
}
/// A structure which contains fields that are to be commited to within
/// an Output's range (bullet) proof.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct ProofMessageElements {
/// The amount, stored to allow for wallet reconstruction as
/// rewinding isn't supported in bulletproofs just yet
pub value: u64,
}
impl Writeable for ProofMessageElements {
fn write<W: Writer>(&self, writer: &mut W) -> Result<(), ser::Error> {
writer.write_u64(self.value)?;
for _ in 8..64 {
let _ = writer.write_u8(0);
}
Ok(())
}
}
impl Readable for ProofMessageElements {
fn read(reader: &mut Reader) -> Result<ProofMessageElements, ser::Error> {
Ok(ProofMessageElements {
value: reader.read_u64()?,
})
}
}
impl ProofMessageElements {
/// Serialise and return a ProofMessage
pub fn to_proof_message(&self)->ProofMessage {
ProofMessage::from_bytes(&ser_vec(self).unwrap())
}
/// Deserialise and return the message elements
pub fn from_proof_message(proof_message:ProofMessage)
-> Result<ProofMessageElements, ser::Error> {
let mut c = Cursor::new(proof_message.as_bytes());
ser::deserialize::<ProofMessageElements>(&mut c)
}
}
#[cfg(test)]
mod test {
use super::*;
@ -967,7 +1011,7 @@ mod test {
&key_id,
);
let msg = secp::pedersen::ProofMessage::empty();
let proof = keychain.range_proof(5, &key_id, commit, msg).unwrap();
let proof = keychain.range_proof(5, &key_id, commit, Some(switch_commit_hash.as_ref().to_vec()), msg).unwrap();
let out = Output {
features: OutputFeatures::DEFAULT_OUTPUT,
@ -989,16 +1033,20 @@ mod test {
fn test_output_value_recovery() {
let keychain = Keychain::from_random_seed().unwrap();
let key_id = keychain.derive_key_id(1).unwrap();
let value = 1003;
let commit = keychain.commit(1003, &key_id).unwrap();
let commit = keychain.commit(value, &key_id).unwrap();
let switch_commit = keychain.switch_commit(&key_id).unwrap();
let switch_commit_hash = SwitchCommitHash::from_switch_commit(
switch_commit,
&keychain,
&key_id,
);
let msg = secp::pedersen::ProofMessage::empty();
let proof = keychain.range_proof(1003, &key_id, commit, msg).unwrap();
let msg = (ProofMessageElements {
value: value,
}).to_proof_message();
let proof = keychain.range_proof(value, &key_id, commit, Some(switch_commit_hash.as_ref().to_vec()), msg).unwrap();
let output = Output {
features: OutputFeatures::DEFAULT_OUTPUT,
@ -1016,14 +1064,7 @@ mod test {
return;
}
// check we cannot recover the value without the original blinding factor
let key_id2 = keychain.derive_key_id(2).unwrap();
let not_recoverable = output.recover_value(&keychain, &key_id2);
match not_recoverable {
Some(_) => panic!("expected value to be None here"),
None => {}
}
// Bulletproofs message unwind will just be gibberish given the wrong blinding factor
}
#[test]

View file

@ -37,8 +37,6 @@ use util::secp::constants::{
SECRET_KEY_SIZE,
};
const BULLET_PROOF_SIZE: usize = 674;
/// Possible errors deriving from serializing or deserializing.
#[derive(Debug)]
pub enum Error {
@ -370,7 +368,7 @@ impl Writeable for RangeProof {
impl Readable for RangeProof {
fn read(reader: &mut Reader) -> Result<RangeProof, Error> {
let p = reader.read_limited_vec(BULLET_PROOF_SIZE)?;
let p = reader.read_limited_vec(MAX_PROOF_SIZE)?;
let mut a = [0; MAX_PROOF_SIZE];
for i in 0..p.len() {
a[i] = p[i];
@ -384,7 +382,7 @@ impl Readable for RangeProof {
impl PMMRable for RangeProof {
fn len() -> usize {
BULLET_PROOF_SIZE + 8
MAX_PROOF_SIZE + 8
}
}

View file

@ -3,12 +3,6 @@ name = "grin_keychain"
version = "0.1.0"
authors = ["Antioch Peverell"]
[features]
#remove this feature to use older-style rangeproofs
#(this flag will disappear in future releases)
default = ["use-bullet-proofs"]
use-bullet-proofs = []
[dependencies]
byteorder = "^1.0"
blake2-rfc = "~0.2.17"

View file

@ -28,11 +28,6 @@ use uuid::Uuid;
use blind::{BlindSum, BlindingFactor};
use extkey::{self, Identifier};
#[cfg(feature = "use-bullet-proofs")]
pub const USE_BULLET_PROOFS:bool = true;
#[cfg(not(feature = "use-bullet-proofs"))]
pub const USE_BULLET_PROOFS:bool = false;
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Error {
ExtendedKey(extkey::Error),
@ -226,33 +221,33 @@ impl Keychain {
Ok(child_key.switch_key)
}
pub fn is_using_bullet_proofs() -> bool {
USE_BULLET_PROOFS
}
pub fn range_proof(
&self,
amount: u64,
key_id: &Identifier,
commit: Commitment,
extra_data: Option<Vec<u8>>,
msg: ProofMessage,
) -> Result<RangeProof, Error> {
let skey = self.derived_key(key_id)?;
let range_proof = match USE_BULLET_PROOFS {
true => self.secp.bullet_proof(amount, skey),
false => self.secp.range_proof(0, amount, skey, commit, msg),
};
Ok(range_proof)
if msg.len() == 0 {
return Ok(self.secp.bullet_proof(amount, skey, extra_data, None));
} else {
if msg.len() != 64 {
error!(LOGGER, "Bullet proof message must be 64 bytes.");
return Err(Error::RangeProof("Bullet proof message must be 64 bytes".to_string()));
}
}
return Ok(self.secp.bullet_proof(amount, skey, extra_data, Some(msg)));
}
pub fn verify_range_proof(
secp: &Secp256k1,
commit: Commitment,
proof: RangeProof) -> Result<(), secp::Error> {
let result = match USE_BULLET_PROOFS {
true => secp.verify_bullet_proof(commit, proof),
false => secp.verify_range_proof(commit, proof),
};
proof: RangeProof,
extra_data: Option<Vec<u8>>)
-> Result<(), secp::Error> {
let result = secp.verify_bullet_proof(commit, proof, extra_data);
match result {
Ok(_) => Ok(()),
Err(e) => Err(e),
@ -263,14 +258,34 @@ impl Keychain {
&self,
key_id: &Identifier,
commit: Commitment,
extra_data: Option<Vec<u8>>,
proof: RangeProof,
) -> Result<ProofInfo, Error> {
let nonce = self.derived_key(key_id)?;
if USE_BULLET_PROOFS {
error!(LOGGER, "Rewinding Bullet proofs not yet supported");
return Err(Error::RangeProof("Rewinding Bullet proofs not yet supported".to_string()));
let proof_message = self.secp.unwind_bullet_proof(commit, nonce, extra_data, proof);
let proof_info = match proof_message {
Ok(p) => ProofInfo {
success: true,
value: 0,
message: p,
mlen: 0,
min: 0,
max: 0,
exp: 0,
mantissa: 0,
},
Err(_) => ProofInfo {
success: false,
value: 0,
message: ProofMessage::empty(),
mlen: 0,
min: 0,
max: 0,
exp: 0,
mantissa: 0,
}
Ok(self.secp.rewind_range_proof(commit, proof, nonce))
};
return Ok(proof_info);
}
pub fn blind_sum(&self, blind_sum: &BlindSum) -> Result<BlindingFactor, Error> {
@ -556,39 +571,40 @@ mod test {
let keychain = Keychain::from_random_seed().unwrap();
let key_id = keychain.derive_key_id(1).unwrap();
let commit = keychain.commit(5, &key_id).unwrap();
let msg = ProofMessage::empty();
let mut msg = ProofMessage::from_bytes(&[0u8; 64]);
let extra_data = [99u8; 64];
//TODO: Remove this check when bullet proofs can be rewound
if Keychain::is_using_bullet_proofs(){
return;
}
let proof = keychain.range_proof(5, &key_id, commit, msg).unwrap();
let proof_info = keychain.rewind_range_proof(&key_id, commit, proof).unwrap();
let proof = keychain.range_proof(5, &key_id, commit, Some(extra_data.to_vec().clone()), msg).unwrap();
let proof_info = keychain.rewind_range_proof(&key_id, commit, Some(extra_data.to_vec().clone()), proof).unwrap();
assert_eq!(proof_info.success, true);
assert_eq!(proof_info.value, 5);
// now check the recovered message is "empty" (but not truncated) i.e. all
// zeroes
//Value is in the message in this case
assert_eq!(
proof_info.message,
secp::pedersen::ProofMessage::from_bytes(&[0; secp::constants::PROOF_MSG_SIZE])
secp::pedersen::ProofMessage::from_bytes(&[0; secp::constants::BULLET_PROOF_MSG_SIZE])
);
let key_id2 = keychain.derive_key_id(2).unwrap();
// cannot rewind with a different nonce
let proof_info = keychain
.rewind_range_proof(&key_id2, commit, proof)
.rewind_range_proof(&key_id2, commit, Some(extra_data.to_vec().clone()), proof)
.unwrap();
assert_eq!(proof_info.success, false);
// With bullet proofs, if you provide the wrong nonce you'll get gibberish back as opposed
// to a failure to recover the message
assert_ne!(
proof_info.message,
secp::pedersen::ProofMessage::from_bytes(&[0; secp::constants::BULLET_PROOF_MSG_SIZE])
);
assert_eq!(proof_info.value, 0);
// cannot rewind with a commitment to the same value using a different key
let commit2 = keychain.commit(5, &key_id2).unwrap();
let proof_info = keychain
.rewind_range_proof(&key_id, commit2, proof)
.rewind_range_proof(&key_id, commit2, Some(extra_data.to_vec().clone()), proof)
.unwrap();
assert_eq!(proof_info.success, false);
assert_eq!(proof_info.value, 0);
@ -596,10 +612,20 @@ mod test {
// cannot rewind with a commitment to a different value
let commit3 = keychain.commit(4, &key_id).unwrap();
let proof_info = keychain
.rewind_range_proof(&key_id, commit3, proof)
.rewind_range_proof(&key_id, commit3, Some(extra_data.to_vec().clone()), proof)
.unwrap();
assert_eq!(proof_info.success, false);
assert_eq!(proof_info.value, 0);
// cannot rewind with wrong extra committed data
let commit3 = keychain.commit(4, &key_id).unwrap();
let wrong_extra_data = [98u8; 64];
let should_err = keychain
.rewind_range_proof(&key_id, commit3, Some(wrong_extra_data.to_vec().clone()), proof)
.unwrap();
assert_eq!(proof_info.success, false);
assert_eq!(proof_info.value, 0);
}
// We plan to "offset" the key used in the kernel commitment

View file

@ -334,7 +334,7 @@ mod tests {
commit: output_commit,
switch_commit_hash: switch_commit_hash,
proof: keychain
.range_proof(100, &key_id1, output_commit, msg)
.range_proof(100, &key_id1, output_commit, Some(switch_commit_hash.as_ref().to_vec()), msg)
.unwrap(),
};

View file

@ -1359,7 +1359,7 @@ mod tests {
&key_id,
);
let msg = secp::pedersen::ProofMessage::empty();
let proof = keychain.range_proof(value, &key_id, commit, msg).unwrap();
let proof = keychain.range_proof(value, &key_id, commit, Some(switch_commit_hash.as_ref().to_vec()), msg).unwrap();
transaction::Output {
features: transaction::OutputFeatures::DEFAULT_OUTPUT,
@ -1381,7 +1381,7 @@ mod tests {
&key_id,
);
let msg = secp::pedersen::ProofMessage::empty();
let proof = keychain.range_proof(value, &key_id, commit, msg).unwrap();
let proof = keychain.range_proof(value, &key_id, commit, Some(switch_commit_hash.as_ref().to_vec()), msg).unwrap();
transaction::Output {
features: transaction::OutputFeatures::COINBASE_OUTPUT,

View file

@ -14,7 +14,11 @@ byteorder = "^1.0"
rand = "^0.3"
serde = "~1.0.8"
serde_derive = "~1.0.8"
secp256k1zkp = { git = "https://github.com/mimblewimble/rust-secp256k1-zkp", tag="grin_integration_8" }
#secp256k1zkp = { path = "../../rust-secp256k1-zkp" }
walkdir = "^2.0.1"
zip = "^0.2.6"
[dependencies.secp256k1zkp]
git = "https://github.com/mimblewimble/rust-secp256k1-zkp"
tag="grin_integration_14"
#path = "../../rust-secp256k1-zkp"
features=["bullet-proof-sizing"]