Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pedersen commitment improvements #182

Merged
merged 11 commits into from
Oct 31, 2023
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ name = "rgbcore-stl"
required-features = ["stl"]

[dependencies]
amplify = "~4.5.0"
amplify = { version = "~4.5.0", features = ["rand"] }
strict_encoding = "~2.6.1"
strict_types = "~1.6.3"
aluvm = { version = "~0.10.6", features = ["std"] }
Expand Down
253 changes: 217 additions & 36 deletions src/contract/fungible.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,12 @@
use std::io;
use std::io::Write;

use amplify::hex::{Error, FromHex, ToHex};
use amplify::hex::ToHex;
// We do not import particular modules to keep aware with namespace prefixes
// that we do not use the standard secp256k1zkp library
use amplify::{hex, Array, Bytes32, Wrapper};
use bp::secp256k1::rand::thread_rng;
use commit_verify::{
CommitEncode, CommitVerify, CommitmentProtocol, Conceal, Digest, Sha256, UntaggedProtocol,
};
use commit_verify::{CommitEncode, CommitVerify, CommitmentProtocol, Conceal, UntaggedProtocol};
use secp256k1_zkp::rand::{Rng, RngCore};
use secp256k1_zkp::SECP256K1;
use strict_encoding::{
Expand Down Expand Up @@ -105,6 +103,27 @@
pub fn as_u64(&self) -> u64 { (*self).into() }
}

/// value provided for a blinding factor overflows prime field order for
/// Secp256k1 curve.
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error, From)]

Check warning on line 108 in src/contract/fungible.rs

View check run for this annotation

Codecov / codecov/patch

src/contract/fungible.rs#L108

Added line #L108 was not covered by tests
#[display(doc_comments)]
#[from(secp256k1_zkp::UpstreamError)]
pub struct InvalidFieldElement;

/// Errors parsing string representation of a blinding factor.
#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error, From)]

Check warning on line 114 in src/contract/fungible.rs

View check run for this annotation

Codecov / codecov/patch

src/contract/fungible.rs#L114

Added line #L114 was not covered by tests
#[display(doc_comments)]
pub enum BlindingParseError {
/// invalid blinding factor hex representation - {0}
#[from]
Hex(hex::Error),

/// blinding factor value is invalid and does not belongs to the Secp256k1
dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
/// curve field.
#[from(InvalidFieldElement)]
InvalidFieldElement,
}

/// Blinding factor used in creating Pedersen commitment to an [`AtomicValue`].
///
/// Knowledge of the blinding factor is important to reproduce the commitment
Expand All @@ -116,7 +135,7 @@
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", from = "secp256k1_zkp::SecretKey")
serde(crate = "serde_crate", try_from = "secp256k1_zkp::SecretKey")
)]
pub struct BlindingFactor(Bytes32);

Expand All @@ -129,46 +148,83 @@
fn to_hex(&self) -> String { self.0.to_hex() }
}

impl FromHex for BlindingFactor {
fn from_hex(s: &str) -> Result<Self, Error> { Bytes32::from_hex(s).map(Self) }
fn from_byte_iter<I>(_: I) -> Result<Self, Error>
where I: Iterator<Item = Result<u8, Error>> + ExactSizeIterator + DoubleEndedIterator {
unreachable!()
}
}

impl FromStr for BlindingFactor {
type Err = hex::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { Self::from_hex(s) }
type Err = BlindingParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = Bytes32::from_str(s)?;
Self::try_from(bytes).map_err(BlindingParseError::from)
}

Check warning on line 156 in src/contract/fungible.rs

View check run for this annotation

Codecov / codecov/patch

src/contract/fungible.rs#L153-L156

Added lines #L153 - L156 were not covered by tests
}

impl From<secp256k1_zkp::SecretKey> for BlindingFactor {
fn from(key: secp256k1_zkp::SecretKey) -> Self { Self(Bytes32::from_inner(*key.as_ref())) }
}

