diff --git a/.travis.yml b/.travis.yml index b582e090..7b6bbc08 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,8 @@ before_install: - sudo apt-get install -y binutils-dev libunwind8-dev script: - # Pin `cc` for Rust 1.29 - - if [ "$TRAVIS_RUST_VERSION" = "1.29.0" ]; then cargo generate-lockfile --verbose && cargo update -p cc --precise "1.0.41" --verbose; fi + # Pin `cc`, `serde` and `serde_derive` for Rust 1.29 + - if [ "$TRAVIS_RUST_VERSION" = "1.29.0" ]; then cargo generate-lockfile --verbose && cargo update -p cc --precise "1.0.41" && cargo update --package "serde" --precise "1.0.98" && cargo update --package "serde_derive" --precise "1.0.98" --verbose; fi - cargo build --verbose - cargo test --verbose - cargo build --verbose --features=serde-feature diff --git a/Cargo.toml b/Cargo.toml index 0ecc49a3..887db57c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,12 +14,14 @@ default = [ "json-contract" ] json-contract = [ "serde_json" ] "serde-feature" = [ "bitcoin/use-serde", + "secp256k1-zkp/use-serde", "serde" ] "fuzztarget" = [] [dependencies] -bitcoin = "0.25" +bitcoin = "0.26" +secp256k1-zkp = { version = "0.2", features = [ "global-context", "hashes" ] } slip21 = "0.2.0" # While this dependency is included in bitcoin, we need this to use the macros. diff --git a/src/address.rs b/src/address.rs index 624cb9d0..d55bf957 100644 --- a/src/address.rs +++ b/src/address.rs @@ -178,10 +178,10 @@ impl Address { params: &'static AddressParams, ) -> Address { let mut hash_engine = PubkeyHash::engine(); - pk.write_into(&mut hash_engine); + pk.write_into(&mut hash_engine).expect("engines don't error"); Address { - params: params, + params, payload: Payload::PubkeyHash(PubkeyHash::from_engine(hash_engine)), blinding_pubkey: blinder, } @@ -196,7 +196,7 @@ impl Address { params: &'static AddressParams, ) -> Address { Address { - params: params, + params, payload: Payload::ScriptHash(ScriptHash::hash(&script[..])), blinding_pubkey: blinder, } @@ -210,10 +210,10 @@ impl Address { params: &'static AddressParams, ) -> Address { let mut hash_engine = WPubkeyHash::engine(); - pk.write_into(&mut hash_engine); + pk.write_into(&mut hash_engine).expect("engines don't error"); Address { - params: params, + params, payload: Payload::WitnessProgram { version: u5::try_from_u8(0).expect("0<32"), program: WPubkeyHash::from_engine(hash_engine)[..].to_vec(), @@ -230,14 +230,14 @@ impl Address { params: &'static AddressParams, ) -> Address { let mut hash_engine = ScriptHash::engine(); - pk.write_into(&mut hash_engine); + pk.write_into(&mut hash_engine).expect("engines don't error"); let builder = script::Builder::new() .push_int(0) .push_slice(&ScriptHash::from_engine(hash_engine)[..]); Address { - params: params, + params, payload: Payload::ScriptHash(ScriptHash::hash(builder.into_script().as_bytes())), blinding_pubkey: blinder, } @@ -250,7 +250,7 @@ impl Address { params: &'static AddressParams, ) -> Address { Address { - params: params, + params, payload: Payload::WitnessProgram { version: u5::try_from_u8(0).expect("0<32"), program: WScriptHash::hash(&script[..])[..].to_vec(), @@ -272,7 +272,7 @@ impl Address { .into_script(); Address { - params: params, + params, payload: Payload::ScriptHash(ScriptHash::hash(&ws[..])), blinding_pubkey: blinder, } @@ -303,7 +303,7 @@ impl Address { return None; }, blinding_pubkey: blinder, - params: params, + params, }) } @@ -357,7 +357,7 @@ impl Address { blech32::decode(s).map_err(AddressError::Blech32)?.1 }; - if payload.len() == 0 { + if payload.is_empty() { return Err(AddressError::InvalidAddress(s.to_owned())); } @@ -400,12 +400,12 @@ impl Address { }; Ok(Address { - params: params, + params, payload: Payload::WitnessProgram { - version: version, - program: program, + version, + program, }, - blinding_pubkey: blinding_pubkey, + blinding_pubkey, }) } @@ -419,13 +419,13 @@ impl Address { let (blinded, prefix) = match data[0] == params.blinded_prefix { true => { if data.len() != 55 { - return Err(base58::Error::InvalidLength(data.len()))?; + return Err(base58::Error::InvalidLength(data.len()).into()); } (true, data[1]) } false => { if data.len() != 21 { - return Err(base58::Error::InvalidLength(data.len()))?; + return Err(base58::Error::InvalidLength(data.len()).into()); } (false, data[0]) } @@ -447,13 +447,13 @@ impl Address { } else if prefix == params.p2sh_prefix { Payload::ScriptHash(ScriptHash::from_slice(payload_data).unwrap()) } else { - return Err(base58::Error::InvalidVersion(vec![prefix]))?; + return Err(base58::Error::InvalidVersion(vec![prefix]).into()); }; Ok(Address { - params: params, - payload: payload, - blinding_pubkey: blinding_pubkey, + params, + payload, + blinding_pubkey, }) } @@ -473,7 +473,7 @@ impl Address { // Base58. if s.len() > 150 { - return Err(base58::Error::InvalidLength(s.len() * 11 / 15))?; + return Err(base58::Error::InvalidLength(s.len() * 11 / 15).into()); } let data = base58::from_check(s)?; Address::from_base58(&data, params) @@ -551,7 +551,7 @@ impl fmt::Debug for Address { /// Returns the same slice when no prefix is found. fn find_prefix(bech32: &str) -> &str { // Split at the last occurrence of the separator character '1'. - match bech32.rfind("1") { + match bech32.rfind('1') { None => bech32, Some(sep) => bech32.split_at(sep).0, } @@ -596,11 +596,11 @@ impl FromStr for Address { // Base58. if s.len() > 150 { - return Err(base58::Error::InvalidLength(s.len() * 11 / 15))?; + return Err(base58::Error::InvalidLength(s.len() * 11 / 15).into()); } let data = base58::from_check(s)?; - if data.len() < 1 { - return Err(base58::Error::InvalidLength(data.len()))?; + if data.is_empty() { + return Err(base58::Error::InvalidLength(data.len()).into()); } let p = data[0]; @@ -709,33 +709,33 @@ mod test { let vectors = [ /* #00 */ Address::p2pkh(&pk, None, &AddressParams::LIQUID), /* #01 */ Address::p2pkh(&pk, None, &AddressParams::ELEMENTS), - /* #02 */ Address::p2pkh(&pk, Some(blinder.clone()), &AddressParams::LIQUID), - /* #03 */ Address::p2pkh(&pk, Some(blinder.clone()), &AddressParams::ELEMENTS), + /* #02 */ Address::p2pkh(&pk, Some(blinder), &AddressParams::LIQUID), + /* #03 */ Address::p2pkh(&pk, Some(blinder), &AddressParams::ELEMENTS), /* #04 */ Address::p2sh(&script, None, &AddressParams::LIQUID), /* #05 */ Address::p2sh(&script, None, &AddressParams::ELEMENTS), - /* #06 */ Address::p2sh(&script, Some(blinder.clone()), &AddressParams::LIQUID), + /* #06 */ Address::p2sh(&script, Some(blinder), &AddressParams::LIQUID), /* #07 */ - Address::p2sh(&script, Some(blinder.clone()), &AddressParams::ELEMENTS), + Address::p2sh(&script, Some(blinder), &AddressParams::ELEMENTS), /* #08 */ Address::p2wpkh(&pk, None, &AddressParams::LIQUID), /* #09 */ Address::p2wpkh(&pk, None, &AddressParams::ELEMENTS), - /* #10 */ Address::p2wpkh(&pk, Some(blinder.clone()), &AddressParams::LIQUID), - /* #11 */ Address::p2wpkh(&pk, Some(blinder.clone()), &AddressParams::ELEMENTS), + /* #10 */ Address::p2wpkh(&pk, Some(blinder), &AddressParams::LIQUID), + /* #11 */ Address::p2wpkh(&pk, Some(blinder), &AddressParams::ELEMENTS), /* #12 */ Address::p2shwpkh(&pk, None, &AddressParams::LIQUID), /* #13 */ Address::p2shwpkh(&pk, None, &AddressParams::ELEMENTS), - /* #14 */ Address::p2shwpkh(&pk, Some(blinder.clone()), &AddressParams::LIQUID), + /* #14 */ Address::p2shwpkh(&pk, Some(blinder), &AddressParams::LIQUID), /* #15 */ - Address::p2shwpkh(&pk, Some(blinder.clone()), &AddressParams::ELEMENTS), + Address::p2shwpkh(&pk, Some(blinder), &AddressParams::ELEMENTS), /* #16 */ Address::p2wsh(&script, None, &AddressParams::LIQUID), /* #17 */ Address::p2wsh(&script, None, &AddressParams::ELEMENTS), - /* #18 */ Address::p2wsh(&script, Some(blinder.clone()), &AddressParams::LIQUID), + /* #18 */ Address::p2wsh(&script, Some(blinder), &AddressParams::LIQUID), /* #19 */ - Address::p2wsh(&script, Some(blinder.clone()), &AddressParams::ELEMENTS), + Address::p2wsh(&script, Some(blinder), &AddressParams::ELEMENTS), /* #20 */ Address::p2shwsh(&script, None, &AddressParams::LIQUID), /* #21 */ Address::p2shwsh(&script, None, &AddressParams::ELEMENTS), /* #22 */ - Address::p2shwsh(&script, Some(blinder.clone()), &AddressParams::LIQUID), + Address::p2shwsh(&script, Some(blinder), &AddressParams::LIQUID), /* #23 */ - Address::p2shwsh(&script, Some(blinder.clone()), &AddressParams::ELEMENTS), + Address::p2shwsh(&script, Some(blinder), &AddressParams::ELEMENTS), ]; for addr in &vectors { diff --git a/src/blech32.rs b/src/blech32.rs index 1790790f..fd35bcca 100644 --- a/src/blech32.rs +++ b/src/blech32.rs @@ -77,7 +77,7 @@ pub fn decode(s: &str) -> Result<(&str, Vec), Error> { } // Split at separator and check for two pieces - let (raw_hrp, raw_data) = match s.rfind("1") { + let (raw_hrp, raw_data) = match s.rfind('1') { None => return Err(Error::MissingSeparator), Some(sep) => { let (hrp, data) = s.split_at(sep); @@ -85,7 +85,7 @@ pub fn decode(s: &str) -> Result<(&str, Vec), Error> { } }; // ELEMENTS: 6->12 - if raw_hrp.len() < 1 || raw_data.len() < 12 || raw_hrp.len() > 83 { + if raw_hrp.is_empty() || raw_data.len() < 12 || raw_hrp.len() > 83 { return Err(Error::InvalidLength); } @@ -193,9 +193,9 @@ fn polymod(values: &[u5]) -> u64 { for v in values { b = (chk >> 55) as u8; // ELEMENTS: 25->55 chk = (chk & 0x7fffffffffffff) << 5 ^ (u64::from(*v.as_ref())); // ELEMENTS 0x1ffffff->0x7fffffffffffff - for i in 0..5 { + for (i, coef) in GEN.iter().enumerate() { if (b >> i) & 1 == 1 { - chk ^= GEN[i] + chk ^= coef } } } @@ -248,7 +248,7 @@ mod test { let data2 = data.to_vec(); let mut data2_b32 = data2.to_base32(); - data2_b32.extend(vec![u5::try_from_u8(0).unwrap(); 1023]); + data2_b32.extend(vec![u5::try_from_u8(0).unwrap(); 1023]); let polymod2 = polymod(&data2_b32); assert_eq!(polymod1, polymod2); } @@ -256,7 +256,7 @@ mod test { #[test] fn test_checksum() { let data = vec![7,2,3,4,5,6,7,8,9,234,123,213,16]; - let cs = create_checksum("lq".as_bytes(), &data.to_base32()); + let cs = create_checksum(b"lq", &data.to_base32()); let expected_cs = vec![22,13,13,5,4,4,23,7,28,21,30,12]; for i in 0..expected_cs.len() { assert_eq!(expected_cs[i], *cs[i].as_ref()); diff --git a/src/block.rs b/src/block.rs index 8f3bc445..bce1e43b 100644 --- a/src/block.rs +++ b/src/block.rs @@ -126,17 +126,15 @@ impl<'de> Deserialize<'de> for ExtData { proposed: prop, signblock_witness: wit, }) + } else if challenge_missing { + Err(de::Error::missing_field("challenge")) } else { - if challenge_missing { - Err(de::Error::missing_field("challenge")) - } else { - Err(de::Error::missing_field("solution")) - } + Err(de::Error::missing_field("solution")) } } } - static FIELDS: &'static [&'static str] = &[ + static FIELDS: &[&str] = &[ "challenge", "solution", "current", @@ -267,10 +265,10 @@ impl BlockHeader { /// the block hash. pub fn clear_witness(&mut self) { match &mut self.ext { - &mut ExtData::Proof { ref mut solution, .. } => { + ExtData::Proof { ref mut solution, .. } => { *solution = Script::new(); }, - &mut ExtData::Dynafed { ref mut signblock_witness, .. } => { + ExtData::Dynafed { ref mut signblock_witness, .. } => { signblock_witness.clear(); }, } @@ -309,7 +307,7 @@ impl Encodable for BlockHeader { } impl Decodable for BlockHeader { - fn consensus_decode(mut d: D) -> Result { + fn consensus_decode(mut d: D) -> Result { let mut version: u32 = Decodable::consensus_decode(&mut d)?; let is_dyna = if version >> 31 == 1 { version &= 0x7fff_ffff; @@ -319,7 +317,7 @@ impl Decodable for BlockHeader { }; Ok(BlockHeader { - version: version, + version, prev_blockhash: Decodable::consensus_decode(&mut d)?, merkle_root: Decodable::consensus_decode(&mut d)?, time: Decodable::consensus_decode(&mut d)?, @@ -749,4 +747,3 @@ mod tests { ); } } - diff --git a/src/confidential.rs b/src/confidential.rs index 0bdab9ce..6fab4806 100644 --- a/src/confidential.rs +++ b/src/confidential.rs @@ -17,253 +17,277 @@ //! Structures representing Pedersen commitments of various types //! +use bitcoin::hashes::{sha256d, Hash}; +use secp256k1_zkp::{ + self, compute_adaptive_blinding_factor, + ecdh::SharedSecret, + rand::{CryptoRng, Rng, RngCore}, + CommitmentSecrets, Generator, PedersenCommitment, PublicKey, Secp256k1, SecretKey, Signing, +}; #[cfg(feature = "serde")] use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::{io, fmt}; +use std::{fmt, io}; -use encode::{self, Encodable, Decodable}; +use encode::{self, Decodable, Encodable}; use issuance::AssetId; -// Helper macro to implement various things for the various confidential -// commitment types -macro_rules! impl_confidential_commitment { - ($name:ident, $inner:ty, $prefixA:expr, $prefixB:expr) => ( - impl_confidential_commitment!($name, $inner, $prefixA, $prefixB, |x|x); - ); - ($name:ident, $inner:ty, $prefixA:expr, $prefixB:expr, $explicit_fn:expr) => ( - impl $name { - /// Create from commitment. - pub fn from_commitment(bytes: &[u8]) -> Result<$name, encode::Error> { - if bytes.len() != 33 { - return Err(encode::Error::ParseFailed("commitments must be 33 bytes long")); - } - let prefix = bytes[0]; - if prefix != $prefixA && prefix != $prefixB { - return Err(encode::Error::InvalidConfidentialPrefix(prefix)); - } - let mut c = [0; 32]; - c.copy_from_slice(&bytes[1..]); - Ok($name::Confidential(prefix, c)) - } +/// A CT commitment to an amount +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub enum Value { + /// No value + Null, + /// Value is explicitly encoded + Explicit(u64), + /// Value is committed + Confidential(PedersenCommitment), +} - /// Check if the object is null. - pub fn is_null(&self) -> bool { - match *self { - $name::Null => true, - _ => false, - } - } +impl Value { + /// Create value commitment. + pub fn new_confidential( + secp: &Secp256k1, + value: u64, + asset: Generator, + bf: ValueBlindingFactor, + ) -> Self { + Value::Confidential(PedersenCommitment::new(secp, value, bf.0, asset)) + } - /// Check if the object is explicit. - pub fn is_explicit(&self) -> bool { - match *self { - $name::Explicit(_) => true, - _ => false, - } - } + /// Serialized length, in bytes + pub fn encoded_length(&self) -> usize { + match *self { + Value::Null => 1, + Value::Explicit(..) => 9, + Value::Confidential(..) => 33, + } + } - /// Check if the object is confidential. - pub fn is_confidential(&self) -> bool { - match *self { - // Impossible to create an object with invalid prefix. - $name::Explicit(_) => true, - _ => false, - } - } + /// Create from commitment. + pub fn from_commitment(bytes: &[u8]) -> Result { + Ok(Value::Confidential(PedersenCommitment::from_slice(bytes)?)) + } - /// Returns the explicit inner value. - /// Returns [None] if [is_explicit] returns false. - pub fn explicit(&self) -> Option<$inner> { - match *self { - $name::Explicit(i) => Some(i), - _ => None, - } - } + /// Check if the object is null. + pub fn is_null(&self) -> bool { + match self { + Value::Null => true, + _ => false + } + } - /// Returns the confidential commitment in case of a confidential value. - /// Returns [None] if [is_confidential] returns false. - pub fn commitment(&self) -> Option<[u8; 33]> { - match *self { - $name::Confidential(p, c) => { - let mut res = [0; 33]; - res[0] = p; - res[1..].copy_from_slice(&c[..]); - Some(res) - } - _ => None, - } - } + /// Check if the object is explicit. + pub fn is_explicit(&self) -> bool { + match self { + Value::Explicit(_) => true, + _ => false } + } - impl Default for $name { - fn default() -> Self { - $name::Null - } + /// Check if the object is confidential. + pub fn is_confidential(&self) -> bool { + match self { + Value::Confidential(_) => true, + _ => false } + } - impl Encodable for $name { - fn consensus_encode(&self, mut s: S) -> Result { - match *self { - $name::Null => 0u8.consensus_encode(s), - $name::Explicit(n) => { - 1u8.consensus_encode(&mut s)?; - // Apply $explicit_fn to allow `Value` to swap the amount bytes - Ok(1 + $explicit_fn(n).consensus_encode(&mut s)?) - } - $name::Confidential(prefix, bytes) => { - Ok(prefix.consensus_encode(&mut s)? + bytes.consensus_encode(&mut s)?) - } - } - } + /// Returns the explicit inner value. + /// Returns [None] if [is_explicit] returns false. + pub fn explicit(&self) -> Option { + match *self { + Value::Explicit(i) => Some(i), + _ => None, } + } - impl Decodable for $name { - fn consensus_decode(mut d: D) -> Result<$name, encode::Error> { - let prefix = u8::consensus_decode(&mut d)?; - match prefix { - 0 => Ok($name::Null), - 1 => { - // Apply $explicit_fn to allow `Value` to swap the amount bytes - let explicit = $explicit_fn(Decodable::consensus_decode(&mut d)?); - Ok($name::Explicit(explicit)) - } - p if p == $prefixA || p == $prefixB => { - let commitment = <[u8; 32]>::consensus_decode(&mut d)?; - Ok($name::Confidential(p, commitment)) - } - p => return Err(encode::Error::InvalidConfidentialPrefix(p)), - } - } + /// Returns the confidential commitment in case of a confidential value. + /// Returns [None] if [is_confidential] returns false. + pub fn commitment(&self) -> Option { + match *self { + Value::Confidential(i) => Some(i), + _ => None, } + } +} - #[cfg(feature = "serde")] - impl Serialize for $name { - fn serialize(&self, s: S) -> Result { - use serde::ser::SerializeSeq; - - let seq_len = if *self == $name::Null { 1 } else { 2 }; - let mut seq = s.serialize_seq(Some(seq_len))?; - - match *self { - $name::Null => seq.serialize_element(&0u8)?, - $name::Explicit(n) => { - seq.serialize_element(&1u8)?; - // Apply $explicit_fn to allow `Value` to swap the amount bytes - seq.serialize_element(&$explicit_fn(n))?; - } - $name::Confidential(prefix, bytes) => { - seq.serialize_element(&prefix)?; - seq.serialize_element(&bytes)?; - } - } - seq.end() +impl From for Value { + fn from(from: PedersenCommitment) -> Self { + Value::Confidential(from) + } +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Value::Null => f.write_str("null"), + Value::Explicit(n) => write!(f, "{}", n), + Value::Confidential(commitment) => write!(f, "{:02x}", commitment), + } + } +} + +impl Default for Value { + fn default() -> Self { + Value::Null + } +} + +impl Encodable for Value { + fn consensus_encode(&self, mut s: S) -> Result { + match *self { + Value::Null => 0u8.consensus_encode(s), + Value::Explicit(n) => { + 1u8.consensus_encode(&mut s)?; + Ok(1 + u64::swap_bytes(n).consensus_encode(&mut s)?) } + Value::Confidential(commitment) => commitment.consensus_encode(&mut s), } + } +} - #[cfg(feature = "serde")] - impl<'de> Deserialize<'de> for $name { - fn deserialize>(d: D) -> Result { - use serde::de::{Error, Visitor, SeqAccess}; - struct CommitVisitor; - - impl <'de> Visitor<'de> for CommitVisitor { - type Value = $name; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("a committed value") - } - - fn visit_seq>(self, mut access: A) -> Result { - let prefix: u8 = if let Some(x) = access.next_element()? { - x - } else { - return Err(A::Error::custom("missing prefix")); - }; - - match prefix { - 0 => Ok($name::Null), - 1 => { - // Apply $explicit_fn to allow `Value` to swap the amount bytes - match access.next_element()? { - Some(x) => Ok($name::Explicit($explicit_fn(x))), - None => Err(A::Error::custom("missing commitment")), - } - } - p if p == $prefixA || p == $prefixB => { - match access.next_element()? { - Some(y) => Ok($name::Confidential(p, y)), - None => Err(A::Error::custom("missing commitment")), - } - } - p => return Err(A::Error::custom(format!( - "invalid commitment, invalid prefix: 0x{:02x}", p - ))), - } - } - } +impl Encodable for PedersenCommitment { + fn consensus_encode(&self, mut e: W) -> Result { + e.write_all(&self.serialize())?; + Ok(33) + } +} + +impl Decodable for Value { + fn consensus_decode(mut d: D) -> Result { + let prefix = { + let buffer = d.fill_buf()?; - d.deserialize_seq(CommitVisitor) + if buffer.is_empty() { + return Err(encode::Error::UnexpectedEOF); + } + + buffer[0] + }; + + match prefix { + 0 => { + // consume null value prefix + d.consume(1); + Ok(Value::Null) + } + 1 => { + // ignore prefix when decoding an explicit value + d.consume(1); + let explicit = u64::swap_bytes(Decodable::consensus_decode(&mut d)?); + Ok(Value::Explicit(explicit)) } + p if p == 0x08 || p == 0x09 => { + let commitment = Decodable::consensus_decode(&mut d)?; + Ok(Value::Confidential(commitment)) + } + p => Err(encode::Error::InvalidConfidentialPrefix(p)), } - ); + } } -/// A CT commitment to an amount -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] -pub enum Value { - /// No value - Null, - /// Value is explicitly encoded - Explicit(u64), - // Split commitments into a 1-byte prefix and 32-byte commitment, because - // they're easy enough to separate and Rust stdlib treats 32-byte arrays - // much much better than 33-byte arrays. - /// Value is committed - Confidential(u8, [u8; 32]), +impl Decodable for PedersenCommitment { + fn consensus_decode(d: D) -> Result { + let bytes = <[u8; 33]>::consensus_decode(d)?; + Ok(PedersenCommitment::from_slice(&bytes)?) + } } -impl_confidential_commitment!(Value, u64, 0x08, 0x09, u64::swap_bytes); -impl Value { - /// Serialized length, in bytes - pub fn encoded_length(&self) -> usize { +#[cfg(feature = "serde")] +impl Serialize for Value { + fn serialize(&self, s: S) -> Result { + use serde::ser::SerializeSeq; + + let seq_len = if *self == Value::Null { 1 } else { 2 }; + let mut seq = s.serialize_seq(Some(seq_len))?; + match *self { - Value::Null => 1, - Value::Explicit(..) => 9, - Value::Confidential(..) => 33, + Value::Null => seq.serialize_element(&0u8)?, + Value::Explicit(n) => { + seq.serialize_element(&1u8)?; + seq.serialize_element(&u64::swap_bytes(n))?; + } + Value::Confidential(commitment) => { + seq.serialize_element(&commitment)?; + } } + seq.end() } } -impl fmt::Display for Value { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Value::Null => f.write_str("null"), - Value::Explicit(n) => write!(f, "{}", n), - Value::Confidential(prefix, bytes) => { - write!(f, "{:02x}", prefix)?; - for b in bytes.iter() { - write!(f, "{:02x}", b)?; +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Value { + fn deserialize>(d: D) -> Result { + use serde::de::{Error, SeqAccess, Visitor}; + struct CommitVisitor; + + impl<'de> Visitor<'de> for CommitVisitor { + type Value = Value; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a committed value") + } + + fn visit_seq>(self, mut access: A) -> Result { + let prefix: u8 = if let Some(x) = access.next_element()? { + x + } else { + return Err(A::Error::custom("missing prefix")); + }; + + match prefix { + 0 => Ok(Value::Null), + 1 => match access.next_element()? { + Some(x) => Ok(Value::Explicit(u64::swap_bytes(x))), + None => Err(A::Error::custom("missing commitment")), + }, + p if p == 0x08 || p == 0x09 => match access.next_element::<[u8; 32]>()? { + Some(y) => { + y.to_vec().insert(0, p); + Ok(Value::Confidential( + PedersenCommitment::from_slice(y.as_ref()) + .map_err(A::Error::custom)?, + )) + } + None => Err(A::Error::custom("missing commitment")), + }, + p => Err(A::Error::custom(format!( + "invalid commitment, invalid prefix: 0x{:02x}", + p + ))), } - Ok(()) } } + + d.deserialize_seq(CommitVisitor) } } /// A CT commitment to an asset -#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] pub enum Asset { /// No value Null, /// Asset entropy is explicitly encoded Explicit(AssetId), /// Asset is committed - Confidential(u8, [u8; 32]), + Confidential(Generator), } -impl_confidential_commitment!(Asset, AssetId, 0x0a, 0x0b); impl Asset { + /// Create asset commitment. + pub fn new_confidential( + secp: &Secp256k1, + asset: AssetId, + bf: AssetBlindingFactor, + ) -> Self { + Asset::Confidential(Generator::new_blinded( + secp, + asset.into_tag(), + bf.into_inner(), + )) + } + /// Serialized length, in bytes pub fn encoded_length(&self) -> usize { match *self { @@ -272,6 +296,59 @@ impl Asset { Asset::Confidential(..) => 33, } } + + /// Create from commitment. + pub fn from_commitment(bytes: &[u8]) -> Result { + Ok(Asset::Confidential(Generator::from_slice(bytes)?)) + } + + /// Check if the object is null. + pub fn is_null(&self) -> bool { + match *self { + Asset::Null => true, + _ => false + } + } + + /// Check if the object is explicit. + pub fn is_explicit(&self) -> bool { + match *self { + Asset::Explicit(_) => true, + _ => false + } + } + + /// Check if the object is confidential. + pub fn is_confidential(&self) -> bool { + match *self { + Asset::Confidential(_) => true, + _ => false + } + } + + /// Returns the explicit inner value. + /// Returns [None] if [is_explicit] returns false. + pub fn explicit(&self) -> Option { + match *self { + Asset::Explicit(i) => Some(i), + _ => None, + } + } + + /// Returns the confidential commitment in case of a confidential value. + /// Returns [None] if [is_confidential] returns false. + pub fn commitment(&self) -> Option { + match *self { + Asset::Confidential(i) => Some(i), + _ => None, + } + } +} + +impl From for Asset { + fn from(from: Generator) -> Self { + Asset::Confidential(from) + } } impl fmt::Display for Asset { @@ -279,14 +356,144 @@ impl fmt::Display for Asset { match *self { Asset::Null => f.write_str("null"), Asset::Explicit(n) => write!(f, "{}", n), - Asset::Confidential(prefix, bytes) => { - write!(f, "{:02x}", prefix)?; - for b in bytes.iter() { - write!(f, "{:02x}", b)?; + Asset::Confidential(generator) => write!(f, "{:02x}", generator), + } + } +} + +impl Default for Asset { + fn default() -> Self { + Asset::Null + } +} + +impl Encodable for Asset { + fn consensus_encode(&self, mut s: S) -> Result { + match *self { + Asset::Null => 0u8.consensus_encode(s), + Asset::Explicit(n) => { + 1u8.consensus_encode(&mut s)?; + Ok(1 + n.consensus_encode(&mut s)?) + } + Asset::Confidential(generator) => generator.consensus_encode(&mut s) + } + } +} + +impl Encodable for Generator { + fn consensus_encode(&self, mut e: W) -> Result { + e.write_all(&self.serialize())?; + Ok(33) + } +} + +impl Decodable for Asset { + fn consensus_decode(mut d: D) -> Result { + let prefix = { + let buffer = d.fill_buf()?; + + if buffer.is_empty() { + return Err(encode::Error::UnexpectedEOF); + } + + buffer[0] + }; + + match prefix { + 0 => { + // consume null value prefix + d.consume(1); + Ok(Asset::Null) + } + 1 => { + // ignore prefix when decoding an explicit asset + d.consume(1); + let explicit = Decodable::consensus_decode(&mut d)?; + Ok(Asset::Explicit(explicit)) + } + p if p == 0x0a || p == 0x0b => { + let generator = Decodable::consensus_decode(&mut d)?; + Ok(Asset::Confidential(generator)) + } + p => Err(encode::Error::InvalidConfidentialPrefix(p)), + } + } +} + +impl Decodable for Generator { + fn consensus_decode(d: D) -> Result { + let bytes = <[u8; 33]>::consensus_decode(d)?; + Ok(Generator::from_slice(&bytes)?) + } +} + + +#[cfg(feature = "serde")] +impl Serialize for Asset { + fn serialize(&self, s: S) -> Result { + use serde::ser::SerializeSeq; + + let seq_len = if *self == Asset::Null { 1 } else { 2 }; + let mut seq = s.serialize_seq(Some(seq_len))?; + + match *self { + Asset::Null => seq.serialize_element(&0u8)?, + Asset::Explicit(n) => { + seq.serialize_element(&1u8)?; + seq.serialize_element(&n)?; + } + Asset::Confidential(commitment) => { + seq.serialize_element(&commitment)?; + } + } + seq.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Asset { + fn deserialize>(d: D) -> Result { + use serde::de::{Error, SeqAccess, Visitor}; + struct CommitVisitor; + + impl<'de> Visitor<'de> for CommitVisitor { + type Value = Asset; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a committed value") + } + + fn visit_seq>(self, mut access: A) -> Result { + let prefix: u8 = if let Some(x) = access.next_element()? { + x + } else { + return Err(A::Error::custom("missing prefix")); + }; + + match prefix { + 0 => Ok(Asset::Null), + 1 => match access.next_element()? { + Some(x) => Ok(Asset::Explicit(x)), + None => Err(A::Error::custom("missing commitment")), + }, + p if p == 0x0a || p == 0x0b => match access.next_element::<[u8; 32]>()? { + Some(y) => { + y.to_vec().insert(0, p); + Ok(Asset::Confidential( + Generator::from_slice(y.as_ref()).map_err(A::Error::custom)?, + )) + } + None => Err(A::Error::custom("missing commitment")), + }, + p => Err(A::Error::custom(format!( + "invalid commitment, invalid prefix: 0x{:02x}", + p + ))), } - Ok(()) } } + + d.deserialize_seq(CommitVisitor) } } @@ -300,11 +507,54 @@ pub enum Nonce { /// that implements all the traits we need. Explicit([u8; 32]), /// Nonce is committed - Confidential(u8, [u8; 32]), + Confidential(PublicKey), } -impl_confidential_commitment!(Nonce, [u8; 32], 0x02, 0x03); impl Nonce { + /// Create nonce commitment. + pub fn new_confidential( + rng: &mut R, + secp: &Secp256k1, + receiver_blinding_pk: &PublicKey, + ) -> (Self, SecretKey) { + let sender_sk = SecretKey::new(rng); + let sender_pk = PublicKey::from_secret_key(&secp, &sender_sk); + + let shared_secret = Self::make_shared_secret(receiver_blinding_pk, &sender_sk); + + (Nonce::Confidential(sender_pk), shared_secret) + } + + /// Calculate the shared secret. + pub fn shared_secret(&self, receiver_blinding_sk: &SecretKey) -> Option { + match self { + Nonce::Confidential(sender_pk) => { + Some(Self::make_shared_secret(&sender_pk, receiver_blinding_sk)) + } + _ => None, + } + } + + /// Create the shared secret. + fn make_shared_secret(pk: &PublicKey, sk: &SecretKey) -> SecretKey { + let shared_secret = SharedSecret::new_with_hash(pk, sk, |x, y| { + // Yes, what follows is the compressed representation of a Bitcoin public key. + // However, this is more by accident then by design, see here: https://github.com/rust-bitcoin/rust-secp256k1/pull/255#issuecomment-744146282 + + let mut dh_secret = [0u8; 33]; + dh_secret[0] = if y.last().unwrap() % 2 == 0 { + 0x02 + } else { + 0x03 + }; + dh_secret[1..].copy_from_slice(&x); + + sha256d::Hash::hash(&dh_secret).into_inner().into() + }); + + SecretKey::from_slice(&shared_secret.as_ref()[..32]).expect("always has exactly 32 bytes") + } + /// Serialized length, in bytes pub fn encoded_length(&self) -> usize { match *self { @@ -313,6 +563,61 @@ impl Nonce { Nonce::Confidential(..) => 33, } } + + /// Create from commitment. + pub fn from_commitment(bytes: &[u8]) -> Result { + Ok(Nonce::Confidential( + PublicKey::from_slice(bytes).map_err(secp256k1_zkp::Error::Upstream)?, + )) + } + + /// Check if the object is null. + pub fn is_null(&self) -> bool { + match *self { + Nonce::Null => true, + _ => false + } + } + + /// Check if the object is explicit. + pub fn is_explicit(&self) -> bool { + match *self { + Nonce::Explicit(_) => true, + _ => false + } + } + + /// Check if the object is confidential. + pub fn is_confidential(&self) -> bool { + match *self { + Nonce::Confidential(_) => true, + _ => false + } + } + + /// Returns the explicit inner value. + /// Returns [None] if [is_explicit] returns false. + pub fn explicit(&self) -> Option<[u8; 32]> { + match *self { + Nonce::Explicit(i) => Some(i), + _ => None, + } + } + + /// Returns the confidential commitment in case of a confidential value. + /// Returns [None] if [is_confidential] returns false. + pub fn commitment(&self) -> Option { + match *self { + Nonce::Confidential(i) => Some(i), + _ => None, + } + } +} + +impl From for Nonce { + fn from(from: PublicKey) -> Self { + Nonce::Confidential(from) + } } impl fmt::Display for Nonce { @@ -324,29 +629,224 @@ impl fmt::Display for Nonce { write!(f, "{:02x}", b)?; } Ok(()) - }, - Nonce::Confidential(prefix, bytes) => { - write!(f, "{:02x}", prefix)?; - for b in bytes.iter() { - write!(f, "{:02x}", b)?; + } + Nonce::Confidential(pk) => write!(f, "{:02x}", pk), + } + } +} + +impl Default for Nonce { + fn default() -> Self { + Nonce::Null + } +} + +impl Encodable for Nonce { + fn consensus_encode(&self, mut s: S) -> Result { + match *self { + Nonce::Null => 0u8.consensus_encode(s), + Nonce::Explicit(n) => { + 1u8.consensus_encode(&mut s)?; + Ok(1 + n.consensus_encode(&mut s)?) + } + Nonce::Confidential(commitment) => commitment.consensus_encode(&mut s), + } + } +} + +impl Encodable for PublicKey { + fn consensus_encode(&self, mut e: W) -> Result { + e.write_all(&self.serialize())?; + Ok(33) + } +} + +impl Decodable for Nonce { + fn consensus_decode(mut d: D) -> Result { + let prefix = { + let buffer = d.fill_buf()?; + + if buffer.is_empty() { + return Err(encode::Error::UnexpectedEOF); + } + + buffer[0] + }; + + match prefix { + 0 => { + // consume null value prefix + d.consume(1); + Ok(Nonce::Null) + } + 1 => { + // ignore prefix when decoding an explicit asset + d.consume(1); + let explicit = Decodable::consensus_decode(&mut d)?; + Ok(Nonce::Explicit(explicit)) + } + p if p == 0x02 || p == 0x03 => { + let pk = Decodable::consensus_decode(&mut d)?; + Ok(Nonce::Confidential(pk)) + } + p => Err(encode::Error::InvalidConfidentialPrefix(p)), + } + } +} + +impl Decodable for PublicKey { + fn consensus_decode(d: D) -> Result { + let bytes = <[u8; 33]>::consensus_decode(d)?; + Ok(PublicKey::from_slice(&bytes)?) + } +} + +#[cfg(feature = "serde")] +impl Serialize for Nonce { + fn serialize(&self, s: S) -> Result { + use serde::ser::SerializeSeq; + + let seq_len = if *self == Nonce::Null { 1 } else { 2 }; + let mut seq = s.serialize_seq(Some(seq_len))?; + + match *self { + Nonce::Null => seq.serialize_element(&0u8)?, + Nonce::Explicit(n) => { + seq.serialize_element(&1u8)?; + seq.serialize_element(&n)?; + } + Nonce::Confidential(commitment) => { + seq.serialize_element(&commitment)?; + } + } + seq.end() + } +} + +#[cfg(feature = "serde")] +impl<'de> Deserialize<'de> for Nonce { + fn deserialize>(d: D) -> Result { + use serde::de::{Error, SeqAccess, Visitor}; + struct CommitVisitor; + + impl<'de> Visitor<'de> for CommitVisitor { + type Value = Nonce; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("a committed value") + } + + fn visit_seq>(self, mut access: A) -> Result { + let prefix: u8 = if let Some(x) = access.next_element()? { + x + } else { + return Err(A::Error::custom("missing prefix")); + }; + + match prefix { + 0 => Ok(Nonce::Null), + 1 => match access.next_element()? { + Some(x) => Ok(Nonce::Explicit(x)), + None => Err(A::Error::custom("missing commitment")), + }, + p if p == 0x02 || p == 0x03 => match access.next_element::<[u8; 32]>()? { + Some(y) => { + y.to_vec().insert(0, p); + Ok(Nonce::Confidential( + PublicKey::from_slice(y.as_ref()).map_err(A::Error::custom)?, + )) + } + None => Err(A::Error::custom("missing commitment")), + }, + p => Err(A::Error::custom(format!( + "invalid commitment, invalid prefix: 0x{:02x}", + p + ))), } - Ok(()) } } + + d.deserialize_seq(CommitVisitor) + } +} + +/// Blinding factor used for asset commitments. +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub struct AssetBlindingFactor(pub(crate) SecretKey); + +impl AssetBlindingFactor { + /// Generate random asset blinding factor. + pub fn random(rng: &mut R) -> Self { + AssetBlindingFactor(SecretKey::new(rng)) + } + + /// Create from bytes. + pub fn from_slice(bytes: &[u8]) -> Result { + Ok(AssetBlindingFactor(SecretKey::from_slice(bytes)?)) + } + + /// Returns the inner value. + pub fn into_inner(self) -> SecretKey { + self.0 + } +} + +/// Blinding factor used for value commitments. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct ValueBlindingFactor(pub(crate) SecretKey); + +impl ValueBlindingFactor { + /// Generate random value blinding factor. + pub fn random(rng: &mut R) -> Self { + ValueBlindingFactor(SecretKey::new(rng)) + } + + /// Create the value blinding factor of the last output of a transaction. + pub fn last( + secp: &Secp256k1, + value: u64, + abf: AssetBlindingFactor, + inputs: &[(u64, AssetBlindingFactor, ValueBlindingFactor)], + outputs: &[(u64, AssetBlindingFactor, ValueBlindingFactor)], + ) -> Self { + let set_a = inputs + .iter() + .map(|(value, abf, vbf)| CommitmentSecrets { + value: *value, + value_blinding_factor: vbf.0, + generator_blinding_factor: abf.into_inner(), + }) + .collect::>(); + let set_b = outputs + .iter() + .map(|(value, abf, vbf)| CommitmentSecrets { + value: *value, + value_blinding_factor: vbf.0, + generator_blinding_factor: abf.into_inner(), + }) + .collect::>(); + + ValueBlindingFactor(compute_adaptive_blinding_factor( + secp, value, abf.0, &set_a, &set_b, + )) } } #[cfg(test)] mod tests { - use bitcoin::hashes::sha256; use super::*; + use bitcoin::hashes::sha256; #[test] fn encode_length() { let vals = [ Value::Null, Value::Explicit(1000), - Value::Confidential(0x08, [1; 32]), + Value::from_commitment(&[ + 0x08, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + ]) + .unwrap(), ]; for v in &vals[..] { let mut x = vec![]; @@ -357,7 +857,11 @@ mod tests { let nonces = [ Nonce::Null, Nonce::Explicit([0; 32]), - Nonce::Confidential(0x02, [1; 32]), + Nonce::from_commitment(&[ + 0x02, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + ]) + .unwrap(), ]; for v in &nonces[..] { let mut x = vec![]; @@ -368,7 +872,11 @@ mod tests { let assets = [ Asset::Null, Asset::Explicit(AssetId::from_inner(sha256::Midstate::from_inner([0; 32]))), - Asset::Confidential(0x0a, [1; 32]), + Asset::from_commitment(&[ + 0x0a, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, + ]) + .unwrap(), ]; for v in &assets[..] { let mut x = vec![]; @@ -379,23 +887,37 @@ mod tests { #[test] fn commitments() { - let x = Value::Confidential(0x08, [1; 32]); - let mut commitment = x.commitment().unwrap(); + let x = Value::from_commitment(&[ + 0x08, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + ]) + .unwrap(); + let commitment = x.commitment().unwrap(); + let mut commitment = commitment.serialize(); assert_eq!(x, Value::from_commitment(&commitment[..]).unwrap()); commitment[0] = 42; assert!(Value::from_commitment(&commitment[..]).is_err()); - let x = Asset::Confidential(0x0a, [1; 32]); - let mut commitment = x.commitment().unwrap(); + let x = Asset::from_commitment(&[ + 0x0a, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + ]) + .unwrap(); + let commitment = x.commitment().unwrap(); + let mut commitment = commitment.serialize(); assert_eq!(x, Asset::from_commitment(&commitment[..]).unwrap()); commitment[0] = 42; assert!(Asset::from_commitment(&commitment[..]).is_err()); - let x = Nonce::Confidential(0x02, [1; 32]); - let mut commitment = x.commitment().unwrap(); + let x = Nonce::from_commitment(&[ + 0x02, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, + ]) + .unwrap(); + let commitment = x.commitment().unwrap(); + let mut commitment = commitment.serialize(); assert_eq!(x, Nonce::from_commitment(&commitment[..]).unwrap()); commitment[0] = 42; assert!(Nonce::from_commitment(&commitment[..]).is_err()); } } - diff --git a/src/dynafed.rs b/src/dynafed.rs index b446ee00..9ac6900d 100644 --- a/src/dynafed.rs +++ b/src/dynafed.rs @@ -149,7 +149,7 @@ impl Params { } match *self { - Params::Null => return sha256::Midstate::from_inner([0u8; 32]), + Params::Null => sha256::Midstate::from_inner([0u8; 32]), Params::Compact { ref elided_root, .. } => *elided_root, Params::Full { ref fedpeg_program, ref fedpegscript, ref extension_space, .. } => { let leaves = [ @@ -200,14 +200,14 @@ impl Params { Params::Null => None, Params::Compact { signblockscript, signblock_witness_limit, elided_root } => { Some(Params::Compact { - signblockscript: signblockscript, + signblockscript, signblock_witness_limit, - elided_root: elided_root, + elided_root, }) } Params::Full { signblockscript, signblock_witness_limit, ..} => { Some(Params::Compact { - signblockscript: signblockscript, + signblockscript, signblock_witness_limit, elided_root: extra_root.unwrap(), }) @@ -344,7 +344,7 @@ impl<'de> Deserialize<'de> for Params { } } - static FIELDS: &'static [&'static str] = &[ + static FIELDS: &[&str] = &[ "signblockscript", "signblock_witness_limit", "fedpeg_program", @@ -428,7 +428,7 @@ impl Encodable for Params { } impl Decodable for Params { - fn consensus_decode(mut d: D) -> Result { + fn consensus_decode(mut d: D) -> Result { let ser_type: u8 = Decodable::consensus_decode(&mut d)?; match ser_type { 0 => Ok(Params::Null), @@ -504,7 +504,7 @@ mod tests { ); let full_entry = Params::Full { - signblockscript: signblockscript, + signblockscript, signblock_witness_limit: signblock_wl, fedpeg_program: fp_program, fedpegscript: fp_script, diff --git a/src/encode.rs b/src/encode.rs index b64c605c..c86f209e 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -20,6 +20,8 @@ use std::{error, fmt, io, mem}; use bitcoin::consensus::encode as btcenc; use bitcoin::hashes::sha256; +use bitcoin::secp256k1; +use secp256k1_zkp; use transaction::{Transaction, TxIn, TxOut}; @@ -41,8 +43,14 @@ pub enum Error { }, /// Parsing error ParseFailed(&'static str), + /// We unexpectedly hit the end of the buffer + UnexpectedEOF, /// Invalid prefix for the confidential type. InvalidConfidentialPrefix(u8), + /// Parsing within libsecp256k1 failed + Secp256k1(secp256k1::Error), + /// Parsing within libsecp256k1-zkp failed + Secp256k1zkp(secp256k1_zkp::Error), } impl fmt::Display for Error { @@ -53,9 +61,18 @@ impl fmt::Display for Error { Error::OversizedVectorAllocation { requested: ref r, max: ref m, - } => write!(f, "oversized vector allocation: requested {}, maximum {}", r, m), + } => write!( + f, + "oversized vector allocation: requested {}, maximum {}", + r, m + ), Error::ParseFailed(ref e) => write!(f, "parse failed: {}", e), - Error::InvalidConfidentialPrefix(p) => write!(f, "invalid confidential prefix: 0x{:02x}", p), + Error::UnexpectedEOF => write!(f, "unexpected EOF"), + Error::InvalidConfidentialPrefix(p) => { + write!(f, "invalid confidential prefix: 0x{:02x}", p) + } + Error::Secp256k1(ref e) => write!(f, "{}", e), + Error::Secp256k1zkp(ref e) => write!(f, "{}", e), } } } @@ -64,6 +81,7 @@ impl error::Error for Error { fn cause(&self) -> Option<&dyn error::Error> { match *self { Error::Bitcoin(ref e) => Some(e), + Error::Secp256k1zkp(ref e) => Some(e), _ => None, } } @@ -83,6 +101,18 @@ impl From for Error { } } +impl From for Error { + fn from(e: secp256k1::Error) -> Self { + Error::Secp256k1(e) + } +} + +impl From for Error { + fn from(e: secp256k1_zkp::Error) -> Self { + Error::Secp256k1zkp(e) + } +} + /// Data which can be encoded in a consensus-consistent way pub trait Encodable { /// Encode an object with a well-defined format, should only ever error if @@ -94,7 +124,7 @@ pub trait Encodable { /// Data which can be encoded in a consensus-consistent way pub trait Decodable: Sized { /// Decode an object with a well-defined format - fn consensus_decode(d: D) -> Result; + fn consensus_decode(d: D) -> Result; } /// Encode an object into a vector @@ -111,7 +141,7 @@ pub fn serialize_hex(data: &T) -> String { /// Deserialize an object from a vector, will error if said deserialization /// doesn't consume the entire vector. -pub fn deserialize<'a, T: Decodable>(data: &'a [u8]) -> Result { +pub fn deserialize(data: &[u8]) -> Result { let (rv, consumed) = deserialize_partial(data)?; // Fail if data are not consumed entirely. @@ -124,7 +154,7 @@ pub fn deserialize<'a, T: Decodable>(data: &'a [u8]) -> Result { /// Deserialize an object from a vector, but will not report an error if said deserialization /// doesn't consume the entire vector. -pub fn deserialize_partial<'a, T: Decodable>(data: &'a [u8]) -> Result<(T, usize), Error> { +pub fn deserialize_partial(data: &[u8]) -> Result<(T, usize), Error> { let mut decoder = Cursor::new(data); let rv = Decodable::consensus_decode(&mut decoder)?; let consumed = decoder.position() as usize; @@ -139,7 +169,7 @@ impl Encodable for sha256::Midstate { } impl Decodable for sha256::Midstate { - fn consensus_decode(d: D) -> Result { + fn consensus_decode(d: D) -> Result { Ok(Self::from_inner(<[u8; 32]>::consensus_decode(d)?)) } } @@ -154,7 +184,7 @@ macro_rules! impl_upstream { } impl Decodable for $type { - fn consensus_decode(mut d: D) -> Result { + fn consensus_decode(mut d: D) -> Result { Ok(btcenc::Decodable::consensus_decode(&mut d)?) } } @@ -163,9 +193,10 @@ macro_rules! impl_upstream { impl_upstream!(u8); impl_upstream!(u32); impl_upstream!(u64); -impl_upstream!([u8;4]); +impl_upstream!([u8; 4]); impl_upstream!([u8; 32]); impl_upstream!(Box<[u8]>); +impl_upstream!([u8; 33]); impl_upstream!(Vec); impl_upstream!(Vec>); impl_upstream!(btcenc::VarInt); @@ -188,7 +219,7 @@ macro_rules! impl_vec { impl Decodable for Vec<$type> { #[inline] - fn consensus_decode(mut d: D) -> Result { + fn consensus_decode(mut d: D) -> Result { let len = btcenc::VarInt::consensus_decode(&mut d)?.0; let byte_size = (len as usize) .checked_mul(mem::size_of::<$type>()) diff --git a/src/hash_types.rs b/src/hash_types.rs index 34b13a62..88c8376b 100644 --- a/src/hash_types.rs +++ b/src/hash_types.rs @@ -16,7 +16,10 @@ //! to avoid mixing data of the same hash format (like SHA256d) but of different meaning //! (transaction id, block hash etc). -use bitcoin::hashes::{Hash, sha256, sha256d, hash160}; +use bitcoin::{ + hashes::{hash160, sha256, sha256d, Hash}, + secp256k1::ThirtyTwoByteHash, +}; macro_rules! impl_hashencode { ($hashtype:ident) => { @@ -27,7 +30,7 @@ macro_rules! impl_hashencode { } impl $crate::encode::Decodable for $hashtype { - fn consensus_decode(d: D) -> Result { + fn consensus_decode(d: D) -> Result { use $crate::bitcoin::hashes::Hash; Ok(Self::from_inner(<<$hashtype as $crate::bitcoin::hashes::Hash>::Inner>::consensus_decode(d)?)) } @@ -53,3 +56,8 @@ impl_hashencode!(SigHash); impl_hashencode!(BlockHash); impl_hashencode!(TxMerkleNode); +impl ThirtyTwoByteHash for SigHash { + fn into_32(self) -> [u8; 32] { + self.0.into_inner() + } +} diff --git a/src/internal_macros.rs b/src/internal_macros.rs index ecf9c878..5b5dda1e 100644 --- a/src/internal_macros.rs +++ b/src/internal_macros.rs @@ -25,7 +25,7 @@ macro_rules! impl_consensus_encoding { impl $crate::encode::Decodable for $thing { #[inline] - fn consensus_decode(mut d: D) -> Result<$thing, $crate::encode::Error> { + fn consensus_decode(mut d: D) -> Result<$thing, $crate::encode::Error> { Ok($thing { $( $field: $crate::encode::Decodable::consensus_decode(&mut d)?, )+ }) @@ -117,7 +117,7 @@ macro_rules! serde_struct_impl { )* let ret = $name { - $($fe: $fe),* + $($fe),* }; Ok(ret) @@ -262,7 +262,7 @@ macro_rules! serde_struct_human_string_impl { )* let ret = $name { - $($fe: $fe),* + $($fe),* }; Ok(ret) @@ -298,7 +298,7 @@ macro_rules! serde_struct_human_string_impl { )* let ret = $name { - $($fe: $fe),* + $($fe),* }; Ok(ret) @@ -388,4 +388,3 @@ macro_rules! hex_script( ::Script::from(v) }) ); - diff --git a/src/issuance.rs b/src/issuance.rs index 39461796..3abc056d 100644 --- a/src/issuance.rs +++ b/src/issuance.rs @@ -21,6 +21,7 @@ use bitcoin::hashes::{self, hex, sha256, sha256d, Hash}; use encode::{self, Encodable, Decodable}; use fast_merkle_root::fast_merkle_root; +use secp256k1_zkp::Tag; use transaction::OutPoint; /// The zero hash. @@ -117,6 +118,10 @@ impl AssetId { }; AssetId(fast_merkle_root(&[entropy.into_inner(), second])) } + + pub(crate) fn into_tag(self) -> Tag { + self.0.into_inner().into() + } } impl hex::FromHex for AssetId { @@ -147,10 +152,10 @@ impl ::std::fmt::LowerHex for AssetId { } impl FromStr for AssetId { - type Err = hex::Error; - fn from_str(s: &str) -> Result { - hex::FromHex::from_hex(s) - } + type Err = hex::Error; + fn from_str(s: &str) -> Result { + hex::FromHex::from_hex(s) + } } impl Encodable for AssetId { @@ -160,7 +165,7 @@ impl Encodable for AssetId { } impl Decodable for AssetId { - fn consensus_decode(d: D) -> Result { + fn consensus_decode(d: D) -> Result { Ok(Self::from_inner(sha256::Midstate::consensus_decode(d)?)) } } diff --git a/src/lib.rs b/src/lib.rs index 3839b808..87626c5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,7 @@ pub extern crate bitcoin; #[macro_use] extern crate bitcoin_hashes as just_imported_for_the_macros; extern crate slip21; +extern crate secp256k1_zkp; #[cfg(feature = "serde")] extern crate serde; #[cfg(test)] extern crate rand; @@ -56,7 +57,7 @@ mod endian; pub use bitcoin::{bech32, hashes, secp256k1}; // export everything at the top level so it can be used as `elements::Transaction` etc. pub use address::{Address, AddressParams, AddressError}; -pub use transaction::{OutPoint, PeginData, PegoutData, SigHashType, TxIn, TxOut, TxInWitness, TxOutWitness, Transaction, AssetIssuance}; +pub use transaction::{OutPoint, PeginData, PegoutData, SigHashType, TxIn, TxOut, TxInWitness, TxOutWitness, Transaction, AssetIssuance, ConfidentialTxOut, ExplicitTxOut, UnblindedTxOut, TxOutError}; pub use block::{BlockHeader, Block}; pub use block::ExtData as BlockExtData; pub use ::bitcoin::consensus::encode::VarInt; @@ -64,4 +65,3 @@ pub use fast_merkle_root::fast_merkle_root; pub use hash_types::*; pub use issuance::{AssetId, ContractHash}; pub use script::Script; - diff --git a/src/script.rs b/src/script.rs index 8e4ddeb0..c6395ee4 100644 --- a/src/script.rs +++ b/src/script.rs @@ -252,7 +252,7 @@ impl Script { let mut verop = ver.to_u8(); assert!(verop <= 16, "incorrect witness version provided: {}", verop); if verop > 0 { - verop = 0x50 + verop; + verop += 0x50; } Builder::new() .push_opcode(verop.into()) @@ -549,11 +549,11 @@ impl<'a> Iterator for Instructions<'a> { self.data = &[]; // Kill iterator so that it does not return an infinite stream of errors return Some(Err(Error::EarlyEndOfScript)); } - if self.enforce_minimal { - if n == 1 && (self.data[1] == 0x81 || (self.data[1] > 0 && self.data[1] <= 16)) { + if self.enforce_minimal + && n == 1 + && (self.data[1] == 0x81 || (self.data[1] > 0 && self.data[1] <= 16)) { self.data = &[]; return Some(Err(Error::NonMinimalPush)); - } } let ret = Some(Ok(Instruction::PushBytes(&self.data[1..n+1]))); self.data = &self.data[n + 1..]; @@ -843,7 +843,7 @@ impl Encodable for Script { impl Decodable for Script { #[inline] - fn consensus_decode(d: D) -> Result { + fn consensus_decode(d: D) -> Result { Ok(Script(Decodable::consensus_decode(d)?)) } } @@ -881,7 +881,7 @@ mod test { script = script.push_int(-10000000); comp.extend([4u8, 128, 150, 152, 128].iter().cloned()); assert_eq!(&script[..], &comp[..]); // data - script = script.push_slice("NRA4VR".as_bytes()); comp.extend([6u8, 78, 82, 65, 52, 86, 82].iter().cloned()); assert_eq!(&script[..], &comp[..]); + script = script.push_slice(b"NRA4VR"); comp.extend([6u8, 78, 82, 65, 52, 86, 82].iter().cloned()); assert_eq!(&script[..], &comp[..]); // keys let keystr = "21032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af"; @@ -1147,7 +1147,7 @@ mod test { assert_eq!(v_nonmin_alt.unwrap(), slop_v_nonmin_alt.unwrap()); } - #[test] + #[test] fn script_ord() { let script_1 = Builder::new().push_slice(&[1,2,3,4]).into_script(); let script_2 = Builder::new().push_int(10).into_script(); @@ -1158,12 +1158,10 @@ mod test { assert!(script_2 < script_3); assert!(script_3 < script_4); - assert!(script_1 <= script_1); - assert!(script_1 >= script_1); + assert_eq!(script_1, script_1); assert!(script_4 > script_3); assert!(script_3 > script_2); assert!(script_2 > script_1); } } - diff --git a/src/sighash.rs b/src/sighash.rs index e272e1cf..fce56e8f 100644 --- a/src/sighash.rs +++ b/src/sighash.rs @@ -50,7 +50,7 @@ impl> SigHashCache { /// script_sig and witnesses. pub fn new(tx: R) -> Self { SigHashCache { - tx: tx, + tx, hash_prevouts: None, hash_sequence: None, hash_outputs: None, @@ -391,4 +391,4 @@ mod tests{ // Test a issuance test with only sighash all test_legacy_sighash("010000000001715df5ccebaf02ff18d6fae7263fa69fed5de59c900f4749556eba41bc7bf2af000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000003e801000000000000000a0201230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000124101100001f5175517551755175517551755175517551755175517551755175517551755101230f4f5d4b7c6fa845806ee4f67713459e1b69e8e60fcee2e4940c7a0d5de1b2010000000005f5e100000000000000", "76a914f54a5851e9372b87810a8e60cdd2e7cfd80b6e3188ac", 0, SigHashType::All, "9f00e1758a230aaf6c9bce777701a604f50b2ac5f2a07e1cd478d8a0e70fc195"); } -} \ No newline at end of file +} diff --git a/src/slip77.rs b/src/slip77.rs index 44ec3998..ccd5f1d7 100644 --- a/src/slip77.rs +++ b/src/slip77.rs @@ -9,7 +9,7 @@ use slip21; use Script; -const SLIP77_DERIVATION: &'static str = "SLIP-0077"; +const SLIP77_DERIVATION: &str = "SLIP-0077"; /// A SLIP-77 master blinding key used to derive shared blinding keys. #[derive(Clone, Debug, PartialEq, Eq)] @@ -69,7 +69,7 @@ mod tests { assert_eq!(master.0, privkey); let scriptpk_hex = "a914afa92d77cd3541b443771649572db096cf49bf8c87"; - let scriptpk: Script = Vec::::from_hex(&scriptpk_hex).unwrap().clone().into(); + let scriptpk: Script = Vec::::from_hex(&scriptpk_hex).unwrap().into(); let blindingkey_hex = "02b067c374bb56c54c016fae29218c000ada60f81ef45b4aeebbeb24931bb8bc"; let blindingkey = SecretKey::from_slice(&Vec::::from_hex(blindingkey_hex).unwrap()).unwrap(); diff --git a/src/transaction.rs b/src/transaction.rs index 6f157ebe..8b10d4d4 100644 --- a/src/transaction.rs +++ b/src/transaction.rs @@ -15,21 +15,28 @@ //! # Transactions //! -use std::{io, fmt}; use std::collections::HashMap; +use std::{self, fmt, io}; +use bitcoin::hashes::{self, Hash}; use bitcoin::{self, VarInt}; -use bitcoin::hashes::Hash; -use confidential; -use encode::{self, Encodable, Decodable}; +use address::Address; +use confidential::{self, Asset, AssetBlindingFactor, Nonce, Value, ValueBlindingFactor}; +use encode::{self, Decodable, Encodable}; use issuance::AssetId; use opcodes; use script::Instruction; +use secp256k1_zkp::{ + self, + rand::{CryptoRng, RngCore}, + Generator, PedersenCommitment, RangeProof, Secp256k1, SecretKey, Signing, SurjectionProof, + Verification, +}; use {Script, Txid, Wtxid}; /// Description of an asset issuance in a transaction input -#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] +#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq)] pub struct AssetIssuance { /// Zero for a new asset issuance; otherwise a blinding factor for the input pub asset_blinding_nonce: [u8; 32], @@ -40,8 +47,20 @@ pub struct AssetIssuance { /// Amount of inflation keys to issue pub inflation_keys: confidential::Value, } -serde_struct_impl!(AssetIssuance, asset_blinding_nonce, asset_entropy, amount, inflation_keys); -impl_consensus_encoding!(AssetIssuance, asset_blinding_nonce, asset_entropy, amount, inflation_keys); +serde_struct_impl!( + AssetIssuance, + asset_blinding_nonce, + asset_entropy, + amount, + inflation_keys +); +impl_consensus_encoding!( + AssetIssuance, + asset_blinding_nonce, + asset_entropy, + amount, + inflation_keys +); /// A reference to a transaction output #[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] @@ -56,10 +75,7 @@ serde_struct_human_string_impl!(OutPoint, "an Elements OutPoint", txid, vout); impl OutPoint { /// Create a new outpoint. pub fn new(txid: Txid, vout: u32) -> OutPoint { - OutPoint { - txid: txid, - vout: vout, - } + OutPoint { txid, vout } } } @@ -75,19 +91,15 @@ impl Default for OutPoint { impl Encodable for OutPoint { fn consensus_encode(&self, mut s: S) -> Result { - Ok(self.txid.consensus_encode(&mut s)? + - self.vout.consensus_encode(&mut s)?) + Ok(self.txid.consensus_encode(&mut s)? + self.vout.consensus_encode(&mut s)?) } } impl Decodable for OutPoint { - fn consensus_decode(mut d: D) -> Result { + fn consensus_decode(mut d: D) -> Result { let txid = Txid::consensus_decode(&mut d)?; let vout = u32::consensus_decode(&mut d)?; - Ok(OutPoint { - txid: txid, - vout: vout, - }) + Ok(OutPoint { txid, vout }) } } @@ -124,20 +136,31 @@ pub struct TxInWitness { /// Pegin witness, basically the same thing pub pegin_witness: Vec>, } -serde_struct_impl!(TxInWitness, amount_rangeproof, inflation_keys_rangeproof, script_witness, pegin_witness); -impl_consensus_encoding!(TxInWitness, amount_rangeproof, inflation_keys_rangeproof, script_witness, pegin_witness); +serde_struct_impl!( + TxInWitness, + amount_rangeproof, + inflation_keys_rangeproof, + script_witness, + pegin_witness +); +impl_consensus_encoding!( + TxInWitness, + amount_rangeproof, + inflation_keys_rangeproof, + script_witness, + pegin_witness +); impl TxInWitness { /// Whether this witness is null pub fn is_empty(&self) -> bool { - self.amount_rangeproof.is_empty() && - self.inflation_keys_rangeproof.is_empty() && - self.script_witness.is_empty() && - self.pegin_witness.is_empty() + self.amount_rangeproof.is_empty() + && self.inflation_keys_rangeproof.is_empty() + && self.script_witness.is_empty() + && self.pegin_witness.is_empty() } } - /// Parsed data from a transaction input's pegin witness #[derive(Copy, Clone, Default, PartialEq, Eq, Debug, Hash)] pub struct PeginData<'tx> { @@ -189,7 +212,16 @@ pub struct TxIn { /// part of the txin. pub witness: TxInWitness, } -serde_struct_impl!(TxIn, previous_output, is_pegin, has_issuance, script_sig, sequence, asset_issuance, witness); +serde_struct_impl!( + TxIn, + previous_output, + is_pegin, + has_issuance, + script_sig, + sequence, + asset_issuance, + witness +); impl Encodable for TxIn { fn consensus_encode(&self, mut s: S) -> Result { @@ -213,7 +245,7 @@ impl Encodable for TxIn { } impl Decodable for TxIn { - fn consensus_decode(mut d: D) -> Result { + fn consensus_decode(mut d: D) -> Result { let mut outp = OutPoint::consensus_decode(&mut d)?; let script_sig = Script::consensus_decode(&mut d)?; let sequence = u32::consensus_decode(&mut d)?; @@ -237,17 +269,16 @@ impl Decodable for TxIn { } Ok(TxIn { previous_output: outp, - is_pegin: is_pegin, - has_issuance: has_issuance, - script_sig: script_sig, - sequence: sequence, + is_pegin, + has_issuance, + script_sig, + sequence, asset_issuance: issuance, witness: TxInWitness::default(), }) } } - impl TxIn { /// Whether the input is a coinbase pub fn is_coinbase(&self) -> bool { @@ -264,11 +295,11 @@ impl TxIn { /// and `pegin_data()` returning `None` indicates an invalid transaction. pub fn pegin_data(&self) -> Option { if !self.is_pegin { - return None + return None; } if self.witness.pegin_witness.len() != 6 { - return None + return None; } macro_rules! opt_try( @@ -281,17 +312,19 @@ impl TxIn { txid: bitcoin::Txid::from(self.previous_output.txid.as_hash()), vout: self.previous_output.vout, }, - value: opt_try!(bitcoin::consensus::deserialize(&self.witness.pegin_witness[0])), - asset: confidential::Asset::Explicit( - opt_try!(encode::deserialize(&self.witness.pegin_witness[1])), - ), - genesis_hash: opt_try!(bitcoin::consensus::deserialize(&self.witness.pegin_witness[2])), + value: opt_try!(bitcoin::consensus::deserialize( + &self.witness.pegin_witness[0] + )), + asset: confidential::Asset::Explicit(opt_try!(encode::deserialize( + &self.witness.pegin_witness[1] + ))), + genesis_hash: opt_try!(bitcoin::consensus::deserialize( + &self.witness.pegin_witness[2] + )), claim_script: &self.witness.pegin_witness[3], tx: &self.witness.pegin_witness[4], merkle_proof: &self.witness.pegin_witness[5], - referenced_block: bitcoin::BlockHash::hash( - &self.witness.pegin_witness[5][0..80], - ), + referenced_block: bitcoin::BlockHash::hash(&self.witness.pegin_witness[5][0..80]), }) } @@ -354,15 +387,15 @@ serde_struct_impl!(TxOut, asset, value, nonce, script_pubkey, witness); impl Encodable for TxOut { fn consensus_encode(&self, mut s: S) -> Result { - Ok(self.asset.consensus_encode(&mut s)? + - self.value.consensus_encode(&mut s)? + - self.nonce.consensus_encode(&mut s)? + - self.script_pubkey.consensus_encode(&mut s)?) + Ok(self.asset.consensus_encode(&mut s)? + + self.value.consensus_encode(&mut s)? + + self.nonce.consensus_encode(&mut s)? + + self.script_pubkey.consensus_encode(&mut s)?) } } impl Decodable for TxOut { - fn consensus_decode(mut d: D) -> Result { + fn consensus_decode(mut d: D) -> Result { Ok(TxOut { asset: Decodable::consensus_decode(&mut d)?, value: Decodable::consensus_decode(&mut d)?, @@ -374,9 +407,159 @@ impl Decodable for TxOut { } impl TxOut { + /// Creates a new confidential output that is **not** the last one in the transaction. + pub fn new_not_last_confidential( + rng: &mut R, + secp: &Secp256k1, + value: u64, + address: Address, + asset: AssetId, + inputs: &[( + AssetId, + u64, + Generator, + AssetBlindingFactor, + ValueBlindingFactor, + )], + ) -> Result<(Self, AssetBlindingFactor, ValueBlindingFactor), TxOutError> + where + R: RngCore + CryptoRng, + C: Signing, + { + let out_abf = AssetBlindingFactor::random(rng); + let out_asset = Asset::new_confidential(secp, asset, out_abf); + + let out_asset_commitment = out_asset.commitment().expect("confidential asset"); + let out_vbf = ValueBlindingFactor::random(rng); + let value_commitment = Value::new_confidential(secp, value, out_asset_commitment, out_vbf); + + let receiver_blinding_pk = &address + .blinding_pubkey + .ok_or(TxOutError::NoBlindingKeyInAddress)?; + let (nonce, shared_secret) = Nonce::new_confidential(rng, secp, receiver_blinding_pk); + + let message = RangeProofMessage { asset, bf: out_abf }; + let rangeproof = RangeProof::new( + secp, + 1, + value_commitment.commitment().expect("confidential value"), + value, + out_vbf.0, + &message.to_bytes(), + address.script_pubkey().as_bytes(), + shared_secret, + 0, + 52, + out_asset_commitment, + )?; + + let inputs = inputs + .iter() + .map(|(id, _, asset, abf, _)| (*asset, id.into_tag(), abf.0)) + .collect::>(); + + let surjection_proof = SurjectionProof::new( + secp, + rng, + asset.into_tag(), + out_abf.into_inner(), + inputs.as_ref(), + )?; + + let txout = TxOut { + asset: out_asset, + value: value_commitment, + nonce, + script_pubkey: address.script_pubkey(), + witness: TxOutWitness { + surjection_proof: surjection_proof.serialize(), + rangeproof: rangeproof.serialize(), + }, + }; + + Ok((txout, out_abf, out_vbf)) + } + + /// Creates a new confidential output that IS the last one in the transaction. + pub fn new_last_confidential( + rng: &mut R, + secp: &Secp256k1, + value: u64, + address: Address, + asset: AssetId, + inputs: &[( + AssetId, + u64, + Generator, + AssetBlindingFactor, + ValueBlindingFactor, + )], + outputs: &[(u64, AssetBlindingFactor, ValueBlindingFactor)], + ) -> Result + where + R: RngCore + CryptoRng, + C: Signing, + { + let (surjection_proof_inputs, value_blind_inputs) = inputs + .iter() + .map(|(id, value, asset, abf, vbf)| { + ((*asset, id.into_tag(), abf.0), (*value, *abf, *vbf)) + }) + .unzip::<_, _, Vec<_>, Vec<_>>(); + + let out_abf = AssetBlindingFactor::random(rng); + let out_asset = Asset::new_confidential(secp, asset, out_abf); + + let out_asset_commitment = out_asset.commitment().expect("confidential asset"); + let out_vbf = + ValueBlindingFactor::last(secp, value, out_abf, &value_blind_inputs, &outputs); + let value_commitment = Value::new_confidential(secp, value, out_asset_commitment, out_vbf); + + let receiver_blinding_pk = &address + .blinding_pubkey + .ok_or(TxOutError::NoBlindingKeyInAddress)?; + let (nonce, shared_secret) = Nonce::new_confidential(rng, secp, receiver_blinding_pk); + + let message = RangeProofMessage { asset, bf: out_abf }; + let rangeproof = RangeProof::new( + secp, + 1, + value_commitment.commitment().expect("confidential value"), + value, + out_vbf.0, + &message.to_bytes(), + address.script_pubkey().as_bytes(), + shared_secret, + 0, + 52, + out_asset_commitment, + )?; + + let surjection_proof = SurjectionProof::new( + secp, + rng, + asset.into_tag(), + out_abf.into_inner(), + surjection_proof_inputs.as_ref(), + )?; + + let txout = TxOut { + asset: out_asset, + value: value_commitment, + nonce, + script_pubkey: address.script_pubkey(), + witness: TxOutWitness { + surjection_proof: surjection_proof.serialize(), + rangeproof: rangeproof.serialize(), + }, + }; + + Ok(txout) + } + /// Create a new fee output. pub fn new_fee(amount: u64, asset: AssetId) -> TxOut { - TxOut{ + TxOut { asset: confidential::Asset::Explicit(asset), value: confidential::Value::Explicit(amount), nonce: confidential::Nonce::Null, @@ -385,6 +568,48 @@ impl TxOut { } } + /// Convert into an explicit output if possible. + pub fn into_explicit(self) -> Option { + Some(ExplicitTxOut { + asset: self.asset.explicit()?, + value: self.value.explicit()?, + script_pubkey: self.script_pubkey, + nonce: self.nonce, + }) + } + + /// Convert into an explicit output if possible. + pub fn to_explicit(&self) -> Option { + Some(ExplicitTxOut { + asset: self.asset.explicit()?, + value: self.value.explicit()?, + script_pubkey: self.script_pubkey.clone(), + nonce: self.nonce, + }) + } + + /// Convert into a confidential output if possible. + pub fn into_confidential(self) -> Option { + Some(ConfidentialTxOut { + asset: self.asset.commitment()?, + value: self.value.commitment()?, + nonce: self.nonce, + script_pubkey: self.script_pubkey, + witness: self.witness, + }) + } + + /// Convert into a confidential output if possible. + pub fn to_confidential(&self) -> Option { + Some(ConfidentialTxOut { + asset: self.asset.commitment()?, + value: self.value.commitment()?, + nonce: self.nonce, + script_pubkey: self.script_pubkey.clone(), + witness: self.witness.clone(), + }) + } + /// Whether this data represents nulldata (OP_RETURN followed by pushes, /// not necessarily minimal) pub fn is_null_data(&self) -> bool { @@ -392,7 +617,11 @@ impl TxOut { if iter.next() == Some(Ok(Instruction::Op(opcodes::all::OP_RETURN))) { for push in iter { match push { - Ok(Instruction::Op(op)) if op.into_u8() > opcodes::all::OP_PUSHNUM_16.into_u8() => return false, + Ok(Instruction::Op(op)) + if op.into_u8() > opcodes::all::OP_PUSHNUM_16.into_u8() => + { + return false + } Err(_) => return false, _ => {} } @@ -455,11 +684,13 @@ impl TxOut { // Return everything let mut found_non_data_push = false; let remainder = iter - .filter_map(|x| if let Ok(Instruction::PushBytes(data)) = x { - Some(data) - } else { - found_non_data_push = true; - None + .filter_map(|x| { + if let Ok(Instruction::PushBytes(data)) = x { + Some(data) + } else { + found_non_data_push = true; + None + } }) .collect(); @@ -467,10 +698,10 @@ impl TxOut { None } else { Some(PegoutData { + value, asset: self.asset, - value: value, - genesis_hash: genesis_hash, - script_pubkey: script_pubkey, + genesis_hash, + script_pubkey, extra_data: remainder, }) } @@ -483,7 +714,11 @@ impl TxOut { /// Extracts the minimum value from the rangeproof, if there is one, or returns 0. pub fn minimum_value(&self) -> u64 { - let min_value = if self.script_pubkey.is_op_return() { 0 } else { 1 }; + let min_value = if self.script_pubkey.is_op_return() { + 0 + } else { + 1 + }; match self.value { confidential::Value::Null => min_value, @@ -502,11 +737,11 @@ impl TxOut { } else if has_nonzero_range { bitcoin::consensus::deserialize::(&self.witness.rangeproof[2..10]) .expect("any 8 bytes is a u64") - .swap_bytes() // min-value is BE + .swap_bytes() // min-value is BE } else { bitcoin::consensus::deserialize::(&self.witness.rangeproof[1..9]) .expect("any 8 bytes is a u64") - .swap_bytes() // min-value is BE + .swap_bytes() // min-value is BE } } } @@ -514,6 +749,191 @@ impl TxOut { } } +/// Errors encountered when constructing confidential transaction outputs. +#[derive(Debug)] +pub enum TxOutError { + /// Address without blinding key. + NoBlindingKeyInAddress, + /// Error originated in `secp256k1_zkp`. + Upstream(secp256k1_zkp::Error), +} + +impl fmt::Display for TxOutError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self { + TxOutError::NoBlindingKeyInAddress => { + write!(f, "address does not include a blinding key") + } + TxOutError::Upstream(e) => write!(f, "{}", e), + } + } +} + +impl std::error::Error for TxOutError { + fn cause(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + TxOutError::NoBlindingKeyInAddress => None, + TxOutError::Upstream(e) => Some(e), + } + } +} + +impl From for TxOutError { + fn from(from: secp256k1_zkp::Error) -> Self { + TxOutError::Upstream(from) + } +} + +/// Explicit transaction output +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub struct ExplicitTxOut { + /// Committed asset + pub asset: AssetId, + /// Committed amount + pub value: u64, + /// Scriptpubkey + pub script_pubkey: Script, + /// There should be no such thing as a nonce for an explicit + /// output, but Elements will deserialize such a thing and even + /// produce it. + pub nonce: Nonce, +} +serde_struct_impl!(ExplicitTxOut, asset, value, script_pubkey, nonce); + +/// Confidential transaction output +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub struct ConfidentialTxOut { + /// Committed asset + pub asset: Generator, + /// Committed amount + pub value: PedersenCommitment, + /// Nonce (ECDH key passed to recipient) + pub nonce: Nonce, + /// Scriptpubkey + pub script_pubkey: Script, + /// Witness data - not deserialized/serialized as part of a `TxIn` object + /// (rather as part of its containing transaction, if any) but is logically + /// part of the txin. + pub witness: TxOutWitness, +} +serde_struct_impl!( + ConfidentialTxOut, + asset, + value, + nonce, + script_pubkey, + witness +); + +impl ConfidentialTxOut { + /// Unblind a confidential transaction output. + pub fn unblind( + &self, + secp: &Secp256k1, + blinding_key: SecretKey, + ) -> Result { + let shared_secret = self + .nonce + .shared_secret(&blinding_key) + .ok_or(UnblindError::MissingNonce)?; + + let rangeproof = RangeProof::from_slice(&self.witness.rangeproof)?; + + let (opening, _) = rangeproof.rewind( + secp, + self.value, + shared_secret, + self.script_pubkey.as_bytes(), + self.asset, + )?; + + let (asset, asset_blinding_factor) = opening.message.as_ref().split_at(32); + let asset = AssetId::from_slice(asset)?; + let asset_blinding_factor = AssetBlindingFactor::from_slice(&asset_blinding_factor[..32])?; + + let value_blinding_factor = ValueBlindingFactor(opening.blinding_factor); + + Ok(UnblindedTxOut { + asset, + value: opening.value, + asset_blinding_factor, + value_blinding_factor, + }) + } +} + +/// Errors encountered when unblinding `ConfidentialTxOut`s. +#[derive(Debug)] +pub enum UnblindError { + /// Transaction output does not have a nonce commitment. + MissingNonce, + /// Malformed asset ID. + MalformedAssetId(hashes::Error), + /// Error originated in `secp256k1_zkp`. + Upstream(secp256k1_zkp::Error), +} + +impl fmt::Display for UnblindError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self { + UnblindError::MissingNonce => write!(f, "missing nonce in confidential txout"), + UnblindError::MalformedAssetId(_) => write!(f, "malformed asset id"), + UnblindError::Upstream(e) => write!(f, "{}", e), + } + } +} + +impl std::error::Error for UnblindError { + fn cause(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + UnblindError::MissingNonce => None, + UnblindError::MalformedAssetId(e) => Some(e), + UnblindError::Upstream(e) => Some(e), + } + } +} + +impl From for UnblindError { + fn from(from: secp256k1_zkp::Error) -> Self { + UnblindError::Upstream(from) + } +} + +impl From for UnblindError { + fn from(from: hashes::Error) -> Self { + UnblindError::MalformedAssetId(from) + } +} + +/// Result of unblinding a `ConfidentialTxOut` +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct UnblindedTxOut { + /// Committed asset + pub asset: AssetId, + /// Committed value + pub value: u64, + /// Asset blinding factor + pub asset_blinding_factor: AssetBlindingFactor, + /// Value blinding factor + pub value_blinding_factor: ValueBlindingFactor, +} + +struct RangeProofMessage { + asset: AssetId, + bf: AssetBlindingFactor, +} + +impl RangeProofMessage { + fn to_bytes(&self) -> [u8; 64] { + let mut message = [0u8; 64]; + + message[..32].copy_from_slice(self.asset.into_tag().as_ref()); + message[32..].copy_from_slice(self.bf.into_inner().as_ref()); + + message + } +} + /// Elements transaction #[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub struct Transaction { @@ -536,8 +956,8 @@ impl Transaction { /// Determines whether a transaction has any non-null witnesses pub fn has_witness(&self) -> bool { - self.input.iter().any(|i| !i.witness.is_empty()) || - self.output.iter().any(|o| !o.witness.is_empty()) + self.input.iter().any(|i| !i.witness.is_empty()) + || self.output.iter().any(|o| !o.witness.is_empty()) } /// Get the "weight" of this transaction; roughly equivalent to BIP141, in that witness data is @@ -554,9 +974,12 @@ impl Transaction { fn get_scaled_size(&self, scale_factor: usize) -> usize { let witness_flag = self.has_witness(); - let input_weight = self.input.iter().map(|input| { - scale_factor * ( - 32 + 4 + 4 + // output + nSequence + let input_weight = self + .input + .iter() + .map(|input| { + scale_factor + * (32 + 4 + 4 + // output + nSequence VarInt(input.script_sig.len() as u64).len() as usize + input.script_sig.len() + if input.has_issuance() { 64 + @@ -564,51 +987,64 @@ impl Transaction { input.asset_issuance.inflation_keys.encoded_length() } else { 0 + }) + if witness_flag { + VarInt(input.witness.amount_rangeproof.len() as u64).len() as usize + + input.witness.amount_rangeproof.len() + + VarInt(input.witness.inflation_keys_rangeproof.len() as u64).len() + as usize + + input.witness.inflation_keys_rangeproof.len() + + VarInt(input.witness.script_witness.len() as u64).len() as usize + + input + .witness + .script_witness + .iter() + .map(|wit| VarInt(wit.len() as u64).len() as usize + wit.len()) + .sum::() + + VarInt(input.witness.pegin_witness.len() as u64).len() as usize + + input + .witness + .pegin_witness + .iter() + .map(|wit| VarInt(wit.len() as u64).len() as usize + wit.len()) + .sum::() + } else { + 0 } - ) + if witness_flag { - VarInt(input.witness.amount_rangeproof.len() as u64).len() as usize + - input.witness.amount_rangeproof.len() + - VarInt(input.witness.inflation_keys_rangeproof.len() as u64).len() as usize + - input.witness.inflation_keys_rangeproof.len() + - VarInt(input.witness.script_witness.len() as u64).len() as usize + - input.witness.script_witness.iter().map(|wit| - VarInt(wit.len() as u64).len() as usize + - wit.len() - ).sum::() + - VarInt(input.witness.pegin_witness.len() as u64).len() as usize + - input.witness.pegin_witness.iter().map(|wit| - VarInt(wit.len() as u64).len() as usize + - wit.len() - ).sum::() - } else { - 0 - } - }).sum::(); - - let output_weight = self.output.iter().map(|output| { - scale_factor * ( - output.asset.encoded_length() + - output.value.encoded_length() + - output.nonce.encoded_length() + - VarInt(output.script_pubkey.len() as u64).len() as usize + - output.script_pubkey.len() - ) + if witness_flag { - VarInt(output.witness.surjection_proof.len() as u64).len() as usize + - output.witness.surjection_proof.len() + - VarInt(output.witness.rangeproof.len() as u64).len() as usize + - output.witness.rangeproof.len() - } else { - 0 - } - }).sum::(); + }) + .sum::(); + + let output_weight = self + .output + .iter() + .map(|output| { + scale_factor + * (output.asset.encoded_length() + + output.value.encoded_length() + + output.nonce.encoded_length() + + VarInt(output.script_pubkey.len() as u64).len() as usize + + output.script_pubkey.len()) + + if witness_flag { + VarInt(output.witness.surjection_proof.len() as u64).len() as usize + + output.witness.surjection_proof.len() + + VarInt(output.witness.rangeproof.len() as u64).len() as usize + + output.witness.rangeproof.len() + } else { + 0 + } + }) + .sum::(); - scale_factor * ( - 4 + // version + scale_factor + * ( + 4 + // version 4 + // locktime VarInt(self.input.len() as u64).len() as usize + VarInt(self.output.len() as u64).len() as usize + - 1 // segwit flag byte (note this is *not* witness data in Elements) - ) + input_weight + output_weight + 1 + // segwit flag byte (note this is *not* witness data in Elements) + ) + + input_weight + + output_weight } /// The txid of the transaction. @@ -632,7 +1068,8 @@ impl Transaction { /// Get the total transaction fee in the given asset. pub fn fee_in(&self, asset: AssetId) -> u64 { // is_fee checks for explicit asset and value, so we can unwrap them here. - self.output.iter() + self.output + .iter() .filter(|o| o.is_fee() && o.asset.explicit().expect("is_fee") == asset) .map(|o| o.value.explicit().expect("is_fee")) .sum() @@ -679,7 +1116,7 @@ impl Encodable for Transaction { } impl Decodable for Transaction { - fn consensus_decode(mut d: D) -> Result { + fn consensus_decode(mut d: D) -> Result { let version = u32::consensus_decode(&mut d)?; let wit_flag = u8::consensus_decode(&mut d)?; let mut input = Vec::::consensus_decode(&mut d)?; @@ -688,10 +1125,10 @@ impl Decodable for Transaction { match wit_flag { 0 => Ok(Transaction { - version: version, - input: input, - output: output, - lock_time: lock_time, + version, + lock_time, + input, + output, }), 1 => { for i in &mut input { @@ -700,15 +1137,18 @@ impl Decodable for Transaction { for o in &mut output { o.witness = Decodable::consensus_decode(&mut d)?; } - if input.iter().all(|input| input.witness.is_empty()) && - output.iter().all(|output| output.witness.is_empty()) { - Err(encode::Error::ParseFailed("witness flag set but no witnesses were given")) + if input.iter().all(|input| input.witness.is_empty()) + && output.iter().all(|output| output.witness.is_empty()) + { + Err(encode::Error::ParseFailed( + "witness flag set but no witnesses were given", + )) } else { Ok(Transaction { - version: version, - input: input, - output: output, - lock_time: lock_time, + version, + lock_time, + input, + output, }) } } @@ -774,11 +1214,13 @@ impl SigHashType { #[cfg(test)] mod tests { - use bitcoin; - use bitcoin::hashes::hex::FromHex; - - use encode::serialize; + use address::AddressParams; + use bitcoin::{self, hashes::hex::FromHex, Network, PrivateKey, PublicKey}; use confidential; + use encode::serialize; + use rand::thread_rng; + use secp256k1_zkp::SECP256K1; + use super::*; #[test] @@ -795,8 +1237,12 @@ mod tests { #[test] fn test_fees() { - let asset1: AssetId = "0000000000000000000000000000000000000000000000000000000000000011".parse().unwrap(); - let asset2: AssetId = "0000000000000000000000000000000000000000000000000000000000000022".parse().unwrap(); + let asset1: AssetId = "0000000000000000000000000000000000000000000000000000000000000011" + .parse() + .unwrap(); + let asset2: AssetId = "0000000000000000000000000000000000000000000000000000000000000022" + .parse() + .unwrap(); let fee1 = TxOut::new_fee(42, asset1); assert!(fee1.is_fee()); @@ -844,11 +1290,16 @@ mod tests { assert_eq!(tx.get_weight(), tx.get_size() * 4); assert_eq!(tx.output[0].is_fee(), false); assert_eq!(tx.output[1].is_fee(), true); - assert_eq!(tx.output[0].value, confidential::Value::Explicit(9999996700)); - assert_eq!(tx.output[1].value, confidential::Value::Explicit( 3300)); + assert_eq!( + tx.output[0].value, + confidential::Value::Explicit(9999996700) + ); + assert_eq!(tx.output[1].value, confidential::Value::Explicit(3300)); assert_eq!(tx.output[0].minimum_value(), 9999996700); - assert_eq!(tx.output[1].minimum_value(), 3300); - let fee_asset = "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23".parse().unwrap(); + assert_eq!(tx.output[1].minimum_value(), 3300); + let fee_asset = "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23" + .parse() + .unwrap(); assert_eq!(tx.fee_in(fee_asset), 3300); assert_eq!(tx.all_fees()[&fee_asset], 3300); @@ -1069,7 +1520,9 @@ mod tests { assert_eq!(tx.output[1].is_null_data(), false); assert_eq!(tx.output[2].is_null_data(), false); - let fee_asset = "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23".parse().unwrap(); + let fee_asset = "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23" + .parse() + .unwrap(); assert_eq!(tx.fee_in(fee_asset), 36480); assert_eq!(tx.all_fees()[&fee_asset], 36480); @@ -1107,7 +1560,9 @@ mod tests { assert_eq!(tx.output[1].is_pegout(), false); assert_eq!(tx.output[0].pegout_data(), None); assert_eq!(tx.output[1].pegout_data(), None); - let fee_asset = "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23".parse().unwrap(); + let fee_asset = "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23" + .parse() + .unwrap(); assert_eq!(tx.fee_in(fee_asset), 0); assert!(tx.all_fees().is_empty()); } @@ -1156,73 +1611,58 @@ mod tests { outpoint: bitcoin::OutPoint { txid: bitcoin::Txid::from_hex( "c9d88eb5130365deed045eab11cfd3eea5ba32ad45fa2e156ae6ead5f1fce93f", - ).unwrap(), + ) + .unwrap(), vout: 0, }, value: 100000000, asset: tx.output[0].asset, genesis_hash: bitcoin::BlockHash::from_hex( "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206" - ).unwrap(), + ) + .unwrap(), claim_script: &[ - 0x00, 0x14, 0x1a, 0xb7, 0xf5, 0x99, 0x5c, 0xf0, - 0xdf, 0xcb, 0x90, 0xcb, 0xb0, 0x2b, 0x63, 0x39, - 0x7e, 0x53, 0x26, 0xea, 0xe6, 0xfe, + 0x00, 0x14, 0x1a, 0xb7, 0xf5, 0x99, 0x5c, 0xf0, 0xdf, 0xcb, 0x90, 0xcb, 0xb0, + 0x2b, 0x63, 0x39, 0x7e, 0x53, 0x26, 0xea, 0xe6, 0xfe, ], tx: &[ - 0x02, 0x00, 0x00, 0x00, 0x01, 0x13, 0x24, 0x4f, - 0xa5, 0x9f, 0xcb, 0x40, 0x71, 0x24, 0x03, 0x8f, - 0xf9, 0x12, 0x1e, 0xd5, 0x46, 0xf6, 0xdc, 0x21, - 0x75, 0x71, 0xcb, 0x36, 0x6a, 0x50, 0xd3, 0x19, - 0x3f, 0x2c, 0x80, 0x29, 0x8c, 0x00, 0x00, 0x00, - 0x00, 0x49, 0x48, 0x30, 0x45, 0x02, 0x21, 0x00, - 0xd1, 0xe2, 0x12, 0x71, 0x5d, 0x2d, 0xcb, 0xc1, - 0xc6, 0x6d, 0x76, 0xf4, 0x3d, 0x9f, 0x32, 0x6f, - 0x54, 0xff, 0x33, 0x9b, 0x56, 0x5c, 0x68, 0xf0, - 0x46, 0xed, 0x74, 0x04, 0x07, 0x30, 0x43, 0x3b, - 0x02, 0x20, 0x1d, 0x9c, 0xcb, 0xad, 0x57, 0x56, - 0x61, 0x00, 0xa0, 0x6b, 0x4b, 0xe4, 0x7a, 0x4c, - 0x77, 0x7c, 0xbd, 0x7c, 0x99, 0xe0, 0xa0, 0x8e, - 0x17, 0xf7, 0xbf, 0x10, 0x45, 0x81, 0x17, 0x42, - 0x6c, 0xd8, 0x01, 0xfe, 0xff, 0xff, 0xff, 0x02, - 0x00, 0xe1, 0xf5, 0x05, 0x00, 0x00, 0x00, 0x00, - 0x17, 0xa9, 0x14, 0x77, 0x4b, 0x87, 0xbe, 0x1e, - 0xf8, 0x71, 0xd8, 0x2a, 0x01, 0xed, 0xbb, 0x89, - 0xa7, 0x0b, 0xf4, 0xbb, 0x59, 0x31, 0x03, 0x87, - 0xa8, 0x8c, 0x8b, 0x44, 0x00, 0x00, 0x00, 0x00, - 0x19, 0x76, 0xa9, 0x14, 0xb1, 0x4b, 0x73, 0x95, - 0x62, 0x39, 0x21, 0xdb, 0xbc, 0xe4, 0x38, 0xf4, - 0xfc, 0x1f, 0xc8, 0xf1, 0xa4, 0x95, 0xaf, 0xfa, - 0x88, 0xac, 0xf4, 0x01, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x01, 0x13, 0x24, 0x4f, 0xa5, 0x9f, 0xcb, 0x40, 0x71, + 0x24, 0x03, 0x8f, 0xf9, 0x12, 0x1e, 0xd5, 0x46, 0xf6, 0xdc, 0x21, 0x75, 0x71, + 0xcb, 0x36, 0x6a, 0x50, 0xd3, 0x19, 0x3f, 0x2c, 0x80, 0x29, 0x8c, 0x00, 0x00, + 0x00, 0x00, 0x49, 0x48, 0x30, 0x45, 0x02, 0x21, 0x00, 0xd1, 0xe2, 0x12, 0x71, + 0x5d, 0x2d, 0xcb, 0xc1, 0xc6, 0x6d, 0x76, 0xf4, 0x3d, 0x9f, 0x32, 0x6f, 0x54, + 0xff, 0x33, 0x9b, 0x56, 0x5c, 0x68, 0xf0, 0x46, 0xed, 0x74, 0x04, 0x07, 0x30, + 0x43, 0x3b, 0x02, 0x20, 0x1d, 0x9c, 0xcb, 0xad, 0x57, 0x56, 0x61, 0x00, 0xa0, + 0x6b, 0x4b, 0xe4, 0x7a, 0x4c, 0x77, 0x7c, 0xbd, 0x7c, 0x99, 0xe0, 0xa0, 0x8e, + 0x17, 0xf7, 0xbf, 0x10, 0x45, 0x81, 0x17, 0x42, 0x6c, 0xd8, 0x01, 0xfe, 0xff, + 0xff, 0xff, 0x02, 0x00, 0xe1, 0xf5, 0x05, 0x00, 0x00, 0x00, 0x00, 0x17, 0xa9, + 0x14, 0x77, 0x4b, 0x87, 0xbe, 0x1e, 0xf8, 0x71, 0xd8, 0x2a, 0x01, 0xed, 0xbb, + 0x89, 0xa7, 0x0b, 0xf4, 0xbb, 0x59, 0x31, 0x03, 0x87, 0xa8, 0x8c, 0x8b, 0x44, + 0x00, 0x00, 0x00, 0x00, 0x19, 0x76, 0xa9, 0x14, 0xb1, 0x4b, 0x73, 0x95, 0x62, + 0x39, 0x21, 0xdb, 0xbc, 0xe4, 0x38, 0xf4, 0xfc, 0x1f, 0xc8, 0xf1, 0xa4, 0x95, + 0xaf, 0xfa, 0x88, 0xac, 0xf4, 0x01, 0x00, 0x00, ], merkle_proof: &[ - 0x00, 0x00, 0x00, 0x20, 0xa0, 0x60, 0x08, 0x6a, - 0xf9, 0x2a, 0xc3, 0x4d, 0xbb, 0xc8, 0xbd, 0x89, - 0xbb, 0xbe, 0x03, 0xef, 0x7e, 0x00, 0x16, 0x93, - 0x0f, 0x7f, 0xdc, 0x80, 0x6f, 0xf1, 0x5d, 0x16, - 0x3b, 0x5f, 0xda, 0x5e, 0x32, 0x10, 0x59, 0x49, - 0xc7, 0x48, 0x22, 0x2d, 0x3e, 0x1c, 0x5b, 0x6e, - 0x0a, 0x4d, 0x47, 0xf8, 0xde, 0x45, 0xb2, 0x5d, - 0x63, 0xf1, 0x45, 0xc4, 0x05, 0x66, 0x82, 0xa7, - 0xb1, 0x5c, 0xc3, 0xda, 0x56, 0xa2, 0x81, 0x5b, - 0xff, 0xff, 0x7f, 0x20, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x00, 0x00, 0x00, 0x03, 0x94, 0x6c, 0x96, - 0x9d, 0x81, 0xa3, 0xb0, 0xca, 0x47, 0x3a, 0xb5, - 0x4c, 0x11, 0xfa, 0x66, 0x52, 0x34, 0xd6, 0xce, - 0x1a, 0xd0, 0x9e, 0x87, 0xa1, 0xdb, 0xc5, 0x6e, - 0xb6, 0xde, 0x40, 0x02, 0xb8, 0x3f, 0xe9, 0xfc, - 0xf1, 0xd5, 0xea, 0xe6, 0x6a, 0x15, 0x2e, 0xfa, - 0x45, 0xad, 0x32, 0xba, 0xa5, 0xee, 0xd3, 0xcf, - 0x11, 0xab, 0x5e, 0x04, 0xed, 0xde, 0x65, 0x03, - 0x13, 0xb5, 0x8e, 0xd8, 0xc9, 0xfc, 0xcd, 0xc0, - 0xd0, 0x7e, 0xaf, 0x48, 0xf9, 0x28, 0xfe, 0xcf, - 0xc0, 0x77, 0x07, 0xb9, 0x57, 0x69, 0x70, 0x4d, - 0x25, 0xf8, 0x55, 0x52, 0x97, 0x11, 0xed, 0x64, - 0x50, 0xcc, 0x9b, 0x3c, 0x95, 0x01, 0x0b, + 0x00, 0x00, 0x00, 0x20, 0xa0, 0x60, 0x08, 0x6a, 0xf9, 0x2a, 0xc3, 0x4d, 0xbb, + 0xc8, 0xbd, 0x89, 0xbb, 0xbe, 0x03, 0xef, 0x7e, 0x00, 0x16, 0x93, 0x0f, 0x7f, + 0xdc, 0x80, 0x6f, 0xf1, 0x5d, 0x16, 0x3b, 0x5f, 0xda, 0x5e, 0x32, 0x10, 0x59, + 0x49, 0xc7, 0x48, 0x22, 0x2d, 0x3e, 0x1c, 0x5b, 0x6e, 0x0a, 0x4d, 0x47, 0xf8, + 0xde, 0x45, 0xb2, 0x5d, 0x63, 0xf1, 0x45, 0xc4, 0x05, 0x66, 0x82, 0xa7, 0xb1, + 0x5c, 0xc3, 0xda, 0x56, 0xa2, 0x81, 0x5b, 0xff, 0xff, 0x7f, 0x20, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x94, 0x6c, 0x96, 0x9d, 0x81, 0xa3, + 0xb0, 0xca, 0x47, 0x3a, 0xb5, 0x4c, 0x11, 0xfa, 0x66, 0x52, 0x34, 0xd6, 0xce, + 0x1a, 0xd0, 0x9e, 0x87, 0xa1, 0xdb, 0xc5, 0x6e, 0xb6, 0xde, 0x40, 0x02, 0xb8, + 0x3f, 0xe9, 0xfc, 0xf1, 0xd5, 0xea, 0xe6, 0x6a, 0x15, 0x2e, 0xfa, 0x45, 0xad, + 0x32, 0xba, 0xa5, 0xee, 0xd3, 0xcf, 0x11, 0xab, 0x5e, 0x04, 0xed, 0xde, 0x65, + 0x03, 0x13, 0xb5, 0x8e, 0xd8, 0xc9, 0xfc, 0xcd, 0xc0, 0xd0, 0x7e, 0xaf, 0x48, + 0xf9, 0x28, 0xfe, 0xcf, 0xc0, 0x77, 0x07, 0xb9, 0x57, 0x69, 0x70, 0x4d, 0x25, + 0xf8, 0x55, 0x52, 0x97, 0x11, 0xed, 0x64, 0x50, 0xcc, 0x9b, 0x3c, 0x95, 0x01, + 0x0b, ], referenced_block: bitcoin::BlockHash::from_hex( "297852caf43464d8f13a3847bd602184c21474cd06760dbf9fc5e87bade234f1" - ).unwrap(), + ) + .unwrap(), }) ); @@ -1233,7 +1673,9 @@ mod tests { assert_eq!(tx.output[1].is_pegout(), false); assert_eq!(tx.output[0].pegout_data(), None); assert_eq!(tx.output[1].pegout_data(), None); - let fee_asset = "630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8".parse().unwrap(); + let fee_asset = "630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8" + .parse() + .unwrap(); assert_eq!(tx.fee_in(fee_asset), 6260); assert_eq!(tx.all_fees()[&fee_asset], 6260); } @@ -1263,7 +1705,9 @@ mod tests { assert_eq!(tx.output.len(), 1); assert_eq!(tx.output[0].is_null_data(), true); assert_eq!(tx.output[0].is_pegout(), true); - let fee_asset = "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23".parse().unwrap(); + let fee_asset = "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23" + .parse() + .unwrap(); assert_eq!(tx.fee_in(fee_asset), 0); assert!(tx.all_fees().is_empty()); assert_eq!( @@ -1273,34 +1717,32 @@ mod tests { value: 99993900, genesis_hash: bitcoin::BlockHash::from_hex( "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206" - ).unwrap(), + ) + .unwrap(), script_pubkey: hex_deserialize!( "1976a914bedb324be05d1a1254afeb3e7ef40fea0368bc1e88ac" ), extra_data: vec![ &[ - 0x02, - 0xe2, 0x5e, 0x58, 0x2a, 0xc1, 0xad, 0xc6, 0x9f, - 0x16, 0x8a, 0xa7, 0xdb, 0xf0, 0xa9, 0x73, 0x41, - 0x42, 0x1e, 0x10, 0xb2, 0x2c, 0x65, 0x99, 0x27, - 0xde, 0x24, 0xfd, 0xac, 0x6e, 0x9f, 0x1f, 0xae, + 0x02, 0xe2, 0x5e, 0x58, 0x2a, 0xc1, 0xad, 0xc6, 0x9f, 0x16, 0x8a, 0xa7, + 0xdb, 0xf0, 0xa9, 0x73, 0x41, 0x42, 0x1e, 0x10, 0xb2, 0x2c, 0x65, 0x99, + 0x27, 0xde, 0x24, 0xfd, 0xac, 0x6e, 0x9f, 0x1f, 0xae, ], &[ - 0x01, 0xa4, 0x8f, 0xe5, 0x27, 0x75, 0x70, 0x15, - 0x56, 0xa4, 0xa2, 0xdb, 0xf3, 0xd9, 0x5c, 0x0c, - 0x13, 0x84, 0x5b, 0xbf, 0x87, 0x27, 0x1e, 0x74, - 0x5b, 0x1c, 0x45, 0x4f, 0x8e, 0xbc, 0xb5, 0xcd, - 0x47, 0x92, 0xa4, 0x13, 0x9f, 0x41, 0x9f, 0x19, - 0x2c, 0xa6, 0xe3, 0x89, 0x53, 0x1d, 0x46, 0xfa, - 0x58, 0x57, 0xf2, 0xc1, 0x09, 0xdf, 0xe4, 0x00, - 0x3a, 0xd8, 0xb2, 0xce, 0x50, 0x4b, 0x48, 0x8b, - 0xed, + 0x01, 0xa4, 0x8f, 0xe5, 0x27, 0x75, 0x70, 0x15, 0x56, 0xa4, 0xa2, 0xdb, + 0xf3, 0xd9, 0x5c, 0x0c, 0x13, 0x84, 0x5b, 0xbf, 0x87, 0x27, 0x1e, 0x74, + 0x5b, 0x1c, 0x45, 0x4f, 0x8e, 0xbc, 0xb5, 0xcd, 0x47, 0x92, 0xa4, 0x13, + 0x9f, 0x41, 0x9f, 0x19, 0x2c, 0xa6, 0xe3, 0x89, 0x53, 0x1d, 0x46, 0xfa, + 0x58, 0x57, 0xf2, 0xc1, 0x09, 0xdf, 0xe4, 0x00, 0x3a, 0xd8, 0xb2, 0xce, + 0x50, 0x4b, 0x48, 0x8b, 0xed, ] ], }) ); - let expected_asset_id = AssetId::from_hex("630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8").unwrap(); + let expected_asset_id = + AssetId::from_hex("630ed6f9b176af03c0cd3f8aa430f9e7b4d988cf2d0b2f204322488f03b00bf8") + .unwrap(); if let confidential::Asset::Explicit(asset_id) = tx.output[0].asset { assert_eq!(expected_asset_id, asset_id); } else { @@ -1310,7 +1752,8 @@ mod tests { #[test] fn issuance() { - let tx: Transaction = hex_deserialize!("\ + let tx: Transaction = hex_deserialize!( + "\ 02000000010173828cbc65fd68ab78dc86992b76ae50ae2bf8ceedbe8de048317\ 2f0886219f7000000806b483045022100a21a578a7f2f98ca65115488facb62d7\ c196d2df14213aed986cfdbdfd05647402204197c1fd1d9e94a14535e0918cd3c\ @@ -1608,7 +2051,8 @@ mod tests { 78fea170dc6956487744de0c263bd0c1847c5df09fad541b2be2d557896b566ae\ 50186f922528705e5d8e7785f8ef9568f5edbb36e2d46ffc89b1b83439ff07ba4\ 5c3d8f741d0000\ - "); + " + ); assert_eq!( tx.txid().to_string(), @@ -1617,7 +2061,9 @@ mod tests { assert_eq!(tx.input.len(), 1); assert_eq!(tx.output.len(), 3); assert_eq!(tx.input[0].has_issuance, true); - let fee_asset = "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23".parse().unwrap(); + let fee_asset = "b2e15d0d7a0c94e4e2ce0fe6e8691b9e451377f6e46e8045a86f7c4b5d4f0f23" + .parse() + .unwrap(); assert_eq!(tx.fee_in(fee_asset), 56400); assert_eq!(tx.all_fees()[&fee_asset], 56400); assert_eq!( @@ -1625,15 +2071,12 @@ mod tests { AssetIssuance { asset_blinding_nonce: [0; 32], asset_entropy: [0; 32], - amount: confidential::Value::Confidential( - 9, - [ - 0x81, 0x65, 0x4e, 0xb5, 0xcc, 0xd9, 0x92, 0x7b, - 0x8b, 0xea, 0x94, 0x99, 0x7d, 0xce, 0x4a, 0xe8, - 0x5b, 0x3d, 0x95, 0xa2, 0x07, 0x00, 0x38, 0x4f, - 0x0b, 0x8c, 0x1f, 0xe9, 0x95, 0x18, 0x06, 0x38 - ], - ), + amount: confidential::Value::from_commitment(&[ + 0x09, 0x81, 0x65, 0x4e, 0xb5, 0xcc, 0xd9, 0x92, 0x7b, 0x8b, 0xea, 0x94, 0x99, + 0x7d, 0xce, 0x4a, 0xe8, 0x5b, 0x3d, 0x95, 0xa2, 0x07, 0x00, 0x38, 0x4f, 0x0b, + 0x8c, 0x1f, 0xe9, 0x95, 0x18, 0x06, 0x38 + ],) + .unwrap(), inflation_keys: confidential::Value::Null, } ); @@ -1642,7 +2085,8 @@ mod tests { #[test] fn txout_null_data() { // Output with high opcodes should not be considered nulldata - let output: TxOut = hex_deserialize!("\ + let output: TxOut = hex_deserialize!( + "\ 0a319c0000000000d3d3d3d3d3d3d3d3d3d3d3d3fdfdfd0101010101010101010\ 101010101010101010101010101012e010101010101010101fdfdfdfdfdfdfdfd\ fdfdfdfdfdfdfdfdfdfdfdfd006a209f6a6a6a6a6a6a806a6afdfdfdfd17fdfdf\ @@ -1654,14 +2098,16 @@ mod tests { 000000000000000000ff000000000000000000000000200000000000011c00000\ 000d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3f3d3d3d3d3d3d3d3d3d3d3d3d3\ d3d3d3d3d3d3\ - "); + " + ); assert!(!output.is_null_data()); assert!(!output.is_pegout()); // Output with pushes that are e.g. OP_1 are nulldata but not pegouts - let output: TxOut = hex_deserialize!("\ - 0a2d3634393536d9a2d0aaba3823f442fb24363831fdfd0101010101010101010\ + let output: TxOut = hex_deserialize!( + "\ + 0a319c0000000000d3d3d3d3d3d3d3d3d3d3d3d3fdfdfd0101010101010101010\ 1010101010101010101010101010101010101016a01010101fdfdfdfdfdfdfdfd\ fdfdfdfdfd3ca059fdfdfb6a2000002323232323232323232323232323232\ 3232323232323232321232323010151232323232323232323232323232323\ @@ -1672,18 +2118,21 @@ mod tests { 23232324232123232323232323232423232323232323232323232323232323232\ 3232323232323232323232323232323232323232323232323232321230d000000\ 2323232323d3\ - "); + " + ); assert!(output.is_null_data()); assert!(!output.is_pegout()); // Output with just one push and nothing else should be nulldata but not pegout - let output: TxOut = hex_deserialize!("\ - 0a2d3634393536d9a2d0aaba3823f442fb24363831fdfd0101010101010101010\ + let output: TxOut = hex_deserialize!( + "\ + 0a319c0000000000d3d3d3d3d3d3d3d3d3d3d3d3fdfdfd0101010101010101010\ 1010101010101010101010101010101010101016a01010101fdfdfdfdfdfdfdfd\ fdfdfdfdfd3ca059fdf2226a20000000000000000000000000000000000000000\ 0000000000000000000000000\ - "); + " + ); assert!(output.is_null_data()); assert!(!output.is_pegout()); @@ -1691,7 +2140,8 @@ mod tests { #[test] fn pegout_tx_vector_1() { - let tx: Transaction = hex_deserialize!("\ + let tx: Transaction = hex_deserialize!( + "\ 0200000000021c39a226160dd8962eb273772950f0b603c319a8e4aa9912c9e8e\ 36b5bdf71a2000000006a473044022071212fcde89d1055d5b74f17a162b3dbe5\ 348ac8527a131dab5dcf8a97d67d2f02202edf12f3c69fed1fa0c23da608e6ade\ @@ -1723,7 +2173,8 @@ mod tests { 2c2ea477a0bd14e670bccb42a0fb7009b41ee86a016d521c38ec1ea15734ae22b\ 7c46064412829c0d0579f0a713d1c04ede979026f0100000000000006fc000054\ 840300\ - "); + " + ); assert_eq!(tx.input.len(), 2); assert_eq!(tx.output.len(), 3); @@ -1742,14 +2193,17 @@ mod tests { assert_eq!(tx.output[0].asset, tx.output[1].asset); assert_eq!(tx.output[2].asset, tx.output[1].asset); - let fee_asset = "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d".parse().unwrap(); + let fee_asset = "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d" + .parse() + .unwrap(); assert_eq!(tx.fee_in(fee_asset), 1788); assert_eq!(tx.all_fees()[&fee_asset], 1788); } #[test] fn pegout_with_numeric_pak() { - let tx: Transaction = hex_deserialize!("\ + let tx: Transaction = hex_deserialize!( + "\ 0200000000021c39a226160dd8962eb273772950f0b603c319a8e4aa9912c9e8\ e36b5bdf71a2000000006a473044022071212fcde89d1055d5b74f17a162b3db\ e5348ac8527a131dab5dcf8a97d67d2f02202edf12f3c69fed1fa0c23da608e6\ @@ -1768,7 +2222,8 @@ mod tests { f5dbac47d54c9ef5ccf49895a4dbac4759005a74375f66c480e6c08651016d52\ 1c38ec1ea15734ae22b7c46064412829c0d0579f0a713d1c04ede979026f0100\ 000000000006fc000054840300\ - "); + " + ); assert_eq!(tx.input.len(), 2); assert_eq!(tx.output.len(), 3); @@ -1786,14 +2241,17 @@ mod tests { assert_eq!(tx.output[0].asset, tx.output[1].asset); assert_eq!(tx.output[2].asset, tx.output[1].asset); - let fee_asset = "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d".parse().unwrap(); + let fee_asset = "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d" + .parse() + .unwrap(); assert_eq!(tx.fee_in(fee_asset), 1788); assert_eq!(tx.all_fees()[&fee_asset], 1788); } #[test] fn pegout_with_null_scriptpubkey() { - let tx: Transaction = hex_deserialize!("\ + let tx: Transaction = hex_deserialize!( + "\ 0200000000021c39a226160dd8962eb273772950f0b603c319a8e4aa9912c9e8\ e36b5bdf71a2000000006a473044022071212fcde89d1055d5b74f17a162b3db\ e5348ac8527a131dab5dcf8a97d67d2f02202edf12f3c69fed1fa0c23da608e6\ @@ -1811,7 +2269,8 @@ mod tests { 0021025f756509f5dbac47d54c9ef5ccf49895a4dbac4759005a74375f66c480\ e6c08600016d521c38ec1ea15734ae22b7c46064412829c0d0579f0a713d1c04\ ede979026f0100000000000006fc000054840300\ - "); + " + ); assert_eq!(tx.input.len(), 2); assert_eq!(tx.output.len(), 3); @@ -1829,9 +2288,68 @@ mod tests { assert_eq!(tx.output[0].asset, tx.output[1].asset); assert_eq!(tx.output[2].asset, tx.output[1].asset); - let fee_asset = "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d".parse().unwrap(); + let fee_asset = "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d" + .parse() + .unwrap(); assert_eq!(tx.fee_in(fee_asset), 1788); assert_eq!(tx.all_fees()[&fee_asset], 1788); } -} + #[test] + fn unblind_txout() { + let value = 10; + + let (address, blinding_sk) = { + let sk = SecretKey::new(&mut thread_rng()); + let pk = PublicKey::from_private_key( + &SECP256K1, + &PrivateKey { + compressed: true, + network: Network::Regtest, + key: sk, + }, + ); + let blinding_sk = SecretKey::new(&mut thread_rng()); + let blinding_pk = PublicKey::from_private_key( + &SECP256K1, + &PrivateKey { + compressed: true, + network: Network::Regtest, + key: blinding_sk, + }, + ); + ( + Address::p2wpkh(&pk, Some(blinding_pk.key), &AddressParams::ELEMENTS), + blinding_sk, + ) + }; + let asset = AssetId::default(); + + let input_abf = AssetBlindingFactor::random(&mut thread_rng()); + let input_asset_commitment = Asset::new_confidential(SECP256K1, asset, input_abf); + let input_vbf = ValueBlindingFactor::random(&mut thread_rng()); + let inputs = &[( + asset, + value, + input_asset_commitment.commitment().unwrap(), + input_abf, + input_vbf, + )]; + + let (txout, _, _) = TxOut::new_not_last_confidential( + &mut thread_rng(), + SECP256K1, + value, + address, + asset, + inputs, + ) + .unwrap(); + + let txout = txout.into_confidential().unwrap(); + let unblinded_txout = txout.unblind(SECP256K1, blinding_sk).unwrap(); + + assert_eq!(unblinded_txout.asset, asset); + assert_eq!(unblinded_txout.value, value); + } +}