diff --git a/frameworks/rooch-framework/doc/bitcoin_address.md b/frameworks/rooch-framework/doc/bitcoin_address.md index 5da6a4e712..56c6b82d8a 100644 --- a/frameworks/rooch-framework/doc/bitcoin_address.md +++ b/frameworks/rooch-framework/doc/bitcoin_address.md @@ -20,7 +20,7 @@ - [Function `from_string`](#0x3_bitcoin_address_from_string) - [Function `verify_with_public_key`](#0x3_bitcoin_address_verify_with_public_key) - [Function `to_rooch_address`](#0x3_bitcoin_address_to_rooch_address) -- [Function `verify_with_pk`](#0x3_bitcoin_address_verify_with_pk) +- [Function `verify_bitcoin_address_with_public_key`](#0x3_bitcoin_address_verify_bitcoin_address_with_public_key) - [Function `derive_multisig_xonly_pubkey_from_xonly_pubkeys`](#0x3_bitcoin_address_derive_multisig_xonly_pubkey_from_xonly_pubkeys) - [Function `derive_bitcoin_taproot_address_from_multisig_xonly_pubkey`](#0x3_bitcoin_address_derive_bitcoin_taproot_address_from_multisig_xonly_pubkey) @@ -52,56 +52,56 @@ We just keep the raw bytes of the address and do care about the network. ## Constants - + -
const E_ARG_NOT_VECTOR_U8: u64 = 2;
+
const ErrorArgNotVectorU8: u64 = 2;
 
- + -
const E_INVALID_ADDRESS: u64 = 1;
+
const ErrorInvalidAddress: u64 = 1;
 
- + -
const E_INVALID_KEY_EGG_CONTEXT: u64 = 5;
+
const ErrorInvalidKeyEggContext: u64 = 5;
 
- + -
const E_INVALID_PUBLIC_KEY: u64 = 3;
+
const ErrorInvalidPublicKey: u64 = 3;
 
- + -
const E_INVALID_THRESHOLD: u64 = 4;
+
const ErrorInvalidThreshold: u64 = 4;
 
- + -
const E_INVALID_XONLY_PUBKEY: u64 = 6;
+
const ErrorInvalidXOnlyPublicKey: u64 = 6;
 
@@ -322,13 +322,13 @@ Empty address is a special address that is used to if we parse address failed fr - + -## Function `verify_with_pk` +## Function `verify_bitcoin_address_with_public_key` -
public fun verify_with_pk(bitcoin_addr: &bitcoin_address::BitcoinAddress, pk: &vector<u8>): bool
+
public fun verify_bitcoin_address_with_public_key(bitcoin_addr: &bitcoin_address::BitcoinAddress, pk: &vector<u8>): bool
 
diff --git a/frameworks/rooch-framework/sources/address_type/bitcoin_address.move b/frameworks/rooch-framework/sources/address_type/bitcoin_address.move index 2bd6ae9c50..12e3e33b6f 100644 --- a/frameworks/rooch-framework/sources/address_type/bitcoin_address.move +++ b/frameworks/rooch-framework/sources/address_type/bitcoin_address.move @@ -14,12 +14,12 @@ module rooch_framework::bitcoin_address { const P2SH_ADDR_BYTE_LEN: u64 = 21; // error code - const E_INVALID_ADDRESS: u64 = 1; - const E_ARG_NOT_VECTOR_U8: u64 = 2; - const E_INVALID_PUBLIC_KEY: u64 = 3; - const E_INVALID_THRESHOLD: u64 = 4; - const E_INVALID_KEY_EGG_CONTEXT: u64 = 5; - const E_INVALID_XONLY_PUBKEY: u64 = 6; + const ErrorInvalidAddress: u64 = 1; + const ErrorArgNotVectorU8: u64 = 2; + const ErrorInvalidPublicKey: u64 = 3; + const ErrorInvalidThreshold: u64 = 4; + const ErrorInvalidKeyEggContext: u64 = 5; + const ErrorInvalidXOnlyPublicKey: u64 = 6; // P2PKH address decimal prefix const P2PKH_ADDR_DECIMAL_PREFIX_MAIN: u8 = 0; // 0x00 @@ -37,7 +37,7 @@ module rooch_framework::bitcoin_address { } public fun new_p2pkh(pubkey_hash: vector): BitcoinAddress{ - assert!(vector::length(&pubkey_hash) == PUBKEY_HASH_LEN, E_INVALID_ADDRESS); + assert!(vector::length(&pubkey_hash) == PUBKEY_HASH_LEN, ErrorInvalidAddress); //we do not distinguish between mainnet and testnet in Move let bytes = vector::singleton(P2PKH_ADDR_DECIMAL_PREFIX_MAIN); vector::append(&mut bytes, pubkey_hash); @@ -47,7 +47,7 @@ module rooch_framework::bitcoin_address { } public fun new_p2sh(script_hash: vector): BitcoinAddress{ - assert!(vector::length(&script_hash) == SCRIPT_HASH_LEN, E_INVALID_ADDRESS); + assert!(vector::length(&script_hash) == SCRIPT_HASH_LEN, ErrorInvalidAddress); let bytes = vector::singleton(P2SH_ADDR_DECIMAL_PREFIX_MAIN); vector::append(&mut bytes, script_hash); BitcoinAddress { @@ -102,7 +102,7 @@ module rooch_framework::bitcoin_address { public fun verify_with_public_key(addr: &String, pk: &vector): bool { let bitcoin_addr = from_string(addr); - verify_with_pk(&bitcoin_addr, pk) + verify_bitcoin_address_with_public_key(&bitcoin_addr, pk) } public fun to_rooch_address(addr: &BitcoinAddress): address{ @@ -111,7 +111,7 @@ module rooch_framework::bitcoin_address { } // verify bitcoin address according to the pk bytes - public native fun verify_with_pk(bitcoin_addr: &BitcoinAddress, pk: &vector): bool; + public native fun verify_bitcoin_address_with_public_key(bitcoin_addr: &BitcoinAddress, pk: &vector): bool; // derive multisig xonly public key from public keys public native fun derive_multisig_xonly_pubkey_from_xonly_pubkeys(public_keys: vector>, threshold: u64): vector; @@ -121,6 +121,8 @@ module rooch_framework::bitcoin_address { /// Parse the Bitcoin address string bytes to Move BitcoinAddress native fun parse(raw_addr: &vector): BitcoinAddress; + // TODO: remove verify_with_pk after upgrade + native fun verify_with_pk(addr: &vector, pk: &vector): bool; #[test_only] public fun random_address_for_testing(): BitcoinAddress { @@ -172,7 +174,35 @@ module rooch_framework::bitcoin_address { let xonly_pubkey = derive_multisig_xonly_pubkey_from_xonly_pubkeys(pk_list, 2); - assert!(expected_xonly_pubkey == xonly_pubkey, E_INVALID_KEY_EGG_CONTEXT); + assert!(expected_xonly_pubkey == xonly_pubkey, ErrorInvalidKeyEggContext); + } + + #[test] + #[expected_failure(location=Self, abort_code = ErrorInvalidThreshold)] + fun test_derive_multisig_xonly_pubkey_from_xonly_pubkeys_fail_invalid_threshold() { + let pk_list = vector::empty>(); + + let pk_1 = x"f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"; + let pk_2 = x"ffa540e2d3df158dfb202fc1a2cbb20c4920ba35e8f75bb11101bfa47d71449a"; + + vector::push_back(&mut pk_list, pk_1); + vector::push_back(&mut pk_list, pk_2); + + derive_multisig_xonly_pubkey_from_xonly_pubkeys(pk_list, 3); + } + + #[test] + #[expected_failure(location=Self, abort_code = ErrorInvalidPublicKey)] + fun test_derive_multisig_xonly_pubkey_from_xonly_pubkeys_fail_invalid_public_key() { + let pk_list = vector::empty>(); + + let pk_1 = x"f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9"; + let pk_2 = x"02481521eb57656db4bc9ec81857e105cc7853fe8cad61be23667bb401840fc7f8"; + + vector::push_back(&mut pk_list, pk_1); + vector::push_back(&mut pk_list, pk_2); + + derive_multisig_xonly_pubkey_from_xonly_pubkeys(pk_list, 2); } #[test] @@ -185,6 +215,14 @@ module rooch_framework::bitcoin_address { bytes: x"020102f97a0a664c8493bfa28cfcf3450628bdc0ba7b3b0af2b57d4d057f15cb41f9", }; - assert!(expected_bitcoin_addr.bytes == bitcoin_addr.bytes, E_INVALID_XONLY_PUBKEY); + assert!(expected_bitcoin_addr.bytes == bitcoin_addr.bytes, ErrorInvalidXOnlyPublicKey); + } + + #[test] + #[expected_failure(location=Self, abort_code = ErrorInvalidXOnlyPublicKey)] + fun test_derive_bitcoin_taproot_address_from_multisig_xonly_pubkey_fail() { + let xonly_pubkey = x"038e3d29b653e40f5b620f9443ee05222d1e40be58f544b6fed3d464edd54db883"; + + derive_bitcoin_taproot_address_from_multisig_xonly_pubkey(&xonly_pubkey); } } \ No newline at end of file diff --git a/frameworks/rooch-framework/src/natives/gas_parameter/bitcoin_address.rs b/frameworks/rooch-framework/src/natives/gas_parameter/bitcoin_address.rs index abc34bfbfc..8ec101ca86 100644 --- a/frameworks/rooch-framework/src/natives/gas_parameter/bitcoin_address.rs +++ b/frameworks/rooch-framework/src/natives/gas_parameter/bitcoin_address.rs @@ -5,10 +5,12 @@ use crate::natives::gas_parameter::native::MUL; use crate::natives::rooch_framework::bitcoin_address::GasParameters; crate::natives::gas_parameter::native::define_gas_parameters_for_natives!(GasParameters, "bitcoin_address", [ - [.new.base, "parse.base", 1000 * MUL], - [.new.per_byte, "parse.per_byte", 30 * MUL], [.verify_with_pk.base, "verify_with_pk.base", 1000 * MUL], [.verify_with_pk.per_byte, "verify_with_pk.per_byte", 30 * MUL], + [.new.base, "parse.base", 1000 * MUL], + [.new.per_byte, "parse.per_byte", 30 * MUL], + [.verify_bitcoin_address_with_public_key.base, optional "verify_bitcoin_address_with_public_key.base", 1000 * MUL], + [.verify_bitcoin_address_with_public_key.per_byte, optional "verify_bitcoin_address_with_public_key.per_byte", 30 * MUL], [.derive_multisig_xonly_pubkey_from_xonly_pubkeys.base, optional "derive_multisig_xonly_pubkey_from_xonly_pubkeys.base", 1000 * MUL], [.derive_multisig_xonly_pubkey_from_xonly_pubkeys.per_byte, optional "derive_multisig_xonly_pubkey_from_xonly_pubkeys.per_byte", 30 * MUL], [.derive_bitcoin_taproot_address_from_multisig_xonly_pubkey.base, optional "derive_bitcoin_taproot_address_from_multisig_xonly_pubkey.base", 1000 * MUL], diff --git a/frameworks/rooch-framework/src/natives/rooch_framework/bitcoin_address.rs b/frameworks/rooch-framework/src/natives/rooch_framework/bitcoin_address.rs index f2ed86eced..f2dc59d32e 100644 --- a/frameworks/rooch-framework/src/natives/rooch_framework/bitcoin_address.rs +++ b/frameworks/rooch-framework/src/natives/rooch_framework/bitcoin_address.rs @@ -31,7 +31,7 @@ pub const E_ARG_NOT_VECTOR_U8: u64 = 2; pub const E_INVALID_PUBLIC_KEY: u64 = 3; pub const E_INVALID_THRESHOLD: u64 = 4; pub const E_INVALID_KEY_EGG_CONTEXT: u64 = 5; -pub const E_INVALID_XONLY_PUBKEY: u64 = 6; +pub const E_INVALID_XONLY_PUBLIC_KEY: u64 = 6; pub fn parse( gas_params: &FromBytesGasParameters, @@ -61,7 +61,7 @@ pub fn parse( )) } -/// Returns true if the given pubkey's bitcoin address is equal to the input bitcoin address. +/// Returns true if the given pubkey is directly related to the address payload. pub fn verify_with_pk( gas_params: &FromBytesGasParameters, _context: &mut NativeContext, @@ -69,26 +69,23 @@ pub fn verify_with_pk( mut args: VecDeque, ) -> PartialVMResult { let pk_bytes = pop_arg!(args, VectorRef); - let addr_bytes = pop_arg!(args, StructRef); + let addr_bytes = pop_arg!(args, VectorRef); let pk_ref = pk_bytes.as_bytes_ref(); - let addr_value = addr_bytes.read_ref()?; + let addr_ref = addr_bytes.as_bytes_ref(); - let bitcoin_addr = BitcoinAddress::from_runtime_value(addr_value).map_err(|e| { - PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) - .with_message(format!("Failed to parse bitcoin address: {}", e)) - })?; let cost = gas_params.base - + gas_params.per_byte - * NumBytes::new((pk_ref.len() + bitcoin_addr.to_bytes().len()) as u64); + + gas_params.per_byte * NumBytes::new((pk_ref.len() + addr_ref.len()) as u64); - // TODO: convert to internal rooch public key and to bitcoin address? let Ok(pk) = PublicKey::from_slice(&pk_ref) else { return Ok(NativeResult::ok(cost, smallvec![Value::bool(false)])); }; - // TODO: compare the input bitcoin address with the converted bitcoin address - let addr = match Address::from_str(&bitcoin_addr.to_string()) { + let Ok(addr_str) = std::str::from_utf8(&addr_ref) else { + return Ok(NativeResult::ok(cost, smallvec![Value::bool(false)])); + }; + + let addr = match Address::from_str(addr_str) { Ok(addr) => addr.assume_checked(), Err(_) => { return Ok(NativeResult::ok(cost, smallvec![Value::bool(false)])); @@ -123,6 +120,54 @@ impl FromBytesGasParameters { } } +// optional function +/// Returns true if the given pubkey's bitcoin address is equal to the input bitcoin address. +pub fn verify_bitcoin_address_with_public_key( + gas_params: &FromBytesGasParametersOptional, + _context: &mut NativeContext, + _ty_args: Vec, + mut args: VecDeque, +) -> PartialVMResult { + let pk_bytes = pop_arg!(args, VectorRef); + let addr_bytes = pop_arg!(args, StructRef); + + let pk_ref = pk_bytes.as_bytes_ref(); + let addr_value = addr_bytes.read_ref()?; + + let bitcoin_addr = BitcoinAddress::from_runtime_value(addr_value).map_err(|e| { + PartialVMError::new(StatusCode::UNKNOWN_INVARIANT_VIOLATION_ERROR) + .with_message(format!("Failed to parse bitcoin address: {}", e)) + })?; + let cost = gas_params.base.unwrap() + + gas_params.per_byte.unwrap() + * NumBytes::new((pk_ref.len() + bitcoin_addr.to_bytes().len()) as u64); + + // TODO: convert to internal rooch public key and to bitcoin address? + let Ok(pk) = PublicKey::from_slice(&pk_ref) else { + return Ok(NativeResult::ok(cost, smallvec![Value::bool(false)])); + }; + + // TODO: compare the input bitcoin address with the converted bitcoin address + let addr = match Address::from_str(&bitcoin_addr.to_string()) { + Ok(addr) => addr.assume_checked(), + Err(_) => { + return Ok(NativeResult::ok(cost, smallvec![Value::bool(false)])); + } + }; + + let is_ok = match addr.address_type() { + Some(AddressType::P2tr) => { + let xonly_pubkey = XOnlyPublicKey::from(pk.inner); + let secp = Secp256k1::verification_only(); + let trust_addr = Address::p2tr(&secp, xonly_pubkey, None, *addr.network()); + addr.is_related_to_pubkey(&pk) || trust_addr.to_string() == addr.to_string() + } + _ => addr.is_related_to_pubkey(&pk), + }; + + Ok(NativeResult::ok(cost, smallvec![Value::bool(is_ok)])) +} + // optional function pub fn derive_multisig_xonly_pubkey_from_xonly_pubkeys( gas_params: &FromBytesGasParametersOptional, @@ -195,7 +240,7 @@ pub fn derive_bitcoin_taproot_address_from_multisig_xonly_pubkey( let internal_key = match XOnlyPublicKey::from_slice(&xonly_pubkey_ref) { Ok(xonly_pubkey) => xonly_pubkey, Err(_) => { - return Ok(NativeResult::err(cost, E_INVALID_XONLY_PUBKEY)); + return Ok(NativeResult::err(cost, E_INVALID_XONLY_PUBLIC_KEY)); } }; @@ -243,6 +288,7 @@ impl FromBytesGasParametersOptional { pub struct GasParameters { pub new: FromBytesGasParameters, pub verify_with_pk: FromBytesGasParameters, + pub verify_bitcoin_address_with_public_key: FromBytesGasParametersOptional, pub derive_multisig_xonly_pubkey_from_xonly_pubkeys: FromBytesGasParametersOptional, pub derive_bitcoin_taproot_address_from_multisig_xonly_pubkey: FromBytesGasParametersOptional, } @@ -252,6 +298,7 @@ impl GasParameters { Self { new: FromBytesGasParameters::zeros(), verify_with_pk: FromBytesGasParameters::zeros(), + verify_bitcoin_address_with_public_key: FromBytesGasParametersOptional::zeros(), derive_multisig_xonly_pubkey_from_xonly_pubkeys: FromBytesGasParametersOptional::zeros( ), derive_bitcoin_taproot_address_from_multisig_xonly_pubkey: @@ -270,6 +317,16 @@ pub fn make_all(gas_params: GasParameters) -> impl Iterator