Skip to content

Commit

Permalink
Support Bitcoin P2WPKH-P2SH (#35)
Browse files Browse the repository at this point in the history
* Support P2WPKH-P2SH
* Unisat support Nested Segwit and Legacy
* Add bitcoin hash160 function
  • Loading branch information
joii2020 authored Dec 26, 2023
1 parent 4046e7c commit 4f6dd1c
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 26 deletions.
46 changes: 31 additions & 15 deletions c/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
118 changes: 107 additions & 11 deletions tools/ckb-auth-cli/src/chain_command/unisat.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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"))
Expand All @@ -40,11 +52,11 @@ impl BlockChain for UnisatLock {
}

fn verify(&self, operate_mathches: &ArgMatches) -> Result<(), Error> {
let address = operate_mathches
.get_one::<String>("address")
.expect("Get address from args");
let (_hrp, address, _v) = bech32::decode(&address).expect("decode bech32");
let address = Vec::<u8>::from_base32(&address[1..33]).unwrap();
let (address, addr_type) = Self::get_publickey_hash(
operate_mathches
.get_one::<String>("address")
.expect("Get address from args"),
);

let mut signature = decode_string(
operate_mathches
Expand All @@ -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::<u8>::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..]);
}
}

0 comments on commit 4f6dd1c

Please sign in to comment.