Skip to content

Commit

Permalink
Add generate_multisig_address
Browse files Browse the repository at this point in the history
  • Loading branch information
zhenlu committed Feb 29, 2024
1 parent 1803f01 commit 913068d
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ecies = { "version" = "0.2.6", default-features = false, features = ["pure"]}
lightspark-remote-signing = "=0.3.0"
serde_json = "1.0.107"
serde = "1.0.188"
bitcoin-bech32 = "0.13.0"

[features]
default = ["uniffi/cli"]
Expand Down
90 changes: 90 additions & 0 deletions src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@ use std::sync::Arc;
use bitcoin::hashes::sha256;
use bitcoin::secp256k1::ecdsa::Signature;
use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, SecretKey};
use bitcoin::{
blockdata::{opcodes::all, script::Builder},
PublicKey as BitcoinPublicKey,
};
use ecies::decrypt;
use ecies::encrypt;

use crate::signer::Network;

#[derive(Clone, Copy, Debug)]
pub enum CryptoError {
Secp256k1Error(bitcoin::secp256k1::Error),
RustSecp256k1Error(ecies::SecpError),
InvalidPublicKeyScriptError,
}

#[derive(Clone)]
Expand All @@ -34,6 +41,7 @@ impl fmt::Display for CryptoError {
match self {
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"),
}
}
}
Expand Down Expand Up @@ -79,6 +87,52 @@ pub fn generate_keypair() -> Result<Arc<KeyPair>, CryptoError> {
Ok(keypair.into())
}

pub fn generate_multisig_address(
network: Network,
pk1: Vec<u8>,
pk2: Vec<u8>,
) -> Result<String, CryptoError> {
let pk1_obj =
BitcoinPublicKey::new(PublicKey::from_slice(&pk1).map_err(CryptoError::Secp256k1Error)?);
let pk2_obj =
BitcoinPublicKey::new(PublicKey::from_slice(&pk2).map_err(CryptoError::Secp256k1Error)?);
let network = match network {
Network::Bitcoin => bitcoin_bech32::constants::Network::Bitcoin,
Network::Testnet => bitcoin_bech32::constants::Network::Testnet,
Network::Regtest => bitcoin_bech32::constants::Network::Regtest,
};
_generate_multisig_address(network, &pk1_obj, &pk2_obj)
}

fn _generate_multisig_address(
network: bitcoin_bech32::constants::Network,
pk1: &BitcoinPublicKey,
pk2: &BitcoinPublicKey,
) -> Result<String, CryptoError> {
let mut builder = Builder::new();
builder = builder.push_opcode(all::OP_PUSHNUM_2);

// The public keys need to be properly ordered in a multisig script.
if pk1 < pk2 {
builder = builder.push_key(&pk1);
builder = builder.push_key(&pk2);
} else {
builder = builder.push_key(&pk2);
builder = builder.push_key(&pk1);
}

builder = builder.push_opcode(all::OP_PUSHNUM_2);
builder = builder.push_opcode(all::OP_CHECKMULTISIG);

let script = builder.into_script().to_v0_p2wsh();

Ok(
bitcoin_bech32::WitnessProgram::from_scriptpubkey(script.as_bytes(), network.into())
.map_err(|_| CryptoError::InvalidPublicKeyScriptError)?
.to_address(),
)
}

#[cfg(test)]
mod tests {
use ecies::utils::generate_keypair;
Expand All @@ -102,4 +156,40 @@ mod tests {
let plain_text = decrypt_ecies(cipher_text, sk.serialize().to_vec()).unwrap();
assert_eq!(plain_text, msg.to_vec());
}

use super::generate_multisig_address;

#[test]
fn test_generate_multisig_address() {
let address = generate_multisig_address(
Network::Regtest,
hex::decode("0247997a5c32ccf934257a675c306bf6ec37019358240156628af62baad7066a83")
.unwrap(),
hex::decode("03b66b574670a7b6bea89c0548903f70a6f059fd9abe737dc4c5aafe14a127408f")
.unwrap(),
)
.unwrap();

assert_eq!(
address,
"bcrt1qwgpja522vatddf0vfggrej8pcjrzvzcpkl5yvxzzq4djwr0gj9asrk86y9"
)
}

#[test]
fn test_generate_multisig_address_reversed() {
let address = generate_multisig_address(
Network::Regtest,
hex::decode("03b66b574670a7b6bea89c0548903f70a6f059fd9abe737dc4c5aafe14a127408f")
.unwrap(),
hex::decode("0247997a5c32ccf934257a675c306bf6ec37019358240156628af62baad7066a83")
.unwrap(),
)
.unwrap();

assert_eq!(
address,
"bcrt1qwgpja522vatddf0vfggrej8pcjrzvzcpkl5yvxzzq4djwr0gj9asrk86y9"
)
}
}
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::generate_multisig_address;
#[cfg(not(target_arch = "wasm32"))]
use crypto::decrypt_ecies;
#[cfg(not(target_arch = "wasm32"))]
Expand Down
4 changes: 4 additions & 0 deletions src/lightspark_crypto.udl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ namespace lightspark_crypto {
[Throws=CryptoError]
sequence<u8> decrypt_ecies(sequence<u8> cipher_text, sequence<u8> private_key_bytes);

[Throws=CryptoError]
string generate_multisig_address(Network network, sequence<u8> pk1, sequence<u8> pk2);

[Throws=CryptoError]
KeyPair generate_keypair();

Expand Down Expand Up @@ -51,6 +54,7 @@ enum RemoteSigningError {
enum CryptoError {
"Secp256k1Error",
"RustSecp256k1Error",
"InvalidPublicKeyScriptError",
};

[Error]
Expand Down
26 changes: 26 additions & 0 deletions src/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,4 +647,30 @@ mod tests {
.verify_ecdsa(&msg, &signature, &verification_key)
.is_ok());
}

#[test]
fn test_verify() {
let master_pubkey = "tpubD6NzVbkrYhZ4WvPEvMK28MVhta7KAkNnmLH1nz4JMCBoTiyhfzWwTTWbE3oB49UjVpM6ikgD39nj1feGfFSdKjqDdL9C7rdosQy6HWbXtwE";
let derivation_path = "m/3/244027188/0";
let message = "4d356a23b93e60d57018fb4fec5d0719f9a58be367b342e1687f9ebeef1c189d";
let signature = "b6b8f8e56e51ba970123b5bf2e595e8f0c375f42195c0c58c921b1f303c9885755f889d6ce50e0fe9383aec8b071c0b0fe510ab779bd4bff6b30ec2499bd9343";

let master_pub = ExtendedPubKey::from_str(master_pubkey).unwrap();
println!("{:?}", hex::encode(master_pub.public_key.serialize()));
let verification_key = master_pub
.derive_pub(
&Secp256k1::new(),
&DerivationPath::from_str(derivation_path).unwrap(),
)
.unwrap()
.public_key;
let msg_bytes = hex::decode(message).unwrap();
let msg = Message::from_slice(&msg_bytes).unwrap();
let signature_bytes = hex::decode(signature).unwrap();
let signature = Signature::from_compact(signature_bytes.as_slice()).unwrap();
let secp = Secp256k1::new();
assert!(secp
.verify_ecdsa(&msg, &signature, &verification_key)
.is_ok());
}
}

0 comments on commit 913068d

Please sign in to comment.