Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
222d442
Display confidential addresses for outputs
miketlk Mar 25, 2025
b793074
Add fallback strlcpy to unit tests
miketlk Apr 2, 2025
b12e4aa
Fix unit tests CMake script
miketlk Apr 2, 2025
ec67f3d
Remove temporary branch from CI
miketlk Apr 2, 2025
aeb1a15
Add testnet support
miketlk Apr 4, 2025
b1d9aa8
Temporarily disable guidelines checks for PRs
miketlk Apr 10, 2025
711d588
Update snapshots
miketlk Apr 10, 2025
0a02121
Merge pull request #2 from miketlk/tmp_display_confidential_address
miketlk Apr 10, 2025
2609ec0
Fix build settings issues for Liquid testnet and regtest
miketlk Apr 12, 2025
4fbd28c
Add tests for Liquid testnet
miketlk Apr 12, 2025
c87f1a5
Add testnet tests to GitHub action
miketlk Apr 12, 2025
32d837d
Disable clang-format on mock os.h
miketlk Apr 12, 2025
29661c9
Fix missing Liquid regtest selection for unit tests
miketlk Apr 12, 2025
58d8ee7
Move testnet tests to CI workflow
miketlk Apr 12, 2025
0263fc9
Merge pull request #3 from miketlk/tmp_testnet_support
miketlk Apr 12, 2025
3d5ed4e
Add testned tL-BTC asset
miketlk Apr 14, 2025
5edcd0a
Change testnet and regtest asset names to 'tl-BTC'
miketlk Apr 14, 2025
f25d484
Update snapshots
miketlk Apr 15, 2025
531e97a
Format code
miketlk Apr 15, 2025
3e45b12
Fix wrong asset order in sorted list
miketlk Apr 15, 2025
adeb152
Merge pull request #5 from miketlk/fix_unknown_testned_bitcoin_asset
miketlk Apr 15, 2025
85f5b43
Merge remote-tracking branch 'origin/liquid_develop' into merge-liqui…
miketlk Apr 15, 2025
d363d43
Change Liquid Bitcoin ticker to LBTC and tLBTC
miketlk Apr 16, 2025
41c9e6e
Merge pull request #9 from miketlk/feature-change-Bitcoin-ticker-LBTC
miketlk Apr 16, 2025
5387679
Merge branch 'liquid_develop' into merge-liquid_develop-into-liquid_r…
miketlk Apr 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 0 additions & 1 deletion .github/workflows/build_and_functional_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,3 @@ jobs:
with:
download_app_binaries_artifact: "compiled_app_binaries"
test_dir: "tests_liquid"

41 changes: 41 additions & 0 deletions .github/workflows/ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ jobs:
make DEBUG=0 COIN=liquid BOLOS_SDK=${{ matrix.SDK }} && mv bin/ liquid-bin/
make clean
make DEBUG=0 COIN=liquid_regtest BOLOS_SDK=${{ matrix.SDK }} && mv bin/ liquid-regtest-bin/
make clean
make DEBUG=0 COIN=liquid_testnet BOLOS_SDK=${{ matrix.SDK }} && mv bin/ liquid-testnet-bin/
- name: Upload Liquid app binary
uses: actions/upload-artifact@v4
with:
Expand All @@ -55,6 +57,12 @@ jobs:
name: liquid-regtest-app-${{ matrix.model }}
path: liquid-regtest-bin

- name: Upload Liquid Testnet app binary
uses: actions/upload-artifact@v4
with:
name: liquid-testnet-app-${{ matrix.model }}
path: liquid-testnet-bin

job_unit_test:
name: Unit test
needs: job_build
Expand Down Expand Up @@ -135,6 +143,39 @@ jobs:
pip install --prefer-binary -r requirements.txt
PYTHONPATH=$PYTHONPATH:/speculos pytest --tb=short -v --device=${{matrix.model}} --speculos_api_port 5000

job_test_testnet:
name: Tests on testnet
strategy:
matrix:
include:
- model: nanos
- model: nanox
- model: nanosp

needs: job_build
runs-on: ubuntu-latest

steps:
- name: Clone
uses: actions/checkout@v4

- name: Download Liquid app binary
uses: actions/download-artifact@v4
with:
name: liquid-testnet-app-${{matrix.model}}
path: build/${{ matrix.model == 'nanosp' && 'nanos2' || matrix.model }}/bin

- name: Install tests dependencies
run: |
sudo apt-get update && sudo apt-get install -y qemu-user-static tesseract-ocr libtesseract-dev
pip install -U pip setuptools

