diff --git a/.github/workflows/test-bdk-ffi-latest.yaml b/.github/workflows/test-bdk-ffi-latest.yaml index 1cc645c..2df71fb 100644 --- a/.github/workflows/test-bdk-ffi-latest.yaml +++ b/.github/workflows/test-bdk-ffi-latest.yaml @@ -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 @@ -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 diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index cc391d1..67779e2 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -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 diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 8f66ff1..546245a 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -27,22 +27,6 @@ tasks.withType { } } -// 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) { diff --git a/lib/src/test/kotlin/org/bitcoindevkit/Constants.kt b/lib/src/test/kotlin/org/bitcoindevkit/Constants.kt index 2db235e..ea8cf4e 100644 --- a/lib/src/test/kotlin/org/bitcoindevkit/Constants.kt +++ b/lib/src/test/kotlin/org/bitcoindevkit/Constants.kt @@ -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) diff --git a/lib/src/test/kotlin/org/bitcoindevkit/CreatingWalletTest.kt b/lib/src/test/kotlin/org/bitcoindevkit/CreatingWalletTest.kt index addbb81..24b3283 100644 --- a/lib/src/test/kotlin/org/bitcoindevkit/CreatingWalletTest.kt +++ b/lib/src/test/kotlin/org/bitcoindevkit/CreatingWalletTest.kt @@ -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 ) } @@ -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 + ) + } + } } } diff --git a/lib/src/test/kotlin/org/bitcoindevkit/DescriptorTest.kt b/lib/src/test/kotlin/org/bitcoindevkit/DescriptorTest.kt index d5b4142..74df37c 100644 --- a/lib/src/test/kotlin/org/bitcoindevkit/DescriptorTest.kt +++ b/lib/src/test/kotlin/org/bitcoindevkit/DescriptorTest.kt @@ -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 @@ -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 @@ -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) + } + } } } diff --git a/lib/src/test/kotlin/org/bitcoindevkit/MnemonicTest.kt b/lib/src/test/kotlin/org/bitcoindevkit/MnemonicTest.kt index 28b947f..c95af9a 100644 --- a/lib/src/test/kotlin/org/bitcoindevkit/MnemonicTest.kt +++ b/lib/src/test/kotlin/org/bitcoindevkit/MnemonicTest.kt @@ -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() ) } diff --git a/lib/src/test/kotlin/org/bitcoindevkit/WalletTest.kt b/lib/src/test/kotlin/org/bitcoindevkit/WalletTest.kt index ba15aae..636ef8a 100644 --- a/lib/src/test/kotlin/org/bitcoindevkit/WalletTest.kt +++ b/lib/src/test/kotlin/org/bitcoindevkit/WalletTest.kt @@ -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) @@ -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 ) @@ -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" + ) + } }