Skip to content
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
4 changes: 2 additions & 2 deletions .github/workflows/test-bdk-ffi-latest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
&& git fetch origin \
&& git checkout master \
&& git pull origin master \
&& echo "Testing commit: $(git log -1 --pretty=format:'%h %s (author: %cn)')"
&& echo "Testing commit: $(git log -1 --pretty=format:'%h %s (author: %an)')"

- name: "Cache"
uses: actions/cache@v3
Expand All @@ -43,5 +43,5 @@ jobs:
- name: "Run JVM tests"
run: |
bash ./scripts/build-linux-x86_64.sh --skip-submodule-update
./gradlew test -P excludeConnectedTests
./gradlew test
./gradlew :examples:build
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,5 @@ jobs:
- name: "Run JVM tests"
run: |
bash ./scripts/build-linux-x86_64.sh
./gradlew test -P excludeConnectedTests
./gradlew test
./gradlew :examples:build
16 changes: 0 additions & 16 deletions lib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,6 @@ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
}
}

// This block ensures that the tests that require access to a blockchain are not
// run if the -P excludeConnectedTests flag is passed to gradle.
// This ensures our CI runs are not fickle by not requiring access to testnet or signet.
// This is a workaround until we have a proper regtest setup for the CI.
// Note that the command in the CI is ./gradlew test -P excludeConnectedTests
tasks.test {
if (project.hasProperty("excludeConnectedTests")) {
exclude("**/LiveElectrumClientTest.class")
exclude("**/LiveMemoryWalletTest.class")
exclude("**/LiveTransactionTest.class")
exclude("**/LiveTxBuilderTest.class")
exclude("**/LiveWalletTest.class")
exclude("**/LiveKyotoTest.class")
}
}