- name: Run tests
run: |
cd tests_liquid_testnet
pip install --prefer-binary -r requirements.txt
PYTHONPATH=$PYTHONPATH:/speculos pytest --tb=short -v --device=${{matrix.model}} --speculos_api_port 5000

job_test_python_lib_legacyapp:
if: false
name: Tests with the Python library and legacy Bitcoin app
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/guidelines-enforcer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ on:
# Temporarily disabled until PR #285 is merged to `github.com/LedgerHQ/ledger-app-database`
# - liquid_develop
# - liquid_release
pull_request:
# Temporarily disabled until PR #285 is merged to `github.com/LedgerHQ/ledger-app-database`
# pull_request:

jobs:
guidelines_enforcer:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ tests/.test_bitcoin
tests/snapshots-tmp
tests_liquid/snapshots-tmp
tests_liquid_main/snapshots-tmp
tests_liquid_testnet/snapshots-tmp

# Fuzzing
fuzzing/build/
Expand Down
30 changes: 23 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ endif

# Setting to allow building variant applications
VARIANT_PARAM = COIN
VARIANT_VALUES = liquid_regtest liquid
VARIANT_VALUES = liquid_regtest liquid_testnet liquid

########################################
# Application custom permissions #
Expand All @@ -97,14 +97,30 @@ DEFINES += COIN_P2PKH_VERSION=111
DEFINES += COIN_P2SH_VERSION=75
DEFINES += COIN_PREFIX_CONFIDENTIAL=4
DEFINES += HAVE_LIQUID
DEFINES += HAVE_LIQUID_TEST
DEFINES += COIN_BLINDED_VERSION=4
DEFINES += COIN_COINID_SHORT=\"L-BTC\"
DEFINES += LIQUID_NET_REGTEST
DEFINES += COIN_COINID_SHORT=\"tLBTC\"
DEFINES += COIN_NATIVE_SEGWIT_PREFIX=\"ert\"
DEFINES += COIN_NATIVE_SEGWIT_PREFIX_CONFIDENTIAL=\"el\"

APPNAME = "Liquid Regtest"

else ifeq ($(COIN),liquid_testnet)

# Liquid testnet
DEFINES += BIP32_PUBKEY_VERSION=0x043587CF
DEFINES += BIP32_PRIVKEY_VERSION=0x04358394
DEFINES += BIP44_COIN_TYPE=1
DEFINES += COIN_P2PKH_VERSION=36
DEFINES += COIN_P2SH_VERSION=19
DEFINES += COIN_PREFIX_CONFIDENTIAL=23
DEFINES += HAVE_LIQUID
DEFINES += LIQUID_NET_TESTNET
DEFINES += COIN_COINID_SHORT=\"tLBTC\"
DEFINES += COIN_NATIVE_SEGWIT_PREFIX=\"tex\"
DEFINES += COIN_NATIVE_SEGWIT_PREFIX_CONFIDENTIAL=\"tlq\"

APPNAME = "Liquid Testnet"

else ifeq ($(COIN),liquid)

# Liquid
Expand All @@ -115,16 +131,16 @@ DEFINES += COIN_P2PKH_VERSION=57
DEFINES += COIN_P2SH_VERSION=39
DEFINES += COIN_PREFIX_CONFIDENTIAL=12
DEFINES += HAVE_LIQUID
DEFINES += COIN_BLINDED_VERSION=12
DEFINES += COIN_COINID_SHORT=\"L-BTC\"
DEFINES += LIQUID_NET_MAINNET
DEFINES += COIN_COINID_SHORT=\"LBTC\"
DEFINES += COIN_NATIVE_SEGWIT_PREFIX=\"ex\"
DEFINES += COIN_NATIVE_SEGWIT_PREFIX_CONFIDENTIAL=\"lq\"

APPNAME = "Liquid"

else
ifeq ($(filter clean,$(MAKECMDGOALS)),)
$(error Unsupported COIN - use liquid_regtest or liquid)
$(error Unsupported COIN - use liquid_regtest, liquid_testnet, liquid)
endif
endif

Expand Down
62 changes: 7 additions & 55 deletions src/handler/get_wallet_address.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,57 +48,6 @@
#include "liquid.h"
#endif

#ifdef HAVE_LIQUID
/// State of the callback function obtaining `scriptPubKey` of the processed descriptor.
typedef struct {
/// Dispatcher context.
dispatcher_context_t *dc;
/// Pointer to the root node of the policy
const policy_node_t *policy;
/// Pointer to wallet header structure.
const policy_map_wallet_header_t *wallet_header;
} get_script_callback_state_t;