impl From<BlindingFactor> for secp256k1_zkp::SecretKey {
fn from(bf: BlindingFactor) -> Self {
secp256k1_zkp::SecretKey::from_slice(bf.0.as_inner())
fn from(bf: BlindingFactor) -> Self { bf.to_secret_key() }

Check warning on line 164 in src/contract/fungible.rs

View check run for this annotation

Codecov / codecov/patch

src/contract/fungible.rs#L164

Added line #L164 was not covered by tests
}

impl BlindingFactor {
/// Creates a random blinding factor.
#[inline]
pub fn random() -> Self { Self::random_custom(&mut thread_rng()) }

/// Generates a random blinding factor using custom random number generator.
#[inline]
pub fn random_custom<R: Rng + RngCore>(rng: &mut R) -> Self {
secp256k1_zkp::SecretKey::new(rng).into()
}

/// Generates new blinding factor which balances a given set of negatives
/// and positives into zero.
///
/// # Errors
///
/// If any subset of the negatives or positives are inverses of other
/// negatives or positives, or if the balancing factor is zero (sum of
/// negatives already equal to the sum of positives).
pub fn zero_balanced(
negative: impl IntoIterator<Item = BlindingFactor>,
positive: impl IntoIterator<Item = BlindingFactor>,
) -> Result<Self, InvalidFieldElement> {
let mut blinding_neg_sum = secp256k1_zkp::Scalar::ZERO;
let mut blinding_pos_sum = secp256k1_zkp::Scalar::ZERO;
for neg in negative {
blinding_neg_sum = neg.to_secret_key().add_tweak(&blinding_neg_sum)?.into();
}
let blinding_neg_sum =
secp256k1_zkp::SecretKey::from_slice(&blinding_neg_sum.to_be_bytes())?.negate();
for pos in positive {
blinding_pos_sum = pos.to_secret_key().add_tweak(&blinding_pos_sum)?.into();
}
let blinding_correction = blinding_neg_sum.add_tweak(&blinding_pos_sum)?.negate();
Ok(blinding_correction.into())
}

fn to_secret_key(self) -> secp256k1_zkp::SecretKey {
secp256k1_zkp::SecretKey::from_slice(self.0.as_slice())
.expect("blinding factor is an invalid secret key")
}
}

#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error)]
#[display(doc_comments)]
/// value provided for a blinding factor overflows prime field order for
/// Secp256k1 curve.
pub struct FieldOrderOverflow;

impl TryFrom<[u8; 32]> for BlindingFactor {
type Error = FieldOrderOverflow;
type Error = InvalidFieldElement;

fn try_from(array: [u8; 32]) -> Result<Self, Self::Error> {
secp256k1_zkp::SecretKey::from_slice(&array)
.map_err(|_| FieldOrderOverflow)
.map_err(|_| InvalidFieldElement)

Check warning on line 215 in src/contract/fungible.rs

View check run for this annotation

Codecov / codecov/patch

src/contract/fungible.rs#L215

Added line #L215 was not covered by tests
.map(Self::from)
}
}

impl TryFrom<Bytes32> for BlindingFactor {
type Error = InvalidFieldElement;

fn try_from(bytes: Bytes32) -> Result<Self, Self::Error> {
Self::try_from(bytes.to_byte_array())
}

Check warning on line 225 in src/contract/fungible.rs

View check run for this annotation

Codecov / codecov/patch

src/contract/fungible.rs#L223-L225

Added lines #L223 - L225 were not covered by tests
}

/// State item for a homomorphically-encryptable state.
///
/// Consists of the 64-bit value and
Expand All @@ -182,23 +238,42 @@

/// Blinding factor used in Pedersen commitment
pub blinding: BlindingFactor,

/// Asset-specific tag preventing mixing assets of different type.
pub asset_tag: Bytes32,
}

