Skip to content

Commit

Permalink
Add a derive_and_tweak_pubkey util function (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
zhenlu authored Apr 9, 2024
1 parent 32e6623 commit 1221e2a
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 1 deletion.
92 changes: 91 additions & 1 deletion src/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::fmt;
use std::str::FromStr as _;
use std::sync::Arc;

use bitcoin::bip32::{DerivationPath, Xpub};
use bitcoin::hashes::sha256;
use bitcoin::secp256k1::ecdsa::Signature;
use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
use bitcoin::secp256k1::{Message, PublicKey, Scalar, Secp256k1, SecretKey};
use bitcoin::{
blockdata::{opcodes::all, script::Builder},
PublicKey as BitcoinPublicKey,
Expand All @@ -18,6 +20,8 @@ pub enum CryptoError {
Secp256k1Error(bitcoin::secp256k1::Error),
RustSecp256k1Error(ecies::SecpError),
InvalidPublicKeyScriptError,
KeyDerivationError,
KeyTweakError,
}

#[derive(Clone)]
Expand All @@ -42,6 +46,8 @@ impl fmt::Display for CryptoError {
Self::Secp256k1Error(err) => write!(f, "Secp256k1 error {}", err),
Self::RustSecp256k1Error(err) => write!(f, "Rust Secp256k1 error {}", err),
Self::InvalidPublicKeyScriptError => write!(f, "Invalid public key script"),
Self::KeyDerivationError => write!(f, "Key derivation error"),
Self::KeyTweakError => write!(f, "Key tweak error"),
}
}
}
Expand Down Expand Up @@ -89,6 +95,41 @@ pub fn generate_keypair() -> Result<Arc<KeyPair>, CryptoError> {
Ok(keypair.into())
}

pub fn derive_and_tweak_pubkey(
pubkey: String,
derivation_path: String,
add_tweak: Option<Vec<u8>>,
mul_tweak: Option<Vec<u8>>,
) -> Result<Vec<u8>, CryptoError> {
let secp = Secp256k1::new();
let path =
DerivationPath::from_str(&derivation_path).map_err(|_| CryptoError::KeyDerivationError)?;
let xpub = Xpub::from_str(&pubkey).map_err(|_| CryptoError::KeyDerivationError)?;
let derived_pubkey = xpub
.derive_pub(&secp, &path)
.map_err(|_| CryptoError::KeyDerivationError)?;

let mut pubkey = derived_pubkey.public_key;
if let Some(tweak) = mul_tweak {
let tweak_bytes: [u8; 32] = tweak.try_into().map_err(|_| CryptoError::KeyTweakError)?;
let tweak_scalar =
Scalar::from_be_bytes(tweak_bytes).map_err(|_| CryptoError::KeyTweakError)?;
pubkey = pubkey
.mul_tweak(&secp, &tweak_scalar)
.map_err(|_| CryptoError::KeyTweakError)?;
}

if let Some(tweak) = add_tweak {
let tweak_bytes: [u8; 32] = tweak.try_into().map_err(|_| CryptoError::KeyTweakError)?;
let tweak_scalar =
Scalar::from_be_bytes(tweak_bytes).map_err(|_| CryptoError::KeyTweakError)?;
pubkey = pubkey
.add_exp_tweak(&secp, &tweak_scalar)
.map_err(|_| CryptoError::KeyTweakError)?;
}
Ok(pubkey.serialize().to_vec())
}

pub fn generate_multisig_address(
network: Network,
pk1: Vec<u8>,
Expand Down Expand Up @@ -139,6 +180,8 @@ fn _generate_multisig_address(
mod tests {
use ecies::utils::generate_keypair;

use crate::signer::{LightsparkSigner, Seed};

use super::*;

#[test]
Expand Down Expand Up @@ -194,4 +237,51 @@ mod tests {
"bcrt1qwgpja522vatddf0vfggrej8pcjrzvzcpkl5yvxzzq4djwr0gj9asrk86y9"
)
}

#[test]
fn test_derive_and_tweak_pubkey() {
let seed_hex_string = "000102030405060708090a0b0c0d0e0f";
let seed_bytes = hex::decode(seed_hex_string).unwrap();
let seed = Seed::new(seed_bytes);

let signer = LightsparkSigner::new(&seed, Network::Bitcoin).unwrap();
let xpub = signer.derive_public_key("m".to_owned()).unwrap();

let message =
hex::decode("9a0c7185121c40850e3e40d3170a5b408374217dc617067f3d7760c522733cef")
.unwrap();

let derivation_path = "m/3/1234856/4";
let add_tweak =
hex::decode("a66cd04862ae9041906f027db9cd43783dad06615fdf9001c5369b315fbef90a")
.unwrap();
let mul_tweak =
hex::decode("d273f16519917211ffee805216b7cb5ae14600eeca5fbc84cefae62cf6a011a4")
.unwrap();

let signature = signer
.derive_key_and_sign(
message.clone(),
derivation_path.to_string(),
true,
Some(add_tweak.clone()),
Some(mul_tweak.clone()),
)
.unwrap();

let pubkey = derive_and_tweak_pubkey(
xpub,
derivation_path.to_string(),
Some(add_tweak.clone()),
Some(mul_tweak.clone()),
)
.unwrap();

let verify_message = Message::from_digest_slice(message.as_slice()).unwrap();
let secp = Secp256k1::new();
let sig = Signature::from_compact(&signature).unwrap();
let pk = PublicKey::from_slice(&pubkey).unwrap();

assert!(secp.verify_ecdsa(&verify_message, &sig, &pk).is_ok());
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ pub mod crypto;
pub mod remote_signing;
pub mod signer;

#[cfg(not(target_arch = "wasm32"))]
use crate::crypto::derive_and_tweak_pubkey;
#[cfg(not(target_arch = "wasm32"))]
use crate::crypto::generate_multisig_address;
#[cfg(not(target_arch = "wasm32"))]
Expand Down
5 changes: 5 additions & 0 deletions src/lightspark_crypto.udl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ namespace lightspark_crypto {
[Throws=CryptoError]
KeyPair generate_keypair();

[Throws=CryptoError]
sequence<u8> derive_and_tweak_pubkey(string pubkey, string derivation_path, sequence<u8>? add_tweak, sequence<u8>? mul_tweak);

[Throws=RemoteSigningError]
RemoteSigningResponse? handle_remote_signing_webhook_event(
sequence<u8> webhook_data,
Expand Down Expand Up @@ -55,6 +58,8 @@ enum CryptoError {
"Secp256k1Error",
"RustSecp256k1Error",
"InvalidPublicKeyScriptError",
"KeyDerivationError",
"KeyTweakError",
};

[Error]
Expand Down

0 comments on commit 1221e2a

Please sign in to comment.