/**
* Callback function obtaining `scriptPubKey` of the processed descriptor.
*
* @param[in,out] state
* Callback state, stores necessary properties of the processed descriptor.
* @param[in] descriptor_idx
* Descriptor index in the in the multipath scheme.
* @param[in] bip44_address_index
* Address index element of the derivation path, defined according to BIP 44.
* @param[out] out_buffer
* Buffer receiving `scriptPubKey`.
*
* @return true if successful, false if error.
*/
static bool get_script_callback(void *state_in,
uint32_t descriptor_idx,
uint32_t bip44_address_index,
buffer_t *out_buffer) {
if (!state_in || descriptor_idx > 1 || !out_buffer ||
buffer_remaining(out_buffer) < MAX_SCRIPT_LEN) {
return false;
}

get_script_callback_state_t *state = (get_script_callback_state_t *) state_in;

int script_len = get_wallet_script(
state->dc,
state->policy,
&(wallet_derivation_info_t){.wallet_version = state->wallet_header->version,
.keys_merkle_root = state->wallet_header->keys_info_merkle_root,
.n_keys = state->wallet_header->n_keys,
.change = !!descriptor_idx,
.address_index = bip44_address_index},
buffer_get_cur(out_buffer));

return script_len > 0 && buffer_seek_cur(out_buffer, script_len);
}

#endif