impl RevealedValue {
/// Constructs new state using the provided value using random blinding
/// factor.
pub fn new_random_blinding(value: impl Into<FungibleState>, asset_tag: Bytes32) -> Self {
Self::with_blinding(value, BlindingFactor::random(), asset_tag)
}

Check warning on line 251 in src/contract/fungible.rs

View check run for this annotation

Codecov / codecov/patch

src/contract/fungible.rs#L249-L251

Added lines #L249 - L251 were not covered by tests

/// Constructs new state using the provided value and random generator for
/// creating blinding factor.
pub fn new<R: Rng + RngCore>(value: impl Into<FungibleState>, rng: &mut R) -> Self {
pub fn with_random_blinding<R: Rng + RngCore>(
value: impl Into<FungibleState>,
rng: &mut R,
asset_tag: Bytes32,
) -> Self {
Self {
value: value.into(),
blinding: BlindingFactor::from(secp256k1_zkp::SecretKey::new(rng)),
blinding: BlindingFactor::random_custom(rng),
asset_tag,
}
}

/// Convenience constructor.
pub fn with(value: impl Into<FungibleState>, blinding: impl Into<BlindingFactor>) -> Self {
pub fn with_blinding(
value: impl Into<FungibleState>,
blinding: BlindingFactor,
asset_tag: Bytes32,
) -> Self {
Self {
value: value.into(),
blinding: blinding.into(),
blinding,
asset_tag,
}
}
}
Expand Down Expand Up @@ -286,12 +361,7 @@
.expect("type guarantees of BlindingFactor are broken");
let FungibleState::Bits64(value) = revealed.value;

// TODO: Check that we create correct generator value.
let one_key = secp256k1_zkp::SecretKey::from_slice(&secp256k1_zkp::constants::ONE)
.expect("secret key from a constant");
let g = secp256k1_zkp::PublicKey::from_secret_key(SECP256K1, &one_key);
let h: [u8; 32] = Sha256::digest(g.serialize_uncompressed()).into();
let tag = Tag::from(h);
let tag = Tag::from(revealed.asset_tag.to_byte_array());
let generator = Generator::new_unblinded(SECP256K1, tag);

secp256k1_zkp::PedersenCommitment::new(SECP256K1, value, blinding, generator).into()
Expand Down Expand Up @@ -424,7 +494,7 @@

#[test]
fn commitments_determinism() {
let value = RevealedValue::new(15, &mut thread_rng());
let value = RevealedValue::with_random_blinding(15, &mut thread_rng(), Bytes32::zero());

let generators = (0..10)
.map(|_| {
Expand All @@ -435,4 +505,115 @@
.collect::<HashSet<_>>();
assert_eq!(generators.len(), 1);
}

#[test]
#[should_panic]
dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
fn pedersen_blinding_mismatch() {
let mut r = thread_rng();
let tag = Bytes32::zero();

let a = PedersenCommitment::commit(&RevealedValue::with_random_blinding(15, &mut r, tag))
.into_inner();
let b = PedersenCommitment::commit(&RevealedValue::with_random_blinding(7, &mut r, tag))
.into_inner();

let c = PedersenCommitment::commit(&RevealedValue::with_random_blinding(13, &mut r, tag))
.into_inner();
let d = PedersenCommitment::commit(&RevealedValue::with_random_blinding(9, &mut r, tag))
.into_inner();

assert!(secp256k1_zkp::verify_commitments_sum_to_equal(SECP256K1, &[a, b], &[c, d]))
}

Check warning on line 526 in src/contract/fungible.rs

View check run for this annotation

Codecov / codecov/patch

src/contract/fungible.rs#L526

Added line #L526 was not covered by tests

#[test]
fn pedersen_blinding_same() {
let blinding =
BlindingFactor::from(secp256k1_zkp::SecretKey::from_slice(&[1u8; 32]).unwrap());
let tag = Bytes32::zero();

let a = PedersenCommitment::commit(&RevealedValue::with_blinding(15, blinding, tag))
.into_inner();
let b = PedersenCommitment::commit(&RevealedValue::with_blinding(7, blinding, tag))
.into_inner();

let c = PedersenCommitment::commit(&RevealedValue::with_blinding(13, blinding, tag))
.into_inner();
let d = PedersenCommitment::commit(&RevealedValue::with_blinding(9, blinding, tag))
.into_inner();

assert!(secp256k1_zkp::verify_commitments_sum_to_equal(SECP256K1, &[a, b], &[c, d]))
}

#[test]
#[should_panic]
dr-orlovsky marked this conversation as resolved.
Show resolved Hide resolved
fn pedersen_blinding_same_tag_differ() {
let blinding =
BlindingFactor::from(secp256k1_zkp::SecretKey::from_slice(&[1u8; 32]).unwrap());
let tag1 = Bytes32::zero();
let tag2 = Bytes32::from_array([1; 32]);

let a = PedersenCommitment::commit(&RevealedValue::with_blinding(15, blinding, tag2))
.into_inner();
let b = PedersenCommitment::commit(&RevealedValue::with_blinding(7, blinding, tag1))
.into_inner();

let c = PedersenCommitment::commit(&RevealedValue::with_blinding(13, blinding, tag2))
.into_inner();
let d = PedersenCommitment::commit(&RevealedValue::with_blinding(9, blinding, tag1))
.into_inner();

assert!(secp256k1_zkp::verify_commitments_sum_to_equal(SECP256K1, &[a, b], &[c, d]))
}

Check warning on line 566 in src/contract/fungible.rs

View check run for this annotation

Codecov / codecov/patch

src/contract/fungible.rs#L566

Added line #L566 was not covered by tests

#[test]
fn pedersen_two_tags() {
let blinding =
BlindingFactor::from(secp256k1_zkp::SecretKey::from_slice(&[1u8; 32]).unwrap());
let tag1 = Bytes32::zero();
let tag2 = Bytes32::from_array([1; 32]);

let a = PedersenCommitment::commit(&RevealedValue::with_blinding(15, blinding, tag2))
.into_inner();
let b = PedersenCommitment::commit(&RevealedValue::with_blinding(7, blinding, tag2))
.into_inner();
let c = PedersenCommitment::commit(&RevealedValue::with_blinding(2, blinding, tag1))
.into_inner();
let d = PedersenCommitment::commit(&RevealedValue::with_blinding(4, blinding, tag1))
.into_inner();

let e = PedersenCommitment::commit(&RevealedValue::with_blinding(13, blinding, tag2))
.into_inner();
let f = PedersenCommitment::commit(&RevealedValue::with_blinding(9, blinding, tag2))
.into_inner();
let g = PedersenCommitment::commit(&RevealedValue::with_blinding(1, blinding, tag1))
.into_inner();
let h = PedersenCommitment::commit(&RevealedValue::with_blinding(5, blinding, tag1))
.into_inner();

assert!(secp256k1_zkp::verify_commitments_sum_to_equal(SECP256K1, &[a, b, c, d], &[
e, f, g, h
]))
}

#[test]
fn pedersen_blinding_balance() {
let tag = Bytes32::from_array([1; 32]);

let blinding1 = BlindingFactor::random();
let blinding2 = BlindingFactor::random();
let blinding3 = BlindingFactor::random();
let blinding4 = BlindingFactor::zero_balanced([blinding1, blinding2], [blinding3]).unwrap();

let a = PedersenCommitment::commit(&RevealedValue::with_blinding(15, blinding1, tag))
.into_inner();
let b = PedersenCommitment::commit(&RevealedValue::with_blinding(7, blinding2, tag))
.into_inner();

let c = PedersenCommitment::commit(&RevealedValue::with_blinding(13, blinding3, tag))
.into_inner();
let d = PedersenCommitment::commit(&RevealedValue::with_blinding(9, blinding4, tag))
.into_inner();

assert!(secp256k1_zkp::verify_commitments_sum_to_equal(SECP256K1, &[a, b], &[c, d]))
}
}
4 changes: 2 additions & 2 deletions src/contract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ pub use contract::{
};
pub use data::{ConcealedData, RevealedData, VoidState};
pub use fungible::{
BlindingFactor, ConcealedValue, FieldOrderOverflow, FungibleState, NoiseDumb,
PedersenCommitment, RangeProof, RangeProofError, RevealedValue,
BlindingFactor, BlindingParseError, ConcealedValue, FungibleState, InvalidFieldElement,
NoiseDumb, PedersenCommitment, RangeProof, RangeProofError, RevealedValue,
};
pub use global::{GlobalState, GlobalValues};
pub use operations::{
Expand Down
2 changes: 1 addition & 1 deletion src/stl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::{Extension, Genesis, SubSchema, TransitionBundle, LIB_NAME_RGB};

/// Strict types id for the library providing data types for RGB consensus.
pub const LIB_ID_RGB: &str =
"urn:ubideco:stl:4fGZWR5mH5zZzRZ1r7CSRe776zm3hLBUngfXc4s3vm3V#saturn-flash-emerald";
"urn:ubideco:stl:BrciKpQhBab3wgq2GhStrjqaE6fG2hbBiCu8NQrTfmai#picture-sincere-shake";

fn _rgb_core_stl() -> Result<TypeLib, CompileError> {
LibBuilder::new(libname!(LIB_NAME_RGB), tiny_bset! {
Expand Down
Loading