darkfi_sdk/crypto/
keypair.rs

1/* This file is part of DarkFi (https://dark.fi)
2 *
3 * Copyright (C) 2020-2025 Dyne.org foundation
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 */
18
19use core::str::FromStr;
20
21#[cfg(feature = "async")]
22use darkfi_serial::async_trait;
23use darkfi_serial::{SerialDecodable, SerialEncodable};
24use halo2_gadgets::ecc::chip::FixedPoint;
25use pasta_curves::{
26    arithmetic::CurveAffine,
27    group::{
28        ff::{Field, PrimeField},
29        Curve, Group, GroupEncoding,
30    },
31    pallas,
32};
33use rand_core::{CryptoRng, RngCore};
34
35use super::{constants::NullifierK, util::fp_mod_fv};
36use crate::error::ContractError;
37
38/// Keypair structure holding a `SecretKey` and its respective `PublicKey`
39#[derive(Copy, Clone, PartialEq, Eq, Debug, SerialEncodable, SerialDecodable)]
40pub struct Keypair {
41    pub secret: SecretKey,
42    pub public: PublicKey,
43}
44
45impl Keypair {
46    /// Instantiate a new `Keypair` given a `SecretKey`
47    pub fn new(secret: SecretKey) -> Self {
48        Self { secret, public: PublicKey::from_secret(secret) }
49    }
50
51    /// Generate a new `Keypair` object given a source of randomness
52    pub fn random(rng: &mut (impl CryptoRng + RngCore)) -> Self {
53        Self::new(SecretKey::random(rng))
54    }
55}
56
57impl Default for Keypair {
58    /// Default Keypair used in genesis block generation
59    fn default() -> Self {
60        let secret = SecretKey::from(pallas::Base::from(42));
61        let public = PublicKey::from_secret(secret);
62        Self { secret, public }
63    }
64}
65
66/// Structure holding a secret key, wrapping a `pallas::Base` element.
67#[derive(Copy, Clone, PartialEq, Eq, Debug, SerialEncodable, SerialDecodable)]
68pub struct SecretKey(pallas::Base);
69
70impl SecretKey {
71    /// Get the inner object wrapped by `SecretKey`
72    pub fn inner(&self) -> pallas::Base {
73        self.0
74    }
75
76    /// Generate a new `SecretKey` given a source of randomness
77    pub fn random(rng: &mut (impl CryptoRng + RngCore)) -> Self {
78        Self(pallas::Base::random(rng))
79    }
80
81    /// Instantiate a `SecretKey` given 32 bytes. Returns an error
82    /// if the representation is noncanonical.
83    pub fn from_bytes(bytes: [u8; 32]) -> Result<Self, ContractError> {
84        match pallas::Base::from_repr(bytes).into() {
85            Some(k) => Ok(Self(k)),
86            None => Err(ContractError::IoError("Could not convert bytes to SecretKey".to_string())),
87        }
88    }
89}
90
91impl From<pallas::Base> for SecretKey {
92    fn from(x: pallas::Base) -> Self {
93        Self(x)
94    }
95}
96
97impl FromStr for SecretKey {
98    type Err = ContractError;
99
100    /// Tries to create a `SecretKey` object from a base58 encoded string.
101    fn from_str(enc: &str) -> Result<Self, Self::Err> {
102        let decoded = bs58::decode(enc).into_vec()?;
103        if decoded.len() != 32 {
104            return Err(Self::Err::IoError(
105                "Failed decoding SecretKey from bytes, len is not 32".to_string(),
106            ))
107        }
108
109        Self::from_bytes(decoded.try_into().unwrap())
110    }
111}
112
113impl core::fmt::Display for SecretKey {
114    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
115        let disp: String = bs58::encode(self.0.to_repr()).into_string();
116        write!(f, "{disp}")
117    }
118}
119
120/// Structure holding a public key, wrapping a `pallas::Point` element.
121#[derive(Copy, Clone, PartialEq, Eq, Debug, SerialEncodable, SerialDecodable)]
122pub struct PublicKey(pallas::Point);
123
124impl PublicKey {
125    /// Get the inner object wrapped by `PublicKey`
126    pub fn inner(&self) -> pallas::Point {
127        self.0
128    }
129
130    /// Derive a new `PublicKey` object given a `SecretKey`
131    pub fn from_secret(s: SecretKey) -> Self {
132        let p = NullifierK.generator() * fp_mod_fv(s.inner());
133        Self(p)
134    }
135
136    /// Instantiate a `PublicKey` given 32 bytes. Returns an error
137    /// if the representation is noncanonical.
138    pub fn from_bytes(bytes: [u8; 32]) -> Result<Self, ContractError> {
139        match <subtle::CtOption<pallas::Point> as Into<Option<pallas::Point>>>::into(
140            pallas::Point::from_bytes(&bytes),
141        ) {
142            Some(k) => {
143                if bool::from(k.is_identity()) {
144                    return Err(ContractError::IoError(
145                        "Could not convert bytes to PublicKey".to_string(),
146                    ))
147                }
148
149                Ok(Self(k))
150            }
151            None => Err(ContractError::IoError("Could not convert bytes to PublicKey".to_string())),
152        }
153    }
154
155    /// Downcast the `PublicKey` to 32 bytes of `pallas::Point`
156    pub fn to_bytes(&self) -> [u8; 32] {
157        self.0.to_bytes()
158    }
159
160    /// Fetch the `x` coordinate of this `PublicKey`
161    pub fn x(&self) -> pallas::Base {
162        *self.0.to_affine().coordinates().unwrap().x()
163    }
164
165    /// Fetch the `y` coordinate of this `PublicKey`
166    pub fn y(&self) -> pallas::Base {
167        *self.0.to_affine().coordinates().unwrap().y()
168    }
169
170    /// Fetch the `x` and `y` coordinates of this `PublicKey` as a tuple
171    pub fn xy(&self) -> (pallas::Base, pallas::Base) {
172        let coords = self.0.to_affine().coordinates().unwrap();
173        (*coords.x(), *coords.y())
174    }
175}
176
177impl TryFrom<pallas::Point> for PublicKey {
178    type Error = ContractError;
179
180    fn try_from(x: pallas::Point) -> Result<Self, Self::Error> {
181        if bool::from(x.is_identity()) {
182            return Err(ContractError::IoError(
183                "Could not convert identity point to PublicKey".to_string(),
184            ))
185        }
186
187        Ok(Self(x))
188    }
189}
190
191impl FromStr for PublicKey {
192    type Err = ContractError;
193
194    /// Tries to create a `PublicKey` object from a base58 encoded string.
195    fn from_str(enc: &str) -> Result<Self, Self::Err> {
196        let decoded = bs58::decode(enc).into_vec()?;
197        if decoded.len() != 32 {
198            return Err(Self::Err::IoError(
199                "Failed decoding PublicKey from bytes, len is not 32".to_string(),
200            ))
201        }
202
203        Self::from_bytes(decoded.try_into().unwrap())
204    }
205}
206
207impl core::fmt::Display for PublicKey {
208    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
209        let disp: String = bs58::encode(self.0.to_bytes()).into_string();
210        write!(f, "{disp}")
211    }
212}
213
214#[derive(Copy, Clone, Eq, PartialEq, Debug)]
215pub enum Network {
216    Mainnet,
217    Testnet,
218}
219
220impl Network {
221    pub fn is_testnet(self) -> bool {
222        self == Network::Testnet
223    }
224}
225
226#[derive(Copy, Clone, Eq, PartialEq, Debug)]
227pub enum AddressPrefix {
228    MainnetStandard = 0x39,
229    TestnetStandard = 0xaf,
230}
231
232impl AddressPrefix {
233    pub fn network(&self) -> Network {
234        match self {
235            Self::MainnetStandard => Network::Mainnet,
236            Self::TestnetStandard => Network::Testnet,
237        }
238    }
239}
240
241impl TryFrom<u8> for AddressPrefix {
242    type Error = ContractError;
243
244    fn try_from(value: u8) -> Result<Self, Self::Error> {
245        match value {
246            0x39 => Ok(Self::MainnetStandard),
247            0xaf => Ok(Self::TestnetStandard),
248            _ => Err(ContractError::IoError("Invalid address type".to_string())),
249        }
250    }
251}
252
253/// Defines a standard DarkFi pasta curve address containing prefix and pubkey.
254#[derive(Copy, Clone, Debug, Eq, PartialEq)]
255pub struct StandardAddress {
256    network: Network,
257    spending_key: PublicKey,
258}
259
260impl StandardAddress {
261    pub fn prefix(&self) -> AddressPrefix {
262        match self.network {
263            Network::Mainnet => AddressPrefix::MainnetStandard,
264            Network::Testnet => AddressPrefix::TestnetStandard,
265        }
266    }
267
268    pub fn public_key(&self) -> &PublicKey {
269        &self.spending_key
270    }
271
272    pub fn from_public(network: Network, public_key: PublicKey) -> Self {
273        Self { network, spending_key: public_key }
274    }
275}
276
277impl From<StandardAddress> for Address {
278    fn from(v: StandardAddress) -> Self {
279        Address::Standard(v)
280    }
281}
282
283/// The address checksum is the first four bytes of the hashed data.
284const ADDR_CHECKSUM_LEN: usize = 4;
285
286/// Standard address consist of `[prefix][public_key][checksum]`.
287const STANDARD_ADDR_LEN: usize = 1 + 32 + ADDR_CHECKSUM_LEN;
288
289/// Addresses defined on DarkFi. Catch-all enum.
290#[derive(Copy, Clone, Debug, Eq, PartialEq)]
291pub enum Address {
292    Standard(StandardAddress),
293}
294
295impl Address {
296    pub fn network(&self) -> Network {
297        match self {
298            Self::Standard(addr) => addr.network,
299        }
300    }
301
302    pub fn public_key(&self) -> &PublicKey {
303        match self {
304            Self::Standard(addr) => addr.public_key(),
305        }
306    }
307}
308
309impl FromStr for Address {
310    type Err = ContractError;
311
312    fn from_str(enc: &str) -> Result<Self, Self::Err> {
313        let dec = bs58::decode(enc).with_check(None).into_vec()?;
314        if dec.is_empty() {
315            return Err(ContractError::IoError("Empty address".to_string()))
316        }
317
318        let r_addrtype = AddressPrefix::try_from(dec[0])?;
319        match r_addrtype {
320            AddressPrefix::MainnetStandard | AddressPrefix::TestnetStandard => {
321                // Standard addresses consist of [prefix][public_key][checksum].
322                // Prefix is 1 byte, key is 32 bytes, and checksum is 4 bytes.
323                // This should total to 37 bytes for standard addresses.
324                if dec.len() != STANDARD_ADDR_LEN {
325                    return Err(Self::Err::IoError("Invalid address length".to_string()))
326                }
327
328                let r_spending_key = PublicKey::from_bytes(
329                    dec[1..STANDARD_ADDR_LEN - ADDR_CHECKSUM_LEN].try_into().unwrap(),
330                )?;
331                let r_checksum = &dec[STANDARD_ADDR_LEN - ADDR_CHECKSUM_LEN..];
332
333                let checksum = blake3::hash(&dec[..STANDARD_ADDR_LEN - ADDR_CHECKSUM_LEN]);
334                if r_checksum != &checksum.as_bytes()[..ADDR_CHECKSUM_LEN] {
335                    return Err(Self::Err::IoError("Invalid address checksum".to_string()))
336                }
337
338                let addr =
339                    StandardAddress { network: r_addrtype.network(), spending_key: r_spending_key };
340
341                Ok(Self::Standard(addr))
342            }
343        }
344    }
345}
346
347impl core::fmt::Display for Address {
348    fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
349        let payload = match self {
350            Self::Standard(addr) => {
351                let mut payload = Vec::with_capacity(STANDARD_ADDR_LEN);
352                payload.push(addr.prefix() as u8);
353                payload.extend_from_slice(&addr.spending_key.to_bytes());
354                let checksum = blake3::hash(&payload);
355                payload.extend_from_slice(&checksum.as_bytes()[..ADDR_CHECKSUM_LEN]);
356                payload
357            }
358        };
359
360        write!(f, "{}", bs58::encode(payload).with_check().into_string())
361    }
362}
363
364#[cfg(test)]
365mod tests {
366    use super::*;
367
368    use rand::rngs::OsRng;
369
370    #[test]
371    fn test_standard_address_encoding() {
372        let s_kp = Keypair::random(&mut OsRng);
373
374        let s_addr = StandardAddress { network: Network::Mainnet, spending_key: s_kp.public };
375
376        let addr: Address = s_addr.into();
377        let encoded = addr.to_string();
378        let decoded = Address::from_str(&encoded).unwrap();
379
380        assert_eq!(addr, decoded);
381
382        println!("{encoded}");
383    }
384}