confium_tc_keys/
stealth_address.rs1use getrandom::SysRng;
4use p256::elliptic_curve::rand_core::UnwrapErr;
5use p256::elliptic_curve::sec1::ToSec1Point;
6use p256::elliptic_curve::{Field, PrimeField};
7use p256::{AffinePoint, FieldBytes, ProjectivePoint, Scalar};
8use sha2::{Digest, Sha256};
9
10#[derive(Debug, Clone)]
12pub struct ScanPubkey {
13 pub point: AffinePoint,
14}
15
16#[derive(Debug, Clone)]
18pub struct SpendKeypair {
19 pub secret: Scalar,
20 pub public: AffinePoint,
21}
22
23#[derive(Debug, Clone)]
25pub struct StealthAddress {
26 pub ephemeral: AffinePoint,
28 pub one_time_pubkey: AffinePoint,
30}
31
32pub fn generate_spend_keypair() -> SpendKeypair {
34 let secret = Scalar::random(&mut UnwrapErr(SysRng));
35 let public = (ProjectivePoint::GENERATOR * secret).to_affine();
36 SpendKeypair { secret, public }
37}
38
39pub fn generate_scan_keypair() -> (Scalar, ScanPubkey) {
41 let secret = Scalar::random(&mut UnwrapErr(SysRng));
42 let public = (ProjectivePoint::GENERATOR * secret).to_affine();
43 (secret, ScanPubkey { point: public })
44}
45
46pub fn create_stealth_address(
49 scan_pubkey: &ScanPubkey,
50 spend_pubkey: &AffinePoint,
51) -> (StealthAddress, Scalar) {
52 let r = Scalar::random(&mut UnwrapErr(SysRng));
54 let ephemeral = (ProjectivePoint::GENERATOR * r).to_affine();
55
56 let shared_point = (ProjectivePoint::from(scan_pubkey.point) * r).to_affine();
58
59 let adjustment = hash_to_scalar(&shared_point);
61
62 let one_time_pubkey = (ProjectivePoint::from(*spend_pubkey)
64 + ProjectivePoint::GENERATOR * adjustment)
65 .to_affine();
66
67 (
68 StealthAddress {
69 ephemeral,
70 one_time_pubkey,
71 },
72 r,
73 )
74}
75
76pub fn scan_stealth_address(
79 scan_secret: &Scalar,
80 spend_secret: &Scalar,
81 address: &StealthAddress,
82) -> Option<Scalar> {
83 let shared_point = (ProjectivePoint::from(address.ephemeral) * scan_secret).to_affine();
85 let adjustment = hash_to_scalar(&shared_point);
86
87 let one_time_secret = spend_secret + &adjustment;
89
90 let expected_pubkey = (ProjectivePoint::GENERATOR * one_time_secret).to_affine();
92 if expected_pubkey == address.one_time_pubkey {
93 Some(one_time_secret)
94 } else {
95 None
96 }
97}
98
99fn reduce_to_scalar(mut bytes: [u8; 32]) -> Scalar {
103 loop {
104 if let Some(s) = Option::<Scalar>::from(Scalar::from_repr(FieldBytes::from(bytes))) {
105 return s;
106 }
107 let mut h = Sha256::new();
108 h.update(b"confium-scalar-reduce-v1");
109 h.update(bytes);
110 bytes = h.finalize().into();
111 }
112}
113
114fn hash_to_scalar(point: &AffinePoint) -> Scalar {
115 let mut hasher = Sha256::new();
116 hasher.update(b"stealth-hash");
117 hasher.update(point.to_sec1_point(true).as_bytes());
118 let bytes: [u8; 32] = hasher.finalize().into();
119 reduce_to_scalar(bytes)
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn create_and_scan() {
128 let (scan_secret, scan_pubkey) = generate_scan_keypair();
129 let spend_kp = generate_spend_keypair();
130
131 let (address, _r) = create_stealth_address(&scan_pubkey, &spend_kp.public);
132
133 let one_time_sk = scan_stealth_address(&scan_secret, &spend_kp.secret, &address);
134
135 assert!(one_time_sk.is_some());
136 }
137
138 #[test]
139 fn wrong_scan_secret_fails() {
140 let (_, scan_pubkey) = generate_scan_keypair();
141 let spend_kp = generate_spend_keypair();
142 let (address, _) = create_stealth_address(&scan_pubkey, &spend_kp.public);
143
144 let wrong_scan = Scalar::random(&mut UnwrapErr(SysRng));
145 let result = scan_stealth_address(&wrong_scan, &spend_kp.secret, &address);
146 assert!(result.is_none());
147 }
148
149 #[test]
150 fn wrong_spend_secret_fails() {
151 let (scan_secret, scan_pubkey) = generate_scan_keypair();
152 let spend_kp = generate_spend_keypair();
153 let (address, _) = create_stealth_address(&scan_pubkey, &spend_kp.public);
154
155 let wrong_spend = Scalar::random(&mut UnwrapErr(SysRng));
156 let result = scan_stealth_address(&scan_secret, &wrong_spend, &address);
157 assert!(result.is_none());
158 }
159
160 #[test]
161 fn different_senders_different_addresses() {
162 let (_, scan_pubkey) = generate_scan_keypair();
163 let spend_kp = generate_spend_keypair();
164
165 let (addr1, _) = create_stealth_address(&scan_pubkey, &spend_kp.public);
166 let (addr2, _) = create_stealth_address(&scan_pubkey, &spend_kp.public);
167
168 assert_ne!(addr1.ephemeral, addr2.ephemeral);
169 assert_ne!(addr1.one_time_pubkey, addr2.one_time_pubkey);
170 }
171
172 #[test]
173 fn one_time_secret_matches_pubkey() {
174 let (scan_secret, scan_pubkey) = generate_scan_keypair();
175 let spend_kp = generate_spend_keypair();
176 let (address, _) = create_stealth_address(&scan_pubkey, &spend_kp.public);
177
178 let one_time_sk = scan_stealth_address(&scan_secret, &spend_kp.secret, &address).unwrap();
179 let derived_pk = (ProjectivePoint::GENERATOR * one_time_sk).to_affine();
180 assert_eq!(derived_pk, address.one_time_pubkey);
181 }
182
183 #[test]
184 fn ephemeral_published() {
185 let (_, scan_pubkey) = generate_scan_keypair();
186 let spend_kp = generate_spend_keypair();
187 let (address, _) = create_stealth_address(&scan_pubkey, &spend_kp.public);
188 assert!(address.ephemeral != AffinePoint::IDENTITY);
190 }
191}