diff --git a/src/operation/commit.rs b/src/operation/commit.rs index 93796a4d..8af62f15 100644 --- a/src/operation/commit.rs +++ b/src/operation/commit.rs @@ -343,7 +343,7 @@ impl ConcealedState { fn commit_encode(&self, e: &mut CommitEngine) { match self { ConcealedState::Void => {} - ConcealedState::Fungible(val) => e.commit_to_serialized(&val.commitment), + ConcealedState::Fungible(val) => e.commit_to_serialized(&val.commitment()), ConcealedState::Structured(dat) => e.commit_to_serialized(dat), ConcealedState::Attachment(att) => e.commit_to_serialized(att), } diff --git a/src/operation/fungible.rs b/src/operation/fungible.rs index 1af77801..d6dc7319 100644 --- a/src/operation/fungible.rs +++ b/src/operation/fungible.rs @@ -30,7 +30,6 @@ //! properties regarding their total sum and, thus, can be made confidential //! using elliptic curve homomorphic cryptography such as Pedesen commitments. -use core::cmp::Ordering; use core::fmt::Debug; use core::num::ParseIntError; use core::ops::Deref; @@ -45,9 +44,7 @@ use amplify::hex::ToHex; use amplify::{hex, Array, Bytes32, Wrapper}; use bp::secp256k1::rand::thread_rng; use chrono::{DateTime, Utc}; -use commit_verify::{ - CommitVerify, CommitmentProtocol, Conceal, DigestExt, Sha256, UntaggedProtocol, -}; +use commit_verify::{CommitVerify, CommitmentProtocol, Conceal, DigestExt, Sha256}; use secp256k1_zkp::rand::{Rng, RngCore}; use secp256k1_zkp::SECP256K1; use strict_encoding::{ @@ -57,7 +54,7 @@ use strict_encoding::{ use super::{ConfidentialState, ExposedState}; use crate::{ - schema, AssignmentType, ConcealedState, RevealedState, StateType, LIB_NAME_RGB_COMMIT, + AssignmentType, ConcealedState, FungibleType, RevealedState, StateType, LIB_NAME_RGB_COMMIT, }; #[derive(Wrapper, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, From)] @@ -102,52 +99,36 @@ impl AssetTag { } /// An atom of an additive state, which thus can be monomorphically encrypted. -#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, From)] +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Default, Debug, Display, From)] #[display(inner)] #[derive(StrictType, StrictEncode, StrictDecode)] -#[strict_type(lib = LIB_NAME_RGB_COMMIT, tags = custom)] +#[strict_type(lib = LIB_NAME_RGB_COMMIT)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), - serde(crate = "serde_crate", rename_all = "camelCase", untagged) + serde(crate = "serde_crate", rename_all = "camelCase", transparent) )] -pub enum FungibleState { - /// 64-bit value. - #[from] - #[strict_type(tag = 8)] // Matches strict types U64 primitive value - Bits64(u64), - // When/if adding more variants do not forget to re-write FromStr impl -} - -impl Default for FungibleState { - fn default() -> Self { FungibleState::Bits64(0) } -} +pub struct FungibleState(u64); impl From for FungibleState { - fn from(revealed: RevealedValue) -> Self { revealed.value } + #[inline] + fn from(revealed: RevealedValue) -> Self { revealed.value() } } impl FromStr for FungibleState { type Err = ParseIntError; - fn from_str(s: &str) -> Result { s.parse().map(FungibleState::Bits64) } + + #[inline] + fn from_str(s: &str) -> Result { s.parse().map(Self) } } impl From for u64 { - fn from(value: FungibleState) -> Self { - match value { - FungibleState::Bits64(val) => val, - } - } + #[inline] + fn from(value: FungibleState) -> Self { value.0 } } impl FungibleState { - pub fn fungible_type(&self) -> schema::FungibleType { - match self { - FungibleState::Bits64(_) => schema::FungibleType::Unsigned64Bit, - } - } - - pub fn as_u64(&self) -> u64 { (*self).into() } + pub fn to_u64(&self) -> u64 { (*self).into() } } /// value provided for a blinding factor overflows prime field order for @@ -280,47 +261,99 @@ impl TryFrom for BlindingFactor { /// /// Consists of the 64-bit value and #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -#[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] -#[strict_type(lib = LIB_NAME_RGB_COMMIT, rename = "RevealedFungible")] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize), serde(crate = "serde_crate"))] -pub struct RevealedValue { - /// Original value in smallest indivisible units - pub value: FungibleState, +#[derive(StrictType, StrictEncode, StrictDecode)] +#[strict_type(lib = LIB_NAME_RGB_COMMIT, rename = "RevealedFungible", tags = custom)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(crate = "serde_crate", untagged) +)] +pub enum RevealedValue { + #[strict_type(tag = 8)] + Asset { + /// Original value in smallest indivisible units + value: FungibleState, + + /// Blinding factor used in hashing + blinding: BlindingFactor, + }, - /// Blinding factor used in Pedersen commitment - pub blinding: BlindingFactor, + #[strict_type(tag = 0xFF)] + ConfidentialAsset { + /// Original value in smallest indivisible units + value: FungibleState, + + /// Blinding factor used in Pedersen commitment + blinding: BlindingFactor, + + /// Asset-specific tag preventing mixing assets of different type. + tag: AssetTag, + }, +} - /// Asset-specific tag preventing mixing assets of different type. - pub tag: AssetTag, +impl StrictDumb for RevealedValue { + fn strict_dumb() -> Self { + Self::Asset { + value: strict_dumb!(), + blinding: strict_dumb!(), + } + } } impl RevealedValue { /// Constructs new state using the provided value using random blinding /// factor. - pub fn new_random_blinding(value: impl Into, tag: AssetTag) -> Self { - Self::with_blinding(value, BlindingFactor::random(), tag) + pub fn confidential_random_blinding(value: impl Into, tag: AssetTag) -> Self { + Self::ConfidentialAsset { + value: value.into(), + blinding: BlindingFactor::random(), + tag, + } } - /// Constructs new state using the provided value and random generator for + /// Constructs new confidential asset using the provided value and random generator for /// creating blinding factor. - pub fn with_rng( + pub fn confidential_with_rng( value: impl Into, rng: &mut R, tag: AssetTag, ) -> Self { - Self::with_blinding(value, BlindingFactor::random_custom(rng), tag) + Self::ConfidentialAsset { + value: value.into(), + blinding: BlindingFactor::random_custom(rng), + tag, + } } - /// Convenience constructor. - pub fn with_blinding( - value: impl Into, - blinding: BlindingFactor, - tag: AssetTag, - ) -> Self { - Self { + /// Constructs new non-confidential asset using the provided value using random blinding + /// factor. + pub fn asset_random_blinding(value: impl Into) -> Self { + Self::Asset { value: value.into(), - blinding, - tag, + blinding: BlindingFactor::random(), + } + } + + /// Constructs new non-confidential asset using the provided value and random generator for + /// creating blinding factor. + pub fn asset_with_rng(value: impl Into, rng: &mut R) -> Self { + Self::Asset { + value: value.into(), + blinding: BlindingFactor::random_custom(rng), + } + } + + pub fn fungible_type(&self) -> FungibleType { + match self { + RevealedValue::Asset { .. } => FungibleType::U64, + RevealedValue::ConfidentialAsset { .. } => FungibleType::ConfidentialAsset, + } + } + + pub fn value(&self) -> FungibleState { + match self { + RevealedValue::Asset { value, .. } => *value, + RevealedValue::ConfidentialAsset { value, .. } => *value, } } } @@ -337,19 +370,6 @@ impl Conceal for RevealedValue { fn conceal(&self) -> Self::Concealed { ConcealedValue::commit(self) } } -impl PartialOrd for RevealedValue { - fn partial_cmp(&self, other: &Self) -> Option { 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, - } - } -} - /// Opaque type holding pedersen commitment for an [`FungibleState`]. #[derive(Wrapper, Copy, Clone, Eq, PartialEq, Hash, Debug, From)] #[wrapper(Deref, FromStr, Display, LowerHex)] @@ -389,21 +409,6 @@ impl StrictDecode for PedersenCommitment { } } -impl CommitVerify 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() - } -} - /// A dumb placeholder for a future bulletproofs. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] #[derive(StrictType, StrictEncode, StrictDecode)] @@ -464,26 +469,42 @@ pub struct PedersenProtocol; impl CommitmentProtocol for PedersenProtocol {} -/// Confidential version of the additive state. +/// Confidential version of the fungible state. /// /// See also revealed version [`RevealedValue`]. #[derive(Clone, Copy, Eq, Debug)] #[derive(StrictType, StrictDumb, StrictEncode, StrictDecode)] -#[strict_type(lib = LIB_NAME_RGB_COMMIT, rename = "ConcealedFungible")] +#[strict_type(lib = LIB_NAME_RGB_COMMIT, rename = "ConcealedFungible", tags = custom, dumb = Self::HashedValue(strict_dumb!()))] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), - serde(crate = "serde_crate", rename_all = "camelCase") + serde(crate = "serde_crate", rename_all = "camelCase", untagged) )] -pub struct ConcealedValue { - /// Pedersen commitment to the original [`FungibleState`]. - pub commitment: PedersenCommitment, - /// Range proof for the [`FungibleState`] not exceeding type boundaries. - pub range_proof: RangeProof, +pub enum ConcealedValue { + #[strict_type(tag = 0x00)] + HashedValue(Bytes32), + + #[strict_type(tag = 0xFF)] + ConfidentialAsset { + /// Pedersen commitment to the original [`FungibleState`]. + commitment: PedersenCommitment, + /// Range proof for the [`FungibleState`] not exceeding type boundaries. + range_proof: RangeProof, + }, } impl PartialEq for ConcealedValue { - fn eq(&self, other: &Self) -> bool { self.commitment == other.commitment } + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::HashedValue(h1), Self::HashedValue(h2)) => h1 == h2, + ( + Self::ConfidentialAsset { commitment: c1, .. }, + Self::ConfidentialAsset { commitment: c2, .. }, + ) => c1 == c2, + (Self::HashedValue(_), Self::ConfidentialAsset { .. }) + | (Self::ConfidentialAsset { .. }, Self::HashedValue(_)) => false, + } + } } impl ConfidentialState for ConcealedValue { @@ -493,12 +514,37 @@ impl ConfidentialState for ConcealedValue { impl CommitVerify for ConcealedValue { fn commit(revealed: &RevealedValue) -> Self { - let commitment = PedersenCommitment::commit(revealed); - // TODO: Do actual conceal upon integration of bulletproofs library - let range_proof = RangeProof::default(); - ConcealedValue { - commitment, - range_proof, + match revealed { + RevealedValue::Asset { value, blinding } => {} + + RevealedValue::ConfidentialAsset { + value, + blinding, + tag, + } => { + use secp256k1_zkp::{Generator, Tag, Tweak}; + + let blinding = Tweak::from_inner(blinding.0.into_inner()) + .expect("type guarantees of BlindingFactor are broken"); + + let tag = Tag::from(tag.to_byte_array()); + let generator = Generator::new_unblinded(SECP256K1, tag); + + let commitment = secp256k1_zkp::PedersenCommitment::new( + SECP256K1, + value.to_u64(), + blinding, + generator, + ) + .into(); + + // TODO: Do actual conceal upon integration of bulletproofs library + let range_proof = RangeProof::default(); + ConcealedValue::ConfidentialAsset { + commitment, + range_proof, + } + } } } } @@ -535,11 +581,15 @@ mod test { let mut r = thread_rng(); let tag = AssetTag::from_byte_array([1u8; 32]); - let a = PedersenCommitment::commit(&RevealedValue::with_rng(15, &mut r, tag)).into_inner(); - let b = PedersenCommitment::commit(&RevealedValue::with_rng(7, &mut r, tag)).into_inner(); + let a = PedersenCommitment::commit(&RevealedValue::confidential_rng(15, &mut r, tag)) + .into_inner(); + let b = PedersenCommitment::commit(&RevealedValue::confidential_rng(7, &mut r, tag)) + .into_inner(); - let c = PedersenCommitment::commit(&RevealedValue::with_rng(13, &mut r, tag)).into_inner(); - let d = PedersenCommitment::commit(&RevealedValue::with_rng(9, &mut r, tag)).into_inner(); + let c = PedersenCommitment::commit(&RevealedValue::confidential_rng(13, &mut r, tag)) + .into_inner(); + let d = PedersenCommitment::commit(&RevealedValue::confidential_rng(9, &mut r, tag)) + .into_inner(); assert!(!secp256k1_zkp::verify_commitments_sum_to_equal(SECP256K1, &[a, b], &[c, d])) } diff --git a/src/operation/operations.rs b/src/operation/operations.rs index e4ed6cf2..eaf7a96c 100644 --- a/src/operation/operations.rs +++ b/src/operation/operations.rs @@ -300,7 +300,7 @@ pub trait Operation { id: self.id(), seals: Confined::from_checked(seals), fungible: Confined::from_iter_checked( - fungible.into_iter().map(|(k, s)| (k, s.commitment)), + fungible.into_iter().map(|(k, s)| (k, s.commitment())), ), data: Confined::from_checked(data), attach: Confined::from_checked(attach), diff --git a/src/operation/state.rs b/src/operation/state.rs index db230257..2eaa0155 100644 --- a/src/operation/state.rs +++ b/src/operation/state.rs @@ -45,7 +45,6 @@ pub trait ExposedState: + StrictDecode + Conceal + Eq - + Ord + Clone { type Confidential: ConfidentialState + StrictEncode + StrictDecode + StrictDumb; @@ -77,7 +76,7 @@ pub enum StateType { } /// Categories of the state -#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)] +#[derive(Clone, Eq, PartialEq, Hash, Debug)] #[cfg_attr( feature = "serde", derive(Serialize, Deserialize), diff --git a/src/schema/state.rs b/src/schema/state.rs index 27db9a15..e4435c69 100644 --- a/src/schema/state.rs +++ b/src/schema/state.rs @@ -88,13 +88,6 @@ impl OwnedStateSchema { } } -/// Today we support only a single format of confidential data, because of the -/// limitations of the underlying secp256k1-zkp library: it works only with -/// u64 numbers. Nevertheless, homomorphic commitments can be created to -/// everything that has up to 256 bits and commutative arithmetics, so in the -/// future we plan to support more types. We reserve this possibility by -/// internally encoding [`ConfidentialFormat`] with the same type specification -/// details as used for [`DateFormat`] #[derive(Copy, Clone, PartialEq, Eq, Debug, Default, Display)] #[derive(StrictType, StrictEncode, StrictDecode)] #[strict_type(lib = LIB_NAME_RGB_COMMIT, tags = repr, into_u8, try_from_u8)] @@ -106,8 +99,11 @@ impl OwnedStateSchema { #[repr(u8)] pub enum FungibleType { #[default] - #[display("64bit")] - Unsigned64Bit = Primitive::U64.into_code(), + #[display("U64")] + U64 = Primitive::U64.into_code(), + + #[display("ConfidentialAsset")] + ConfidentialAsset = 0xFF, } #[derive(Clone, PartialEq, Eq, Hash, Debug)] diff --git a/src/validation/logic.rs b/src/validation/logic.rs index 0afd7f93..b579f7a1 100644 --- a/src/validation/logic.rs +++ b/src/validation/logic.rs @@ -630,13 +630,13 @@ impl OwnedStateSchema { }); } (OwnedStateSchema::Fungible(schema), RevealedState::Fungible(v)) - if v.value.fungible_type() != *schema => + if v.fungible_type() != *schema => { status.add_failure(validation::Failure::FungibleTypeMismatch { opid, state_type, expected: *schema, - found: v.value.fungible_type(), + found: v.fungible_type(), }); } (OwnedStateSchema::Fungible(_), RevealedState::Fungible(_)) => {} diff --git a/src/vm/op_contract.rs b/src/vm/op_contract.rs index 5d971ff9..8078be27 100644 --- a/src/vm/op_contract.rs +++ b/src/vm/op_contract.rs @@ -373,7 +373,7 @@ impl InstructionSet for ContractOp { else { fail!() }; - regs.set_n(RegA::A64, *reg, state.map(|s| s.value.as_u64())); + regs.set_n(RegA::A64, *reg, state.map(|s| s.value().to_u64())); } ContractOp::LdG(state_type, reg_8, reg_s) => { let Some(reg_32) = *regs.get_n(RegA::A8, *reg_8) else {