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