From 4f6dd1ca4ba849beea424b3cb3b60c3a4fd9f1c2 Mon Sep 17 00:00:00 2001 From: joii2020 <87224197+joii2020@users.noreply.github.com> Date: Tue, 26 Dec 2023 16:44:31 +0800 Subject: [PATCH] Support Bitcoin P2WPKH-P2SH (#35) * Support P2WPKH-P2SH * Unisat support Nested Segwit and Legacy * Add bitcoin hash160 function --- c/auth.c | 46 ++++--- .../ckb-auth-cli/src/chain_command/unisat.rs | 118 ++++++++++++++++-- 2 files changed, 138 insertions(+), 26 deletions(-) diff --git a/c/auth.c b/c/auth.c index fd3bb9f..40b910b 100644 --- a/c/auth.c +++ b/c/auth.c @@ -140,14 +140,16 @@ static int _recover_secp256k1_pubkey(const uint8_t *sig, size_t sig_len, } // Refer to: https://en.bitcoin.it/wiki/BIP_0137 -int get_btc_recid(uint8_t d, bool *compressed) { +int get_btc_recid(uint8_t d, bool *compressed, bool *p2sh_hash) { *compressed = true; + *p2sh_hash = false; if (d >= 27 && d <= 30) { // P2PKH uncompressed *compressed = false; return d - 27; } else if (d >= 31 && d <= 34) { // P2PKH compressed return d - 31; } else if (d >= 35 && d <= 38) { // Segwit P2SH + *p2sh_hash = true; return d - 35; } else if (d >= 39 && d <= 42) { // Segwit Bech32 return d - 39; @@ -156,6 +158,20 @@ int get_btc_recid(uint8_t d, bool *compressed) { } } +int bitcoin_hash160(const uint8_t *data, size_t size, uint8_t *output) { + int err = 0; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + unsigned char temp[SHA256_SIZE]; + err = md_string(md_info, data, size, temp); + if (err) return err; + + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_RIPEMD160); + err = md_string(md_info, temp, SHA256_SIZE, output); + if (err) return err; + return 0; +} + static int _recover_secp256k1_pubkey_btc(const uint8_t *sig, size_t sig_len, const uint8_t *msg, size_t msg_len, uint8_t *out_pubkey, @@ -169,7 +185,8 @@ static int _recover_secp256k1_pubkey_btc(const uint8_t *sig, size_t sig_len, return ERROR_INVALID_ARG; } bool compressed = true; - int recid = get_btc_recid(sig[0], &compressed); + bool p2sh_hash = false; + int recid = get_btc_recid(sig[0], &compressed, &p2sh_hash); if (recid == -1) { return ERROR_INVALID_ARG; } @@ -203,6 +220,15 @@ static int _recover_secp256k1_pubkey_btc(const uint8_t *sig, size_t sig_len, return ERROR_WRONG_STATE; } + if (p2sh_hash) { + int err = + bitcoin_hash160(out_pubkey, *out_pubkey_size, out_pubkey + 2); + if (err) return err; + + out_pubkey[0] = 0; + out_pubkey[1] = 20; // RIPEMD160 size + *out_pubkey_size = 22; + } } else { *out_pubkey_size = UNCOMPRESSED_SECP256K1_PUBKEY_SIZE; flag = SECP256K1_EC_UNCOMPRESSED; @@ -320,15 +346,8 @@ int validate_signature_btc(void *prefilled_data, const uint8_t *sig, &out_pubkey_size); CHECK(err); - const mbedtls_md_info_t *md_info = - mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); - unsigned char temp[SHA256_SIZE]; - err = md_string(md_info, out_pubkey, out_pubkey_size, temp); - CHECK(err); - - md_info = mbedtls_md_info_from_type(MBEDTLS_MD_RIPEMD160); - err = md_string(md_info, temp, SHA256_SIZE, temp); - CHECK(err); + unsigned char temp[AUTH160_SIZE]; + err = bitcoin_hash160(out_pubkey, out_pubkey_size, temp); memcpy(output, temp, AUTH160_SIZE); *output_len = AUTH160_SIZE; @@ -884,10 +903,7 @@ int convert_litecoin_message(const uint8_t *msg, size_t msg_len, int convert_ripple_message(const uint8_t *msg, size_t msg_len, uint8_t *new_msg, size_t new_msg_len) { int err = 0; - CHECK(mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), msg, msg_len, - new_msg)); - CHECK(mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_RIPEMD160), new_msg, - SHA256_SIZE, new_msg)); + CHECK(bitcoin_hash160(msg, msg_len, new_msg)); memset(new_msg + 20, 0, 12); exit: return err; diff --git a/tools/ckb-auth-cli/src/chain_command/unisat.rs b/tools/ckb-auth-cli/src/chain_command/unisat.rs index ab0d26a..40921c2 100644 --- a/tools/ckb-auth-cli/src/chain_command/unisat.rs +++ b/tools/ckb-auth-cli/src/chain_command/unisat.rs @@ -1,4 +1,8 @@ -use crate::{auth_script::run_auth_exec, utils::decode_string, BlockChain, BlockChainArgs}; +use crate::{ + auth_script::run_auth_exec, + utils::{calculate_sha256, decode_string}, + BlockChain, BlockChainArgs, +}; use anyhow::{anyhow, Error}; use bitcoin::bech32::{self, FromBase32}; use ckb_auth_rs::AuthAlgorithmIdType; @@ -30,6 +34,14 @@ impl BlockChainArgs for UnisatLockArgs { pub struct UnisatLock {} +#[derive(PartialEq, Eq)] +enum UnisatLockAddressType { + NativeSegwit, + NestedSegwit, + Taproot, + Legacy, +} + impl BlockChain for UnisatLock { fn parse(&self, _operate_mathches: &ArgMatches) -> Result<(), Error> { Err(anyhow!("unisat does not parse")) @@ -40,11 +52,11 @@ impl BlockChain for UnisatLock { } fn verify(&self, operate_mathches: &ArgMatches) -> Result<(), Error> { - let address = operate_mathches - .get_one::("address") - .expect("Get address from args"); - let (_hrp, address, _v) = bech32::decode(&address).expect("decode bech32"); - let address = Vec::::from_base32(&address[1..33]).unwrap(); + let (address, addr_type) = Self::get_publickey_hash( + operate_mathches + .get_one::("address") + .expect("Get address from args"), + ); let mut signature = decode_string( operate_mathches @@ -61,20 +73,104 @@ impl BlockChain for UnisatLock { ) .expect("decode message"); - if address.len() < 20 { - return Err(anyhow!("unisat address invalidate")); - } if signature.len() != 65 { return Err(anyhow!("unisat signature size is not 65")); } - signature[0] += 4; + if addr_type == UnisatLockAddressType::NestedSegwit { + let recid = (signature[0] - 27) % 4; + signature[0] = recid + 35; + } else { + let recid = (signature[0] - 27) % 4; + signature[0] = recid + 31; + } + if message.len() != 32 { return Err(anyhow!("unisat message size is not 32")); } - run_auth_exec(AuthAlgorithmIdType::Bitcoin, &address, &message, &signature)?; println!("Signature verification succeeded!"); Ok(()) } } + +impl UnisatLock { + fn get_publickey_hash(address: &str) -> ([u8; 20], UnisatLockAddressType) { + let r_address = Self::parse_address_with_native_segwit(address); + if r_address.is_some() { + return (r_address.unwrap(), UnisatLockAddressType::NativeSegwit); + } + + let r_address = Self::parse_address_with_nested_segwit(address); + if r_address.is_some() { + return (r_address.unwrap(), UnisatLockAddressType::NestedSegwit); + } + + let r_address = Self::_parse_address_with_taproot(address); + if r_address.is_some() { + return (r_address.unwrap(), UnisatLockAddressType::Taproot); + } + + let r_address = Self::parse_address_with_legacy(address); + if r_address.is_some() { + return (r_address.unwrap(), UnisatLockAddressType::Legacy); + } + panic!("unknow parse address"); + } + + fn parse_address_with_native_segwit(address: &str) -> Option<[u8; 20]> { + let r = bech32::decode(address); + if r.is_err() { + return None; + } + let (_hrp, address, _v) = r.unwrap(); + + let address = Vec::::from_base32(&address[1..33]); + if address.is_err() { + return None; + } + let address = address.unwrap(); + if address.len() != 20 { + return None; + } + + Some(address.try_into().unwrap()) + } + + fn parse_address_with_nested_segwit(address: &str) -> Option<[u8; 20]> { + let address = bs58::decode(address).into_vec(); + if address.is_err() { + return None; + } + let address = address.unwrap(); + Self::check_sum(&address); + + Some(address[1..21].try_into().unwrap()) + } + + fn _parse_address_with_taproot(_address: &str) -> Option<[u8; 20]> { + // Unsupport + None + } + + fn parse_address_with_legacy(address: &str) -> Option<[u8; 20]> { + let address = bs58::decode(address).into_vec(); + if address.is_err() { + return None; + } + let address = address.unwrap(); + + if address.len() < 21 { + return None; + } + + Self::check_sum(&address); + + Some(address[1..21].try_into().unwrap()) + } + + fn check_sum(data: &[u8]) { + let checksum = calculate_sha256(&calculate_sha256(&data[0..21])); + assert_eq!(checksum[..4], data[21..]); + } +}