From bc167cbcc7e35b212d04ba6c08f0cd4b78de468e Mon Sep 17 00:00:00 2001 From: mx <53249469+mx819812523@users.noreply.github.com> Date: Wed, 9 Oct 2024 23:19:55 +0800 Subject: [PATCH] [bitcoin-move] babylon integration (#2734) * [draft] babylon integration * derive babylon utxo * fix: build * format code and fix build * Update docs --------- Co-authored-by: mx819812523 Co-authored-by: jolestar --- frameworks/bitcoin-move/doc/README.md | 1 + frameworks/bitcoin-move/doc/bbn.md | 153 +++++++++++ frameworks/bitcoin-move/doc/genesis.md | 1 + frameworks/bitcoin-move/doc/script_buf.md | 21 ++ frameworks/bitcoin-move/sources/bbn.move | 255 ++++++++++++++++++ frameworks/bitcoin-move/sources/genesis.move | 2 + .../bitcoin-move/sources/script_buf.move | 32 +++ frameworks/bitcoin-move/sources/utxo.move | 1 + 8 files changed, 466 insertions(+) create mode 100644 frameworks/bitcoin-move/doc/bbn.md create mode 100644 frameworks/bitcoin-move/sources/bbn.move diff --git a/frameworks/bitcoin-move/doc/README.md b/frameworks/bitcoin-move/doc/README.md index 2cf05ae33..c3ff1b646 100644 --- a/frameworks/bitcoin-move/doc/README.md +++ b/frameworks/bitcoin-move/doc/README.md @@ -12,6 +12,7 @@ This is the reference documentation of the Bitcoin Move Framework. ## Index +- [`0x4::bbn`](bbn.md#0x4_bbn) - [`0x4::bitcoin`](bitcoin.md#0x4_bitcoin) - [`0x4::bitcoin_hash`](bitcoin_hash.md#0x4_bitcoin_hash) - [`0x4::bitcoin_multisign_validator`](bitcoin_multisign_validator.md#0x4_bitcoin_multisign_validator) diff --git a/frameworks/bitcoin-move/doc/bbn.md b/frameworks/bitcoin-move/doc/bbn.md new file mode 100644 index 000000000..5091c09e3 --- /dev/null +++ b/frameworks/bitcoin-move/doc/bbn.md @@ -0,0 +1,153 @@ + + + +# Module `0x4::bbn` + + + +- [Resource `BBNGlobalParam`](#0x4_bbn_BBNGlobalParam) +- [Resource `BBNGlobalParams`](#0x4_bbn_BBNGlobalParams) +- [Struct `BBNOpReturnData`](#0x4_bbn_BBNOpReturnData) +- [Constants](#@Constants_0) +- [Function `genesis_init`](#0x4_bbn_genesis_init) +- [Function `try_get_bbn_op_return_data`](#0x4_bbn_try_get_bbn_op_return_data) +- [Function `try_get_staking_output`](#0x4_bbn_try_get_staking_output) +- [Function `derive_bbn_utxo`](#0x4_bbn_derive_bbn_utxo) + + +
use 0x1::option;
+use 0x1::vector;
+use 0x2::object;
+use 0x3::bitcoin_address;
+use 0x4::bitcoin;
+use 0x4::script_buf;
+use 0x4::types;
+use 0x4::utxo;
+
+ + + + + +## Resource `BBNGlobalParam` + + + +
struct BBNGlobalParam has store, key
+
+ + + + + +## Resource `BBNGlobalParams` + + + +
struct BBNGlobalParams has key
+
+ + + + + +## Struct `BBNOpReturnData` + + + +
struct BBNOpReturnData has copy, drop, store
+
+ + + + + +## Constants + + + + + + +
const ErrorNotBabylonOpReturn: u64 = 2;
+
+ + + + + + + +
const ErrorNotBabylonUTXO: u64 = 0;
+
+ + + + + + + +
const ErrorNotTransaction: u64 = 1;
+
+ + + + + + + +
const ErrorTransactionLockTime: u64 = 3;
+
+ + + + + + + +
const UNSPENDABLEKEYPATHKEY: vector<u8> = [48, 50, 53, 48, 57, 50, 57, 98, 55, 52, 99, 49, 97, 48, 52, 57, 53, 52, 98, 55, 56, 98, 52, 98, 54, 48, 51, 53, 101, 57, 55, 97, 53, 101, 48, 55, 56, 97, 53, 97, 48, 102, 50, 56, 101, 99, 57, 54, 100, 53, 52, 55, 98, 102, 101, 101, 57, 97, 99, 101, 56, 48, 51, 97, 99, 48];
+
+ + + + + +## Function `genesis_init` + + + +
public(friend) fun genesis_init()
+
+ + + + + +## Function `try_get_bbn_op_return_data` + + + +
public fun try_get_bbn_op_return_data(transaction: types::Transaction): (bool, u64, bbn::BBNOpReturnData)
+
+ + + + + +## Function `try_get_staking_output` + + + +
public fun try_get_staking_output(transaction: types::Transaction, staking_output_script: &vector<u8>): (bool, u64, option::Option<object::ObjectID>)
+
+ + + + + +## Function `derive_bbn_utxo` + + + +
public fun derive_bbn_utxo(utxo_obj: &object::Object<utxo::UTXO>)
+
diff --git a/frameworks/bitcoin-move/doc/genesis.md b/frameworks/bitcoin-move/doc/genesis.md index eb9e4c247..afd598f81 100644 --- a/frameworks/bitcoin-move/doc/genesis.md +++ b/frameworks/bitcoin-move/doc/genesis.md @@ -14,6 +14,7 @@ use 0x2::signer; use 0x2::tx_context; use 0x3::bitcoin_address; +use 0x4::bbn; use 0x4::bitcoin; use 0x4::bitcoin_multisign_validator; use 0x4::multisign_account; diff --git a/frameworks/bitcoin-move/doc/script_buf.md b/frameworks/bitcoin-move/doc/script_buf.md index 5bf833b1a..260f310a9 100644 --- a/frameworks/bitcoin-move/doc/script_buf.md +++ b/frameworks/bitcoin-move/doc/script_buf.md @@ -20,6 +20,7 @@ - [Function `is_witness_program`](#0x4_script_buf_is_witness_program) - [Function `witness_program`](#0x4_script_buf_witness_program) - [Function `is_op_return`](#0x4_script_buf_is_op_return) +- [Function `unpack_bbn_stake_data`](#0x4_script_buf_unpack_bbn_stake_data) - [Function `push_opcode`](#0x4_script_buf_push_opcode) - [Function `push_data`](#0x4_script_buf_push_data) - [Function `push_int`](#0x4_script_buf_push_int) @@ -68,6 +69,15 @@ + + + + +
const ErrorInvalidBytesLen: u64 = 2;
+
+ + + @@ -228,6 +238,17 @@ Checks if the given script is an OP_RETURN script. + + +## Function `unpack_bbn_stake_data` + + + +
public fun unpack_bbn_stake_data(self: &script_buf::ScriptBuf): (vector<u8>, u64, vector<u8>, vector<u8>, u16)
+
+ + + ## Function `push_opcode` diff --git a/frameworks/bitcoin-move/sources/bbn.move b/frameworks/bitcoin-move/sources/bbn.move new file mode 100644 index 000000000..48a1cbadb --- /dev/null +++ b/frameworks/bitcoin-move/sources/bbn.move @@ -0,0 +1,255 @@ +module bitcoin_move::bbn { + + use std::option; + use std::option::{is_none, Option, none, is_some, some}; + use std::vector; + use std::vector::{length, borrow}; + use bitcoin_move::bitcoin; + use bitcoin_move::utxo::UTXO; + use bitcoin_move::types; + use bitcoin_move::utxo; + use bitcoin_move::script_buf; + use bitcoin_move::types::{ + Transaction, + tx_id, + tx_output, + txout_value, + tx_lock_time, + txout_script_pubkey + }; + use bitcoin_move::bitcoin::get_tx_height; + use rooch_framework::bitcoin_address::{ + derive_bitcoin_taproot_address_from_pubkey, + to_rooch_address + }; + use bitcoin_move::script_buf::{unpack_bbn_stake_data}; + use moveos_std::object::{Object, ObjectID}; + use moveos_std::object; + + friend bitcoin_move::genesis; + + struct BBNGlobalParam has key, store { + version: u64, + activation_height: u64, + staking_cap: u64, + cap_height: u64, + tag: vector, + covenant_pks: vector>, + covenant_quorum: u32, + unbonding_time: u16, + unbonding_fee: u64, + max_staking_amount: u64, + min_staking_amount: u64, + min_staking_time: u16, + max_staking_time: u16, + confirmation_depth: u16 + } + + struct BBNGlobalParams has key { + bbn_global_param: vector + } + + struct BBNOpReturnData has copy, store, drop { + tag: vector, + version: u64, + staker_pub_key: vector, + finality_provider_pub_key: vector, + staking_time: u16 + } + + const UNSPENDABLEKEYPATHKEY: vector = b"0250929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0"; + + const ErrorNotBabylonUTXO: u64 = 0; + const ErrorNotTransaction: u64 = 1; + const ErrorNotBabylonOpReturn: u64 = 2; + const ErrorTransactionLockTime: u64 = 3; + + public(friend) fun genesis_init() { + // TODO here just add bbn test-4 version 2 + let bbn_global_params_2 = BBNGlobalParam { + version: 2, + activation_height: 200665, + staking_cap: 0, + cap_height: 201385, + tag: b"62627434", + covenant_pks: vector[ + b"03fa9d882d45f4060bdb8042183828cd87544f1ea997380e586cab77d5fd698737", + b"020aee0509b16db71c999238a4827db945526859b13c95487ab46725357c9a9f25", + b"0217921cf156ccb4e73d428f996ed11b245313e37e27c978ac4d2cc21eca4672e4", + b"02113c3a32a9d320b72190a04a020a0db3976ef36972673258e9a38a364f3dc3b0", + b"0379a71ffd71c503ef2e2f91bccfc8fcda7946f4653cef0d9f3dde20795ef3b9f0", + b"023bb93dfc8b61887d771f3630e9a63e97cbafcfcc78556a474df83a31a0ef899c", + b"03d21faf78c6751a0d38e6bd8028b907ff07e9a869a43fc837d6b3f8dff6119a36", + b"0340afaf47c4ffa56de86410d8e47baa2bb6f04b604f4ea24323737ddc3fe092df", + b"03f5199efae3f28bb82476163a7e458c7ad445d9bffb0682d10d3bdb2cb41f8e8e" + ], + covenant_quorum: 6, + unbonding_time: 1008, + unbonding_fee: 10000, + max_staking_amount: 5000000, + min_staking_amount: 50000, + min_staking_time: 64000, + max_staking_time: 64000, + confirmation_depth: 10 + }; + let obj = + object::new_named_object( + BBNGlobalParams { bbn_global_param: vector[bbn_global_params_2] } + ); + object::to_shared(obj); + } + + fun borrow_bbn_params(): &Object { + let object_id = object::named_object_id(); + object::borrow_object(object_id) + } + + fun borrow_bbn_params_mut(): &mut Object { + let object_id = object::named_object_id(); + object::borrow_mut_object_shared(object_id) + } + + public fun try_get_bbn_op_return_data(transaction: Transaction): + (bool, u64, BBNOpReturnData) { + let bbn_op_return_data = BBNOpReturnData { + tag: vector[], + version: 0, + staker_pub_key: vector[], + finality_provider_pub_key: vector[], + staking_time: 0 + }; + let tx_output = tx_output(&transaction); + if (vector::length(tx_output) < 2) { + return (false, 0, bbn_op_return_data) + }; + + // this case should not happen as standard bitcoin node propagation rules + // disallow multiple op return outputs in a single transaction. However, miner could + // include multiple op return outputs in a single transaction. In such case, we should + // return an error. + let index = 0; + let i = 0; + while (i < length(tx_output)) { + let output = borrow(tx_output, i); + let (tag, version, staker_pub_key, finality_provider_pub_key, staking_time) = + unpack_bbn_stake_data(txout_script_pubkey(output)); + bbn_op_return_data.tag = tag; + bbn_op_return_data.version = version; + bbn_op_return_data.staker_pub_key = staker_pub_key; + bbn_op_return_data.finality_provider_pub_key = finality_provider_pub_key; + bbn_op_return_data.staking_time = staking_time; + if (vector::length(&bbn_op_return_data.tag) != 0) { break }; + index = index + 1; + i = i + 1; + }; + if (vector::length(&bbn_op_return_data.tag) == 0) { + return (false, 0, bbn_op_return_data) + }; + let option_tx_height = get_tx_height(tx_id(&transaction)); + if (is_none(&option_tx_height)) { + return (false, 0, bbn_op_return_data) + }; + let tx_height = option::destroy_some(option_tx_height); + let bbn_params = object::borrow(borrow_bbn_params()); + let i = 0; + while (i < length(&bbn_params.bbn_global_param)) { + let param = borrow(&bbn_params.bbn_global_param, i); + i = i + 1; + if (bbn_op_return_data.version != param.version + || bbn_op_return_data.tag != param.tag + || tx_height < param.activation_height + || param.covenant_quorum > (length(¶m.covenant_pks) as u32)) { + continue + }; + if (param.cap_height != 0 + && tx_height > (param.activation_height + param.cap_height)) { + continue + }; + if (bbn_op_return_data.staking_time < param.min_staking_time + || bbn_op_return_data.staking_time > param.max_staking_time) { + continue + }; + if (!vector::contains( + ¶m.covenant_pks, &bbn_op_return_data.finality_provider_pub_key + )) { + continue + }; + return (true, index, bbn_op_return_data) + }; + return (false, 0, bbn_op_return_data) + } + + public fun try_get_staking_output( + transaction: Transaction, staking_output_script: &vector + ): (bool, u64, Option) { + let tx_outputs = tx_output(&transaction); + let tx_id = tx_id(&transaction); + if (vector::length(tx_outputs) == 0) { + return (false, 0, none()) + }; + let index = 0; + + // should not multiple staking outputs + while (index < length(tx_outputs)) { + let tx_output = borrow(tx_outputs, index); + if (script_buf::bytes(txout_script_pubkey(tx_output)) + == staking_output_script) { + let out_point = types::new_outpoint( + tx_id, (txout_value(tx_output) as u32) + ); + return (true, index, option::some(utxo::derive_utxo_id(out_point))) + }; + index = index + 1; + }; + return (false, index, none()) + } + + public fun derive_bbn_utxo(utxo_obj: &Object) { + // assert!(object::owner(utxo_obj) == @bitcoin_move, ErrorNotBabylonUTXO); + let utxo = object::borrow(utxo_obj); + let txid = utxo::txid(utxo); + let option_tx = bitcoin::get_tx(txid); + assert!(is_some(&option_tx), ErrorNotTransaction); + let transaction = option::destroy_some(option_tx); + let (is_true, op_return_index, op_return_data) = + try_get_bbn_op_return_data(transaction); + assert!(is_true, ErrorNotBabylonOpReturn); + // TODO here should replace to check staking output + // try_get_staking_output() + + assert!( + tx_lock_time(&transaction) >= (op_return_data.staking_time as u32), + ErrorTransactionLockTime + ); + let tx_outputs = tx_output(&transaction); + let index = 0; + // TODO bbn should not multiple staking outputs, we temporarily support + while (index < length(tx_outputs)) { + let tx_output = borrow(tx_outputs, index); + if (index == op_return_index) { + continue + }; + let out_point = types::new_outpoint(txid, (txout_value(tx_output) as u32)); + let borrow_utxo = utxo::borrow_utxo(out_point); + if (object::owner(borrow_utxo) != @bitcoin_move) { + continue + }; + let utxo_id = utxo::derive_utxo_id(out_point); + let utxo_obj = utxo::take(utxo_id); + utxo::add_temp_state(&mut utxo_obj, op_return_data); + // TODO here should modify sender to trigger event queue? + utxo::transfer( + utxo_obj, + some(@bitcoin_move), + pubkey_to_rooch_address(&op_return_data.staker_pub_key) + ); + index = index + 1; + }; + } + + // TODO build stake info + + fun pubkey_to_rooch_address(pubkey: &vector): address { + to_rooch_address(&derive_bitcoin_taproot_address_from_pubkey(pubkey)) + } +} diff --git a/frameworks/bitcoin-move/sources/genesis.move b/frameworks/bitcoin-move/sources/genesis.move index 7ed4e6814..2a613ce4e 100644 --- a/frameworks/bitcoin-move/sources/genesis.move +++ b/frameworks/bitcoin-move/sources/genesis.move @@ -3,6 +3,7 @@ module bitcoin_move::genesis{ use std::option; + use bitcoin_move::bbn; use moveos_std::tx_context; use moveos_std::signer; use rooch_framework::bitcoin_address::{Self, BitcoinAddress}; @@ -40,6 +41,7 @@ module bitcoin_move::genesis{ network::genesis_init(genesis_context.network); utxo::genesis_init(); ord::genesis_init(); + bbn::genesis_init(); bitcoin::genesis_init(&genesis_account, genesis_context.genesis_block_height, genesis_context.genesis_block_hash); pending_block::genesis_init(genesis_context.reorg_block_count); bitcoin_multisign_validator::genesis_init(); diff --git a/frameworks/bitcoin-move/sources/script_buf.move b/frameworks/bitcoin-move/sources/script_buf.move index b8d7d7ac0..bb302bc6c 100644 --- a/frameworks/bitcoin-move/sources/script_buf.move +++ b/frameworks/bitcoin-move/sources/script_buf.move @@ -6,6 +6,7 @@ module bitcoin_move::script_buf{ use bitcoin_move::opcode; const ErrorInvalidKeySize: u64 = 1; + const ErrorInvalidBytesLen: u64 = 2; const BITCOIN_X_ONLY_PUBKEY_SIZE: u64 = 32; const BITCOIN_PUBKEY_SIZE: u64 = 33; @@ -93,6 +94,37 @@ module bitcoin_move::script_buf{ *vector::borrow(&self.bytes, 0) == opcode::op_return() } + public fun unpack_bbn_stake_data(self: &ScriptBuf): (vector, u64, vector, vector, u16){ + // 1. OP_RETURN opcode - which signalizes that data is provably unspendable + // 2. OP_DATA_71 opcode - which pushes 71 bytes of data to the stack + if (vector::length(&self.bytes) != 73 || *vector::borrow(&self.bytes, 0) != opcode::op_return() || *vector::borrow(&self.bytes, 1) != opcode::op_pushbytes_71()){ + return (vector[], 0, vector[], vector[], 0) + }; + let tag = vector::slice(&self.bytes, 2, 6); + let version = bytes_to_u64(vector::slice(&self.bytes, 6, 7)); + let staker_pub_key = vector::slice(&self.bytes, 7, 39); + let finality_provider_pub_key = vector::slice(&self.bytes, 39, 71); + let staking_time = bytes_to_u16(vector::slice(&self.bytes, 71, 73)); + return (tag, version, staker_pub_key, finality_provider_pub_key, staking_time) + } + + fun bytes_to_u16(bytes: vector): u16 { + assert!(vector::length(&bytes) == 2, ErrorInvalidBytesLen); + let high_byte = vector::borrow(&bytes, 0); + let low_byte = vector::borrow(&bytes, 1); + ((*high_byte as u16) << 8) | (*low_byte as u16) + } + + fun bytes_to_u64(bytes: vector): u64 { + let value = 0u64; + let i = 0u64; + while (i < 8) { + value = value | ((*vector::borrow(&bytes, i) as u64) << ((8 * (7 - i)) as u8)); + i = i + 1; + }; + return value + } + // ====== Script Builder ====== public fun push_opcode(self: &mut ScriptBuf, opcode: u8) { diff --git a/frameworks/bitcoin-move/sources/utxo.move b/frameworks/bitcoin-move/sources/utxo.move index 7930a8917..26988065e 100644 --- a/frameworks/bitcoin-move/sources/utxo.move +++ b/frameworks/bitcoin-move/sources/utxo.move @@ -17,6 +17,7 @@ module bitcoin_move::utxo{ friend bitcoin_move::ord; friend bitcoin_move::bitcoin; friend bitcoin_move::inscription_updater; + friend bitcoin_move::bbn; const TEMPORARY_AREA: vector = b"temporary_area";