void handler_get_wallet_address(dispatcher_context_t *dc, uint8_t protocol_version) {
(void) protocol_version;

Expand Down Expand Up @@ -272,13 +221,16 @@ void handler_get_wallet_address(dispatcher_context_t *dc, uint8_t protocol_versi
if (liquid_policy_is_blinded(&wallet_policy_map.parsed)) {
// Derive blinding public key from script
uint8_t blinding_pubkey[33];
get_script_callback_state_t callback_state = {.dc = dc,
.policy = &wallet_policy_map.parsed,
.wallet_header = &wallet_header};
get_wallet_script_callback_state_t callback_state = {
.dc = dc,
.policy = &wallet_policy_map.parsed,
.wallet_version = wallet_header.version,
.keys_merkle_root = wallet_header.keys_info_merkle_root,
.n_keys = wallet_header.n_keys};
if (!liquid_get_blinding_public_key(&wallet_policy_map.parsed,
script,
script_len,
get_script_callback,
get_wallet_script_callback,
&callback_state,
blinding_pubkey)) {
explicit_bzero(blinding_pubkey, sizeof(blinding_pubkey));
Expand Down
26 changes: 26 additions & 0 deletions src/handler/lib/policy.c
Original file line number Diff line number Diff line change
Expand Up @@ -1165,6 +1165,32 @@ int get_wallet_script(dispatcher_context_t *dispatcher_context,
return -1;
}

#ifdef HAVE_LIQUID
bool get_wallet_script_callback(void *state,
uint32_t descriptor_idx,
uint32_t bip44_address_index,
buffer_t *out_buffer) {
if (!state || descriptor_idx > 1 || !out_buffer ||
buffer_remaining(out_buffer) < MAX_SCRIPT_LEN) {
return false;
}

get_wallet_script_callback_state_t *st = (get_wallet_script_callback_state_t *) (state);

int script_len =
get_wallet_script(st->dc,
st->policy,
&(wallet_derivation_info_t){.wallet_version = st->wallet_version,
.keys_merkle_root = st->keys_merkle_root,
.n_keys = st->n_keys,
.change = !!descriptor_idx,
.address_index = bip44_address_index},
buffer_get_cur(out_buffer));

return script_len > 0 && buffer_seek_cur(out_buffer, script_len);
}
#endif // HAVE_LIQUID

__attribute__((noinline)) int get_wallet_internal_script_hash(
dispatcher_context_t *dispatcher_context,
const policy_node_t *policy,
Expand Down
37 changes: 37 additions & 0 deletions src/handler/lib/policy.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,22 @@ typedef struct {
bool change; // whether a change address or a receive address is derived
} wallet_derivation_info_t;

#ifdef HAVE_LIQUID
/// State of the callback function obtaining `scriptPubKey` of the processed descriptor.
typedef struct {
/// Dispatcher context.
dispatcher_context_t *dc;
/// Pointer to the root node of the policy
const policy_node_t *policy;
/// The wallet policy version, either WALLET_POLICY_VERSION_V1 or WALLET_POLICY_VERSION_V2
int wallet_version;
/// Pointer to the Merkle root of the tree of key informations in the policy
const uint8_t *keys_merkle_root;
/// The number of key information placeholders in the policy
uint32_t n_keys;
} get_wallet_script_callback_state_t;
#endif // HAVE_LIQUID

/**
* Computes the hash of a taptree, to be used as tweak for the internal key per BIP-0341;
* The returned hash is the second value in the tuple returned by taproot_tree_helper in
Expand Down Expand Up @@ -93,6 +109,27 @@ __attribute__((warn_unused_result)) int get_wallet_script(dispatcher_context_t *
const wallet_derivation_info_t *wdi,
uint8_t out[static MAX_SCRIPT_LEN]);

#ifdef HAVE_LIQUID
/**
* A general purpose callback function obtaining `scriptPubKey` of the processed descriptor.
*
* @param[in,out] state
* Callback state, an instance of `get_wallet_script_callback_state_t`.
* @param[in] descriptor_idx
* Descriptor index in the in the multipath scheme.
* @param[in] bip44_address_index
* Address index element of the derivation path, defined according to BIP 44.
* @param[out] out_buffer
* Buffer receiving `scriptPubKey`.
*
* @return true if successful, false if error.
*/
__attribute__((warn_unused_result)) bool get_wallet_script_callback(void *state,
uint32_t descriptor_idx,
uint32_t bip44_address_index,
buffer_t *out_buffer);
#endif // HAVE_LIQUID

/**
* Computes the script corresponding to a wallet policy, for a certain change and address index.
*
Expand Down
63 changes: 62 additions & 1 deletion src/handler/sign_psbt.c
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ typedef struct {
uint8_t asset_commitment[LIQUID_COMMITMENT_LEN];
/// Asset tag
uint8_t asset_tag[LIQUID_ASSET_TAG_LEN];
/// Information about the asset: ticher and precision
/// Information about the asset: ticker and precision
asset_info_t asset_info;
/// If true the asset is defined in internal asset list
uint8_t built_in_asset;
Expand Down Expand Up @@ -1864,6 +1864,61 @@ static void output_keys_callback(dispatcher_context_t *dc,
}
}

#ifdef HAVE_LIQUID

/**
* Computes the confidential or unconfidential address corresponding to the given output.
*
* The termination character is added.
*
* @param[in,out] dc
* Dispatcher state.
* @param[in] output
* The current output being processed.
* @param[out] address
* The destination buffer for the computed address.
* @param[in] address_len
* The length of the destination buffer.
*
* @return the length of the computed address on success; -1 if the script is invalid, if it does
* not have an associated address (e.g. OP_RETURN), or the resulting address is too long to fit in
* out.
*/
static int __attribute__((noinline)) get_liquid_script_address(dispatcher_context_t *dc,
const output_info_t *output,
char *address,
size_t address_len) {
if (output->in_out.key_presence & HAS_BLINDING_PUBKEY) {
// Get blinding public key from PSET
uint8_t blinding_pubkey[33];
if (sizeof(blinding_pubkey) !=
call_get_merkleized_map_value(dc,
&output->in_out.map,
PSBT_ELEMENTS_OUT_BLINDING_PUBKEY,
sizeof(PSBT_ELEMENTS_OUT_BLINDING_PUBKEY),
blinding_pubkey,
sizeof(blinding_pubkey))) {
PRINTF("Error fetching blinding pubkey\n");
return -1;
}
int result = liquid_get_script_confidential_address(output->in_out.scriptPubKey,
output->in_out.scriptPubKey_len,
&G_liquid_network_config,
blinding_pubkey,
sizeof(blinding_pubkey),
address,
address_len);

explicit_bzero(blinding_pubkey, sizeof(blinding_pubkey));
return result;
} else {
return get_script_address(output->in_out.scriptPubKey,
output->in_out.scriptPubKey_len,
address,
address_len);
}
}
#endif
static bool __attribute__((noinline)) display_output(dispatcher_context_t *dc,
sign_psbt_state_t *st,
int cur_output_index,
Expand All @@ -1873,10 +1928,16 @@ static bool __attribute__((noinline)) display_output(dispatcher_context_t *dc,

// show this output's address
char output_address[MAX(MAX_ADDRESS_LENGTH_STR + 1, MAX_OPRETURN_OUTPUT_DESC_SIZE)];

#ifdef HAVE_LIQUID
int address_len = get_liquid_script_address(dc, output, output_address, sizeof(output_address));
#else
int address_len = get_script_address(output->in_out.scriptPubKey,
output->in_out.scriptPubKey_len,
output_address,
sizeof(output_address));
#endif

if (address_len < 0) {
// script does not have an address; check if OP_RETURN
#ifdef HAVE_LIQUID
Expand Down
Loading
Loading