• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tari-project / tari / 8189257874

07 Mar 2024 01:54PM UTC coverage: 76.004% (-0.01%) from 76.018%
8189257874

push

github

web-flow
ci(fix): add windows ffi libraries (#6193)

Description
Add Windows ffi libraries

Motivation and Context
Include Windows ffi libraries

How Has This Been Tested?
Built in local fork

What process can a PR reviewer use to test or verify this change?
---

<!-- Checklist -->
<!-- 1. Is the title of your PR in the form that would make nice release
notes? The title, excluding the conventional commit
tag, will be included exactly as is in the CHANGELOG, so please think
about it carefully. -->


Breaking Changes
---

- [x] None
- [ ] Requires data directory on base node to be deleted
- [ ] Requires hard fork
- [ ] Other - Please specify

<!-- Does this include a breaking change? If so, include this line as a
footer -->
<!-- BREAKING CHANGE: Description what the user should do, e.g. delete a
database, resync the chain -->

75204 of 98948 relevant lines covered (76.0%)

293479.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.5
/base_layer/core/src/transactions/transaction_components/transaction_output.rs
1
// Copyright 2018 The Tari Project
2
//
3
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
4
// following conditions are met:
5
//
6
// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
7
// disclaimer.
8
//
9
// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
10
// following disclaimer in the documentation and/or other materials provided with the distribution.
11
//
12
// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
13
// products derived from this software without specific prior written permission.
14
//
15
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
16
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
20
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
21
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
22
//
23
// Portions of this file were originally copyrighted (c) 2018 The Grin Developers, issued under the Apache License,
24
// Version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0.
25

26
use std::{
27
    cmp::Ordering,
28
    fmt::{Display, Formatter},
29
};
30

31
use blake2::Blake2b;
32
use borsh::{BorshDeserialize, BorshSerialize};
33
use digest::consts::{U32, U64};
34
use rand::rngs::OsRng;
35
use serde::{Deserialize, Serialize};
36
use tari_common_types::types::{
37
    ComAndPubSignature,
38
    Commitment,
39
    CommitmentFactory,
40
    FixedHash,
41
    PrivateKey,
42
    PublicKey,
43
    RangeProof,
44
    RangeProofService,
45
};
46
use tari_crypto::{
47
    commitment::HomomorphicCommitmentFactory,
48
    errors::RangeProofError,
49
    extended_range_proof::{ExtendedRangeProofService, Statement},
50
    keys::SecretKey,
51
    ristretto::bulletproofs_plus::RistrettoAggregatedPublicStatement,
52
    tari_utilities::hex::Hex,
53
};
54
use tari_hashing::TransactionHashDomain;
55
use tari_script::TariScript;
56

57
use super::TransactionOutputVersion;
58
use crate::{
59
    borsh::SerializedSize,
60
    consensus::DomainSeparatedConsensusHasher,
61
    covenants::Covenant,
62
    transactions::{
63
        tari_amount::MicroMinotari,
64
        transaction_components,
65
        transaction_components::{
66
            EncryptedData,
67
            OutputFeatures,
68
            OutputType,
69
            RangeProofType,
70
            TransactionError,
71
            TransactionInput,
72
            WalletOutput,
73
        },
74
    },
75
};
76

77
/// Output for a transaction, defining the new ownership of coins that are being transferred. The commitment is a
78
/// blinded/masked value for the output while the range proof guarantees the commitment includes a positive value
79
/// without overflow and the ownership of the private key.
80
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
37,303✔
81
pub struct TransactionOutput {
82
    pub version: TransactionOutputVersion,
83
    /// Options for an output's structure or use
84
    pub features: OutputFeatures,
85
    /// The homomorphic commitment representing the output amount
86
    pub commitment: Commitment,
87
    /// A proof that the commitment is in the right range
88
    pub proof: Option<RangeProof>,
89
    /// The script that will be executed when spending this output
90
    pub script: TariScript,
91
    /// Tari script offset pubkey, K_O
92
    pub sender_offset_public_key: PublicKey,
93
    /// UTXO signature with the script offset private key, k_O
94
    pub metadata_signature: ComAndPubSignature,
95
    /// The covenant that will be executed when spending this output
96
    #[serde(default)]
97
    pub covenant: Covenant,
98
    /// Encrypted value.
99
    pub encrypted_data: EncryptedData,
100
    /// The minimum value of the commitment that is proven by the range proof
101
    #[serde(default)]
102
    pub minimum_value_promise: MicroMinotari,
103
}
104

105
/// An output for a transaction, includes a range proof and Tari script metadata
106
impl TransactionOutput {
107
    /// Create new Transaction Output
108

109
    pub fn new(
22,854✔
110
        version: TransactionOutputVersion,
22,854✔
111
        features: OutputFeatures,
22,854✔
112
        commitment: Commitment,
22,854✔
113
        proof: Option<RangeProof>,
22,854✔
114
        script: TariScript,
22,854✔
115
        sender_offset_public_key: PublicKey,
22,854✔
116
        metadata_signature: ComAndPubSignature,
22,854✔
117
        covenant: Covenant,
22,854✔
118
        encrypted_data: EncryptedData,
22,854✔
119
        minimum_value_promise: MicroMinotari,
22,854✔
120
    ) -> TransactionOutput {
22,854✔
121
        TransactionOutput {
22,854✔
122
            version,
22,854✔
123
            features,
22,854✔
124
            commitment,
22,854✔
125
            proof,
22,854✔
126
            script,
22,854✔
127
            sender_offset_public_key,
22,854✔
128
            metadata_signature,
22,854✔
129
            covenant,
22,854✔
130
            encrypted_data,
22,854✔
131
            minimum_value_promise,
22,854✔
132
        }
22,854✔
133
    }
22,854✔
134

135
    pub fn new_current_version(
25✔
136
        features: OutputFeatures,
25✔
137
        commitment: Commitment,
25✔
138
        proof: Option<RangeProof>,
25✔
139
        script: TariScript,
25✔
140
        sender_offset_public_key: PublicKey,
25✔
141
        metadata_signature: ComAndPubSignature,
25✔
142
        covenant: Covenant,
25✔
143
        encrypted_data: EncryptedData,
25✔
144
        minimum_value_promise: MicroMinotari,
25✔
145
    ) -> TransactionOutput {
25✔
146
        TransactionOutput::new(
25✔
147
            TransactionOutputVersion::get_current_version(),
25✔
148
            features,
25✔
149
            commitment,
25✔
150
            proof,
25✔
151
            script,
25✔
152
            sender_offset_public_key,
25✔
153
            metadata_signature,
25✔
154
            covenant,
25✔
155
            encrypted_data,
25✔
156
            minimum_value_promise,
25✔
157
        )
25✔
158
    }
25✔
159

160
    /// Accessor method for the commitment contained in an output
161
    pub fn commitment(&self) -> &Commitment {
55✔
162
        &self.commitment
55✔
163
    }
55✔
164

165
    /// Accessor method for the encrypted_data contained in an output
166
    pub fn encrypted_data(&self) -> &EncryptedData {
14✔
167
        &self.encrypted_data
14✔
168
    }
14✔
169

170
    /// Accessor method for the range proof contained in an output
171
    pub fn proof_result(&self) -> Result<&RangeProof, RangeProofError> {
172
        if let Some(proof) = self.proof.as_ref() {
1,324✔
173
            Ok(proof)
1,324✔
174
        } else {
175
            Err(RangeProofError::InvalidRangeProof {
×
176
                reason: "Range proof not found".to_string(),
×
177
            })
×
178
        }
179
    }
1,324✔
180

181
    /// Accessor method for the range proof hex option display
182
    pub fn proof_hex_display(&self, full: bool) -> String {
183
        if let Some(proof) = self.proof.as_ref() {
×
184
            if full {
×
185
                "Some(".to_owned() + &proof.to_hex() + ")"
×
186
            } else {
187
                let proof_hex = proof.to_hex();
×
188
                if proof_hex.len() > 32 {
×
189
                    format!(
×
190
                        "Some({}..{})",
×
191
                        &proof_hex[0..16],
×
192
                        &proof_hex[proof_hex.len() - 16..proof_hex.len()]
×
193
                    )
×
194
                } else {
195
                    "Some(".to_owned() + &proof_hex + ")"
×
196
                }
197
            }
198
        } else {
199
            "None".to_string()
×
200
        }
201
    }
×
202

203
    /// Accessor method for the TariScript contained in an output
204
    pub fn script(&self) -> &TariScript {
1,575✔
205
        &self.script
1,575✔
206
    }
1,575✔
207

208
    pub fn hash(&self) -> FixedHash {
23,231✔
209
        let rp_hash = match &self.proof {
23,231✔
210
            Some(rp) => rp.hash(),
21,630✔
211
            None => FixedHash::zero(),
1,601✔
212
        };
213
        transaction_components::hash_output(
23,231✔
214
            self.version,
23,231✔
215
            &self.features,
23,231✔
216
            &self.commitment,
23,231✔
217
            &rp_hash,
23,231✔
218
            &self.script,
23,231✔
219
            &self.sender_offset_public_key,
23,231✔
220
            &self.metadata_signature,
23,231✔
221
            &self.covenant,
23,231✔
222
            &self.encrypted_data,
23,231✔
223
            self.minimum_value_promise,
23,231✔
224
        )
23,231✔
225
    }
23,231✔
226

227
    pub fn smt_hash(&self, mined_height: u64) -> FixedHash {
6,554✔
228
        let utxo_hash = self.hash();
6,554✔
229
        let smt_hash = DomainSeparatedConsensusHasher::<TransactionHashDomain, Blake2b<U32>>::new("smt_hash")
6,554✔
230
            .chain(&utxo_hash)
6,554✔
231
            .chain(&mined_height);
6,554✔
232

6,554✔
233
        match self.version {
6,554✔
234
            TransactionOutputVersion::V0 | TransactionOutputVersion::V1 => smt_hash.finalize().into(),
6,554✔
235
        }
6,554✔
236
    }
6,554✔
237

238
    /// Verify that range proof is valid
239
    pub fn verify_range_proof(&self, prover: &RangeProofService) -> Result<(), TransactionError> {
31✔
240
        match self.features.range_proof_type {
31✔
241
            RangeProofType::RevealedValue => match self.revealed_value_range_proof_check() {
2✔
242
                Ok(_) => Ok(()),
2✔
243
                Err(e) => Err(TransactionError::RangeProofError(format!(
×
244
                    "Recipient output RevealedValue range proof for commitment {} failed to verify ({})",
×
245
                    self.commitment.to_hex(),
×
246
                    e
×
247
                ))),
×
248
            },
249
            RangeProofType::BulletProofPlus => {
250
                let statement = RistrettoAggregatedPublicStatement {
29✔
251
                    statements: vec![Statement {
29✔
252
                        commitment: self.commitment.clone(),
29✔
253
                        minimum_value_promise: self.minimum_value_promise.as_u64(),
29✔
254
                    }],
29✔
255
                };
29✔
256
                match prover.verify_batch(vec![&self.proof_result()?.0], vec![&statement]) {
29✔
257
                    Ok(_) => Ok(()),
28✔
258
                    Err(e) => Err(TransactionError::RangeProofError(format!(
1✔
259
                        "Recipient output BulletProofPlus range proof for commitment {} failed to verify ({})",
1✔
260
                        self.commitment.to_hex(),
1✔
261
                        e
1✔
262
                    ))),
1✔
263
                }
264
            },
265
        }
266
    }
31✔
267

268
    // As an alternate range proof check, the value of the commitment with a deterministic ephemeral_commitment nonce
269
    // `r_a` of zero can optionally be bound into the metadata signature. This is a much faster check than the full
270
    // range proof verification.
271
    fn revealed_value_range_proof_check(&self) -> Result<(), RangeProofError> {
109✔
272
        if self.features.range_proof_type != RangeProofType::RevealedValue {
109✔
273
            return Err(RangeProofError::InvalidRangeProof {
×
274
                reason: format!(
×
275
                    "Commitment {} does not have a RevealedValue range proof",
×
276
                    self.commitment.to_hex()
×
277
                ),
×
278
            });
×
279
        }
109✔
280
        // Let's first verify that the metadata signature is valid.
281
        // Note: If normal code paths are followed, this is checked elsewhere already, but it is theoretically possible
282
        //       to meddle with the metadata signature after it has been verified and before it is used here, so we
283
        //       check it again. It is also a very cheap test in comparison to a range proof verification
284
        let e_bytes = match self.verify_metadata_signature_internal() {
109✔
285
            Ok(val) => val,
108✔
286
            Err(e) => {
1✔
287
                return Err(RangeProofError::InvalidRangeProof {
1✔
288
                    reason: format!("{}", e),
1✔
289
                });
1✔
290
            },
291
        };
292
        // Now we can perform the balance proof
293
        let e = PrivateKey::from_uniform_bytes(&e_bytes).unwrap();
108✔
294
        let value_as_private_key = PrivateKey::from(self.minimum_value_promise.as_u64());
108✔
295
        let commit_nonce_a = PrivateKey::default(); // This is the deterministic nonce `r_a` of zero
108✔
296
        if self.metadata_signature.u_a() == &(commit_nonce_a + e * value_as_private_key) {
108✔
297
            Ok(())
108✔
298
        } else {
299
            Err(RangeProofError::InvalidRangeProof {
×
300
                reason: format!(
×
301
                    "RevealedValue range proof check for commitment {} failed",
×
302
                    self.commitment.to_hex()
×
303
                ),
×
304
            })
×
305
        }
306
    }
109✔
307

308
    fn verify_metadata_signature_internal(&self) -> Result<[u8; 64], TransactionError> {
1,671✔
309
        let challenge = TransactionOutput::build_metadata_signature_challenge(
1,671✔
310
            &self.version,
1,671✔
311
            &self.script,
1,671✔
312
            &self.features,
1,671✔
313
            &self.sender_offset_public_key,
1,671✔
314
            self.metadata_signature.ephemeral_commitment(),
1,671✔
315
            self.metadata_signature.ephemeral_pubkey(),
1,671✔
316
            &self.commitment,
1,671✔
317
            &self.covenant,
1,671✔
318
            &self.encrypted_data,
1,671✔
319
            self.minimum_value_promise,
1,671✔
320
        );
1,671✔
321
        if !self.metadata_signature.verify_challenge(
1,671✔
322
            &self.commitment,
1,671✔
323
            &self.sender_offset_public_key,
1,671✔
324
            &challenge,
1,671✔
325
            &CommitmentFactory::default(),
1,671✔
326
            &mut OsRng,
1,671✔
327
        ) {
1,671✔
328
            return Err(TransactionError::InvalidSignatureError(
13✔
329
                "Metadata signature not valid!".to_string(),
13✔
330
            ));
13✔
331
        }
1,658✔
332
        Ok(challenge)
1,658✔
333
    }
1,671✔
334

335
    /// Verify that the metadata signature is valid
336
    pub fn verify_metadata_signature(&self) -> Result<(), TransactionError> {
1,562✔
337
        let _challenge = self.verify_metadata_signature_internal()?;
1,562✔
338
        Ok(())
1,550✔
339
    }
1,562✔
340

341
    pub fn verify_validator_node_signature(&self) -> Result<(), TransactionError> {
342
        if let Some(validator_node_reg) = self
×
343
            .features
×
344
            .sidechain_feature
×
345
            .as_ref()
×
346
            .and_then(|f| f.validator_node_registration())
×
347
        {
348
            if !validator_node_reg.is_valid_signature_for(&[]) {
×
349
                return Err(TransactionError::InvalidSignatureError(
×
350
                    "Validator node signature is not valid!".to_string(),
×
351
                ));
×
352
            }
×
353
        }
×
354
        Ok(())
×
355
    }
×
356

357
    /// Attempt to verify a recovered mask (blinding factor) for a proof against the commitment.
358
    pub fn verify_mask(
3✔
359
        &self,
3✔
360
        prover: &RangeProofService,
3✔
361
        spending_key: &PrivateKey,
3✔
362
        value: u64,
3✔
363
    ) -> Result<bool, TransactionError> {
3✔
364
        Ok(prover.verify_mask(&self.commitment, spending_key, value)?)
3✔
365
    }
3✔
366

367
    /// This will check if the input and the output is the same commitment by looking at the commitment and features.
368
    /// This will ignore the output range proof
369
    #[inline]
370
    pub fn is_equal_to(&self, output: &TransactionInput) -> bool {
11✔
371
        self.hash() == output.output_hash()
11✔
372
    }
11✔
373

374
    /// Returns true if the output is a coinbase, otherwise false
375
    pub fn is_coinbase(&self) -> bool {
4,557✔
376
        matches!(self.features.output_type, OutputType::Coinbase)
4,557✔
377
    }
4,557✔
378

379
    /// Returns true if the output is burned, otherwise false
380
    pub fn is_burned(&self) -> bool {
10,992✔
381
        matches!(self.features.output_type, OutputType::Burn)
10,992✔
382
    }
10,992✔
383

384
    /// Convenience function that calculates the challenge for the metadata commitment signature
385
    pub fn build_metadata_signature_challenge(
1,671✔
386
        version: &TransactionOutputVersion,
1,671✔
387
        script: &TariScript,
1,671✔
388
        features: &OutputFeatures,
1,671✔
389
        sender_offset_public_key: &PublicKey,
1,671✔
390
        ephemeral_commitment: &Commitment,
1,671✔
391
        ephemeral_pubkey: &PublicKey,
1,671✔
392
        commitment: &Commitment,
1,671✔
393
        covenant: &Covenant,
1,671✔
394
        encrypted_data: &EncryptedData,
1,671✔
395
        minimum_value_promise: MicroMinotari,
1,671✔
396
    ) -> [u8; 64] {
1,671✔
397
        // We build the message separately to help with hardware wallet support. This reduces the amount of data that
1,671✔
398
        // needs to be transferred in order to sign the signature.
1,671✔
399
        let message = TransactionOutput::metadata_signature_message_from_parts(
1,671✔
400
            version,
1,671✔
401
            script,
1,671✔
402
            features,
1,671✔
403
            covenant,
1,671✔
404
            encrypted_data,
1,671✔
405
            &minimum_value_promise,
1,671✔
406
        );
1,671✔
407
        TransactionOutput::finalize_metadata_signature_challenge(
1,671✔
408
            version,
1,671✔
409
            sender_offset_public_key,
1,671✔
410
            ephemeral_commitment,
1,671✔
411
            ephemeral_pubkey,
1,671✔
412
            commitment,
1,671✔
413
            &message,
1,671✔
414
        )
1,671✔
415
    }
1,671✔
416

417
    pub fn finalize_metadata_signature_challenge(
11,293✔
418
        version: &TransactionOutputVersion,
11,293✔
419
        sender_offset_public_key: &PublicKey,
11,293✔
420
        ephemeral_commitment: &Commitment,
11,293✔
421
        ephemeral_pubkey: &PublicKey,
11,293✔
422
        commitment: &Commitment,
11,293✔
423
        message: &[u8; 32],
11,293✔
424
    ) -> [u8; 64] {
11,293✔
425
        let common = DomainSeparatedConsensusHasher::<TransactionHashDomain, Blake2b<U64>>::new("metadata_signature")
11,293✔
426
            .chain(ephemeral_pubkey)
11,293✔
427
            .chain(ephemeral_commitment)
11,293✔
428
            .chain(sender_offset_public_key)
11,293✔
429
            .chain(commitment)
11,293✔
430
            .chain(&message);
11,293✔
431
        match version {
11,293✔
432
            TransactionOutputVersion::V0 | TransactionOutputVersion::V1 => common.finalize().into(),
11,293✔
433
        }
11,293✔
434
    }
11,293✔
435

436
    /// Convenience function to get the entire metadata signature message for the challenge. This contains all data
437
    /// outside of the signing keys and nonces.
438
    pub fn metadata_signature_message(wallet_output: &WalletOutput) -> [u8; 32] {
10✔
439
        TransactionOutput::metadata_signature_message_from_parts(
10✔
440
            &wallet_output.version,
10✔
441
            &wallet_output.script,
10✔
442
            &wallet_output.features,
10✔
443
            &wallet_output.covenant,
10✔
444
            &wallet_output.encrypted_data,
10✔
445
            &wallet_output.minimum_value_promise,
10✔
446
        )
10✔
447
    }
10✔
448

449
    /// Convenience function to create the entire metadata signature message for the challenge. This contains all data
450
    /// outside of the signing keys and nonces.
451
    pub fn metadata_signature_message_from_parts(
6,504✔
452
        version: &TransactionOutputVersion,
6,504✔
453
        script: &TariScript,
6,504✔
454
        features: &OutputFeatures,
6,504✔
455
        covenant: &Covenant,
6,504✔
456
        encrypted_data: &EncryptedData,
6,504✔
457
        minimum_value_promise: &MicroMinotari,
6,504✔
458
    ) -> [u8; 32] {
6,504✔
459
        let common = DomainSeparatedConsensusHasher::<TransactionHashDomain, Blake2b<U32>>::new("metadata_message")
6,504✔
460
            .chain(version)
6,504✔
461
            .chain(script)
6,504✔
462
            .chain(features)
6,504✔
463
            .chain(covenant)
6,504✔
464
            .chain(encrypted_data)
6,504✔
465
            .chain(minimum_value_promise);
6,504✔
466
        match version {
6,504✔
467
            TransactionOutputVersion::V0 | TransactionOutputVersion::V1 => common.finalize().into(),
6,504✔
468
        }
6,504✔
469
    }
6,504✔
470

471
    pub fn get_features_and_scripts_size(&self) -> std::io::Result<usize> {
2,912✔
472
        Ok(self.features.get_serialized_size()? +
2,912✔
473
            self.script.get_serialized_size()? +
2,912✔
474
            self.covenant.get_serialized_size()?)
2,912✔
475
    }
2,912✔
476
}
477

478
impl Default for TransactionOutput {
479
    fn default() -> Self {
3✔
480
        TransactionOutput::new_current_version(
3✔
481
            OutputFeatures::default(),
3✔
482
            CommitmentFactory::default().zero(),
3✔
483
            Some(RangeProof::default()),
3✔
484
            TariScript::default(),
3✔
485
            PublicKey::default(),
3✔
486
            ComAndPubSignature::default(),
3✔
487
            Covenant::default(),
3✔
488
            EncryptedData::default(),
3✔
489
            MicroMinotari::zero(),
3✔
490
        )
3✔
491
    }
3✔
492
}
493

494
impl Display for TransactionOutput {
495
    fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
×
496
        write!(
×
497
            fmt,
×
498
            "({}, {}) [{:?}], Script: ({}), Offset Pubkey: ({}), Metadata Signature: ({}, {}, {}, {}, {}), Encrypted \
×
499
             data ({}), Proof: {}",
×
500
            self.commitment.to_hex(),
×
501
            self.hash(),
×
502
            self.features,
×
503
            self.script,
×
504
            self.sender_offset_public_key.to_hex(),
×
505
            self.metadata_signature.u_a().to_hex(),
×
506
            self.metadata_signature.u_x().to_hex(),
×
507
            self.metadata_signature.u_y().to_hex(),
×
508
            self.metadata_signature.ephemeral_commitment().to_hex(),
×
509
            self.metadata_signature.ephemeral_pubkey().to_hex(),
×
510
            self.encrypted_data.hex_display(false),
×
511
            self.proof_hex_display(false),
×
512
        )
×
513
    }
×
514
}
515