testing {
suites {
val test by getting(JvmTestSuite::class) {
Expand Down
66 changes: 34 additions & 32 deletions lib/src/test/kotlin/org/bitcoindevkit/Constants.kt
Original file line number Diff line number Diff line change
@@ -1,38 +1,40 @@
package org.bitcoindevkit

// Test networks
const val TEST_EXTENDED_PRIVKEY = "tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B"
// 1. Extended Keys
// These are generated from the MNEMONIC_AWESOME mnemonic. The keys with the TEST prefix are valid for Regtest, Signet,
// Testnet3, and Testnet4. The keys with the MAIN prefix are valid for Mainnet.
const val TEST_EXTENDED_PRIVKEY = "tprv8ZgxMBicQKsPdWAHbugK2tjtVtRjKGixYVZUdL7xLHMgXZS6BFbFi1UDb1CHT25Z5PU1F9j7wGxwUiRhqz9E3nZRztikGUV6HoRDYcqPhM4"
const val TEST_EXTENDED_PUBKEY = "tpubD6NzVbkrYhZ4WyC5VZLuSJQ14uwfUbus7oAFurAFkZA5N3groeQqtW65m8pG1TT1arPpfWu9RbBsc5rSBncrX2d84BAwJJHQfaRjnMCQwuT"
const val MAIN_EXTENDED_PRIVKEY = "xprv9s21ZrQH143K2gvkwLposF7uBm1X5kgxCweMkuhVrJsCjxh1BtFWCG6mfq2dSehEhwwEF47MmvP91rsximoHEjHqUFWSc7m3Nhfo6yAmTtq"
const val MAIN_EXTENDED_PUBKEY = "xpub661MyMwAqRbcFB1E3NMpEP4djnr1VDQoaAZxZJ77QeQBcm29jRZkk4RFX6jUUxVmnwrv3wPGtVMpuuMpnvmcdbApzaRdcmdB5cqL2Q1yjam"

// 2. Mnemonics
const val MNEMONIC_AWESOME = "awesome awesome awesome awesome awesome awesome awesome awesome awesome awesome awesome awesome"
const val MNEMONIC_ALL = "all all all all all all all all all all all all"

// 3. Derivation Paths
const val BIP84_TEST_RECEIVE_PATH = "84h/1h/0h/0"
const val BIP84_TEST_CHANGE_PATH = "84h/1h/0h/1"
const val BIP84_TEST_CHANGE_PATH = "84h/1h/0h/1"
const val BIP86_TEST_RECEIVE_PATH = "86h/1h/0h/0"
const val BIP86_TEST_CHANGE_PATH = "86h/1h/0h/1"
const val BIP86_TEST_CHANGE_PATH = "86h/1h/0h/1"
const val BIP86_TEST_MULTIPATH = "86h/1h/0h/<0;1>"

const val BIP84_MAIN_RECEIVE_PATH = "84h/0h/0h/0"
const val BIP84_MAIN_CHANGE_PATH = "84h/0h/0h/1"
const val BIP86_MAIN_RECEIVE_PATH = "86h/0h/0h/0"
const val BIP86_MAIN_CHANGE_PATH = "86h/0h/0h/1"
const val BIP86_MAIN_MULTIPATH = "86h/0h/0h/<0;1>"

// Mainnet
const val MAINNET_EXTENDED_PRIVKEY = "xprv9s21ZrQH143K3LRcTnWpaCSYb75ic2rGuSgicmJhSVQSbfaKgPXfa8PhnYszgdcyWLoc8n1E2iHUnskjgGTAyCEpJYv7fqKxUcRNaVngA1V"
const val BIP84_MAINNET_RECEIVE_PATH = "84h/0h/0h/1"
const val BIP86_MAINNET_RECEIVE_PATH = "86h/0h/0h/1"
// 4. Descriptors
// These are also generated from the MNEMONIC_AWESOME mnemonic. The descriptors with the TEST prefix are valid for
// Regtest, Signet, Testnet3, and Testnet4. The descriptors with the MAIN prefix are valid for Mainnet.
val TEST_BIP84_DESCRIPTOR = Descriptor("wpkh($TEST_EXTENDED_PRIVKEY/$BIP84_TEST_RECEIVE_PATH/*)", Network.TESTNET4)
val TEST_BIP84_CHANGE_DESCRIPTOR = Descriptor("wpkh($TEST_EXTENDED_PRIVKEY/$BIP84_TEST_CHANGE_PATH/*)", Network.TESTNET4)
val TEST_BIP86_DESCRIPTOR = Descriptor("tr($TEST_EXTENDED_PRIVKEY/$BIP86_TEST_RECEIVE_PATH/*)", Network.TESTNET4)
val TEST_BIP86_CHANGE_DESCRIPTOR = Descriptor("tr($TEST_EXTENDED_PRIVKEY/$BIP86_TEST_CHANGE_PATH/*)", Network.TESTNET4)
val TEST_DEFINITE_DESCRIPTOR_0 = Descriptor("wpkh($TEST_EXTENDED_PRIVKEY/$BIP84_TEST_RECEIVE_PATH/0)", Network.TESTNET4)
val TEST_DEFINITE_DESCRIPTOR_1 = Descriptor("wpkh($TEST_EXTENDED_PRIVKEY/$BIP84_TEST_RECEIVE_PATH/1)", Network.TESTNET4)
val TEST_MULTIPATH_DESCRIPTOR = Descriptor("tr($TEST_EXTENDED_PUBKEY/<0;1>/*)", Network.TESTNET4)

val BIP84_DESCRIPTOR: Descriptor = Descriptor(
"wpkh($TEST_EXTENDED_PRIVKEY/$BIP84_TEST_RECEIVE_PATH/*)",
Network.TESTNET
)
val BIP84_CHANGE_DESCRIPTOR: Descriptor = Descriptor(
"wpkh($TEST_EXTENDED_PRIVKEY/$BIP84_TEST_CHANGE_PATH/*)",
Network.TESTNET
)
val BIP86_DESCRIPTOR: Descriptor = Descriptor(
"tr($TEST_EXTENDED_PRIVKEY/$BIP86_TEST_RECEIVE_PATH/*)",
Network.TESTNET
)
val BIP86_CHANGE_DESCRIPTOR: Descriptor = Descriptor(
"tr($TEST_EXTENDED_PRIVKEY/$BIP86_TEST_CHANGE_PATH/*)",
Network.TESTNET
)
val NON_EXTENDED_DESCRIPTOR_0: Descriptor = Descriptor(
descriptor = "wpkh($TEST_EXTENDED_PRIVKEY/$BIP84_TEST_RECEIVE_PATH/0)",
network = Network.TESTNET
)
val NON_EXTENDED_DESCRIPTOR_1: Descriptor = Descriptor(
descriptor = "wpkh($TEST_EXTENDED_PRIVKEY/$BIP84_TEST_RECEIVE_PATH/1)",
network = Network.TESTNET
)
val MAIN_BIP86_DESCRIPTOR = Descriptor("tr($MAIN_EXTENDED_PRIVKEY/$BIP86_TEST_RECEIVE_PATH/*)", Network.BITCOIN)
val MAIN_BIP86_CHANGE_DESCRIPTOR = Descriptor("tr($MAIN_EXTENDED_PRIVKEY/$BIP86_TEST_CHANGE_PATH/*)", Network.BITCOIN)
71 changes: 55 additions & 16 deletions lib/src/test/kotlin/org/bitcoindevkit/CreatingWalletTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,30 +11,48 @@ class CreatingWalletTest {
inner class Success {
@Test
fun `Create WPKH wallet`() {
val wallet: Wallet = Wallet(
descriptor = BIP84_DESCRIPTOR,
changeDescriptor = BIP84_CHANGE_DESCRIPTOR,
network = Network.TESTNET,
Wallet(
descriptor = TEST_BIP84_DESCRIPTOR,
changeDescriptor = TEST_BIP84_CHANGE_DESCRIPTOR,
network = Network.TESTNET4,
persister = conn
)
}

@Test
fun `Create TR wallet`() {
val wallet: Wallet = Wallet(
descriptor = BIP86_DESCRIPTOR,
changeDescriptor = BIP86_CHANGE_DESCRIPTOR,
network = Network.TESTNET,
Wallet(
descriptor = TEST_BIP86_DESCRIPTOR,
changeDescriptor = TEST_BIP86_CHANGE_DESCRIPTOR,
network = Network.TESTNET4,
persister = conn
)
}

@Test
fun `Create a wallet with a single descriptor`() {
Wallet.createSingle(
descriptor = TEST_BIP84_DESCRIPTOR,
network = Network.TESTNET4,
persister = conn
)
}

@Test
fun `Create a wallet with a non-extended descriptor`() {
val wallet: Wallet = Wallet(
descriptor = NON_EXTENDED_DESCRIPTOR_0,
changeDescriptor = NON_EXTENDED_DESCRIPTOR_1,
network = Network.TESTNET,
Wallet(
descriptor = TEST_DEFINITE_DESCRIPTOR_0,
changeDescriptor = TEST_DEFINITE_DESCRIPTOR_1,
network = Network.TESTNET4,
persister = conn
)
}

@Test
fun `Create a wallet with a public multipath descriptor`() {
Wallet.createFromTwoPathDescriptor(
twoPathDescriptor = TEST_MULTIPATH_DESCRIPTOR,
network = Network.TESTNET4,
persister = conn
)
}
Expand All @@ -44,15 +62,36 @@ class CreatingWalletTest {
inner class Failure {
@Test
fun `Descriptors do not match provided network`() {
// The descriptors provided are for Testnet 3, but the wallet attempts to build for mainnet
// The descriptors provided are for Testnet4, but the wallet attempts to build for mainnet
assertFails {
val wallet: Wallet = Wallet(
descriptor = NON_EXTENDED_DESCRIPTOR_0,
changeDescriptor = NON_EXTENDED_DESCRIPTOR_1,
Wallet(
descriptor = TEST_DEFINITE_DESCRIPTOR_0,
changeDescriptor = TEST_DEFINITE_DESCRIPTOR_1,
network = Network.BITCOIN,
persister = conn
)
}
}

@Test
fun `You cannot create a wallet with two identical descriptors`() {
assertFails {
Wallet(
descriptor = TEST_DEFINITE_DESCRIPTOR_0,
changeDescriptor = TEST_DEFINITE_DESCRIPTOR_0,
network = Network.TESTNET4,
persister = conn
)
}

assertFails {
Wallet(
descriptor = TEST_BIP84_DESCRIPTOR,
changeDescriptor = TEST_BIP84_DESCRIPTOR,
network = Network.TESTNET4,
persister = conn
)
}
}
}
}
29 changes: 23 additions & 6 deletions lib/src/test/kotlin/org/bitcoindevkit/DescriptorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ class DescriptorTest {
@Nested
inner class Success {
@Test
fun `Create extended WPKH descriptors for all networks 2`() {
fun `Create extended WPKH descriptors for all networks`() {
Descriptor("wpkh($TEST_EXTENDED_PRIVKEY/$BIP84_TEST_RECEIVE_PATH/*)", Network.REGTEST)
Descriptor("wpkh($TEST_EXTENDED_PRIVKEY/$BIP84_TEST_RECEIVE_PATH/*)", Network.TESTNET)
Descriptor("wpkh($TEST_EXTENDED_PRIVKEY/$BIP84_TEST_RECEIVE_PATH/*)", Network.TESTNET4)
Descriptor("wpkh($TEST_EXTENDED_PRIVKEY/$BIP84_TEST_RECEIVE_PATH/*)", Network.SIGNET)
Descriptor("wpkh($MAINNET_EXTENDED_PRIVKEY/$BIP84_MAINNET_RECEIVE_PATH/*)", Network.BITCOIN)
Descriptor("wpkh($MAIN_EXTENDED_PRIVKEY/$BIP84_MAIN_RECEIVE_PATH/*)", Network.BITCOIN)
}

@Test
Expand All @@ -22,7 +22,7 @@ class DescriptorTest {
Descriptor("tr($TEST_EXTENDED_PRIVKEY/$BIP86_TEST_RECEIVE_PATH/*)", Network.TESTNET)
Descriptor("tr($TEST_EXTENDED_PRIVKEY/$BIP86_TEST_RECEIVE_PATH/*)", Network.TESTNET4)
Descriptor("tr($TEST_EXTENDED_PRIVKEY/$BIP86_TEST_RECEIVE_PATH/*)", Network.SIGNET)
Descriptor("tr($MAINNET_EXTENDED_PRIVKEY/$BIP86_MAINNET_RECEIVE_PATH/*)", Network.BITCOIN)
Descriptor("tr($MAIN_EXTENDED_PRIVKEY/$BIP86_MAIN_RECEIVE_PATH/*)", Network.BITCOIN)
}

@Test
Expand All @@ -31,20 +31,37 @@ class DescriptorTest {
Descriptor("tr($TEST_EXTENDED_PRIVKEY/$BIP86_TEST_RECEIVE_PATH/0)", Network.TESTNET)
Descriptor("tr($TEST_EXTENDED_PRIVKEY/$BIP86_TEST_RECEIVE_PATH/0)", Network.TESTNET4)
Descriptor("tr($TEST_EXTENDED_PRIVKEY/$BIP86_TEST_RECEIVE_PATH/0)", Network.SIGNET)
Descriptor("tr($MAINNET_EXTENDED_PRIVKEY/$BIP86_MAINNET_RECEIVE_PATH/0)", Network.BITCOIN)
Descriptor("tr($MAIN_EXTENDED_PRIVKEY/$BIP86_MAIN_RECEIVE_PATH/0)", Network.BITCOIN)
}

@Test
fun `Create descriptors from multipath public descriptor strings`() {
Descriptor("tr($TEST_EXTENDED_PUBKEY/<0;1>/*)", Network.REGTEST)
Descriptor("tr($TEST_EXTENDED_PUBKEY/<0;1>/*)", Network.TESTNET)
Descriptor("tr($TEST_EXTENDED_PUBKEY/<0;1>/*)", Network.TESTNET4)
Descriptor("tr($TEST_EXTENDED_PUBKEY/<0;1>/*)", Network.SIGNET)
Descriptor("tr($MAIN_EXTENDED_PUBKEY/<0;1>/*)", Network.BITCOIN)
}
}


@Nested
inner class Failure {
@Test
fun `Cannot create addr() descriptor`() {
assertFails {
val descriptor: Descriptor = Descriptor(
Descriptor(
"addr(tb1qhjys9wxlfykmte7ftryptx975uqgd6kcm6a7z4)",
Network.TESTNET
Network.TESTNET4
)
}
}

@Test
fun `Descriptor cannot be created from multipath private descriptor string`() {
assertFails {
Descriptor("tr($TEST_EXTENDED_PRIVKEY/$BIP86_TEST_MULTIPATH/*)", Network.REGTEST)
}
}
}
}
8 changes: 4 additions & 4 deletions lib/src/test/kotlin/org/bitcoindevkit/MnemonicTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import kotlin.test.assertEquals
class MnemonicTest {
@Test
fun `Mnemonics create valid descriptors`() {
val mnemonic: Mnemonic = Mnemonic.fromString("space echo position wrist orient erupt relief museum myself grain wisdom tumble")
val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET, mnemonic, null)
val descriptor: Descriptor = Descriptor.newBip86(descriptorSecretKey, KeychainKind.EXTERNAL, Network.TESTNET)
val mnemonic: Mnemonic = Mnemonic.fromString(MNEMONIC_AWESOME)
val descriptorSecretKey: DescriptorSecretKey = DescriptorSecretKey(Network.TESTNET4, mnemonic, null)
val descriptor: Descriptor = Descriptor.newBip86(descriptorSecretKey, KeychainKind.EXTERNAL, Network.TESTNET4)

assertEquals(
expected = "tr([be1eec8f/86'/1'/0']tpubDCTtszwSxPx3tATqDrsSyqScPNnUChwQAVAkanuDUCJQESGBbkt68nXXKRDifYSDbeMa2Xg2euKbXaU3YphvGWftDE7ozRKPriT6vAo3xsc/0/*)#m7puekcx",
expected = "tr([5bc5d243/86'/1'/0']tpubDC72NVP1RK5qwy2QdEfWphDsUBAfBu7oiV6jEFooHP8tGQGFVUeFxhgZxuk1j6EQRJ1YsS3th2RyDgReRqCL4zqp4jtuV2z7gbiqDH2iyUS/0/*)#xh44xwsp",
actual = descriptor.toString()
)
}
Expand Down
32 changes: 26 additions & 6 deletions lib/src/test/kotlin/org/bitcoindevkit/WalletTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ class WalletTest {
@Test
fun `Wallet produces valid addresses for its network`() {
val wallet: Wallet = Wallet(
descriptor = BIP84_DESCRIPTOR,
changeDescriptor = BIP84_CHANGE_DESCRIPTOR,
network = Network.TESTNET,
descriptor = TEST_BIP84_DESCRIPTOR,
changeDescriptor = TEST_BIP84_CHANGE_DESCRIPTOR,
network = Network.TESTNET4,
persister = conn
)
val addressInfo: AddressInfo = wallet.revealNextAddress(KeychainKind.EXTERNAL)
Expand All @@ -29,9 +29,9 @@ class WalletTest {
@Test
fun `Wallet has 0 balance prior to sync`() {
val wallet: Wallet = Wallet(
descriptor = BIP84_DESCRIPTOR,
changeDescriptor = BIP84_CHANGE_DESCRIPTOR,
network = Network.TESTNET,
descriptor = TEST_BIP84_DESCRIPTOR,
changeDescriptor = TEST_BIP84_CHANGE_DESCRIPTOR,
network = Network.TESTNET4,
persister = conn
)

Expand All @@ -40,4 +40,24 @@ class WalletTest {
actual = wallet.balance().total.toSat()
)
}

// Single-descriptor wallets return an address on the external keychain even if a change descriptor is not provided
// and the wallet.revealNextAddress(KeychainKind.INTERNAL) or wallet.peekAddress(KeychainKind.EXTERNAL, 0u) APIs are
// used.
@Test
fun `Single-descriptor wallets can create addresses`() {
val wallet: Wallet = Wallet.createSingle(
descriptor = TEST_BIP84_DESCRIPTOR,
network = Network.TESTNET4,
persister = conn
)
val address1 = wallet.peekAddress(KeychainKind.EXTERNAL, 0u)
val address2 = wallet.peekAddress(KeychainKind.INTERNAL, 0u)

assertEquals(
expected = address1.address,
actual = address2.address,
message = "Addresses should be the same"
)
}
}