use core::cmp::Ordering;
use core::fmt::Debug;
use core::num::ParseIntError;
use core::ops::Deref;
use core::str::FromStr;
use std::io;
use std::io::Write;
use std::time::SystemTime;
use amplify::confinement::U8;
use amplify::hex::ToHex;
use amplify::{hex, Array, Bytes32, Wrapper};
use bp::secp256k1::rand::thread_rng;
use commit_verify::{
CommitEncode, CommitVerify, CommitmentProtocol, Conceal, DigestExt, Sha256, UntaggedProtocol,
};
use secp256k1_zkp::rand::{Rng, RngCore};
use secp256k1_zkp::SECP256K1;
use strict_encoding::{
DecodeError, ReadTuple, StrictDecode, StrictDumb, StrictEncode, TypedRead, TypedWrite,
WriteTuple,
};
use super::{ConfidentialState, ExposedState};
use crate::{schema, AssignmentType, StateCommitment, StateData, StateType, LIB_NAME_RGB};
#[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)]
#[wrapper(Deref, BorrowSlice, Hex, Index, RangeOps)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
pub struct AssetTag(
#[from]
#[from([u8; 32])]
Bytes32,
);
impl AssetTag {
pub fn new_random(contract_domain: impl AsRef<str>, assignment_type: AssignmentType) -> Self {
let rand = thread_rng().next_u64();
let timestamp = SystemTime::now().elapsed().expect("system time error");
let mut hasher = Sha256::default();
hasher.input_with_len::<U8>(contract_domain.as_ref().as_bytes());
hasher.input_raw(&assignment_type.to_le_bytes());
hasher.input_raw(×tamp.as_nanos().to_le_bytes());
hasher.input_raw(&rand.to_le_bytes());
AssetTag::from(hasher.finish())
}
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)]
#[display(inner)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB, tags = custom)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub enum FungibleState {
#[from]
#[strict_type(tag = 8)] Bits64(u64),
}
impl Default for FungibleState {
fn default() -> Self { FungibleState::Bits64(0) }
}
impl From<RevealedValue> for FungibleState {
fn from(revealed: RevealedValue) -> Self { revealed.value }
}
impl FromStr for FungibleState {
type Err = ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> { s.parse().map(FungibleState::Bits64) }
}
impl From<FungibleState> for u64 {
fn from(value: FungibleState) -> Self {
match value {
FungibleState::Bits64(val) => val,
}
}
}
impl FungibleState {
pub fn fungible_type(&self) -> schema::FungibleType {
match self {
FungibleState::Bits64(_) => schema::FungibleType::Unsigned64Bit,
}
}
pub fn as_u64(&self) -> u64 { (*self).into() }
}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Display, Error, From)]
#[display(doc_comments)]
#[from(secp256k1_zkp::UpstreamError)]
pub struct InvalidFieldElement;
#[derive(Copy, Clone, Eq, PartialEq, Debug, Display, Error, From)]
#[display(doc_comments)]
pub enum BlindingParseError {
#[from]
Hex(hex::Error),
#[from(InvalidFieldElement)]
InvalidFieldElement,
}
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display)]
#[display(Self::to_hex)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", try_from = "secp256k1_zkp::SecretKey")
)]
pub struct BlindingFactor(Bytes32);
impl BlindingFactor {
pub const EMPTY: Self = BlindingFactor(Bytes32::from_array([0x7E; 32]));
}
impl Deref for BlindingFactor {
type Target = [u8; 32];
fn deref(&self) -> &Self::Target { self.0.as_inner() }
}
impl ToHex for BlindingFactor {
fn to_hex(&self) -> String { self.0.to_hex() }
}
impl FromStr for BlindingFactor {
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)
}
}
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 { bf.to_secret_key() }
}
impl BlindingFactor {
#[inline]
pub fn random() -> Self { Self::random_custom(&mut thread_rng()) }
#[inline]
pub fn random_custom<R: Rng + RngCore>(rng: &mut R) -> Self {
secp256k1_zkp::SecretKey::new(rng).into()
}
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")
}
}
impl TryFrom<[u8; 32]> for BlindingFactor {
type Error = InvalidFieldElement;
fn try_from(array: [u8; 32]) -> Result<Self, Self::Error> {
secp256k1_zkp::SecretKey::from_slice(&array)
.map_err(|_| InvalidFieldElement)
.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())
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB, rename = "RevealedFungible")]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))]
pub struct RevealedValue {
pub value: FungibleState,
pub blinding: BlindingFactor,
pub tag: AssetTag,
}
impl RevealedValue {
pub fn new_random_blinding(value: impl Into<FungibleState>, tag: AssetTag) -> Self {
Self::with_blinding(value, BlindingFactor::random(), tag)
}
pub fn with_random_blinding<R: Rng + RngCore>(
value: impl Into<FungibleState>,
rng: &mut R,
tag: AssetTag,
) -> Self {
Self::with_blinding(value, BlindingFactor::random_custom(rng), tag)
}
pub fn with_blinding(
value: impl Into<FungibleState>,
blinding: BlindingFactor,
tag: AssetTag,
) -> Self {
Self {
value: value.into(),
blinding,
tag,
}
}
}
impl ExposedState for RevealedValue {
type Confidential = ConcealedValue;
fn state_type(&self) -> StateType { StateType::Fungible }
fn state_data(&self) -> StateData { StateData::Fungible(*self) }
}
impl Conceal for RevealedValue {
type Concealed = ConcealedValue;
fn conceal(&self) -> Self::Concealed { ConcealedValue::commit(self) }
}
impl CommitEncode for RevealedValue {
fn commit_encode(&self, e: &mut impl Write) {
let commitment = PedersenCommitment::commit(self);
commitment.commit_encode(e);
}
}
impl PartialOrd for RevealedValue {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.cmp(other)) }
}
impl Ord for RevealedValue {
fn cmp(&self, other: &Self) -> Ordering {
match self.value.cmp(&other.value) {
Ordering::Equal => self.blinding.0.cmp(&other.blinding.0),
other => other,
}
}
}
#[derive(Wrapper, Copy, Clone, Eq, PartialEq, Hash, Debug, From)]
#[wrapper(Deref, FromStr, Display, LowerHex)]
#[derive(StrictType)]
#[strict_type(lib = LIB_NAME_RGB)]
#[derive(CommitEncode)]
#[commit_encode(strategy = strict)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
pub struct PedersenCommitment(secp256k1_zkp::PedersenCommitment);
impl StrictDumb for PedersenCommitment {
fn strict_dumb() -> Self {
secp256k1_zkp::PedersenCommitment::from_slice(&[0x08; 33])
.expect("hardcoded pedersen commitment value")
.into()
}
}
impl StrictEncode for PedersenCommitment {
fn strict_encode<W: TypedWrite>(&self, writer: W) -> io::Result<W> {
writer.write_tuple::<Self>(|w| Ok(w.write_field(&self.0.serialize())?.complete()))
}
}
impl StrictDecode for PedersenCommitment {
fn strict_decode(reader: &mut impl TypedRead) -> Result<Self, DecodeError> {
reader.read_tuple(|r| {
let commitment = r.read_field::<[u8; 33]>()?;
secp256k1_zkp::PedersenCommitment::from_slice(&commitment)
.map_err(|_| {
DecodeError::DataIntegrityError(s!("invalid pedersen commitment data"))
})
.map(PedersenCommitment::from_inner)
})
}
}
impl CommitVerify<RevealedValue, UntaggedProtocol> for PedersenCommitment {
fn commit(revealed: &RevealedValue) -> Self {
use secp256k1_zkp::{Generator, Tag, Tweak};
let blinding = Tweak::from_inner(revealed.blinding.0.into_inner())
.expect("type guarantees of BlindingFactor are broken");
let FungibleState::Bits64(value) = revealed.value;
let tag = Tag::from(revealed.tag.to_byte_array());
let generator = Generator::new_unblinded(SECP256K1, tag);
secp256k1_zkp::PedersenCommitment::new(SECP256K1, value, blinding, generator).into()
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", transparent)
)]
pub struct NoiseDumb(Array<u8, 512>);
impl Default for NoiseDumb {
fn default() -> Self {
let mut dumb = [0u8; 512];
thread_rng().fill(&mut dumb);
NoiseDumb(dumb.into())
}
}
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
#[derive(StrictType, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB, tags = custom)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub enum RangeProof {
#[strict_type(tag = 0xFF)]
Placeholder(NoiseDumb),
}
impl Default for RangeProof {
fn default() -> Self { RangeProof::Placeholder(default!()) }
}
pub struct PedersenProtocol;
impl CommitmentProtocol for PedersenProtocol {}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)]
#[strict_type(lib = LIB_NAME_RGB, rename = "ConcealedFungible")]
#[derive(CommitEncode)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(crate = "serde_crate", rename_all = "camelCase")
)]
pub struct ConcealedValue {
pub commitment: PedersenCommitment,
#[commit_encode(skip)]
pub range_proof: RangeProof,
}
impl ConfidentialState for ConcealedValue {
fn state_type(&self) -> StateType { StateType::Fungible }
fn state_commitment(&self) -> StateCommitment { StateCommitment::Fungible(*self) }
}
impl CommitVerify<RevealedValue, PedersenProtocol> for ConcealedValue {
fn commit(revealed: &RevealedValue) -> Self {
eprintln!(
"Warning: current version of RGB Core doesn't support production of bulletproofs; \
thus, fungible state must be never kept concealed"
);
let commitment = PedersenCommitment::commit(revealed);
let range_proof = RangeProof::default();
ConcealedValue {
commitment,
range_proof,
}
}
}
impl ConcealedValue {
pub fn verify(&self) -> bool {
match self.range_proof {
RangeProof::Placeholder(_) => false,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug, Display, Error)]
#[display(doc_comments)]
pub enum RangeProofError {
InvalidBlinding(BlindingFactor),
BulletproofsAbsent,
}
impl ConcealedValue {
pub fn verify_range_proof(&self) -> Result<bool, RangeProofError> {
Err(RangeProofError::BulletproofsAbsent)
}
}
#[cfg(test)]
mod test {
use std::collections::HashSet;
use amplify::ByteArray;
use super::*;
#[test]
fn commitments_determinism() {
let tag = AssetTag::from_byte_array([1u8; 32]);
let value = RevealedValue::with_random_blinding(15, &mut thread_rng(), tag);
let generators = (0..10)
.map(|_| {
let mut val = vec![];
value.commit_encode(&mut val);
val
})
.collect::<HashSet<_>>();
assert_eq!(generators.len(), 1);
}
#[test]
fn pedersen_blinding_mismatch() {
let mut r = thread_rng();
let tag = AssetTag::from_byte_array([1u8; 32]);
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]))
}
#[test]
fn pedersen_blinding_same() {
let blinding =
BlindingFactor::from(secp256k1_zkp::SecretKey::from_slice(&[1u8; 32]).unwrap());
let tag = AssetTag::from_byte_array([1u8; 32]);
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]
fn pedersen_blinding_same_tag_differ() {
let blinding =
BlindingFactor::from(secp256k1_zkp::SecretKey::from_slice(&[1u8; 32]).unwrap());
let tag = AssetTag::from_byte_array([1u8; 32]);
let tag2 = AssetTag::from_byte_array([2u8; 32]);
let a = PedersenCommitment::commit(&RevealedValue::with_blinding(15, blinding, tag2))
.into_inner();
let b = PedersenCommitment::commit(&RevealedValue::with_blinding(7, blinding, tag))
.into_inner();
let c = PedersenCommitment::commit(&RevealedValue::with_blinding(13, blinding, tag2))
.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]
fn pedersen_two_tags() {
let blinding =
BlindingFactor::from(secp256k1_zkp::SecretKey::from_slice(&[1u8; 32]).unwrap());
let tag = AssetTag::from_byte_array([1u8; 32]);
let tag2 = AssetTag::from_byte_array([2u8; 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, tag))
.into_inner();
let d = PedersenCommitment::commit(&RevealedValue::with_blinding(4, blinding, tag))
.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, tag))
.into_inner();
let h = PedersenCommitment::commit(&RevealedValue::with_blinding(5, blinding, tag))
.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 blinding1 = BlindingFactor::random();
let blinding2 = BlindingFactor::random();
let blinding3 = BlindingFactor::random();
let blinding4 = BlindingFactor::zero_balanced([blinding1, blinding2], [blinding3]).unwrap();
let tag = AssetTag::from_byte_array([1u8; 32]);
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]))
}
}