516
impl PartialOrd for TransactionOutput {
517
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
30,274✔
518
        self.commitment.partial_cmp(&other.commitment)
30,274✔
519
    }
30,274✔
520
}
521

522
impl Ord for TransactionOutput {
523
    fn cmp(&self, other: &Self) -> Ordering {
×
524
        self.commitment.cmp(&other.commitment)
×
525
    }
×
526
}
527

528
/// Performs batched range proof verification for an arbitrary number of outputs
529
pub fn batch_verify_range_proofs(
141✔
530
    prover: &RangeProofService,
141✔
531
    outputs: &[&TransactionOutput],
141✔
532
) -> Result<(), RangeProofError> {
141✔
533
    let bulletproof_plus_proofs = outputs
141✔
534
        .iter()
141✔
535
        .filter(|o| o.features.range_proof_type == RangeProofType::BulletProofPlus)
1,399✔
536
        .copied()
141✔
537
        .collect::<Vec<&TransactionOutput>>();
141✔
538
    if !bulletproof_plus_proofs.is_empty() {
141✔
539
        let mut statements = Vec::with_capacity(bulletproof_plus_proofs.len());
136✔
540
        let mut proofs = Vec::with_capacity(bulletproof_plus_proofs.len());
136✔
541
        for output in &bulletproof_plus_proofs {
1,430✔
542
            statements.push(RistrettoAggregatedPublicStatement {
1,294✔
543
                statements: vec![Statement {
1,294✔
544
                    commitment: output.commitment.clone(),
1,294✔
545
                    minimum_value_promise: output.minimum_value_promise.into(),
1,294✔
546
                }],
1,294✔
547
            });
1,294✔
548
            proofs.push(output.proof_result()?.as_vec());
1,294✔
549
        }
550

551
        // Attempt to verify the range proofs in a batch
552
        prover.verify_batch(proofs, statements.iter().collect())?;
136✔
553
    }
5✔
554

555
    let revealed_value_proofs = outputs
139✔
556
        .iter()
139✔
557
        .filter(|o| o.features.range_proof_type == RangeProofType::RevealedValue)
1,392✔
558
        .copied()
139✔
559
        .collect::<Vec<&TransactionOutput>>();
139✔
560
    for output in revealed_value_proofs {
244✔
561
        output.revealed_value_range_proof_check()?;
105✔
562
    }
563

564
    // An empty batch is valid
565
    Ok(())
139✔
566
}
141✔
567

