1#![deny(clippy::arithmetic_side_effects)]
2#![deny(clippy::cast_possible_truncation)]
3
4use clap::ValueEnum;
5use fuel_core_types::{
6 fuel_crypto::{
7 SecretKey,
8 rand::{
9 SeedableRng,
10 prelude::StdRng,
11 },
12 },
13 fuel_tx::Input,
14 fuel_types::Address,
15};
16use libp2p_identity::{
17 Keypair,
18 PeerId,
19 secp256k1,
20};
21use serde::Serialize;
22use std::{
23 ops::Deref,
24 str::FromStr,
25};
26
27#[derive(Clone, Copy, Debug, Default, Serialize, ValueEnum)]
28#[serde(rename_all = "kebab-case")]
29pub enum KeyType {
30 #[default]
31 BlockProduction,
32 Peering,
33}
34
35impl From<KeyType> for &'static str {
36 fn from(key_type: KeyType) -> Self {
37 match key_type {
38 KeyType::BlockProduction => "block-production",
39 KeyType::Peering => "p2p",
40 }
41 }
42}
43
44#[derive(Clone, Debug, Serialize)]
45pub struct ParseSecretResponse {
46 #[serde(skip_serializing_if = "Option::is_none")]
47 address: Option<Address>,
48 #[serde(
49 serialize_with = "serialize_option_to_string",
50 skip_serializing_if = "Option::is_none"
51 )]
52 peer_id: Option<PeerId>,
53 #[serde(rename = "type")]
54 typ: KeyType,
55}
56
57#[derive(Clone, Debug, Serialize)]
58pub struct NewKeyResponse {
59 secret: SecretKey,
60 #[serde(skip_serializing_if = "Option::is_none")]
61 address: Option<Address>,
62 #[serde(
63 serialize_with = "serialize_option_to_string",
64 skip_serializing_if = "Option::is_none"
65 )]
66 peer_id: Option<PeerId>,
67 #[serde(rename = "type")]
68 typ: KeyType,
69}
70
71fn serialize_option_to_string<S, T>(
72 opt: &Option<T>,
73 serializer: S,
74) -> Result<S::Ok, S::Error>
75where
76 S: serde::Serializer,
77 T: ToString,
78{
79 if let Some(value) = opt.as_ref() {
80 value.to_string().serialize(serializer)
81 } else {
82 serializer.serialize_none()
83 }
84}
85
86pub fn new_key(key_type: KeyType) -> anyhow::Result<NewKeyResponse> {
87 let mut rng = StdRng::from_entropy();
88 let secret = SecretKey::random(&mut rng);
89 let public_key = secret.public_key();
90
91 Ok(match key_type {
92 KeyType::BlockProduction => {
93 let address = Input::owner(&public_key);
94 NewKeyResponse {
95 secret,
96 address: Some(address),
97 peer_id: None,
98 typ: key_type,
99 }
100 }
101 KeyType::Peering => {
102 let mut bytes = *secret.deref();
103 let p2p_secret = secp256k1::SecretKey::try_from_bytes(&mut bytes)
104 .expect("Should be a valid private key");
105 let p2p_keypair = secp256k1::Keypair::from(p2p_secret);
106 let libp2p_keypair = Keypair::from(p2p_keypair);
107 let peer_id = PeerId::from_public_key(&libp2p_keypair.public());
108 NewKeyResponse {
109 secret,
110 address: None,
111 peer_id: Some(peer_id),
112 typ: key_type,
113 }
114 }
115 })
116}
117
118pub fn parse_secret(
119 key_type: KeyType,
120 secret: &str,
121) -> anyhow::Result<ParseSecretResponse> {
122 let secret =
123 SecretKey::from_str(secret).map_err(|_| anyhow::anyhow!("invalid secret key"))?;
124 Ok(match key_type {
125 KeyType::BlockProduction => {
126 let address = Input::owner(&secret.public_key());
127 ParseSecretResponse {
128 address: Some(address),
129 peer_id: None,
130 typ: key_type,
131 }
132 }
133 KeyType::Peering => {
134 let mut bytes = *secret.deref();
135 let p2p_secret = secp256k1::SecretKey::try_from_bytes(&mut bytes)
136 .expect("Should be a valid private key");
137 let p2p_keypair = secp256k1::Keypair::from(p2p_secret);
138 let libp2p_keypair = Keypair::from(p2p_keypair);
139 let peer_id = PeerId::from_public_key(&libp2p_keypair.public());
140 ParseSecretResponse {
141 address: None,
142 peer_id: Some(peer_id),
143 typ: key_type,
144 }
145 }
146 })
147}