-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsigner.rs
More file actions
135 lines (117 loc) · 4.24 KB
/
Copy pathsigner.rs
File metadata and controls
135 lines (117 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
//! The in-process IDENTITY signer: a [`dig_ipc_protocol::SessionSigner`] backed by a profile's
//! derived identity key.
//!
//! This is the identity path ONLY — session-attach challenges, `dign sign`, directed-message auth. It
//! is NOT the money path (spend-bundle signing lives behind [`MoneySigner`](crate::wallet::money_signer::MoneySigner)).
//! When the backing seed is absent (locked), [`try_sign`](SessionSigner::try_sign) returns `None`
//! rather than framing an all-zero signature into a bogus success.
use std::sync::Arc;
use dig_ipc_protocol::domain::{Signature, SigningPublicKey};
use dig_ipc_protocol::signer::SessionSigner;
use dig_session::UnlockedMasterSeed;
use crate::id::ProfileIx;
/// An identity signer for one profile.
///
/// Constructed unlocked (holding the seed) via [`new`](Self::new), or [`locked`](Self::locked) as a
/// key-less handle whose [`try_sign`](SessionSigner::try_sign) always returns `None`.
pub struct ProfileSigner {
seed: Option<Arc<UnlockedMasterSeed>>,
profile_ix: ProfileIx,
}
impl ProfileSigner {
/// An unlocked identity signer for `profile_ix`, backed by `seed`.
pub fn new(seed: Arc<UnlockedMasterSeed>, profile_ix: ProfileIx) -> Self {
Self {
seed: Some(seed),
profile_ix,
}
}
/// A locked, key-less signer for `profile_ix`: names no key and signs nothing.
pub fn locked(profile_ix: ProfileIx) -> Self {
Self {
seed: None,
profile_ix,
}
}
/// Whether this signer currently holds a seed.
pub fn is_locked(&self) -> bool {
self.seed.is_none()
}
}
impl SessionSigner for ProfileSigner {
fn signing_public_key(&self) -> SigningPublicKey {
let seed = self
.seed
.as_ref()
.expect("signing_public_key called on a locked ProfileSigner");
SigningPublicKey::new(seed.profile_public_key(self.profile_ix.0))
}
fn sign(&self, message: &[u8]) -> Signature {
let seed = self
.seed
.as_ref()
.expect("sign called on a locked ProfileSigner; use try_sign");
Signature::new(seed.profile_sign(self.profile_ix.0, message))
}
fn try_sign(&self, message: &[u8]) -> Option<Signature> {
let seed = self.seed.as_ref()?;
Some(Signature::new(
seed.profile_sign(self.profile_ix.0, message),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use dig_keystore::{BackendKey, MemoryBackend};
use dig_session::{Password, Session, ENTROPY_LEN};
const SEED: [u8; ENTROPY_LEN] = [0x7E; ENTROPY_LEN];
fn seed() -> Arc<UnlockedMasterSeed> {
Arc::new(
Session::enroll_master_seed(
Arc::new(MemoryBackend::new()),
BackendKey::new("k".to_string()),
Password::new("pw"),
&SEED,
)
.unwrap(),
)
}
#[test]
fn a_locked_signer_holds_no_key_and_signs_nothing() {
let signer = ProfileSigner::locked(ProfileIx::ROOT);
assert!(signer.is_locked());
assert!(
signer.try_sign(b"challenge").is_none(),
"a locked signer must return None, never a bogus signature"
);
}
#[test]
fn an_unlocked_signer_signs_and_exposes_its_public_key() {
let signer = ProfileSigner::new(seed(), ProfileIx::ROOT);
assert!(!signer.is_locked());
assert!(signer.try_sign(b"challenge").is_some());
// sign + signing_public_key must not panic on an unlocked signer.
let _pk = signer.signing_public_key();
let _sig = signer.sign(b"challenge");
}
#[test]
fn the_public_key_matches_the_backing_profile_key() {
let s = seed();
let signer = ProfileSigner::new(s.clone(), ProfileIx(2));
assert_eq!(
signer.signing_public_key().as_bytes(),
&s.profile_public_key(2)
);
}
#[test]
#[should_panic(expected = "locked ProfileSigner")]
fn signing_public_key_panics_when_locked() {
ProfileSigner::locked(ProfileIx::ROOT).signing_public_key();
}
#[test]
#[should_panic(expected = "locked ProfileSigner")]
fn sign_panics_when_locked() {
ProfileSigner::locked(ProfileIx::ROOT).sign(b"x");
}
}