568
#[cfg(test)]
569
mod test {
570
    use tari_crypto::errors::RangeProofError;
571

572
    use super::{batch_verify_range_proofs, TransactionOutput};
573
    use crate::transactions::{
574
        key_manager::{create_memory_db_key_manager, MemoryDbKeyManager, TransactionKeyManagerInterface},
575
        tari_amount::MicroMinotari,
576
        test_helpers::{TestParams, UtxoTestParams},
577
        transaction_components::{OutputFeatures, RangeProofType},
578
        CryptoFactories,
579
    };
580

581
    #[tokio::test]
1✔
582
    async fn it_builds_correctly() {
1✔
583
        let factories = CryptoFactories::default();
1✔
584
        let key_manager = create_memory_db_key_manager();
1✔
585
        let test_params = TestParams::new(&key_manager).await;
1✔
586

587
        let value = MicroMinotari(10);
1✔
588
        let minimum_value_promise = MicroMinotari(10);
1✔
589
        let tx_output = create_output(
1✔
590
            &test_params,
1✔
591
            value,
1✔
592
            minimum_value_promise,
1✔
593
            RangeProofType::BulletProofPlus,
1✔
594
            &key_manager,
1✔
595
        )
1✔
596
        .await
×
597
        .unwrap();
1✔
598

1✔
599
        assert!(tx_output.verify_range_proof(&factories.range_proof).is_ok());
1✔
600
        assert!(tx_output.verify_metadata_signature().is_ok());
1✔
601
        let (_, recovered_value) = key_manager.try_output_key_recovery(&tx_output, None).await.unwrap();
1✔
602
        assert_eq!(recovered_value, value);
1✔
603
    }
604

605
    #[tokio::test]
1✔
606
    async fn it_does_not_verify_incorrect_minimum_value() {
1✔
607
        let factories = CryptoFactories::default();
1✔
608
        let key_manager = create_memory_db_key_manager();
1✔
609
        let test_params = TestParams::new(&key_manager).await;
1✔
610

611
        let value = MicroMinotari(10);
1✔
612
        let minimum_value_promise = MicroMinotari(11);
1✔
613
        let tx_output = create_invalid_output(
1✔
614
            &test_params,
1✔
615
            value,
1✔
616
            minimum_value_promise,
1✔
617
            RangeProofType::BulletProofPlus,
1✔
618
            &key_manager,
1✔
619
        )
1✔
620
        .await;
×
621

622
        assert!(tx_output.verify_range_proof(&factories.range_proof).is_err());
1✔
623
    }
624

625
    #[tokio::test]
1✔
626
    async fn it_does_batch_verify_correct_minimum_values() {
1✔
627
        let factories = CryptoFactories::default();
1✔
628
        let key_manager = create_memory_db_key_manager();
1✔
629
        let test_params = TestParams::new(&key_manager).await;
1✔
630

631
        let outputs = [
1✔
632
            &create_output(
1✔
633
                &test_params,
1✔
634
                MicroMinotari(10),
1✔
635
                MicroMinotari::zero(),
1✔
636
                RangeProofType::BulletProofPlus,
1✔
637
                &key_manager,
1✔
638
            )
1✔
639
            .await
×
640
            .unwrap(),
1✔
641
            &create_output(
1✔
642
                &test_params,
1✔
643
                MicroMinotari(10),
1✔
644
                MicroMinotari(5),
1✔
645
                RangeProofType::BulletProofPlus,
1✔
646
                &key_manager,
1✔
647
            )
1✔
648
            .await
×
649
            .unwrap(),
1✔
650
            &create_output(
1✔
651
                &test_params,
1✔
652
                MicroMinotari(10),
1✔
653
                MicroMinotari(10),
1✔
654
                RangeProofType::BulletProofPlus,
1✔
655
                &key_manager,
1✔
656
            )
1✔
657
            .await
×
658
            .unwrap(),
1✔
659
        ];
1✔
660

1✔
661
        assert!(batch_verify_range_proofs(&factories.range_proof, &outputs,).is_ok());
1✔
662
    }
663

664
    #[tokio::test]
1✔
665
    async fn it_does_batch_verify_with_mixed_range_proof_types() {
1✔
666
        let key_manager = create_memory_db_key_manager();
1✔
667
        let factories = CryptoFactories::default();
1✔
668
        let test_params = TestParams::new(&key_manager).await;
1✔
669

670
        let outputs = [
1✔
671
            &create_output(
1✔
672
                &test_params,
1✔
673
                MicroMinotari(10),
1✔
674
                MicroMinotari::zero(),
1✔
675
                RangeProofType::BulletProofPlus,
1✔
676
                &key_manager,
1✔
677
            )
1✔
678
            .await
×
679
            .unwrap(),
1✔
680
            &create_output(
1✔
681
                &test_params,
1✔
682
                MicroMinotari(10),
1✔
683
                MicroMinotari(10),
1✔
684
                RangeProofType::RevealedValue,
1✔
685
                &key_manager,
1✔
686
            )
1✔
687
            .await
×
688
            .unwrap(),
1✔
689
            &create_output(
1✔
690
                &test_params,
1✔
691
                MicroMinotari(10),
1✔
692
                MicroMinotari::zero(),
1✔
693
                RangeProofType::BulletProofPlus,
1✔
694
                &key_manager,
1✔
695
            )
1✔
696
            .await
×
697
            .unwrap(),
1✔
698
            &create_output(
1✔
699
                &test_params,
1✔
700
                MicroMinotari(20),
1✔
701
                MicroMinotari(20),
1✔
702
                RangeProofType::RevealedValue,
1✔
703
                &key_manager,
1✔
704
            )
1✔
705
            .await
×
706
            .unwrap(),
1✔
707
        ];
1✔
708

1✔
709
        assert!(batch_verify_range_proofs(&factories.range_proof, &outputs,).is_ok());
1✔
710
    }
711

712
    #[tokio::test]
1✔
713
    async fn invalid_revealed_value_proofs_are_blocked() {
1✔
714
        let key_manager = create_memory_db_key_manager();
1✔
715
        let test_params = TestParams::new(&key_manager).await;
1✔
716
        assert!(create_output(
1✔
717
            &test_params,
1✔
718
            MicroMinotari(20),
1✔
719
            MicroMinotari::zero(),
1✔
720
            RangeProofType::BulletProofPlus,
1✔
721
            &key_manager
1✔
722
        )
1✔
723
        .await
×
724
        .is_ok());
1✔
725
        match create_output(
1✔
726
            &test_params,
1✔
727
            MicroMinotari(20),
1✔
728
            MicroMinotari::zero(),
1✔
729
            RangeProofType::RevealedValue,
1✔
730
            &key_manager,
1✔
731
        )
1✔
732
        .await
×
733
        {
734
            Ok(_) => panic!("Should not have been able to create output"),
×
735
            Err(e) => assert_eq!(
1✔
736
                e,
1✔
737
                "A range proof construction or verification has produced an error: Invalid revealed value: Expected \
1✔
738
                 20 µT, received 0 µT"
1✔
739
            ),
1✔
740
        }
741
    }
742

743
    #[tokio::test]
1✔
744
    async fn revealed_value_proofs_only_succeed_with_valid_metadata_signatures() {
1✔
745
        let key_manager = create_memory_db_key_manager();
1✔
746
        let test_params = TestParams::new(&key_manager).await;
1✔
747
        let mut output = create_output(
1✔
748
            &test_params,
1✔
749
            MicroMinotari(20),
1✔
750
            MicroMinotari(20),
1✔
751
            RangeProofType::RevealedValue,
1✔
752
            &key_manager,
1✔
753
        )
1✔
754
        .await
×
755
        .unwrap();
1✔
756
        assert!(output.verify_metadata_signature().is_ok());
1✔
757
        assert!(output.revealed_value_range_proof_check().is_ok());
1✔
758

759
        output.features.maturity += 1;
1✔
760
        assert!(output.verify_metadata_signature().is_err());
1✔
761
        match output.revealed_value_range_proof_check() {
1✔
762
            Ok(_) => panic!("Should not have passed check"),
×
763
            Err(e) => assert_eq!(e, RangeProofError::InvalidRangeProof {
1✔
764
                reason: "Signature is invalid: Metadata signature not valid!".to_string()
1✔
765
            }),
1✔
766
        }
767
    }
768

769
    #[tokio::test]
1✔
770
    async fn it_does_not_batch_verify_incorrect_minimum_values() {
1✔
771
        let factories = CryptoFactories::default();
1✔
772
        let key_manager = create_memory_db_key_manager();
1✔
773
        let test_params = TestParams::new(&key_manager).await;
1✔
774

775
        let outputs = [
1✔
776
            &create_output(
1✔
777
                &test_params,
1✔
778
                MicroMinotari(10),
1✔
779
                MicroMinotari(10),
1✔
780
                RangeProofType::BulletProofPlus,
1✔
781
                &key_manager,
1✔
782
            )
1✔
783
            .await
×
784
            .unwrap(),
1✔
785
            &create_invalid_output(
1✔
786
                &test_params,
1✔
787
                MicroMinotari(10),
1✔
788
                MicroMinotari(11),
1✔
789
                RangeProofType::BulletProofPlus,
1✔
790
                &key_manager,
1✔
791
            )
1✔
792
            .await,
×
793
        ];
794

795
        assert!(batch_verify_range_proofs(&factories.range_proof, &outputs).is_err());
1✔
796
    }
797

798
    async fn create_output(
14✔
799
        test_params: &TestParams,
14✔
800
        value: MicroMinotari,
14✔
801
        minimum_value_promise: MicroMinotari,
14✔
802
        range_proof_type: RangeProofType,
14✔
803
        key_manager: &MemoryDbKeyManager,
14✔
804
    ) -> Result<TransactionOutput, String> {
14✔
805
        let utxo = test_params
14✔
806
            .create_output(
14✔
807
                UtxoTestParams {
14✔
808
                    value,
14✔
809
                    minimum_value_promise,
14✔
810
                    features: OutputFeatures {
14✔
811
                        range_proof_type,
14✔
812
                        ..Default::default()
14✔
813
                    },
14✔
814
                    ..Default::default()
14✔
815
                },
14✔
816
                key_manager,
14✔
817
            )
14✔
818
            .await;
×
819
        utxo?
14✔
820
            .to_transaction_output(key_manager)
14✔
821
            .await
×
822
            .map_err(|e| e.to_string())
14✔
823
    }
14✔
824

825
    async fn create_invalid_output(
2✔
826
        test_params: &TestParams,
2✔
827
        value: MicroMinotari,
2✔
828
        minimum_value_promise: MicroMinotari,
2✔
829
        range_proof_type: RangeProofType,
2✔
830
        key_manager: &MemoryDbKeyManager,
2✔
831
    ) -> TransactionOutput {
2✔
832
        // we need first to create a valid minimum value, regardless of the minimum_value_promise
833
        // because this test function should allow creating an invalid proof for later testing
834
        let mut output = create_output(test_params, value, MicroMinotari::zero(), range_proof_type, key_manager)
2✔
835
            .await
×
836
            .unwrap();
2✔
837

2✔
838
        // Now we can updated the minimum value, even to an invalid value
2✔
839
        output.minimum_value_promise = minimum_value_promise;
2✔
840

2✔
841
        output
2✔
842
    }
2✔
843
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc