diff --git a/client/docker-finance.d/client/Dockerfiles/finance/Dockerfile.archlinux.in b/client/docker-finance.d/client/Dockerfiles/finance/Dockerfile.archlinux.in
index 504c44e..c247a95 100644
--- a/client/docker-finance.d/client/Dockerfiles/finance/Dockerfile.archlinux.in
+++ b/client/docker-finance.d/client/Dockerfiles/finance/Dockerfile.archlinux.in
@@ -54,6 +54,18 @@ RUN pacman -Syu --noconfirm
#WORKDIR /home/@DOCKER_FINANCE_USER@
#RUN pipx install xlsx2csv
+##
+## Plugins (dependencies)
+##
+
+## libbitcoinkernel (bitcoin)
+#RUN pacman -Syu \
+# boost \
+# capnproto \
+# cmake \
+# db \
+# --noconfirm --disable-download-timeout
+
## Add yours below, as needed
diff --git a/client/plugins/docker/bitcoin.bash b/client/plugins/docker/bitcoin.bash
new file mode 100755
index 0000000..dfcf2a8
--- /dev/null
+++ b/client/plugins/docker/bitcoin.bash
@@ -0,0 +1,341 @@
+#!/usr/bin/env bash
+
+# docker-finance | modern accounting for the power-user
+#
+# Copyright (C) 2025 Aaron Fiore (Founder, Evergreen Crypto LLC)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+#
+# NOTES:
+#
+# This plugin provides a rudimentary clone/pull/build process that
+# adheres to the expectations from the container-side bitcoin plugin.
+#
+# The only requirement for this plugin is that build dependencies
+# are installed on your existing/running image (which is assumed to
+# have also been built with the `root` module enabled):
+#
+# 1. Uncomment plugin's image build requirements:
+#
+# $ dfi edit type=build
+#
+# 2. Build/re-build your image with `root` module:
+#
+# $ dfi build type=default
+#
+# If you do bitcoin development on your client (host) and prefer to
+# use your own build process, you can skip the above requirements and,
+# instead, ensure the following:
+#
+# 1. Your bitcoin repository is dropped into your client-side shared
+# directory:
+#
+# $DOCKER_FINANCE_CLIENT_SHARED
+#
+# The expected path by the container-side plugin will be in your
+# container-side shared directory:
+#
+# "${DOCKER_FINANCE_CONTAINER_SHARED}"/bitcoin
+#
+# To see or edit your shared client/container path prior to
+# running this (or the container's) plugin, run the following:
+#
+# $ dfi edit type=env
+#
+# 2. Bitcoin's libraries are built as a shared (when possible) and
+# should continue to remain within the repo's build directory,
+# prior to running the container-side `root` bitcoin plugin.
+#
+
+#
+# "Libraries"
+#
+
+[ -z "$DOCKER_FINANCE_CLIENT_REPO" ] && exit 1
+source "${DOCKER_FINANCE_CLIENT_REPO}/client/src/docker/lib/lib_docker.bash" || exit 1
+
+[[ -z "$global_platform" || -z "$global_arg_delim_1" || -z "$global_user" || -z "$global_tag" ]] && lib_utils::die_fatal
+instance="${global_platform}${global_arg_delim_1}${global_user}:${global_tag}"
+
+# Initialize "constructor"
+# NOTE: "constructor" only needed if calling library directly
+lib_docker::docker "$instance" || lib_utils::die_fatal
+
+#
+# Implementation
+#
+
+# WARNING: *MUST* be synced with image (assuming Arch Linux finance image)
+declare -r plugin_bitcoin_deps=("boost" "capnproto" "cmake" "db")
+
+# WARNING: *MUST* be synced with container's plugin implementation
+[ -z "$DOCKER_FINANCE_CONTAINER_SHARED" ] && lib_utils::die_fatal
+declare -r plugin_bitcoin_path="${DOCKER_FINANCE_CONTAINER_SHARED}/bitcoin"
+
+function bitcoin::__parse_args()
+{
+ [ -z "$global_usage" ] && lib_utils::die_fatal
+ [ -z "$global_arg_delim_1" ] && lib_utils::die_fatal
+ [ -z "$global_arg_delim_2" ] && lib_utils::die_fatal
+
+ local -r _default_arg_remote="https://github.com/bitcoin/bitcoin"
+ local -r _default_arg_branch="master"
+ local -r _default_arg_cmake="-DBUILD_BITCOIN_BIN=OFF -DBUILD_CLI=OFF -DBUILD_DAEMON=OFF -DBUILD_TESTS=OFF -DBUILD_UTIL=OFF -DBUILD_WALLET_TOOL=OFF -DINSTALL_MAN=OFF -DSECP256K1_BUILD_TESTS=OFF -DSECP256K1_BUILD_EXHAUSTIVE_TESTS=OFF -DBUILD_KERNEL_LIB=ON -DBUILD_SHARED_LIBS=ON"
+
+ # Re-seat global usage
+ local _global_usage
+ _global_usage="$global_usage plugins repo${global_arg_delim_1}$(basename $0)"
+ declare -r _global_usage
+
+ local -r _usage="
+\e[32mDescription:\e[0m
+
+ Build bitcoin libraries for \`root\` container-side plugin.
+
+\e[32mUsage:\e[0m
+
+ $ $_global_usage [remote${global_arg_delim_2}] [branch${global_arg_delim_2}] [cmake${global_arg_delim_2}]
+
+\e[32mArguments:\e[0m
+
+ Git remote to get; default:
+
+ remote${global_arg_delim_2}\"$_default_arg_remote\"
+
+ Git branch to build from; default:
+
+ branch${global_arg_delim_2}\"$_default_arg_branch\"
+
+ CMake build options; default:
+
+ cmake${global_arg_delim_2}\"$_default_arg_cmake\"
+
+\e[32mExamples:\e[0m
+
+ \e[37;2m# Get from default\e[0m
+ $ $_global_usage get
+
+ \e[37;2m# Get from given remote 'https://my.custom.remote/bitcoin'\e[0m
+ $ $_global_usage get remote${global_arg_delim_2}\"https://my.custom.remote/bitcoin\"
+
+ \e[37;2m# Build default branch\e[0m
+ $ $_global_usage build
+
+ \e[37;2m# Checkout and build given branch\e[0m
+ $ $_global_usage build branch=\"my_dev\"
+
+ \e[37;2m# Checkout and build given commit\e[0m
+ $ $_global_usage build branch=\"b30262dcaa28c40a0a5072847b7194b3db203160\"
+
+ \e[37;2m# Build given branch with custom CMake options\e[0m
+ $ $_global_usage build branch=\"my_dev\" cmake${global_arg_delim_2}\"-DBUILD_BITCOIN_BIN=ON -DBUILD_CLI=ON -DBUILD_DAEMON=ON -DBUILD_TESTS=ON -DBUILD_UTIL=ON -DBUILD_WALLET_TOOL=ON -DINSTALL_MAN=ON -DSECP256K1_BUILD_TESTS=ON -DSECP256K1_BUILD_EXHAUSTIVE_TESTS=ON -DBUILD_KERNEL_LIB=ON -DBUILD_SHARED_LIBS=ON\"
+
+ \e[37;2m# Clean entire build directory\e[0m
+ $ $_global_usage clean
+"
+
+ [ $# -eq 0 ] && lib_utils::die_usage "$_usage"
+
+ [[ ! "$1" =~ ^get$|^build$|^clean$ ]] \
+ && lib_utils::die_usage "$_usage" \
+ || declare -gr plugin_arg_cmd="$1"
+
+ for _arg in "${@:2}"; do
+ [[ ! "$_arg" =~ ^remote${global_arg_delim_2} ]] \
+ && [[ ! "$_arg" =~ ^branch${global_arg_delim_2} ]] \
+ && [[ ! "$_arg" =~ ^cmake${global_arg_delim_2} ]] \
+ && lib_utils::die_usage "$_usage"
+ done
+
+ for _arg in "${@:2}"; do
+ local _key="${_arg%${global_arg_delim_2}*}"
+ local _len="$((${#_key} + 1))"
+
+ if [[ "$_key" =~ ^remote$ ]]; then
+ local _arg_remote="${_arg:${_len}}"
+ [ -z "$_arg_remote" ] && lib_utils::die_usage "$_usage"
+ fi
+
+ if [[ "$_key" =~ ^branch$ ]]; then
+ local _arg_branch="${_arg:${_len}}"
+ [ -z "$_arg_branch" ] && lib_utils::die_usage "$_usage"
+ fi
+
+ if [[ "$_key" =~ ^cmake$ ]]; then
+ local _arg_cmake="${_arg:${_len}}"
+ [ -z "$_arg_cmake" ] && lib_utils::die_usage "$_usage"
+ fi
+ done
+
+ # Arg: get
+ if [[ "$plugin_arg_cmd" =~ ^get$ ]]; then
+ if [[ ! -z "$_arg_branch" || ! -z "$_arg_cmake" ]]; then
+ lib_utils::die_usage "$_usage"
+ fi
+
+ # Arg: remote
+ if [ -z "$_arg_remote" ]; then
+ _arg_remote="$_default_arg_remote"
+ fi
+ declare -gr plugin_arg_remote="$_arg_remote"
+ lib_utils::print_info "Using remote: ${_arg_remote}"
+ fi
+
+ # Arg: build
+ if [[ "$plugin_arg_cmd" =~ ^build$ ]]; then
+ if [[ ! -z "$_arg_remote" ]]; then
+ lib_utils::die_usage "$_usage"
+ fi
+
+ # Arg: branch
+ if [ -z "$_arg_branch" ]; then
+ _arg_branch="$_default_arg_branch"
+ fi
+ declare -gr plugin_arg_branch="$_arg_branch"
+ lib_utils::print_info "Using branch: ${plugin_arg_branch}"
+
+ # Arg: cmake
+ if [ -z "$_arg_cmake" ]; then
+ _arg_cmake="$_default_arg_cmake"
+ fi
+ declare -gr plugin_arg_cmake="$_arg_cmake"
+ lib_utils::print_info "Using CMake options: ${plugin_arg_cmake}"
+ fi
+
+ # Arg: clean
+ if [[ "$plugin_arg_cmd" =~ ^clean$ ]]; then
+ if [[ ! -z "$_arg_remote" || ! -z "$_arg_branch" || ! -z "$_arg_cmake" ]]; then
+ lib_utils::die_usage "$_usage"
+ fi
+ fi
+}
+
+function bitcoin::__getter()
+{
+ [ -z "$plugin_arg_remote" ] && lib_utils::die_fatal
+ [ -z "$plugin_bitcoin_path" ] && lib_utils::die_fatal
+
+ lib_docker::exec "if [ ! -d $plugin_bitcoin_path ]; then git clone $plugin_arg_remote $plugin_bitcoin_path ; fi" \
+ || lib_utils::die_fatal "Could not clone '${plugin_arg_remote}' to '${plugin_bitcoin_path}'"
+
+ lib_docker::exec "pushd $plugin_bitcoin_path 1>/dev/null && git pull $plugin_arg_remote && popd 1>/dev/null" \
+ || lib_utils::die_fatal "Could not pull '${plugin_arg_remote}' to '${plugin_bitcoin_path}'"
+}
+
+function bitcoin::__builder()
+{
+ #
+ # Ensure dependencies
+ #
+
+ local -r _no_deps="Build dependencies not found!
+
+ Did you uncomment them with:
+
+\t\$ dfi $instance edit type=build
+
+ and then re-build your image with:
+
+\t\$ dfi $instance build type="
+
+ # NOTE: *MUST* be synced with end-user Dockerfile (`edit type=build`)
+ # TODO: not ideal, would prefer runtime dependency pulling but that has drawbacks as well
+ [ -z "${plugin_bitcoin_deps[*]}" ] && lib_utils::die_fatal
+ lib_docker::exec "pacman -Q ${plugin_bitcoin_deps[*]}" | grep "^error: package" \
+ && lib_utils::die_fatal "$_no_deps"
+
+ #
+ # Execute build
+ #
+
+ [ -z "$plugin_arg_cmake" ] && lib_utils::die_fatal
+ [ -z "$plugin_bitcoin_path" ] && lib_utils::die_fatal
+
+ lib_docker::exec "pushd $plugin_bitcoin_path 1>/dev/null && git checkout $plugin_arg_branch && popd 1>/dev/null" \
+ || lib_utils::die_fatal "Could not checkout '${plugin_arg_branch}'"
+
+ lib_docker::exec "cmake $plugin_arg_cmake -B ${plugin_bitcoin_path}/build $plugin_bitcoin_path" \
+ || lib_utils::die_fatal "Could not prepare build with given options"
+
+ lib_docker::exec "cmake --build ${plugin_bitcoin_path}/build -j$(nproc)" \
+ || lib_utils::die_fatal "Could not build"
+
+ lib_utils::print_info "$(lib_docker::exec "find ${plugin_bitcoin_path} -name libbitcoinkernel.so")"
+}
+
+function bitcoin::__cleaner()
+{
+ [ -z "$plugin_bitcoin_path" ] && lib_utils::die_fatal
+ local -r _build="${plugin_bitcoin_path}/build"
+
+ lib_utils::print_info "Removing '${_build}'"
+ lib_docker::exec "rm -fr $_build" || lib_utils::die_fatal "Could not delete= '${_build}'"
+}
+
+#
+# Facade
+#
+
+function bitcoin::get()
+{
+ lib_utils::print_info "Getting repository"
+ bitcoin::__getter
+}
+
+function bitcoin::build()
+{
+ lib_utils::print_info "Building repository"
+ bitcoin::__builder
+}
+
+function bitcoin::clean()
+{
+ lib_utils::print_info "Cleaning repository"
+ bitcoin::__cleaner
+}
+
+function main()
+{
+ bitcoin::__parse_args "$@"
+
+ # Ensure running instance
+ local -r _no_instance="Did you start a running instance?
+
+ Run the following in a separate shell:
+
+\t$ dfi $instance up"
+
+ lib_docker::exec "" || lib_utils::die_fatal "$_no_instance"
+
+ [ -z "$plugin_arg_cmd" ] && lib_utils::die_fatal
+ case "$plugin_arg_cmd" in
+ get)
+ bitcoin::get
+ ;;
+ build)
+ bitcoin::build
+ ;;
+ clean)
+ bitcoin::clean
+ ;;
+ *)
+ lib_utils::die_fatal "Not implemented"
+ ;;
+ esac
+}
+
+main "$@"
+
+# vim: sw=2 sts=2 si ai et
diff --git a/container/plugins/root/bitcoin/bitcoin.cc b/container/plugins/root/bitcoin/bitcoin.cc
new file mode 100644
index 0000000..ec98941
--- /dev/null
+++ b/container/plugins/root/bitcoin/bitcoin.cc
@@ -0,0 +1,104 @@
+// docker-finance | modern accounting for the power-user
+//
+// Copyright (C) 2025 Aaron Fiore (Founder, Evergreen Crypto LLC)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation either version 3 of the License or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not see .
+
+//! \file
+//! \author Aaron Fiore (Founder, Evergreen Crypto LLC)
+//! \note File intended to be loaded into ROOT.cern framework / Cling interpreter
+//! \since docker-finance 1.1.0
+
+//! \namespace dfi::plugin
+//! \brief docker-finance plugins
+//! \warning All plugins (repo/custom) must exist within this namespace
+//! and work within their own inner namespace
+//! \since docker-finance 1.0.0
+namespace dfi::plugin
+{
+//! \namespace dfi::plugin::bitcoin
+//! \brief docker-finance bitcoin plugin namespace
+//! \warning Bitcoin inherently pollutes the global namespace - be wary
+//! \since docker-finance 1.1.0
+namespace bitcoin
+{
+//! \brief Get container-side path to bitcoin repository
+//! \return Absolute path of shared (bind-mounted) bitcoin repository
+//! \todo To avoid future include conflicts, consider adjusting base path
+//! \since docker-finance 1.1.0
+inline std::string get_repo_path()
+{
+ return ::dfi::common::get_env("DOCKER_FINANCE_CONTAINER_SHARED")
+ + "/bitcoin/";
+}
+
+//! \brief Initialize this bitcoin plugin
+//! \details Bare-essential initializations, will require manual loading for any further functionality
+//! \warning Initializations *MUST* occur here before any other bitcoin lib use
+//! \ingroup cpp_plugin_impl
+//! \since docker-finance 1.1.0
+void load(const std::string& arg = {})
+{
+ namespace common = ::dfi::common;
+
+ const std::string repo{get_repo_path()};
+
+ // Minimum requirements
+ common::add_include_dir(repo + "src");
+ common::add_linked_lib(repo + "build/lib/libbitcoinkernel.so");
+ // WARNING: assertions *MUST* be enabled before loading any bitcoin headers
+ common::line("#undef NDEBUG");
+ common::load(repo + "src/kernel/bitcoinkernel_wrapper.h");
+
+ // `dfi`-specific requirements (API, macros, tests, etc.)
+ common::line("#define __DFI_PLUGIN_BITCOIN__");
+ common::load(repo + "src/random.h");
+ // ...add as needed
+
+ if (!arg.empty())
+ common::line(arg);
+}
+
+//! \brief Deinitialize this bitcoin plugin
+//! \details Bare-essential deinitializations, will require manual unloading for any other previously loaded functionality
+//! \warning Unloading certain bitcoin headers may result in Cling segfault due to bitcoin library design
+//! \ingroup cpp_plugin_impl
+//! \since docker-finance 1.1.0
+//! \todo It appears that, due to bitcoin design, unloading will result in
+//! Cling segfault "*** Break *** illegal instruction". With that said, it's
+//! best to exit `root` if you want a clean workspace (instead of unloading this plugin).
+void unload(const std::string& arg = {})
+{
+ namespace common = ::dfi::common;
+
+ if (!arg.empty())
+ common::line(arg);
+
+ const std::string repo{get_repo_path()};
+
+ // `dfi`-specific requirements (API, macros, tests, etc.)
+ common::line("#undef __DFI_PLUGIN_BITCOIN__");
+ common::unload(repo + "src/random.h");
+ // ...add as needed
+
+ // Minimum requirements
+ common::unload(repo + "src/kernel/bitcoinkernel_wrapper.h");
+ common::line("#define NDEBUG");
+ // TODO(unassigned): remove_include_dir()
+ common::remove_linked_lib(repo + "build/lib/libbitcoinkernel.so");
+}
+} // namespace bitcoin
+} // namespace dfi::plugin
+
+// # vim: sw=2 sts=2 si ai et
diff --git a/container/src/root/macro/common/crypto.hh b/container/src/root/macro/common/crypto.hh
index 276ccfb..32f9bfb 100644
--- a/container/src/root/macro/common/crypto.hh
+++ b/container/src/root/macro/common/crypto.hh
@@ -105,6 +105,21 @@ using Random = ::dfi::crypto::libsodium::Random;
auto g_Random = std::make_unique();
} // namespace crypto::libsodium
+#ifdef __DFI_PLUGIN_BITCOIN__
+//! \namespace dfi::macro::common::crypto::bitcoin
+//! \since docker-finance 1.1.0
+namespace crypto::bitcoin
+{
+//! \brief ROOT's bitcoin Random class
+//! \ingroup cpp_macro
+using Random = ::dfi::crypto::bitcoin::Random;
+
+//! \brief ROOT's bitcoin Random instance
+//! \ingroup cpp_macro
+auto g_Random = std::make_unique();
+} // namespace crypto::bitcoin
+#endif
+
} // namespace common
} // namespace macro
} // namespace dfi
diff --git a/container/src/root/macro/crypto/random.C b/container/src/root/macro/crypto/random.C
index 769b0d6..bd0da58 100644
--- a/container/src/root/macro/crypto/random.C
+++ b/container/src/root/macro/crypto/random.C
@@ -47,6 +47,9 @@ namespace common = ::dfi::macro::common;
namespace libsodium = common::crypto::libsodium;
namespace cryptopp = common::crypto::cryptopp;
namespace botan = common::crypto::botan;
+#ifdef __DFI_PLUGIN_BITCOIN__
+namespace bitcoin = common::crypto::bitcoin;
+#endif
//! \brief CSPRNG macro
class Random final
@@ -110,6 +113,24 @@ class Random final
return rng;
}
+#ifdef __DFI_PLUGIN_BITCOIN__
+ //! \brief bitcon RNG
+ //! \return t_rng Random map to print (label, num)
+ static t_rng bitcoin_generate()
+ {
+ t_rng rng;
+
+ rng["int16_t (unsupported)"] = 0;
+ rng["int32_t (unsupported)"] = 0;
+
+ rng["uint16_t (unsupported)"] = 0;
+ rng["uint32_t"] = bitcoin::g_Random->generate();
+ rng["uint64_t"] = bitcoin::g_Random->generate();
+
+ return rng;
+ }
+#endif
+
public:
//! \brief Print t_rng of CSPRNG numbers in CSV format
static void generate()
@@ -122,6 +143,9 @@ class Random final
};
std::cout << "\nimpl,type,num\n";
+#ifdef __DFI_PLUGIN_BITCOIN__
+ print("bitcoin::Random", Random::bitcoin_generate());
+#endif
print("botan::Random", Random::botan_generate());
print("cryptopp::Random", Random::cryptopp_generate());
print("libsodium::Random", Random::libsodium_generate());
diff --git a/container/src/root/macro/web/internal/crypto.C b/container/src/root/macro/web/internal/crypto.C
index 1f7ef65..18ec291 100644
--- a/container/src/root/macro/web/internal/crypto.C
+++ b/container/src/root/macro/web/internal/crypto.C
@@ -351,6 +351,15 @@ class Random final
const std::string timestamp{common::make_timestamp()};
+#ifdef __DFI_PLUGIN_BITCOIN__
+ data.title = "Bitcoin_FastRandomContext_32_RNG_" + timestamp;
+ random(data, []() -> uint32_t {
+ return common::crypto::bitcoin::g_Random->generate();
+ });
+
+ // TODO(afiore): uint64_t (requires canvas-related refactor)
+#endif
+
data.title = "Botan_RNG_" + timestamp;
random(data, []() -> uint32_t {
return common::crypto::botan::g_Random->generate();
diff --git a/container/src/root/src/internal/impl/random.hh b/container/src/root/src/internal/impl/random.hh
index bb158ab..877fb25 100644
--- a/container/src/root/src/internal/impl/random.hh
+++ b/container/src/root/src/internal/impl/random.hh
@@ -19,6 +19,7 @@
//! \author Aaron Fiore (Founder, Evergreen Crypto LLC)
//! \note File intended to be loaded into ROOT.cern framework / Cling interpreter
//! \since docker-finance 1.0.0
+//! \todo C++20 refactor
#ifndef CONTAINER_SRC_ROOT_SRC_INTERNAL_IMPL_RANDOM_HH_
#define CONTAINER_SRC_ROOT_SRC_INTERNAL_IMPL_RANDOM_HH_
@@ -259,6 +260,69 @@ class Random : public common::RandomImpl
};
} // namespace libsodium
+#ifdef __DFI_PLUGIN_BITCOIN__
+//! \namespace dfi::crypto::impl::bitcoin
+//! \since docker-finance 1.1.0
+namespace bitcoin
+{
+//! \concept dfi::crypto::impl::bitcoin::RType
+//! \brief Random number type
+//! \details Requirements include that "interface" specialization
+//! has not changed and that given type is supported by bitcoin.
+//! \since docker-finance 1.1.0
+template
+concept RType =
+ ::dfi::internal::type::is_real_integral::value
+ && (std::same_as || std::same_as);
+
+//! \brief Implements Random API with bitcoin
+//! \ingroup cpp_API_impl
+//! \since docker-finance 1.1.0
+//! \todo span/bytes
+//! \todo reseed
+//! \todo insecure RNG option
+class Random : public common::RandomImpl
+{
+ public:
+ Random() = default;
+ ~Random() = default;
+
+ Random(const Random&) = delete;
+ Random& operator=(const Random&) = delete;
+
+ Random(Random&&) = delete;
+ Random& operator=(Random&&) = delete;
+
+ private:
+ //! \brief Implements random number generator
+ template
+ t_random generate_impl()
+ {
+ if constexpr (std::same_as)
+ {
+ return m_ctx.rand32();
+ }
+ else if constexpr (std::same_as)
+ {
+ return m_ctx.rand64();
+ }
+ }
+
+ public:
+ //! \brief Generate random number
+ template
+ t_random generate()
+ {
+ return this->generate_impl();
+ }
+
+ private:
+ //! \warning Bitcoin's RNG is not thread-safe (but dfi's CRTP provides external locks, by default)
+ ::FastRandomContext m_ctx;
+};
+} // namespace bitcoin
+#endif
+
} // namespace impl
} // namespace crypto
} // namespace dfi
diff --git a/container/src/root/src/random.hh b/container/src/root/src/random.hh
index 2eeb56a..ec20839 100644
--- a/container/src/root/src/random.hh
+++ b/container/src/root/src/random.hh
@@ -118,6 +118,20 @@ namespace libsodium
using Random = ::dfi::crypto::common::Random;
} // namespace libsodium
+#ifdef __DFI_PLUGIN_BITCOIN__
+//! \namespace dfi::crypto::bitcoin
+//! \brief Public-facing API namespace (bitcoin)
+//! \since docker-finance 1.1.0
+namespace bitcoin
+{
+//! \brief bitcoin Random API specialization ("interface" / implementation)
+//! \note For public consumption
+//! \ingroup cpp_API
+//! \since docker-finance 1.1.0
+using Random = ::dfi::crypto::common::Random;
+} // namespace bitcoin
+#endif
+
} // namespace crypto
} // namespace dfi
diff --git a/container/src/root/test/benchmark/random.hh b/container/src/root/test/benchmark/random.hh
index 9d836bb..05116ec 100644
--- a/container/src/root/test/benchmark/random.hh
+++ b/container/src/root/test/benchmark/random.hh
@@ -128,6 +128,30 @@ BENCHMARK_F(RandomLibsodium, generate)(::benchmark::State& state) // NOLINT
random.generate();
}
}
+
+//
+// Bitcoin
+//
+
+#ifdef __DFI_PLUGIN_BITCOIN__
+//! \brief Bitcoin Random fixture w/ real implementation
+//! \since docker-finance 1.1.0
+struct RandomBitcoin : public ::benchmark::Fixture,
+ public tests::RandomBitcoin_Impl
+{
+ void SetUp(const ::benchmark::State& state) {}
+ void TearDown(const ::benchmark::State& state) {}
+};
+
+BENCHMARK_F(RandomBitcoin, generate)(::benchmark::State& state) // NOLINT
+{
+ for (auto st : state)
+ {
+ random.generate();
+ }
+}
+#endif
+
} // namespace benchmarks
} // namespace tests
} // namespace dfi
diff --git a/container/src/root/test/common/random.hh b/container/src/root/test/common/random.hh
index 3b83312..ee7e397 100644
--- a/container/src/root/test/common/random.hh
+++ b/container/src/root/test/common/random.hh
@@ -90,6 +90,18 @@ struct RandomLibsodium_Impl
using Random = ::dfi::crypto::libsodium::Random;
Random random;
};
+
+#ifdef __DFI_PLUGIN_BITCOIN__
+//! \brief Bitcoin random implementation fixture
+//! \since docker-finance 1.1.0
+struct RandomBitcoin_Impl
+{
+ protected:
+ using Random = ::dfi::crypto::bitcoin::Random;
+ Random random;
+};
+#endif
+
} // namespace tests
} // namespace dfi
diff --git a/container/src/root/test/unit/random.hh b/container/src/root/test/unit/random.hh
index b74e361..ae5c314 100644
--- a/container/src/root/test/unit/random.hh
+++ b/container/src/root/test/unit/random.hh
@@ -70,6 +70,11 @@ TEST_F(RandomInterface, generate_uint32_t)
ASSERT_EQ(random.generate(), std::numeric_limits::max());
}
+TEST_F(RandomInterface, generate_uint64_t)
+{
+ ASSERT_EQ(random.generate(), std::numeric_limits::max());
+}
+
TEST_F(RandomInterface, generate_int16_t)
{
ASSERT_EQ(random.generate(), std::numeric_limits::max());
@@ -96,9 +101,13 @@ struct RandomFixture : public ::testing::Test, protected t_impl
auto two = t_impl::random.template generate();
static_assert(std::is_same_v);
- // NOTE: t_random limits are implementation-specific
- ASSERT_NEAR(0, one, std::numeric_limits::max());
- ASSERT_NEAR(0, two, std::numeric_limits::max());
+ // TODO(unassigned): gtest implicit conversion of uint64_t with ASSERT_NEAR
+ if constexpr (!std::is_same_v)
+ {
+ // NOTE: t_random limits are implementation-specific
+ ASSERT_NEAR(0, one, std::numeric_limits::max());
+ ASSERT_NEAR(0, two, std::numeric_limits::max());
+ }
// Exceedingly rare (tests the obvious, but is not an accurate entropy test)
// If fails, re-run tests to confirm
@@ -176,6 +185,28 @@ TEST_F(RandomLibsodium, generate_uint32_t)
ASSERT_NO_THROW(generate());
}
+#ifdef __DFI_PLUGIN_BITCOIN__
+//
+// Bitcoin
+//
+
+//! \brief Bitcoin random number fixture
+//! \since docker-finance 1.1.0
+struct RandomBitcoin : public RandomFixture
+{
+};
+
+TEST_F(RandomBitcoin, generate_uint32_t)
+{
+ ASSERT_NO_THROW(generate());
+}
+
+TEST_F(RandomBitcoin, generate_uint64_t)
+{
+ ASSERT_NO_THROW(generate());
+}
+#endif
+
} // namespace unit
} // namespace tests
} // namespace dfi