Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[gh-2295] support bitcoin multisig address #2298

Merged
merged 8 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ rustc-hash = { version = "2.0.0" }
xorf = { version = "0.11.0" }
vergen-git2 = { version = "1.0.0", features = ["build", "cargo", "rustc"] }
vergen-pretty = "0.3.4"
musig2 = { version = "0.0.11" }

# Note: the BEGIN and END comments below are required for external tooling. Do not remove.
# BEGIN MOVE DEPENDENCIES
Expand Down
2 changes: 1 addition & 1 deletion crates/rooch-types/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ impl BitcoinAddress {
pub fn new_witness_program(witness_program: &bitcoin::WitnessProgram) -> Self {
// First byte is BitcoinAddress Payload type
let mut bytes = vec![BitcoinAddressPayloadType::WitnessProgram.to_num()];
// Third byte represents Version 0 or PUSHNUM_1-PUSHNUM_16
// Second byte represents Version 0 or PUSHNUM_1-PUSHNUM_16
bytes.push(witness_program.version().to_num());
// Remain are Program data
bytes.extend_from_slice(witness_program.program().as_bytes());
Expand Down
1 change: 1 addition & 0 deletions frameworks/rooch-framework/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ smallvec = { workspace = true }
hex = { workspace = true }
tracing = { workspace = true }
bitcoin = { workspace = true }
musig2 = { workspace = true }

move-binary-format = { workspace = true }
move-bytecode-utils = { workspace = true }
Expand Down
85 changes: 83 additions & 2 deletions frameworks/rooch-framework/doc/bitcoin_address.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
- [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_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)


<pre><code><b>use</b> <a href="">0x1::string</a>;
Expand Down Expand Up @@ -49,11 +52,56 @@ We just keep the raw bytes of the address and do care about the network.
## Constants


<a name="0x3_bitcoin_address_ErrorAddressBytesLen"></a>
<a name="0x3_bitcoin_address_ErrorArgNotVectorU8"></a>



<pre><code><b>const</b> <a href="bitcoin_address.md#0x3_bitcoin_address_ErrorAddressBytesLen">ErrorAddressBytesLen</a>: u64 = 1;
<pre><code><b>const</b> <a href="bitcoin_address.md#0x3_bitcoin_address_ErrorArgNotVectorU8">ErrorArgNotVectorU8</a>: u64 = 2;
</code></pre>



<a name="0x3_bitcoin_address_ErrorInvalidAddress"></a>



<pre><code><b>const</b> <a href="bitcoin_address.md#0x3_bitcoin_address_ErrorInvalidAddress">ErrorInvalidAddress</a>: u64 = 1;
</code></pre>



<a name="0x3_bitcoin_address_ErrorInvalidKeyEggContext"></a>



<pre><code><b>const</b> <a href="bitcoin_address.md#0x3_bitcoin_address_ErrorInvalidKeyEggContext">ErrorInvalidKeyEggContext</a>: u64 = 5;
</code></pre>



<a name="0x3_bitcoin_address_ErrorInvalidPublicKey"></a>



<pre><code><b>const</b> <a href="bitcoin_address.md#0x3_bitcoin_address_ErrorInvalidPublicKey">ErrorInvalidPublicKey</a>: u64 = 3;
</code></pre>



<a name="0x3_bitcoin_address_ErrorInvalidThreshold"></a>



<pre><code><b>const</b> <a href="bitcoin_address.md#0x3_bitcoin_address_ErrorInvalidThreshold">ErrorInvalidThreshold</a>: u64 = 4;
</code></pre>



<a name="0x3_bitcoin_address_ErrorInvalidXOnlyPublicKey"></a>



<pre><code><b>const</b> <a href="bitcoin_address.md#0x3_bitcoin_address_ErrorInvalidXOnlyPublicKey">ErrorInvalidXOnlyPublicKey</a>: u64 = 6;
</code></pre>


Expand Down Expand Up @@ -271,3 +319,36 @@ Empty address is a special address that is used to if we parse address failed fr

<pre><code><b>public</b> <b>fun</b> <a href="bitcoin_address.md#0x3_bitcoin_address_to_rooch_address">to_rooch_address</a>(addr: &<a href="bitcoin_address.md#0x3_bitcoin_address_BitcoinAddress">bitcoin_address::BitcoinAddress</a>): <b>address</b>
</code></pre>



<a name="0x3_bitcoin_address_verify_bitcoin_address_with_public_key"></a>

## Function `verify_bitcoin_address_with_public_key`



<pre><code><b>public</b> <b>fun</b> <a href="bitcoin_address.md#0x3_bitcoin_address_verify_bitcoin_address_with_public_key">verify_bitcoin_address_with_public_key</a>(bitcoin_addr: &<a href="bitcoin_address.md#0x3_bitcoin_address_BitcoinAddress">bitcoin_address::BitcoinAddress</a>, pk: &<a href="">vector</a>&lt;u8&gt;): bool
</code></pre>



<a name="0x3_bitcoin_address_derive_multisig_xonly_pubkey_from_xonly_pubkeys"></a>

## Function `derive_multisig_xonly_pubkey_from_xonly_pubkeys`



<pre><code><b>public</b> <b>fun</b> <a href="bitcoin_address.md#0x3_bitcoin_address_derive_multisig_xonly_pubkey_from_xonly_pubkeys">derive_multisig_xonly_pubkey_from_xonly_pubkeys</a>(public_keys: <a href="">vector</a>&lt;<a href="">vector</a>&lt;u8&gt;&gt;, threshold: u64): <a href="">vector</a>&lt;u8&gt;
</code></pre>



<a name="0x3_bitcoin_address_derive_bitcoin_taproot_address_from_multisig_xonly_pubkey"></a>

## Function `derive_bitcoin_taproot_address_from_multisig_xonly_pubkey`



<pre><code><b>public</b> <b>fun</b> <a href="bitcoin_address.md#0x3_bitcoin_address_derive_bitcoin_taproot_address_from_multisig_xonly_pubkey">derive_bitcoin_taproot_address_from_multisig_xonly_pubkey</a>(xonly_pubkey: &<a href="">vector</a>&lt;u8&gt;): <a href="bitcoin_address.md#0x3_bitcoin_address_BitcoinAddress">bitcoin_address::BitcoinAddress</a>
</code></pre>
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ module rooch_framework::bitcoin_address {
const P2SH_ADDR_BYTE_LEN: u64 = 21;

// error code
const ErrorAddressBytesLen: u64 = 1;
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
Expand All @@ -32,7 +37,7 @@ module rooch_framework::bitcoin_address {
}

public fun new_p2pkh(pubkey_hash: vector<u8>): BitcoinAddress{
assert!(vector::length(&pubkey_hash) == PUBKEY_HASH_LEN, ErrorAddressBytesLen);
assert!(vector::length(&pubkey_hash) == PUBKEY_HASH_LEN, ErrorInvalidAddress);
//we do not distinguish between mainnet and testnet in Move
let bytes = vector::singleton<u8>(P2PKH_ADDR_DECIMAL_PREFIX_MAIN);
vector::append(&mut bytes, pubkey_hash);
Expand All @@ -42,7 +47,7 @@ module rooch_framework::bitcoin_address {
}

public fun new_p2sh(script_hash: vector<u8>): BitcoinAddress{
assert!(vector::length(&script_hash) == SCRIPT_HASH_LEN, ErrorAddressBytesLen);
assert!(vector::length(&script_hash) == SCRIPT_HASH_LEN, ErrorInvalidAddress);
let bytes = vector::singleton<u8>(P2SH_ADDR_DECIMAL_PREFIX_MAIN);
vector::append(&mut bytes, script_hash);
BitcoinAddress {
Expand Down Expand Up @@ -96,18 +101,28 @@ module rooch_framework::bitcoin_address {
}

public fun verify_with_public_key(addr: &String, pk: &vector<u8>): bool {
let raw_bytes = string::bytes(addr);
verify_with_pk(raw_bytes, pk)
let bitcoin_addr = from_string(addr);
verify_bitcoin_address_with_public_key(&bitcoin_addr, pk)
}

public fun to_rooch_address(addr: &BitcoinAddress): address{
let hash = moveos_std::hash::blake2b256(&addr.bytes);
moveos_std::bcs::to_address(hash)
}

// verify bitcoin address according to the pk bytes
public native fun verify_bitcoin_address_with_public_key(bitcoin_addr: &BitcoinAddress, pk: &vector<u8>): bool;

// derive multisig xonly public key from public keys
public native fun derive_multisig_xonly_pubkey_from_xonly_pubkeys(public_keys: vector<vector<u8>>, threshold: u64): vector<u8>;

// derive bitcoin taproot address from the multisig xonly public key
public native fun derive_bitcoin_taproot_address_from_multisig_xonly_pubkey(xonly_pubkey: &vector<u8>): BitcoinAddress;

/// Parse the Bitcoin address string bytes to Move BitcoinAddress
native fun parse(raw_addr: &vector<u8>): BitcoinAddress;
native fun verify_with_pk (addr: &vector<u8>, pk: &vector<u8>): bool;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

原有的方法不能变,不然无法升级,需要通过新增方法解决。

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved.

// TODO: remove verify_with_pk after upgrade
native fun verify_with_pk(addr: &vector<u8>, pk: &vector<u8>): bool;

#[test_only]
public fun random_address_for_testing(): BitcoinAddress {
Expand All @@ -116,7 +131,7 @@ module rooch_framework::bitcoin_address {
}

#[test]
fun test_verify_with_pk_success() {
fun test_verify_with_public_key_success() {
// p2tr
let addr = string::utf8(b"bc1p8xpjpkc9uzj2dexcxjg9sw8lxje85xa4070zpcys589e3rf6k20qm6gjrt");
let pk = x"038e3d29b653e40f5b620f9443ee05222d1e40be58f544b6fed3d464edd54db883";
Expand All @@ -141,8 +156,73 @@ module rooch_framework::bitcoin_address {

#[test]
fun test_validate_signature_fail() {
let addr = string::utf8(b"ac1p8xpjpkc9uzj2dexcxjg9sw8lxje85xa4070zpcys589e3rf6k20qm6gjrt");
let pk = x"038e3d29b653e40f5b620f9443ee05222d1e40be58f544b6fed3d464edd54db883";
let addr = string::utf8(b"bc1p8xpjpkc9uzj2dexcxjg9sw8lxje85xa4070zpcys589e3rf6k20qm6gjrt");
let pk = x"038e3d29b653e40f5b620f9443ee05222d1e40be58f544b6fed3d464edd54db884";
assert!(!verify_with_public_key(&addr, &pk), 1004);
}

#[test]
fun test_derive_multisig_xonly_pubkey_from_xonly_pubkeys_success() {
let expected_xonly_pubkey = x"7b6474bd9206ad07c0bc6b0ac90d43f6f232235c9e9cbf0c47775bf47ca9c402";
let pk_list = vector::empty<vector<u8>>();

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);

let xonly_pubkey = derive_multisig_xonly_pubkey_from_xonly_pubkeys(pk_list, 2);

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<vector<u8>>();

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<vector<u8>>();

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]
fun test_derive_bitcoin_taproot_address_from_multisig_xonly_pubkey_success() {
let xonly_pubkey = x"7b6474bd9206ad07c0bc6b0ac90d43f6f232235c9e9cbf0c47775bf47ca9c402";

let bitcoin_addr = derive_bitcoin_taproot_address_from_multisig_xonly_pubkey(&xonly_pubkey);

let expected_bitcoin_addr = BitcoinAddress {
bytes: x"020102f97a0a664c8493bfa28cfcf3450628bdc0ba7b3b0af2b57d4d057f15cb41f9",
};

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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ crate::natives::gas_parameter::native::define_gas_parameters_for_natives!(GasPar
[.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],
[.derive_bitcoin_taproot_address_from_multisig_xonly_pubkey.per_byte, optional "derive_bitcoin_taproot_address_from_multisig_xonly_pubkey.per_byte", 30 * MUL],
]);
Loading
Loading