Skip to content

Commit a8033b1

Browse files
feat: add ecdsa support
1 parent efb835a commit a8033b1

File tree

7 files changed

+180
-31
lines changed

7 files changed

+180
-31
lines changed

cli/src/modules/message.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use kaspa_addresses::Version;
22
use kaspa_bip32::secp256k1::XOnlyPublicKey;
3-
use kaspa_wallet_core::message::SignMessageOptions;
3+
use kaspa_wallet_core::message::{SignMessageOptions, SignatureType};
44
use kaspa_wallet_core::{
55
account::{BIP32_ACCOUNT_KIND, KEYPAIR_ACCOUNT_KIND},
66
message::{sign_message, verify_message, PersonalMessage},
@@ -88,7 +88,7 @@ impl Message {
8888

8989
let pm = PersonalMessage(message);
9090
let privkey = self.get_address_private_key(&ctx, kaspa_address).await?;
91-
let sign_options = SignMessageOptions { no_aux_rand: false };
91+
let sign_options = SignMessageOptions { no_aux_rand: false, signature_type: SignatureType::Schnorr };
9292

9393
let sig_result = sign_message(&pm, &privkey, &sign_options);
9494

consensus/core/src/merkle.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ mod tests {
1717

1818
#[test]
1919
fn merkle_root_test() {
20-
let txs = vec![
20+
let txs = [
2121
Transaction::new(
2222
0,
2323
vec![],

consensus/core/src/sign.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
22
hashing::{
3-
sighash::{calc_schnorr_signature_hash, SigHashReusedValuesUnsync},
3+
sighash::{calc_ecdsa_signature_hash, calc_schnorr_signature_hash, SigHashReusedValuesUnsync},
44
sighash_type::{SigHashType, SIG_HASH_ALL},
55
},
66
tx::{SignableTransaction, VerifiableTransaction},
@@ -183,6 +183,39 @@ pub fn verify(tx: &impl VerifiableTransaction) -> Result<(), Error> {
183183
Ok(())
184184
}
185185

186+
/// Sign a transaction using ECDSA
187+
#[allow(clippy::result_large_err)]
188+
pub fn sign_with_multiple_ecdsa(mut mutable_tx: SignableTransaction, privkeys: &[[u8; 32]]) -> Signed {
189+
let mut map = BTreeMap::new();
190+
for privkey in privkeys {
191+
let secret_key = secp256k1::SecretKey::from_slice(privkey).unwrap();
192+
let public_key = secret_key.public_key(secp256k1::SECP256K1);
193+
let script_pub_key_script = once(0x21).chain(public_key.serialize().into_iter()).chain(once(0xac)).collect_vec();
194+
map.insert(script_pub_key_script, secret_key);
195+
}
196+
197+
let reused_values = SigHashReusedValuesUnsync::new();
198+
let mut additional_signatures_required = false;
199+
for i in 0..mutable_tx.tx.inputs.len() {
200+
let script = mutable_tx.entries[i].as_ref().unwrap().script_public_key.script();
201+
if let Some(secret_key) = map.get(script) {
202+
let sig_hash = calc_ecdsa_signature_hash(&mutable_tx.as_verifiable(), i, SIG_HASH_ALL, &reused_values);
203+
let msg = secp256k1::Message::from_digest_slice(sig_hash.as_bytes().as_slice()).unwrap();
204+
let sig = secp256k1::SECP256K1.sign_ecdsa(&msg, secret_key);
205+
let sig_bytes = sig.serialize_compact();
206+
// This represents OP_DATA_65 <SIGNATURE+SIGHASH_TYPE> (since signature length is 64 bytes and SIGHASH_TYPE is one byte)
207+
mutable_tx.tx.inputs[i].signature_script = std::iter::once(65u8).chain(sig_bytes).chain([SIG_HASH_ALL.to_u8()]).collect();
208+
} else {
209+
additional_signatures_required = true;
210+
}
211+
}
212+
if additional_signatures_required {
213+
Signed::Partially(mutable_tx)
214+
} else {
215+
Signed::Fully(mutable_tx)
216+
}
217+
}
218+
186219
#[cfg(test)]
187220
mod tests {
188221
use super::*;

rpc/grpc/core/src/convert/header.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,13 +102,15 @@ mod tests {
102102
RpcHash::from_u64_word(c)
103103
}
104104

105+
#[allow(clippy::needless_range_loop)]
105106
fn test_parents_by_level_rxr(r: &[Vec<RpcHash>], r2: &[Vec<RpcHash>]) {
106107
for i in 0..r.len() {
107108
for j in 0..r[i].len() {
108109
assert_eq!(r[i][j], r2[i][j]);
109110
}
110111
}
111112
}
113+
#[allow(clippy::needless_range_loop)]
112114
fn test_parents_by_level_rxp(r: &[Vec<RpcHash>], p: &[protowire::RpcBlockLevelParents]) {
113115
for i in 0..r.len() {
114116
for j in 0..r[i].len() {

wallet/core/src/message.rs

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
//!
44
55
use kaspa_hashes::{Hash, PersonalMessageSigningHash};
6-
use secp256k1::{Error, XOnlyPublicKey};
6+
use rand::RngCore;
7+
use secp256k1::{Error, PublicKey, XOnlyPublicKey};
78

89
/// A personal message (text) that can be signed.
910
#[derive(Clone)]
@@ -15,6 +16,12 @@ impl AsRef<[u8]> for PersonalMessage<'_> {
1516
}
1617
}
1718

19+
#[derive(Clone, Debug, PartialEq)]
20+
pub enum SignatureType {
21+
Schnorr,
22+
ECDSA,
23+
}
24+
1825
#[derive(Clone)]
1926
pub struct SignMessageOptions {
2027
/// The auxiliary randomness exists only to mitigate specific kinds of power analysis
@@ -23,25 +30,41 @@ pub struct SignMessageOptions {
2330
/// mitigations against such attacks. To read more about the relevant discussions that
2431
/// arose in adding this randomness please see: <https://github.com/sipa/bips/issues/195>
2532
pub no_aux_rand: bool,
33+
/// Signature type to use for signing
34+
pub signature_type: SignatureType,
2635
}
2736

2837
/// Sign a message with the given private key
2938
pub fn sign_message(msg: &PersonalMessage, privkey: &[u8; 32], options: &SignMessageOptions) -> Result<Vec<u8>, Error> {
3039
let hash = calc_personal_message_hash(msg);
31-
3240
let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice())?;
33-
let schnorr_key = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, privkey)?;
34-
35-
let sig: [u8; 64] = if options.no_aux_rand {
36-
*secp256k1::SECP256K1.sign_schnorr_no_aux_rand(&msg, &schnorr_key).as_ref()
37-
} else {
38-
*schnorr_key.sign_schnorr(msg).as_ref()
39-
};
4041

41-
Ok(sig.to_vec())
42+
match options.signature_type {
43+
SignatureType::Schnorr => {
44+
let schnorr_key = secp256k1::Keypair::from_seckey_slice(secp256k1::SECP256K1, privkey)?;
45+
let sig: [u8; 64] = if options.no_aux_rand {
46+
*secp256k1::SECP256K1.sign_schnorr_no_aux_rand(&msg, &schnorr_key).as_ref()
47+
} else {
48+
*schnorr_key.sign_schnorr(msg).as_ref()
49+
};
50+
Ok(sig.to_vec())
51+
}
52+
SignatureType::ECDSA => {
53+
let secret_key = secp256k1::SecretKey::from_slice(privkey)?;
54+
let sig = if options.no_aux_rand {
55+
secp256k1::SECP256K1.sign_ecdsa(&msg, &secret_key)
56+
} else {
57+
// TODO: Use sign_ecdsa_with_noncedata with random noncedata to add auxiliary randomness
58+
let mut nonce_data = [0u8; 32];
59+
rand::thread_rng().fill_bytes(&mut nonce_data);
60+
secp256k1::SECP256K1.sign_ecdsa_with_noncedata(&msg, &secret_key, &nonce_data)
61+
};
62+
Ok(sig.serialize_compact().to_vec())
63+
}
64+
}
4265
}
4366

44-
/// Verifies signed message.
67+
/// Verifies Schnorr signed message.
4568
///
4669
/// Produces `Ok(())` if the signature matches the given message and [`secp256k1::Error`]
4770
/// if any of the inputs are incorrect, or the signature is invalid.
@@ -53,6 +76,18 @@ pub fn verify_message(msg: &PersonalMessage, signature: &Vec<u8>, pubkey: &XOnly
5376
sig.verify(&msg, pubkey)
5477
}
5578

79+
/// Verifies ECDSA signed message.
80+
///
81+
/// Produces `Ok(())` if the signature matches the given message and [`secp256k1::Error`]
82+
/// if any of the inputs are incorrect, or the signature is invalid.
83+
///
84+
pub fn verify_message_ecdsa(msg: &PersonalMessage, signature: &Vec<u8>, pubkey: &PublicKey) -> Result<(), Error> {
85+
let hash = calc_personal_message_hash(msg);
86+
let msg = secp256k1::Message::from_digest_slice(hash.as_bytes().as_slice())?;
87+
let sig = secp256k1::ecdsa::Signature::from_compact(signature.as_slice())?;
88+
sig.verify(&msg, pubkey)
89+
}
90+
5691
fn calc_personal_message_hash(msg: &PersonalMessage) -> Hash {
5792
let mut hasher = PersonalMessageSigningHash::new();
5893
hasher.write(msg);
@@ -89,8 +124,8 @@ mod tests {
89124
])
90125
.unwrap();
91126

92-
let sign_with_aux_rand = SignMessageOptions { no_aux_rand: false };
93-
let sign_with_no_aux_rand = SignMessageOptions { no_aux_rand: true };
127+
let sign_with_aux_rand = SignMessageOptions { no_aux_rand: false, signature_type: SignatureType::Schnorr };
128+
let sign_with_no_aux_rand = SignMessageOptions { no_aux_rand: true, signature_type: SignatureType::Schnorr };
94129
verify_message(&pm, &sign_message(&pm, &privkey, &sign_with_aux_rand).expect("sign_message failed"), &pubkey)
95130
.expect("verify_message failed");
96131
verify_message(&pm, &sign_message(&pm, &privkey, &sign_with_no_aux_rand).expect("sign_message failed"), &pubkey)
@@ -105,7 +140,7 @@ mod tests {
105140
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
106141
];
107142

108-
let sign_with_no_aux_rand = SignMessageOptions { no_aux_rand: true };
143+
let sign_with_no_aux_rand = SignMessageOptions { no_aux_rand: true, signature_type: SignatureType::Schnorr };
109144
let signature = sign_message(&pm, &privkey, &sign_with_no_aux_rand).expect("sign_message failed");
110145
let signature_twice = sign_message(&pm, &privkey, &sign_with_no_aux_rand).expect("sign_message failed");
111146
assert_eq!(signature, signature_twice);
@@ -124,8 +159,8 @@ mod tests {
124159
])
125160
.unwrap();
126161

127-
let sign_with_aux_rand = SignMessageOptions { no_aux_rand: false };
128-
let sign_with_no_aux_rand = SignMessageOptions { no_aux_rand: true };
162+
let sign_with_aux_rand = SignMessageOptions { no_aux_rand: false, signature_type: SignatureType::Schnorr };
163+
let sign_with_no_aux_rand = SignMessageOptions { no_aux_rand: true, signature_type: SignatureType::Schnorr };
129164
verify_message(&pm, &sign_message(&pm, &privkey, &sign_with_aux_rand).expect("sign_message failed"), &pubkey)
130165
.expect("verify_message failed");
131166
verify_message(&pm, &sign_message(&pm, &privkey, &sign_with_no_aux_rand).expect("sign_message failed"), &pubkey)
@@ -149,8 +184,8 @@ Ut omnis magnam et accusamus earum rem impedit provident eum commodi repellat qu
149184
])
150185
.unwrap();
151186

152-
let sign_with_aux_rand = SignMessageOptions { no_aux_rand: false };
153-
let sign_with_no_aux_rand = SignMessageOptions { no_aux_rand: true };
187+
let sign_with_aux_rand = SignMessageOptions { no_aux_rand: false, signature_type: SignatureType::Schnorr };
188+
let sign_with_no_aux_rand = SignMessageOptions { no_aux_rand: true, signature_type: SignatureType::Schnorr };
154189
verify_message(&pm, &sign_message(&pm, &privkey, &sign_with_aux_rand).expect("sign_message failed"), &pubkey)
155190
.expect("verify_message failed");
156191
verify_message(&pm, &sign_message(&pm, &privkey, &sign_with_no_aux_rand).expect("sign_message failed"), &pubkey)

wallet/core/src/tx/generator/signer.rs

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,28 @@
44
55
use crate::imports::*;
66
use kaspa_bip32::PrivateKey;
7-
use kaspa_consensus_core::{sign::sign_with_multiple_v2, tx::SignableTransaction};
7+
use kaspa_consensus_core::{
8+
sign::{sign_with_multiple_ecdsa, sign_with_multiple_v2},
9+
tx::SignableTransaction,
10+
};
811

912
pub trait SignerT: Send + Sync + 'static {
1013
fn try_sign(&self, transaction: SignableTransaction, addresses: &[Address]) -> Result<SignableTransaction>;
14+
fn signature_type(&self) -> SignatureType;
15+
}
16+
17+
#[derive(Clone, Debug, PartialEq)]
18+
pub enum SignatureType {
19+
Schnorr,
20+
ECDSA,
1121
}
1222

1323
struct Inner {
1424
keydata: PrvKeyData,
1525
account: Arc<dyn Account>,
1626
payment_secret: Option<Secret>,
1727
keys: Mutex<AHashMap<Address, [u8; 32]>>,
28+
signature_type: SignatureType,
1829
}
1930

2031
pub struct Signer {
@@ -23,7 +34,24 @@ pub struct Signer {
2334

2435
impl Signer {
2536
pub fn new(account: Arc<dyn Account>, keydata: PrvKeyData, payment_secret: Option<Secret>) -> Self {
26-
Self { inner: Arc::new(Inner { keydata, account, payment_secret, keys: Mutex::new(AHashMap::new()) }) }
37+
Self {
38+
inner: Arc::new(Inner {
39+
keydata,
40+
account,
41+
payment_secret,
42+
keys: Mutex::new(AHashMap::new()),
43+
signature_type: SignatureType::Schnorr, // Default to Schnorr
44+
}),
45+
}
46+
}
47+
48+
pub fn new_with_signature_type(
49+
account: Arc<dyn Account>,
50+
keydata: PrvKeyData,
51+
payment_secret: Option<Secret>,
52+
signature_type: SignatureType,
53+
) -> Self {
54+
Self { inner: Arc::new(Inner { keydata, account, payment_secret, keys: Mutex::new(AHashMap::new()), signature_type }) }
2755
}
2856

2957
fn ingest(&self, addresses: &[Address]) -> Result<()> {
@@ -55,16 +83,24 @@ impl SignerT for Signer {
5583
let keys = self.inner.keys.lock().unwrap();
5684
let mut keys_for_signing = addresses.iter().map(|address| *keys.get(address).unwrap()).collect::<Vec<_>>();
5785
// TODO - refactor for multisig
58-
let signable_tx = sign_with_multiple_v2(mutable_tx, &keys_for_signing).fully_signed()?;
86+
let signable_tx = match self.inner.signature_type {
87+
SignatureType::Schnorr => sign_with_multiple_v2(mutable_tx, &keys_for_signing).fully_signed()?,
88+
SignatureType::ECDSA => sign_with_multiple_ecdsa(mutable_tx, &keys_for_signing).fully_signed()?,
89+
};
5990
keys_for_signing.zeroize();
6091
Ok(signable_tx)
6192
}
93+
94+
fn signature_type(&self) -> SignatureType {
95+
self.inner.signature_type.clone()
96+
}
6297
}
6398

6499
// ---
65100

66101
struct KeydataSignerInner {
67102
keys: HashMap<Address, [u8; 32]>,
103+
signature_type: SignatureType,
68104
}
69105

70106
pub struct KeydataSigner {
@@ -74,16 +110,32 @@ pub struct KeydataSigner {
74110
impl KeydataSigner {
75111
pub fn new(keydata: Vec<(Address, secp256k1::SecretKey)>) -> Self {
76112
let keys = keydata.into_iter().map(|(address, key)| (address, key.to_bytes())).collect();
77-
Self { inner: Arc::new(KeydataSignerInner { keys }) }
113+
Self {
114+
inner: Arc::new(KeydataSignerInner {
115+
keys,
116+
signature_type: SignatureType::Schnorr, // Default to Schnorr
117+
}),
118+
}
119+
}
120+
121+
pub fn new_with_signature_type(keydata: Vec<(Address, secp256k1::SecretKey)>, signature_type: SignatureType) -> Self {
122+
let keys = keydata.into_iter().map(|(address, key)| (address, key.to_bytes())).collect();
123+
Self { inner: Arc::new(KeydataSignerInner { keys, signature_type }) }
78124
}
79125
}
80126

81127
impl SignerT for KeydataSigner {
82128
fn try_sign(&self, mutable_tx: SignableTransaction, addresses: &[Address]) -> Result<SignableTransaction> {
83129
let mut keys_for_signing = addresses.iter().map(|address| *self.inner.keys.get(address).unwrap()).collect::<Vec<_>>();
84-
// TODO - refactor for multisig
85-
let signable_tx = sign_with_multiple_v2(mutable_tx, &keys_for_signing).fully_signed()?;
130+
131+
let signable_tx = match self.inner.signature_type {
132+
SignatureType::Schnorr => sign_with_multiple_v2(mutable_tx, &keys_for_signing).fully_signed()?,
133+
SignatureType::ECDSA => sign_with_multiple_ecdsa(mutable_tx, &keys_for_signing).fully_signed()?,
134+
};
86135
keys_for_signing.zeroize();
87136
Ok(signable_tx)
88137
}
138+
fn signature_type(&self) -> SignatureType {
139+
self.inner.signature_type.clone()
140+
}
89141
}

0 commit comments

Comments
 (0)