nash_protocol/protocol/
signer.rs1#[cfg(feature = "k256")]
2use k256::ecdsa::signature::Signer as k256_Signer;
3#[cfg(feature = "k256")]
4use k256::ecdsa::{Signature, SigningKey};
5#[cfg(feature = "secp256k1")]
6use rust_bigint::traits::Converter;
7#[cfg(feature = "secp256k1")]
8use secp256k1::constants::{COMPACT_SIGNATURE_SIZE, MESSAGE_SIZE, SECRET_KEY_SIZE};
9#[cfg(feature = "secp256k1")]
10use secp256k1::{Message, SecretKey};
11
12use nash_mpc::client::APIchildkey;
13use nash_mpc::common::Curve;
14#[cfg(feature = "secp256k1")]
15use nash_mpc::curves::secp256_k1::get_context;
16#[cfg(feature = "k256")]
17use nash_mpc::curves::secp256_k1_rust::Secp256k1Scalar;
18#[cfg(feature = "k256")]
19use nash_mpc::curves::traits::ECScalar;
20use nash_mpc::paillier_common;
21use nash_mpc::rust_bigint::BigInt;
22
23use crate::errors::{ProtocolError, Result};
24use crate::protocol::RequestPayloadSignature;
25use crate::types::ApiKeys;
26use crate::types::Blockchain;
27use crate::types::PublicKey;
28#[cfg(feature = "secp256k1")]
29use crate::utils::{der_encode_sig, hash_message};
30use std::sync::atomic::{AtomicU32, Ordering};
31
32pub fn chain_path(chain: Blockchain) -> &'static str {
33 match chain {
34 Blockchain::NEO => "m/44'/888'/0'/0/0",
35 Blockchain::Ethereum => "m/44'/60'/0'/0/0",
36 Blockchain::Bitcoin => "m/44'/0'/0'/0/0",
37 }
38}
39
40#[derive(Debug)]
41pub struct Signer {
42 pub api_keys: ApiKeys,
43 k1_remaining: AtomicU32,
44 r1_remaining: AtomicU32,
45}
46
47impl Signer {
48 pub fn new(key_path: &str) -> Result<Self> {
49 Ok(Self {
50 api_keys: ApiKeys::new(key_path)?,
51 k1_remaining: AtomicU32::new(0),
52 r1_remaining: AtomicU32::new(0),
53 })
54 }
55
56 pub fn from_data(secret: &str, session: &str) -> Result<Self> {
57 Ok(Self {
58 api_keys: ApiKeys::from_data(secret, session)?,
59 k1_remaining: AtomicU32::new(0),
60 r1_remaining: AtomicU32::new(0),
61 })
62 }
63
64 #[cfg(feature = "rustcrypto")]
68 pub fn sign_canonical_string(&self, request: &str) -> RequestPayloadSignature {
69 let signing_key: Secp256k1Scalar =
70 ECScalar::from(&self.api_keys.keys.payload_signing_key).expect("Invalid key");
71 let key = SigningKey::from_bytes(&signing_key.to_vec()).expect("invalid secret key");
72 let sig_pre: Signature = key.try_sign(request.as_bytes()).expect("signing failed");
73 let sig = sig_pre.to_der();
74 RequestPayloadSignature {
75 signed_digest: hex::encode(sig),
76 public_key: self.request_payload_public_key(),
77 }
78 }
79 #[cfg(feature = "secp256k1")]
80 pub fn sign_canonical_string(&self, request: &str) -> RequestPayloadSignature {
81 let message_hash = hash_message(request).to_bytes();
83 let mut msg_vec = vec![0; MESSAGE_SIZE - message_hash.len()];
85 msg_vec.extend_from_slice(&message_hash);
86 let msg = Message::from_slice(&msg_vec).unwrap();
87
88 let vec = BigInt::to_vec(&self.api_keys.keys.payload_signing_key);
90 let mut v = vec![0; SECRET_KEY_SIZE - vec.len()];
91 v.extend(&vec);
92 let key = SecretKey::from_slice(&v).expect("invalid secret key");
93
94 let signature = get_context().sign(&msg, &key).serialize_compact();
96 let r = BigInt::from_bytes(&signature[0..COMPACT_SIGNATURE_SIZE / 2]);
97 let s = BigInt::from_bytes(&signature[COMPACT_SIGNATURE_SIZE / 2..COMPACT_SIGNATURE_SIZE]);
98 let sig = der_encode_sig(&r, &s);
99
100 RequestPayloadSignature {
101 signed_digest: hex::encode(sig),
102 public_key: self.request_payload_public_key(),
103 }
104 }
105
106 pub fn sign_child_key(
108 &self,
109 data: BigInt,
110 chain: Blockchain,
111 ) -> Result<(BigInt, BigInt, String)> {
112 if self.get_remaining_r_vals(&chain) <= 0 {
113 return Err(ProtocolError("Ran out of R values"));
114 }
115 let key = self.get_child_key(chain);
116 let curve = match chain {
117 Blockchain::Ethereum | Blockchain::Bitcoin => Curve::Secp256k1,
118 Blockchain::NEO => Curve::Secp256r1,
119 };
120 let (sig, r) = nash_mpc::client::compute_presig(&key, &data, curve)
122 .map_err(|_| ProtocolError("Error computing presignature"))?;
123 self.decr_r_vals(chain);
125 Ok((sig, r, key.public_key))
126 }
127
128 pub fn child_public_key(&self, chain: Blockchain) -> Result<PublicKey> {
130 PublicKey::new(chain, &self.get_child_key(chain).public_key)
131 }
132
133 pub fn request_payload_public_key(&self) -> String {
136 let mut key_str = self.api_keys.keys.payload_public_key.to_str_radix(16);
137 if key_str.len() % 2 != 0 {
138 key_str = format!("0{}", &key_str);
139 }
140 key_str
141 }
142
143 pub fn paillier_pk(&self) -> &paillier_common::EncryptionKey {
144 &self.api_keys.keys.paillier_pk
145 }
146
147 pub fn get_address(&self, chain: Blockchain) -> &str {
148 &self.api_keys.keys.child_keys[chain_path(chain)].address
149 }
150
151 pub fn get_child_key(&self, chain: Blockchain) -> APIchildkey {
152 let key = &self.api_keys.keys.child_keys[chain_path(chain)];
153 APIchildkey {
157 client_secret_share: key.client_secret_share.clone(),
158 paillier_pk: self.paillier_pk().clone(),
159 public_key: key.public_key.clone(),
160 server_secret_share_encrypted: key.server_secret_share_encrypted.clone(),
161 }
162 }
163
164 pub fn get_remaining_r_vals(&self, chain: &Blockchain) -> u32 {
166 match chain {
167 Blockchain::Ethereum | Blockchain::Bitcoin => self.k1_remaining.load(Ordering::Acquire),
168 Blockchain::NEO => self.r1_remaining.load(Ordering::Acquire),
169 }
170 }
171
172 pub fn fill_r_vals(&self, chain: Blockchain, n: u32) {
174 match chain {
175 Blockchain::Ethereum | Blockchain::Bitcoin => self.k1_remaining.fetch_add(n, Ordering::Release),
176 Blockchain::NEO => self.r1_remaining.fetch_add(n, Ordering::Release),
177 };
178 tracing::info!("filled {:?}: +{}", chain, n);
179 }
180
181 fn decr_r_vals(&self, chain: Blockchain) {
182 match chain {
183 Blockchain::Ethereum | Blockchain::Bitcoin => self.k1_remaining.fetch_sub(1, Ordering::Release),
184 Blockchain::NEO => self.r1_remaining.fetch_sub(1, Ordering::Release),
185 };
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::Signer;
192
193 #[test]
194 fn test_signing() {
195 let base64_key = "eyJjaGlsZF9rZXlzIjp7fSwKICAgICAgICAicGFpbGxpZXJfcGsiOnsibiI6IjU5ODdlNjIyMjYxY2FmOTZlMjU4MjZjNzBjZjMyM2IyNjE5NGZmOWNmZTY5ZTNmNDBmMzBkMzA2NTcxNjQyY2FlYThhMzE0M2QxMWZmOTRjMTM4ODM2MDQ4NjczNTdhZThjMGU2NjNiZjAzZDAwOTMwMTZkN2Y0ZDc5MGFlMjRlMjkxNzgwM2Q4MTJiNjQxYWYyZDZjMDk1NzNkMTEyZWI3Njg2NDY1MjkxY2QxNDZmZDY2MmY3N2Y1OTVlZjgzMjc3YmUxNjgwZDA0MGIxZjNjNDk5YzgxOTE3NTcyMDZlNTEwYWU1NDcyNGQ2NjdmYzA0MWEyYzdjMmZmM2QzYjY2YzM3MjlkYzI1ZTAyYzQwMTllZDNhMDEyZmQ3NWVjMGUwMzk0OGNmNzgzYWQzOTAyY2U1ZTVlNzIyMjljM2RkM2ExNGI5MzRkNjAyNjlhY2I3YmEwYmQ0MTVkMmRlMTI4ZWYxODcyMjQwMGJhZWEyZTg1MGU2ZDFmZDg3ODdhMDEzMGQ1MTYyMDZkNzE4YTQ5ZDdhMjFkNDI4YjBmYTM3NzMwNzliNjQ4NjE4MTExOTFiNTUwMDFkNGMyYzI5ZjYzMDMxNGJlMTkxY2YzY2EzZjBmOGUwOWVlMDk1NDNmZmRkYTNmOTdjZjE2OWQ1MmUwNjdjZmQ0MGNiMzAzOTQxIn0sCiAgICAgICAgInBheWxvYWRfcHVibGljX2tleSI6IjA0NjE2NDZmZGM0NTQ0ZjEwMjk0ZTIwZTk5NGNlNTZkOGMwZmY4NTI1OTZlYjZiM2FhMGJhOWQ0YjIwNzlkODZkNDJiM2I1ZTg0OTFhNDhmZjZlMTYyMDczMjU3OTgwNzkxNmVlYjA3YmViNmY5OTcwZGM1OTUyYmQ0NDQ0MDRmNzQiLAogICAgICAgICJwYXlsb2FkX3NpZ25pbmdfa2V5IjoiYmI4YmNmNTJhNWY5NDRmMzUxYzViYzg1NmI3YTRjNDFhNWYzNzBmNWNlOTlkY2UwYzhkNmYxZDQ5MWNkMzRiZiIsCiAgICAgICAgInZlcnNpb24iOjB9";
196 let signer = Signer::from_data(&base64_key, "").unwrap();
197 let signature = signer.sign_canonical_string("hello, world!");
198 assert_eq!(signature.signed_digest, "30440220135a79b11caa321f1548d4b86e17c9b53525ffcdeab5e559d6cca310623cc45d02205a0bb368cf79e41d4760f48c9d16ccd8351ac5e97e0a6825eac6acbe662007c4");
199 }
200}