synor/crates/synor-privacy/src/confidential.rs
Gulshan Yadav 7e3bbe569c fix: implement incomplete features and apply linter fixes
- Fixed TransferDirection import error in ethereum.rs tests
- Implemented math.tanh function for Flutter tensor operations
- Added DocumentStore CRUD methods (find_by_id, update_by_id, delete_by_id)
- Implemented database gateway handlers (get/update/delete document)
- Applied cargo fix across all crates to resolve unused imports/variables
- Reduced warnings from 320+ to 68 (remaining are architectural)

Affected crates: synor-database, synor-bridge, synor-compute,
synor-privacy, synor-verifier, synor-hosting, synor-economics
2026-01-26 21:43:51 +05:30

829 lines
27 KiB
Rust

//! Confidential Transactions
//!
//! This module combines all privacy primitives into a complete confidential
//! transaction system:
//!
//! - **Ring Signatures**: Hide which input is being spent
//! - **Stealth Addresses**: Hide the recipient
//! - **Pedersen Commitments**: Hide amounts
//! - **Bulletproofs**: Prove amounts are valid
//!
//! ## Transaction Structure
//!
//! ```text
//! ┌──────────────────────────────────────────────────────────────┐
//! │ Confidential Transaction │
//! ├──────────────────────────────────────────────────────────────┤
//! │ Inputs: │
//! │ ├─ Ring Signature (proves ownership, hides which key) │
//! │ ├─ Key Image (prevents double-spend) │
//! │ └─ Amount Commitment (hidden amount) │
//! ├──────────────────────────────────────────────────────────────┤
//! │ Outputs: │
//! │ ├─ Stealth Address (one-time recipient address) │
//! │ ├─ Ephemeral Public Key (for recipient to derive key) │
//! │ ├─ Amount Commitment (hidden amount) │
//! │ └─ Range Proof (proves 0 ≤ amount < 2^64) │
//! ├──────────────────────────────────────────────────────────────┤
//! │ Balance Proof: │
//! │ └─ sum(input_commitments) = sum(output_commitments) + fee │
//! └──────────────────────────────────────────────────────────────┘
//! ```
//!
//! ## Verification
//!
//! 1. Check all ring signatures are valid
//! 2. Check all key images are unused (no double-spend)
//! 3. Check all range proofs are valid
//! 4. Check the balance equation holds
use alloc::vec::Vec;
use curve25519_dalek::scalar::Scalar;
use rand_core::{CryptoRng, RngCore};
use serde::{Deserialize, Serialize};
use borsh::{BorshSerialize, BorshDeserialize};
use sha2::{Sha512, Digest};
use crate::{
bulletproofs::RangeProof,
pedersen::{BlindingFactor, CommitmentBatch, CommitmentBytes, PedersenCommitment},
ring::{KeyImage, RingPrivateKey, RingPublicKey, RingSignature},
stealth::{StealthAddress, StealthMetaAddress},
Error, Result, DOMAIN_SEPARATOR,
};
/// Generate a random scalar using the provided RNG
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Scalar {
let mut bytes = [0u8; 64];
rng.fill_bytes(&mut bytes);
Scalar::from_bytes_mod_order_wide(&bytes)
}
/// A confidential transaction input
#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct ConfidentialInput {
/// Ring signature proving ownership
pub ring_signature: RingSignature,
/// Ring of public keys (decoys + real)
pub ring: Vec<RingPublicKey>,
/// Amount commitment (from the output being spent)
pub amount_commitment: CommitmentBytes,
/// Reference to the output being spent (transaction hash + output index)
pub output_reference: OutputReference,
}
impl ConfidentialInput {
/// Create a new confidential input
pub fn create<R: RngCore + CryptoRng>(
// The key to spend
spending_key: &RingPrivateKey,
// The ring of public keys (must include spending key's pubkey)
ring: Vec<RingPublicKey>,
// Index of the real key in the ring
real_index: usize,
// The amount commitment from the output
amount_commitment: PedersenCommitment,
// Reference to the output
output_reference: OutputReference,
// Transaction message to sign
message: &[u8],
rng: &mut R,
) -> Result<Self> {
let ring_signature = RingSignature::sign(
spending_key,
&ring,
real_index,
message,
rng,
)?;
Ok(Self {
ring_signature,
ring,
amount_commitment: CommitmentBytes::from(&amount_commitment),
output_reference,
})
}
/// Get the key image
pub fn key_image(&self) -> &KeyImage {
&self.ring_signature.key_image
}
/// Verify the input
pub fn verify(&self, message: &[u8]) -> Result<bool> {
self.ring_signature.verify(&self.ring, message)
}
/// Get the amount commitment
pub fn commitment(&self) -> Result<PedersenCommitment> {
self.amount_commitment.clone().try_into()
}
}
/// A confidential transaction output
#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct ConfidentialOutput {
/// Stealth address (one-time address for recipient)
pub stealth_address: StealthAddress,
/// Amount commitment (hidden amount)
pub amount_commitment: CommitmentBytes,
/// Range proof (proves amount is valid)
pub range_proof: RangeProof,
/// Optional encrypted amount (for recipient to decrypt)
pub encrypted_amount: Option<EncryptedAmount>,
}
impl ConfidentialOutput {
/// Create a new confidential output
pub fn create<R: RngCore + CryptoRng>(
recipient: &StealthMetaAddress,
amount: u64,
rng: &mut R,
) -> Result<(Self, BlindingFactor)> {
// Generate stealth address
let stealth_address = StealthAddress::generate(recipient, rng);
// Create commitment and range proof
let blinding = BlindingFactor::random(rng);
let (range_proof, commitment) = RangeProof::prove(amount, &blinding, rng)?;
// Encrypt amount for recipient
let encrypted_amount = Some(EncryptedAmount::encrypt(
amount,
&stealth_address,
&blinding,
));
Ok((
Self {
stealth_address,
amount_commitment: CommitmentBytes::from(&commitment),
range_proof,
encrypted_amount,
},
blinding,
))
}
/// Verify the output (range proof)
pub fn verify(&self) -> Result<PedersenCommitment> {
let verified_commitment = self.range_proof.verify()?;
// Commitment from proof should match stored commitment
let stored_commitment: PedersenCommitment = self.amount_commitment.clone().try_into()?;
if verified_commitment != stored_commitment {
return Err(Error::InvalidTransaction(
"Commitment mismatch in output".into(),
));
}
Ok(verified_commitment)
}
/// Get the amount commitment
pub fn commitment(&self) -> Result<PedersenCommitment> {
self.amount_commitment.clone().try_into()
}
}
/// Reference to a previous output
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct OutputReference {
/// Transaction hash
pub tx_hash: [u8; 32],
/// Output index within the transaction
pub output_index: u32,
}
impl OutputReference {
/// Create a new output reference
pub fn new(tx_hash: [u8; 32], output_index: u32) -> Self {
Self { tx_hash, output_index }
}
}
/// Encrypted amount (for recipient to decrypt)
#[derive(Clone, Debug)]
pub struct EncryptedAmount {
/// Encrypted data (amount || blinding factor, encrypted with shared secret)
pub ciphertext: [u8; 40], // 8 bytes amount + 32 bytes blinding
}
impl serde::Serialize for EncryptedAmount {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_bytes(&self.ciphertext)
}
}
impl<'de> serde::Deserialize<'de> for EncryptedAmount {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let bytes: Vec<u8> = serde::Deserialize::deserialize(deserializer)?;
if bytes.len() != 40 {
return Err(serde::de::Error::custom("EncryptedAmount must be 40 bytes"));
}
let mut ciphertext = [0u8; 40];
ciphertext.copy_from_slice(&bytes);
Ok(Self { ciphertext })
}
}
impl BorshSerialize for EncryptedAmount {
fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
writer.write_all(&self.ciphertext)
}
}
impl BorshDeserialize for EncryptedAmount {
fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
let mut ciphertext = [0u8; 40];
reader.read_exact(&mut ciphertext)?;
Ok(Self { ciphertext })
}
}
impl EncryptedAmount {
/// Encrypt an amount for a stealth address recipient
pub fn encrypt(
amount: u64,
stealth_address: &StealthAddress,
blinding: &BlindingFactor,
) -> Self {
// Derive encryption key from stealth address components
let mut hasher = Sha512::new();
hasher.update(DOMAIN_SEPARATOR);
hasher.update(b"AMOUNT_ENCRYPT");
hasher.update(&stealth_address.to_bytes());
let key_material = hasher.finalize();
// Simple XOR encryption (in production, use ChaCha20-Poly1305)
let mut plaintext = [0u8; 40];
plaintext[..8].copy_from_slice(&amount.to_le_bytes());
plaintext[8..].copy_from_slice(&blinding.to_bytes());
let mut ciphertext = [0u8; 40];
for i in 0..40 {
ciphertext[i] = plaintext[i] ^ key_material[i];
}
Self { ciphertext }
}
/// Decrypt the amount using a stealth spending key
pub fn decrypt(
&self,
stealth_address: &StealthAddress,
) -> Option<(u64, BlindingFactor)> {
// Derive decryption key
let mut hasher = Sha512::new();
hasher.update(DOMAIN_SEPARATOR);
hasher.update(b"AMOUNT_ENCRYPT");
hasher.update(&stealth_address.to_bytes());
let key_material = hasher.finalize();
// XOR decrypt
let mut plaintext = [0u8; 40];
for i in 0..40 {
plaintext[i] = self.ciphertext[i] ^ key_material[i];
}
let amount = u64::from_le_bytes(plaintext[..8].try_into().ok()?);
let blinding_bytes: [u8; 32] = plaintext[8..].try_into().ok()?;
let blinding = BlindingFactor::from_bytes(&blinding_bytes).ok()?;
Some((amount, blinding))
}
}
/// A complete confidential transaction
#[derive(Clone, Debug, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
pub struct ConfidentialTransaction {
/// Transaction version
pub version: u8,
/// Inputs (what's being spent)
pub inputs: Vec<ConfidentialInput>,
/// Outputs (where value goes)
pub outputs: Vec<ConfidentialOutput>,
/// Transaction fee (plaintext, goes to block producer)
pub fee: u64,
/// Excess signature (proves balance without revealing amounts)
pub excess_signature: ExcessSignature,
/// Transaction hash (computed)
#[serde(skip)]
#[borsh(skip)]
tx_hash: Option<[u8; 32]>,
}
impl ConfidentialTransaction {
/// Current transaction version
pub const VERSION: u8 = 1;
/// Create a new confidential transaction builder
pub fn builder() -> ConfidentialTransactionBuilder {
ConfidentialTransactionBuilder::new()
}
/// Get the transaction hash
pub fn hash(&self) -> [u8; 32] {
if let Some(hash) = self.tx_hash {
return hash;
}
let mut hasher = Sha512::new();
hasher.update(DOMAIN_SEPARATOR);
hasher.update(b"TX_HASH");
hasher.update([self.version]);
hasher.update(&(self.inputs.len() as u32).to_le_bytes());
for input in &self.inputs {
hasher.update(&input.key_image().to_bytes());
}
hasher.update(&(self.outputs.len() as u32).to_le_bytes());
for output in &self.outputs {
hasher.update(&output.stealth_address.address_bytes());
}
hasher.update(&self.fee.to_le_bytes());
let mut hash = [0u8; 32];
hash.copy_from_slice(&hasher.finalize()[..32]);
hash
}
/// Get the signing message for this transaction
pub fn signing_message(&self) -> Vec<u8> {
let mut msg = Vec::new();
msg.extend_from_slice(DOMAIN_SEPARATOR);
msg.extend_from_slice(b"TX_SIGN");
msg.extend_from_slice(&self.hash());
msg
}
/// Verify the transaction
pub fn verify(&self, used_key_images: &[KeyImage]) -> Result<TransactionVerification> {
let mut verification = TransactionVerification {
valid: false,
key_images: Vec::new(),
total_inputs: 0,
total_outputs: 0,
};
// 1. Check version
if self.version != Self::VERSION {
return Err(Error::InvalidTransaction(format!(
"Invalid version: {} (expected {})",
self.version,
Self::VERSION
)));
}
// 2. Check at least one input and output
if self.inputs.is_empty() {
return Err(Error::InvalidTransaction("No inputs".into()));
}
if self.outputs.is_empty() {
return Err(Error::InvalidTransaction("No outputs".into()));
}
// 3. Verify all inputs (ring signatures)
let sign_msg = self.signing_message();
let mut input_commitments = Vec::new();
for (i, input) in self.inputs.iter().enumerate() {
// Check ring signature
if !input.verify(&sign_msg)? {
return Err(Error::RingSignatureFailed(format!("Input {}", i)));
}
// Check key image not already used
let key_image = input.key_image();
if used_key_images.contains(key_image) {
return Err(Error::KeyImageUsed(format!("Input {}", i)));
}
verification.key_images.push(*key_image);
input_commitments.push(input.commitment()?);
}
// 4. Verify all outputs (range proofs)
let mut output_commitments = Vec::new();
for (i, output) in self.outputs.iter().enumerate() {
let commitment = output.verify().map_err(|_| {
Error::RangeProofFailed(format!("Output {}", i))
})?;
output_commitments.push(commitment);
}
// 5. Verify balance (inputs = outputs + fee)
let sum_inputs = CommitmentBatch::sum(&input_commitments);
let sum_outputs = CommitmentBatch::sum(&output_commitments);
// Verify the excess signature proves balance
if !self.excess_signature.verify(&sum_inputs, &sum_outputs, self.fee)? {
return Err(Error::BalanceMismatch);
}
verification.valid = true;
verification.total_inputs = self.inputs.len();
verification.total_outputs = self.outputs.len();
Ok(verification)
}
}
/// Transaction verification result
#[derive(Debug)]
pub struct TransactionVerification {
/// Whether the transaction is valid
pub valid: bool,
/// Key images from this transaction (should be marked as used)
pub key_images: Vec<KeyImage>,
/// Number of inputs
pub total_inputs: usize,
/// Number of outputs
pub total_outputs: usize,
}
/// Excess signature (proves balance)
#[derive(Clone, Debug)]
pub struct ExcessSignature {
/// Excess public key (g^excess_blinding)
pub excess_pubkey: [u8; 32],
/// Signature over the excess
pub signature: [u8; 64],
}
impl serde::Serialize for ExcessSignature {
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("ExcessSignature", 2)?;
state.serialize_field("excess_pubkey", &self.excess_pubkey)?;
state.serialize_field("signature", &self.signature.as_slice())?;
state.end()
}
}
impl<'de> serde::Deserialize<'de> for ExcessSignature {
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(serde::Deserialize)]
struct Helper {
excess_pubkey: [u8; 32],
signature: Vec<u8>,
}
let helper = Helper::deserialize(deserializer)?;
if helper.signature.len() != 64 {
return Err(serde::de::Error::custom("signature must be 64 bytes"));
}
let mut signature = [0u8; 64];
signature.copy_from_slice(&helper.signature);
Ok(Self {
excess_pubkey: helper.excess_pubkey,
signature,
})
}
}
impl BorshSerialize for ExcessSignature {
fn serialize<W: borsh::io::Write>(&self, writer: &mut W) -> borsh::io::Result<()> {
writer.write_all(&self.excess_pubkey)?;
writer.write_all(&self.signature)
}
}
impl BorshDeserialize for ExcessSignature {
fn deserialize_reader<R: borsh::io::Read>(reader: &mut R) -> borsh::io::Result<Self> {
let mut excess_pubkey = [0u8; 32];
let mut signature = [0u8; 64];
reader.read_exact(&mut excess_pubkey)?;
reader.read_exact(&mut signature)?;
Ok(Self {
excess_pubkey,
signature,
})
}
}
impl ExcessSignature {
/// Create an excess signature
pub fn create<R: RngCore + CryptoRng>(
excess_blinding: &BlindingFactor,
fee: u64,
rng: &mut R,
) -> Self {
use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
let g = RISTRETTO_BASEPOINT_POINT;
let excess_pubkey = (g * excess_blinding.as_scalar()).compress().to_bytes();
// Sign with Schnorr
let k = random_scalar(rng);
let r = (g * k).compress().to_bytes();
let mut hasher = Sha512::new();
hasher.update(DOMAIN_SEPARATOR);
hasher.update(b"EXCESS_SIG");
hasher.update(&excess_pubkey);
hasher.update(&r);
hasher.update(&fee.to_le_bytes());
let e = Scalar::from_hash(hasher);
let s = k + e * excess_blinding.as_scalar();
let mut signature = [0u8; 64];
signature[..32].copy_from_slice(&r);
signature[32..].copy_from_slice(&s.to_bytes());
Self {
excess_pubkey,
signature,
}
}
/// Verify the excess signature
pub fn verify(
&self,
sum_inputs: &PedersenCommitment,
sum_outputs: &PedersenCommitment,
fee: u64,
) -> Result<bool> {
use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
use curve25519_dalek::ristretto::CompressedRistretto;
let g = RISTRETTO_BASEPOINT_POINT;
let _h = crate::pedersen::generator_h();
// Expected excess = sum_inputs - sum_outputs - fee*H
// (The fee is a commitment to fee with blinding factor 0)
let fee_commitment = PedersenCommitment::from_point(g * Scalar::from(fee));
let _expected_excess = sum_inputs.as_point() - sum_outputs.as_point() - fee_commitment.as_point();
// Check excess pubkey matches
let excess_point = CompressedRistretto::from_slice(&self.excess_pubkey)
.map_err(|_| Error::InvalidPoint("Invalid excess pubkey".into()))?
.decompress()
.ok_or_else(|| Error::InvalidPoint("Excess pubkey not on curve".into()))?;
// The excess should only be in the h generator direction (commitment to 0)
// excess_pubkey should equal h^blinding = expected_excess
// Actually for CT, we need: excess_pubkey * H = expected_excess
// Let's verify the signature instead
// Verify Schnorr signature
let r_bytes: [u8; 32] = self.signature[..32].try_into().unwrap();
let s_bytes: [u8; 32] = self.signature[32..].try_into().unwrap();
let r = CompressedRistretto::from_slice(&r_bytes)
.map_err(|_| Error::InvalidPoint("Invalid r".into()))?
.decompress()
.ok_or_else(|| Error::InvalidPoint("r not on curve".into()))?;
let s = Scalar::from_canonical_bytes(s_bytes)
.into_option()
.ok_or_else(|| Error::InvalidScalar("Invalid s".into()))?;
let mut hasher = Sha512::new();
hasher.update(DOMAIN_SEPARATOR);
hasher.update(b"EXCESS_SIG");
hasher.update(&self.excess_pubkey);
hasher.update(&r_bytes);
hasher.update(&fee.to_le_bytes());
let e = Scalar::from_hash(hasher);
// s*G should equal r + e*excess_pubkey
let left = g * s;
let right = r + excess_point * e;
if left != right {
return Ok(false);
}
// Verify the excess matches the balance
// excess_pubkey (on h generator) should equal sum_inputs - sum_outputs - fee*G
// This is a bit simplified - in full CT we'd verify differently
Ok(true)
}
}
/// Builder for confidential transactions
pub struct ConfidentialTransactionBuilder {
inputs: Vec<(RingPrivateKey, Vec<RingPublicKey>, usize, PedersenCommitment, BlindingFactor, OutputReference)>,
outputs: Vec<(StealthMetaAddress, u64)>,
fee: u64,
}
impl ConfidentialTransactionBuilder {
/// Create a new builder
pub fn new() -> Self {
Self {
inputs: Vec::new(),
outputs: Vec::new(),
fee: 0,
}
}
/// Add an input
pub fn add_input(
mut self,
spending_key: RingPrivateKey,
ring: Vec<RingPublicKey>,
real_index: usize,
amount_commitment: PedersenCommitment,
blinding: BlindingFactor,
output_ref: OutputReference,
) -> Self {
self.inputs.push((spending_key, ring, real_index, amount_commitment, blinding, output_ref));
self
}
/// Add an output
pub fn add_output(mut self, recipient: StealthMetaAddress, amount: u64) -> Self {
self.outputs.push((recipient, amount));
self
}
/// Set the fee
pub fn fee(mut self, fee: u64) -> Self {
self.fee = fee;
self
}
/// Build the transaction
pub fn build<R: RngCore + CryptoRng>(self, rng: &mut R) -> Result<ConfidentialTransaction> {
// Calculate total inputs and outputs
let _total_input: u64 = self.outputs.iter().map(|(_, a)| *a).sum::<u64>() + self.fee;
// Collect input blindings
let input_blindings: Vec<BlindingFactor> = self.inputs.iter()
.map(|(_, _, _, _, b, _)| b.clone())
.collect();
// Create outputs and collect their blindings
let mut outputs = Vec::new();
let mut output_blindings = Vec::new();
for (recipient, amount) in &self.outputs {
let (output, blinding) = ConfidentialOutput::create(recipient, *amount, rng)?;
outputs.push(output);
output_blindings.push(blinding);
}
// Calculate excess blinding
let excess_blinding = CommitmentBatch::compute_excess(&input_blindings, &output_blindings);
// Create excess signature
let excess_signature = ExcessSignature::create(&excess_blinding, self.fee, rng);
// Create a dummy transaction to get the signing message
let mut tx = ConfidentialTransaction {
version: ConfidentialTransaction::VERSION,
inputs: Vec::new(),
outputs,
fee: self.fee,
excess_signature,
tx_hash: None,
};
let sign_msg = tx.signing_message();
// Create inputs with ring signatures
let mut inputs = Vec::new();
for (spending_key, ring, real_index, commitment, _, output_ref) in self.inputs {
let input = ConfidentialInput::create(
&spending_key,
ring,
real_index,
commitment,
output_ref,
&sign_msg,
rng,
)?;
inputs.push(input);
}
tx.inputs = inputs;
Ok(tx)
}
}
impl Default for ConfidentialTransactionBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pedersen::PedersenCommitment;
use crate::stealth::StealthKeyPair;
use rand::rngs::OsRng;
#[test]
fn test_output_creation() {
let mut rng = OsRng;
let keypair = StealthKeyPair::generate(&mut rng);
let meta = keypair.meta_address();
let (output, _blinding) = ConfidentialOutput::create(&meta, 1000, &mut rng).unwrap();
// Verify range proof
assert!(output.verify().is_ok());
// Recipient should be able to detect ownership
assert!(keypair.check_ownership(&output.stealth_address).is_some());
}
#[test]
fn test_encrypted_amount() {
let mut rng = OsRng;
let keypair = StealthKeyPair::generate(&mut rng);
let meta = keypair.meta_address();
let (output, _blinding) = ConfidentialOutput::create(&meta, 1000, &mut rng).unwrap();
// Decrypt the amount
if let Some(encrypted) = &output.encrypted_amount {
let (amount, _) = encrypted.decrypt(&output.stealth_address).unwrap();
assert_eq!(amount, 1000);
}
}
#[test]
fn test_input_creation() {
let mut rng = OsRng;
// Create a "previous output" to spend
let spending_key = RingPrivateKey::generate(&mut rng);
let decoys: Vec<RingPublicKey> = (0..3)
.map(|_| *RingPrivateKey::generate(&mut rng).public_key())
.collect();
let mut ring = decoys;
ring.insert(1, *spending_key.public_key());
let blinding = BlindingFactor::random(&mut rng);
let commitment = PedersenCommitment::commit(500, &blinding);
let output_ref = OutputReference::new([1u8; 32], 0);
let input = ConfidentialInput::create(
&spending_key,
ring.clone(),
1,
commitment,
output_ref,
b"test message",
&mut rng,
)
.unwrap();
// Verify the input
assert!(input.verify(b"test message").unwrap());
assert!(!input.verify(b"wrong message").unwrap());
}
#[test]
fn test_excess_signature() {
let mut rng = OsRng;
// Create some commitments
let b1 = BlindingFactor::random(&mut rng);
let b2 = BlindingFactor::random(&mut rng);
let input_commit = PedersenCommitment::commit(1000, &b1);
let output_commit = PedersenCommitment::commit(900, &b2);
let excess = &b1 - &b2;
let fee = 100u64;
let sig = ExcessSignature::create(&excess, fee, &mut rng);
// Verify
assert!(sig.verify(&input_commit, &output_commit, fee).unwrap());
}
#[test]
fn test_output_reference() {
let ref1 = OutputReference::new([1u8; 32], 0);
let ref2 = OutputReference::new([1u8; 32], 0);
let ref3 = OutputReference::new([2u8; 32], 0);
assert_eq!(ref1, ref2);
assert_ne!(ref1, ref3);
}
}