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

tari-project / tari / 17549972632

08 Sep 2025 11:57AM UTC coverage: 60.917% (-0.2%) from 61.111%
17549972632

push

github

web-flow
feat: wallet multisignature feature (#7481)

Description
---
This PR introduces support for multisignature (multisig) functionality
across the CLI and gRPC interfaces.

Cli methods provided
- SignMessage - Needed in Tari_Universe to sign messages 
- SignScriptMessage- Needed in tari_universe to sign script message
- PrepareDepositMultisigTransaction - Test purpose only. Create
transaction for converting normal utxo into multisign one. Without
signing it (needed in tari_universe for offline signing)
- PrepareWithdrawMultisigTransaction - Test purpose only. Create
transaction for converting multisig utxo into normal one. Without
signing it (needed in tari_universe for offline signing)
- SignOneSidedDepositMultisigTransaction - Sign the Prepared deposit
transaction (for offline signing in tari_universe).
- SignOneSidedWithdrawMultisigTransaction - Sign the Prepared withdraw
transaction (for offline signing in tari_universe).
- CreateMultisigUtxo - It is the same as
PrepareDepositMultisigTransaction /
SignOneSidedDepositMultisigTransaction but it can be called only from
cli.(won't work with offline signing)
- GetMultisigUtxoData - Return Data from utxo that are needed for
multisig (challenge / public_offset_key).
- SendMultisigUtxo - It is the same as
PrepareWithdrawMultisigTransaction /
SignOneSidedWithdrawMultisigTransaction but it can be called only from
cli.(won't work with offline signing)


GRPC methods
- PrepareDepositMultisigTransaction 
- PrepareWithdrawMultisigTransaction - transaction for converting
multisig utxo into normal one. Without signing it (needed in
tari_universe for offline signing)

Motivation and Context
---

How Has This Been Tested?
---
Written unit test and manually both from CLI and from tari universe
offline signing.

What process can a PR reviewer use to test or verify this change?
---
The change can be tested from the wallet using Docker.

**1. Create a multisignature UTXO (replace with your own data):**

```
docker compose ... (continued)

847 of 1880 new or added lines in 17 files covered. (45.05%)

8 existing lines in 5 files now uncovered.

73757 of 121077 relevant lines covered (60.92%)

296113.71 hits per line

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

69.71
/base_layer/transaction_components/src/key_manager/wrapper.rs
1
//  Copyright 2023, 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
use std::{
24
    fmt::{Debug, Formatter},
25
    sync::Arc,
26
};
27

28
use blake2::Blake2b;
29
use digest::consts::U64;
30
use tari_common_types::{
31
    seeds::cipher_seed::CipherSeed,
32
    tari_address::TariAddress,
33
    types::{
34
        ComAndPubSignature,
35
        CommsDHKE,
36
        CompressedCommitment,
37
        CompressedPublicKey,
38
        CompressedSignature,
39
        PrivateKey,
40
        RangeProof,
41
        WalletMessageSchnorrSignature,
42
    },
43
    wallet_types::WalletType,
44
};
45
use tari_crypto::{hashing::DomainSeparatedHash, ristretto::RistrettoComSig};
46
use tari_script::{CompressedCheckSigSchnorrSignature, TariScript};
47
use tokio::sync::RwLock;
48

49
use crate::{
50
    crypto_factories::CryptoFactories,
51
    key_manager::{
52
        error::KeyManagerServiceError,
53
        interface::{SecretTransactionKeyManagerInterface, TariKeyAndId, TransactionKeyManagerBackend, TxoStage},
54
        AddResult,
55
        TariKeyId,
56
        TransactionKeyManagerInner,
57
        TransactionKeyManagerInterface,
58
    },
59
    transaction_components::{
60
        EncryptedData,
61
        KernelFeatures,
62
        MemoField,
63
        RangeProofType,
64
        TransactionError,
65
        TransactionInputVersion,
66
        TransactionKernelVersion,
67
        TransactionOutputVersion,
68
    },
69
    MicroMinotari,
70
};
71

72
/// The key manager provides a hierarchical key derivation function (KDF) that derives uniformly random secret keys from
73
/// a single seed key for arbitrary branches, using an implementation of `KeyManagerBackend` to store the current index
74
/// for each branch.
75
///
76
/// This handle can be cloned cheaply and safely shared across multiple threads.
77
#[derive(Clone)]
78
pub struct TransactionKeyManagerWrapper<TBackend> {
79
    transaction_key_manager_inner: Arc<RwLock<TransactionKeyManagerInner<TBackend>>>,
80
}
81

82
impl<TBackend> TransactionKeyManagerWrapper<TBackend>
83
where TBackend: TransactionKeyManagerBackend + 'static
84
{
85
    /// Creates a new key manager.
86
    /// * `master_seed` is the primary seed that will be used to derive all unique branch keys with their indexes
87
    /// * `db` implements `KeyManagerBackend` and is used for persistent storage of branches and indices.
88
    pub async fn new(
195✔
89
        master_seed: CipherSeed,
195✔
90
        db: TBackend,
195✔
91
        crypto_factories: CryptoFactories,
195✔
92
        wallet_type: Arc<WalletType>,
195✔
93
    ) -> Result<Self, KeyManagerServiceError> {
195✔
94
        Ok(TransactionKeyManagerWrapper {
195✔
95
            transaction_key_manager_inner: Arc::new(RwLock::new(
195✔
96
                TransactionKeyManagerInner::new(master_seed, db, crypto_factories, wallet_type).await?,
195✔
97
            )),
98
        })
99
    }
195✔
100

101
    /// Get the wallet type
102
    pub async fn get_wallet_type(&self) -> Arc<WalletType> {
×
103
        self.transaction_key_manager_inner.read().await.get_wallet_type()
×
104
    }
×
105

106
    /// Get the birthday of the wallet seed
107
    pub async fn get_birthday(&self) -> u16 {
×
108
        self.transaction_key_manager_inner.read().await.master_seed().birthday()
×
109
    }
×
110
}
111

112
#[async_trait::async_trait]
113
impl<TBackend> TransactionKeyManagerInterface for TransactionKeyManagerWrapper<TBackend>
114
where TBackend: TransactionKeyManagerBackend + 'static
115
{
116
    async fn add_new_branch<T: Into<String> + Send>(&self, branch: T) -> Result<AddResult, KeyManagerServiceError> {
×
117
        self.transaction_key_manager_inner
×
118
            .write()
×
119
            .await
×
120
            .add_key_manager_branch(&branch.into())
×
121
            .await
×
122
    }
×
123

124
    async fn get_next_key<T: Into<String> + Send>(&self, branch: T) -> Result<TariKeyAndId, KeyManagerServiceError> {
18,207✔
125
        self.transaction_key_manager_inner
18,207✔
126
            .read()
18,207✔
127
            .await
18,207✔
128
            .get_next_key(&branch.into())
18,207✔
129
            .await
18,207✔
130
    }
36,414✔
131

132
    async fn get_random_key(&self) -> Result<TariKeyAndId, KeyManagerServiceError> {
×
133
        self.transaction_key_manager_inner.read().await.get_random_key().await
×
134
    }
×
135

136
    async fn get_static_key<T: Into<String> + Send>(&self, branch: T) -> Result<TariKeyId, KeyManagerServiceError> {
×
137
        self.transaction_key_manager_inner
×
138
            .read()
×
139
            .await
×
140
            .get_static_key(&branch.into())
×
141
            .await
×
142
    }
×
143

144
    async fn get_public_key_at_key_id(
145
        &self,
146
        key_id: &TariKeyId,
147
    ) -> Result<CompressedPublicKey, KeyManagerServiceError> {
3,418✔
148
        self.transaction_key_manager_inner
3,418✔
149
            .read()
3,418✔
150
            .await
3,418✔
151
            .get_public_key_at_key_id(key_id)
3,418✔
152
            .await
3,418✔
153
    }
6,836✔
154

155
    async fn import_key(&self, private_key: PrivateKey) -> Result<TariKeyId, KeyManagerServiceError> {
631✔
156
        self.transaction_key_manager_inner
631✔
157
            .read()
631✔
158
            .await
631✔
159
            .import_key(private_key)
631✔
160
            .await
631✔
161
    }
1,262✔
162

163
    async fn get_commitment(
164
        &self,
165
        commitment_mask_key_id: &TariKeyId,
166
        value: &PrivateKey,
167
    ) -> Result<CompressedCommitment, KeyManagerServiceError> {
1,658✔
168
        self.transaction_key_manager_inner
1,658✔
169
            .read()
1,658✔
170
            .await
1,658✔
171
            .get_commitment(commitment_mask_key_id, value)
1,658✔
172
            .await
1,658✔
173
    }
3,316✔
174

175
    async fn verify_mask(
176
        &self,
177
        commitment: &CompressedCommitment,
178
        commitment_mask_key_id: &TariKeyId,
179
        value: u64,
180
    ) -> Result<bool, KeyManagerServiceError> {
1✔
181
        self.transaction_key_manager_inner
1✔
182
            .read()
1✔
183
            .await
1✔
184
            .verify_mask(commitment, commitment_mask_key_id, value)
1✔
185
            .await
1✔
186
    }
2✔
187

188
    async fn get_view_key(&self) -> Result<TariKeyAndId, KeyManagerServiceError> {
203✔
189
        self.transaction_key_manager_inner.read().await.get_view_key().await
203✔
190
    }
406✔
191

192
    async fn get_private_view_key(&self) -> Result<PrivateKey, KeyManagerServiceError> {
7✔
193
        self.transaction_key_manager_inner
7✔
194
            .read()
7✔
195
            .await
7✔
196
            .get_private_view_key()
7✔
197
            .await
7✔
198
    }
14✔
199

200
    async fn get_spend_key(&self) -> Result<TariKeyAndId, KeyManagerServiceError> {
209✔
201
        self.transaction_key_manager_inner.read().await.get_spend_key().await
209✔
202
    }
418✔
203

204
    async fn sign_message_with_spend_key(
205
        &self,
206
        message: &[u8],
207
        sender_offset_pub_key: Option<&CompressedPublicKey>,
NEW
208
    ) -> Result<WalletMessageSchnorrSignature, KeyManagerServiceError> {
×
NEW
209
        self.transaction_key_manager_inner
×
NEW
210
            .read()
×
NEW
211
            .await
×
NEW
212
            .sign_message(message, sender_offset_pub_key)
×
NEW
213
            .await
×
NEW
214
    }
×
215

216
    async fn get_comms_key(&self) -> Result<TariKeyAndId, KeyManagerServiceError> {
×
217
        self.transaction_key_manager_inner.read().await.get_comms_key().await
×
218
    }
×
219

220
    async fn get_next_commitment_mask_and_script_key(
221
        &self,
222
    ) -> Result<(TariKeyAndId, TariKeyAndId), KeyManagerServiceError> {
783✔
223
        self.transaction_key_manager_inner
783✔
224
            .read()
783✔
225
            .await
783✔
226
            .get_next_commitment_mask_and_script_key()
783✔
227
            .await
783✔
228
    }
1,566✔
229

230
    async fn find_script_key_id_from_commitment_mask_key_id(
231
        &self,
232
        commitment_mask_key_id: &TariKeyId,
233
        public_script_key: Option<&CompressedPublicKey>,
234
    ) -> Result<Option<TariKeyId>, KeyManagerServiceError> {
×
235
        self.transaction_key_manager_inner
×
236
            .read()
×
237
            .await
×
238
            .find_script_key_id_from_commitment_mask_key_id(commitment_mask_key_id, public_script_key)
×
239
            .await
×
240
    }
×
241

242
    async fn get_diffie_hellman_shared_secret(
243
        &self,
244
        secret_key_id: &TariKeyId,
245
        public_key: &CompressedPublicKey,
246
    ) -> Result<CommsDHKE, TransactionError> {
248✔
247
        self.transaction_key_manager_inner
248✔
248
            .read()
248✔
249
            .await
248✔
250
            .get_diffie_hellman_shared_secret(secret_key_id, public_key)
248✔
251
            .await
248✔
252
    }
496✔
253

254
    async fn get_diffie_hellman_stealth_domain_hasher(
255
        &self,
256
        secret_key_id: &TariKeyId,
257
        public_key: &CompressedPublicKey,
258
    ) -> Result<DomainSeparatedHash<Blake2b<U64>>, TransactionError> {
×
259
        self.transaction_key_manager_inner
×
260
            .read()
×
261
            .await
×
262
            .get_diffie_hellman_stealth_domain_hasher(secret_key_id, public_key)
×
263
            .await
×
264
    }
×
265

266
    async fn construct_range_proof(
267
        &self,
268
        commitment_mask_key_id: &TariKeyId,
269
        value: u64,
270
        min_value: u64,
271
    ) -> Result<RangeProof, TransactionError> {
1,569✔
272
        self.transaction_key_manager_inner
1,569✔
273
            .read()
1,569✔
274
            .await
1,569✔
275
            .construct_range_proof(commitment_mask_key_id, value, min_value)
1,569✔
276
            .await
1,569✔
277
    }
3,138✔
278

279
    async fn get_script_signature(
280
        &self,
281
        script_key_id: &TariKeyId,
282
        commitment_mask_key_id: &TariKeyId,
283
        value: &PrivateKey,
284
        txi_version: &TransactionInputVersion,
285
        script_message: &[u8; 32],
286
    ) -> Result<ComAndPubSignature, TransactionError> {
308✔
287
        self.transaction_key_manager_inner
308✔
288
            .read()
308✔
289
            .await
308✔
290
            .get_script_signature(
308✔
291
                script_key_id,
308✔
292
                commitment_mask_key_id,
308✔
293
                value,
308✔
294
                txi_version,
308✔
295
                script_message,
308✔
296
            )
308✔
297
            .await
308✔
298
    }
616✔
299

300
    async fn get_partial_script_signature(
301
        &self,
302
        commitment_mask_id: &TariKeyId,
303
        value: &PrivateKey,
304
        txi_version: &TransactionInputVersion,
305
        ephemeral_pubkey: &CompressedPublicKey,
306
        script_public_key: &CompressedPublicKey,
307
        script_message: &[u8; 32],
308
    ) -> Result<ComAndPubSignature, TransactionError> {
×
309
        self.transaction_key_manager_inner
×
310
            .read()
×
311
            .await
×
312
            .get_partial_script_signature(
×
313
                commitment_mask_id,
×
314
                value,
×
315
                txi_version,
×
316
                ephemeral_pubkey,
×
317
                script_public_key,
×
318
                script_message,
×
319
            )
×
320
            .await
×
321
    }
×
322

323
    async fn get_partial_txo_kernel_signature(
324
        &self,
325
        commitment_mask_key_id: &TariKeyId,
326
        nonce_id: &TariKeyId,
327
        total_nonce: &CompressedPublicKey,
328
        total_excess: &CompressedPublicKey,
329
        kernel_version: &TransactionKernelVersion,
330
        kernel_message: &[u8; 32],
331
        kernel_features: &KernelFeatures,
332
        txo_type: TxoStage,
333
    ) -> Result<CompressedSignature, TransactionError> {
1,321✔
334
        self.transaction_key_manager_inner
1,321✔
335
            .read()
1,321✔
336
            .await
1,321✔
337
            .get_partial_txo_kernel_signature(
1,321✔
338
                commitment_mask_key_id,
1,321✔
339
                nonce_id,
1,321✔
340
                total_nonce,
1,321✔
341
                total_excess,
1,321✔
342
                kernel_version,
1,321✔
343
                kernel_message,
1,321✔
344
                kernel_features,
1,321✔
345
                txo_type,
1,321✔
346
            )
1,321✔
347
            .await
1,321✔
348
    }
2,642✔
349

350
    async fn get_txo_kernel_signature_excess_with_offset(
351
        &self,
352
        commitment_mask_key_id: &TariKeyId,
353
        nonce_id: &TariKeyId,
354
    ) -> Result<CompressedPublicKey, TransactionError> {
1,072✔
355
        self.transaction_key_manager_inner
1,072✔
356
            .read()
1,072✔
357
            .await
1,072✔
358
            .get_txo_kernel_signature_excess_with_offset(commitment_mask_key_id, nonce_id)
1,072✔
359
            .await
1,072✔
360
    }
2,144✔
361

362
    async fn get_txo_private_kernel_offset(
363
        &self,
364
        commitment_mask_key_id: &TariKeyId,
365
        nonce_id: &TariKeyId,
366
    ) -> Result<PrivateKey, TransactionError> {
1,068✔
367
        self.transaction_key_manager_inner
1,068✔
368
            .read()
1,068✔
369
            .await
1,068✔
370
            .get_txo_private_kernel_offset(commitment_mask_key_id, nonce_id)
1,068✔
371
            .await
1,068✔
372
    }
2,136✔
373

374
    async fn encrypt_data_for_recovery(
375
        &self,
376
        commitment_mask_key_id: &TariKeyId,
377
        custom_recovery_key_id: Option<&TariKeyId>,
378
        value: u64,
379
        payment_id: MemoField,
380
    ) -> Result<EncryptedData, TransactionError> {
1,592✔
381
        self.transaction_key_manager_inner
1,592✔
382
            .read()
1,592✔
383
            .await
1,592✔
384
            .encrypt_data_for_recovery(commitment_mask_key_id, custom_recovery_key_id, value, payment_id)
1,592✔
385
            .await
1,592✔
386
    }
3,184✔
387

388
    async fn extract_payment_id_from_encrypted_data(
389
        &self,
390
        encrypted_data: &EncryptedData,
391
        commitment: &CompressedCommitment,
392
        custom_recovery_key_id: Option<&TariKeyId>,
393
    ) -> Result<MemoField, TransactionError> {
×
394
        self.transaction_key_manager_inner
×
395
            .read()
×
396
            .await
×
397
            .extract_payment_id_from_encrypted_data(encrypted_data, commitment, custom_recovery_key_id)
×
398
            .await
×
399
    }
×
400

401
    async fn try_output_key_recovery(
402
        &self,
403
        commitment: &CompressedCommitment,
404
        encrypted_data: &EncryptedData,
405
        custom_recovery_key: Option<PrivateKey>,
406
    ) -> Result<(TariKeyId, MicroMinotari, MemoField), TransactionError> {
4✔
407
        self.transaction_key_manager_inner
4✔
408
            .read()
4✔
409
            .await
4✔
410
            .try_output_key_recovery(commitment, encrypted_data, custom_recovery_key)
4✔
411
            .await
4✔
412
    }
8✔
413

414
    async fn is_this_output_ours(
415
        &self,
416
        commitment: &CompressedCommitment,
417
        encrypted_data: &EncryptedData,
418
        custom_recovery_key_id: Option<&TariKeyId>,
419
    ) -> Result<bool, TransactionError> {
4✔
420
        self.transaction_key_manager_inner
4✔
421
            .read()
4✔
422
            .await
4✔
423
            .is_this_output_ours(commitment, encrypted_data, custom_recovery_key_id)
4✔
424
            .await
4✔
425
    }
8✔
426

427
    async fn get_script_offset(
428
        &self,
429
        script_key_ids: &[TariKeyId],
430
        sender_offset_key_ids: &[TariKeyId],
431
    ) -> Result<PrivateKey, TransactionError> {
168✔
432
        self.transaction_key_manager_inner
168✔
433
            .read()
168✔
434
            .await
168✔
435
            .get_script_offset(script_key_ids, sender_offset_key_ids)
168✔
436
            .await
168✔
437
    }
336✔
438

439
    async fn get_metadata_signature_ephemeral_commitment(
440
        &self,
441
        nonce_id: &TariKeyId,
442
        range_proof_type: RangeProofType,
443
    ) -> Result<CompressedCommitment, TransactionError> {
×
444
        self.transaction_key_manager_inner
×
445
            .read()
×
446
            .await
×
447
            .get_metadata_signature_ephemeral_commitment(nonce_id, range_proof_type)
×
448
            .await
×
449
    }
×
450

451
    async fn get_metadata_signature(
452
        &self,
453
        commitment_mask_key_id: &TariKeyId,
454
        value_as_private_key: &PrivateKey,
455
        sender_offset_key_id: &TariKeyId,
456
        txo_version: &TransactionOutputVersion,
457
        metadata_signature_message: &[u8; 32],
458
        range_proof_type: RangeProofType,
459
    ) -> Result<ComAndPubSignature, TransactionError> {
1,576✔
460
        self.transaction_key_manager_inner
1,576✔
461
            .read()
1,576✔
462
            .await
1,576✔
463
            .get_metadata_signature(
1,576✔
464
                commitment_mask_key_id,
1,576✔
465
                value_as_private_key,
1,576✔
466
                sender_offset_key_id,
1,576✔
467
                txo_version,
1,576✔
468
                metadata_signature_message,
1,576✔
469
                range_proof_type,
1,576✔
470
            )
1,576✔
471
            .await
1,576✔
472
    }
3,152✔
473

474
    async fn get_one_sided_metadata_signature(
475
        &self,
476
        commitment_mask_key_id: &TariKeyId,
477
        value: MicroMinotari,
478
        sender_offset_key_id: &TariKeyId,
479
        txo_version: &TransactionOutputVersion,
480
        metadata_signature_message_common: &[u8; 32],
481
        range_proof_type: RangeProofType,
482
        script: &TariScript,
483
        receiver_address: &TariAddress,
484
    ) -> Result<ComAndPubSignature, TransactionError> {
16✔
485
        self.transaction_key_manager_inner
16✔
486
            .read()
16✔
487
            .await
16✔
488
            .get_one_sided_metadata_signature(
16✔
489
                commitment_mask_key_id,
16✔
490
                value,
16✔
491
                sender_offset_key_id,
16✔
492
                txo_version,
16✔
493
                metadata_signature_message_common,
16✔
494
                range_proof_type,
16✔
495
                script,
16✔
496
                receiver_address,
16✔
497
            )
16✔
498
            .await
16✔
499
    }
32✔
500

501
    async fn sign_script_message(
502
        &self,
503
        private_key_id: &TariKeyId,
504
        challenge: &[u8],
505
    ) -> Result<CompressedCheckSigSchnorrSignature, TransactionError> {
×
506
        self.transaction_key_manager_inner
×
507
            .read()
×
508
            .await
×
509
            .sign_script_message(private_key_id, challenge)
×
510
            .await
×
511
    }
×
512

513
    async fn sign_script_message_with_spend_key(
514
        &self,
515
        message: &[u8],
516
        sender_offset_pub_key: Option<&CompressedPublicKey>,
517
    ) -> Result<CompressedCheckSigSchnorrSignature, KeyManagerServiceError> {
2✔
518
        self.transaction_key_manager_inner
2✔
519
            .read()
2✔
520
            .await
2✔
521
            .sign_script_message_with_spend_key(message, sender_offset_pub_key)
2✔
522
            .await
2✔
523
    }
4✔
524

525
    async fn sign_with_nonce_and_challenge(
526
        &self,
527
        private_key_id: &TariKeyId,
528
        nonce: &TariKeyId,
529
        challenge: &[u8; 64],
530
    ) -> Result<CompressedSignature, TransactionError> {
×
531
        self.transaction_key_manager_inner
×
532
            .read()
×
533
            .await
×
534
            .sign_with_nonce_and_challenge(private_key_id, nonce, challenge)
×
535
            .await
×
536
    }
×
537

538
    async fn get_receiver_partial_metadata_signature(
539
        &self,
540
        commitment_mask_key_id: &TariKeyId,
541
        value: &PrivateKey,
542
        sender_offset_public_key: &CompressedPublicKey,
543
        ephemeral_pubkey: &CompressedPublicKey,
544
        txo_version: &TransactionOutputVersion,
545
        metadata_signature_message: &[u8; 32],
546
        range_proof_type: RangeProofType,
547
    ) -> Result<ComAndPubSignature, TransactionError> {
1✔
548
        self.transaction_key_manager_inner
1✔
549
            .read()
1✔
550
            .await
1✔
551
            .get_receiver_partial_metadata_signature(
1✔
552
                commitment_mask_key_id,
1✔
553
                value,
1✔
554
                sender_offset_public_key,
1✔
555
                ephemeral_pubkey,
1✔
556
                txo_version,
1✔
557
                metadata_signature_message,
1✔
558
                range_proof_type,
1✔
559
            )
1✔
560
            .await
1✔
561
    }
2✔
562

563
    // In the case where the sender is an aggregated signer, we need to parse in the other public key shares, this is
564
    // done in: aggregated_sender_offset_public_keys and aggregated_ephemeral_public_keys. If there is no aggregated
565
    // signers, this can be left as none
566
    async fn get_sender_partial_metadata_signature(
567
        &self,
568
        ephemeral_private_nonce_id: &TariKeyId,
569
        sender_offset_key_id: &TariKeyId,
570
        commitment: &CompressedCommitment,
571
        ephemeral_commitment: &CompressedCommitment,
572
        txo_version: &TransactionOutputVersion,
573
        metadata_signature_message: &[u8; 32],
574
    ) -> Result<ComAndPubSignature, TransactionError> {
1✔
575
        self.transaction_key_manager_inner
1✔
576
            .read()
1✔
577
            .await
1✔
578
            .get_sender_partial_metadata_signature(
1✔
579
                ephemeral_private_nonce_id,
1✔
580
                sender_offset_key_id,
1✔
581
                commitment,
1✔
582
                ephemeral_commitment,
1✔
583
                txo_version,
1✔
584
                metadata_signature_message,
1✔
585
            )
1✔
586
            .await
1✔
587
    }
2✔
588

589
    async fn generate_burn_proof(
590
        &self,
591
        commitment_mask_key_id: &TariKeyId,
592
        amount: &PrivateKey,
593
        claim_public_key: &CompressedPublicKey,
594
    ) -> Result<RistrettoComSig, TransactionError> {
×
595
        self.transaction_key_manager_inner
×
596
            .read()
×
597
            .await
×
598
            .generate_burn_proof(commitment_mask_key_id, amount, claim_public_key)
×
599
            .await
×
600
    }
×
601

602
    async fn stealth_address_script_spending_key(
603
        &self,
604
        commitment_mask_key_id: &TariKeyId,
605
        spend_key: &CompressedPublicKey,
606
    ) -> Result<CompressedPublicKey, TransactionError> {
13✔
607
        self.transaction_key_manager_inner
13✔
608
            .read()
13✔
609
            .await
13✔
610
            .stealth_address_script_spending_key(commitment_mask_key_id, spend_key)
13✔
611
            .await
13✔
612
    }
26✔
613

614
    async fn add_offset_to_spend_key(
615
        &self,
616
        spend_key_id: &TariKeyId,
617
        sender_offset_pub_key: &CompressedPublicKey,
NEW
618
    ) -> Result<TariKeyId, KeyManagerServiceError> {
×
NEW
619
        self.transaction_key_manager_inner
×
NEW
620
            .write()
×
NEW
621
            .await
×
NEW
622
            .add_offset_to_spend_key(spend_key_id, sender_offset_pub_key)
×
NEW
623
            .await
×
NEW
624
    }
×
625

626
    async fn encrypted_key(
627
        &self,
628
        key_id: &TariKeyId,
629
        encryption_key_id: Option<&TariKeyId>,
630
    ) -> Result<Vec<u8>, KeyManagerServiceError> {
34✔
631
        self.transaction_key_manager_inner
34✔
632
            .read()
34✔
633
            .await
34✔
634
            .encrypted_key(key_id, encryption_key_id)
34✔
635
            .await
34✔
636
    }
68✔
637

638
    async fn import_encrypted_key(
639
        &self,
640
        encrypted: Vec<u8>,
641
        encryption_key_id: Option<&TariKeyId>,
642
    ) -> Result<TariKeyId, KeyManagerServiceError> {
34✔
643
        self.transaction_key_manager_inner
34✔
644
            .read()
34✔
645
            .await
34✔
646
            .import_encrypted_key(encrypted, encryption_key_id)
34✔
647
            .await
34✔
648
    }
68✔
649
}
650

651
#[async_trait::async_trait]
652
impl<TBackend> SecretTransactionKeyManagerInterface for TransactionKeyManagerWrapper<TBackend>
653
where TBackend: TransactionKeyManagerBackend + 'static
654
{
655
    async fn get_private_key(&self, key_id: &TariKeyId) -> Result<PrivateKey, KeyManagerServiceError> {
2✔
656
        self.transaction_key_manager_inner
2✔
657
            .read()
2✔
658
            .await
2✔
659
            .get_private_key(key_id)
2✔
660
            .await
2✔
661
    }
4✔
662
}
663

664
impl<KM> Debug for TransactionKeyManagerWrapper<KM> {
665
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
×
666
        f.debug_struct("Key Manager").finish()
×
667
    }
×
668
}
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

© 2026 Coveralls, Inc