From a1df05c71653535d8dbbbdab614e2b65e6258abe Mon Sep 17 00:00:00 2001 From: joii2020 Date: Tue, 26 Dec 2023 16:13:19 +0800 Subject: [PATCH] Support bitcoin P2SH and unisat support NestedSegwit Legacy --- c/auth.c | 22 +++- .../ckb-auth-cli/src/chain_command/unisat.rs | 118 ++++++++++++++++-- 2 files changed, 127 insertions(+), 13 deletions(-) diff --git a/c/auth.c b/c/auth.c index fd3bb9f..93c6d15 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; @@ -169,7 +171,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 +206,21 @@ static int _recover_secp256k1_pubkey_btc(const uint8_t *sig, size_t sig_len, return ERROR_WRONG_STATE; } + if (p2sh_hash) { + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + unsigned char temp[SHA256_SIZE]; + int err = md_string(md_info, out_pubkey, *out_pubkey_size, temp); + if (err) return err; + + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_RIPEMD160); + err = md_string(md_info, temp, SHA256_SIZE, temp); + if (err) return err; + out_pubkey[0] = 0; + out_pubkey[1] = 20; // RIPEMD160 size + memcpy(out_pubkey + 2, temp, 20); + *out_pubkey_size = 22; + } } else { *out_pubkey_size = UNCOMPRESSED_SECP256K1_PUBKEY_SIZE; flag = SECP256K1_EC_UNCOMPRESSED; 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..]); + } +}