Skip to content

Commit e10704c

Browse files
committed
Add support for testnet4
1 parent d8de09f commit e10704c

File tree

10 files changed

+87
-37
lines changed

10 files changed

+87
-37
lines changed

src/commonMain/kotlin/fr/acinq/bitcoin/Bech32.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ public object Bech32 {
4848

4949
@JvmStatic
5050
public fun hrp(chainHash: BlockHash): String = when (chainHash) {
51-
Block.TestnetGenesisBlock.hash -> "tb"
51+
Block.Testnet4GenesisBlock.hash -> "tb"
52+
Block.Testnet3GenesisBlock.hash -> "tb"
5253
Block.SignetGenesisBlock.hash -> "tb"
5354
Block.RegtestGenesisBlock.hash -> "bcrt"
5455
Block.LivenetGenesisBlock.hash -> "bc"

src/commonMain/kotlin/fr/acinq/bitcoin/Bitcoin.kt

+10-7
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ public object Bitcoin {
116116
Script.isPay2pkh(pubkeyScript) -> {
117117
val prefix = when (chainHash) {
118118
Block.LivenetGenesisBlock.hash -> Base58.Prefix.PubkeyAddress
119-
Block.TestnetGenesisBlock.hash, Block.RegtestGenesisBlock.hash, Block.SignetGenesisBlock.hash -> Base58.Prefix.PubkeyAddressTestnet
119+
Block.Testnet4GenesisBlock.hash, Block.Testnet3GenesisBlock.hash, Block.RegtestGenesisBlock.hash, Block.SignetGenesisBlock.hash -> Base58.Prefix.PubkeyAddressTestnet
120120
else -> return Either.Left(BitcoinError.InvalidChainHash)
121121
}
122122
Either.Right(Base58Check.encode(prefix, (pubkeyScript[2] as OP_PUSHDATA).data))
@@ -125,7 +125,7 @@ public object Bitcoin {
125125
Script.isPay2sh(pubkeyScript) -> {
126126
val prefix = when (chainHash) {
127127
Block.LivenetGenesisBlock.hash -> Base58.Prefix.ScriptAddress
128-
Block.TestnetGenesisBlock.hash, Block.RegtestGenesisBlock.hash, Block.SignetGenesisBlock.hash -> Base58.Prefix.ScriptAddressTestnet
128+
Block.Testnet4GenesisBlock.hash, Block.Testnet3GenesisBlock.hash, Block.RegtestGenesisBlock.hash, Block.SignetGenesisBlock.hash -> Base58.Prefix.ScriptAddressTestnet
129129
else -> return Either.Left(BitcoinError.InvalidChainHash)
130130
}
131131
Either.Right(Base58Check.encode(prefix, (pubkeyScript[1] as OP_PUSHDATA).data))
@@ -204,13 +204,13 @@ public object Bitcoin {
204204
return runCatching { Base58Check.decode(address) }.fold(
205205
onSuccess = {
206206
when {
207-
it.first == Base58.Prefix.PubkeyAddressTestnet && (chainHash == Block.TestnetGenesisBlock.hash || chainHash == Block.RegtestGenesisBlock.hash || chainHash == Block.SignetGenesisBlock.hash) ->
207+
it.first == Base58.Prefix.PubkeyAddressTestnet && (chainHash == Block.Testnet4GenesisBlock.hash || chainHash == Block.Testnet3GenesisBlock.hash || chainHash == Block.RegtestGenesisBlock.hash || chainHash == Block.SignetGenesisBlock.hash) ->
208208
Either.Right(Script.pay2pkh(it.second))
209209

210210
it.first == Base58.Prefix.PubkeyAddress && chainHash == Block.LivenetGenesisBlock.hash ->
211211
Either.Right(Script.pay2pkh(it.second))
212212

213-
it.first == Base58.Prefix.ScriptAddressTestnet && (chainHash == Block.TestnetGenesisBlock.hash || chainHash == Block.RegtestGenesisBlock.hash || chainHash == Block.SignetGenesisBlock.hash) ->
213+
it.first == Base58.Prefix.ScriptAddressTestnet && (chainHash == Block.Testnet4GenesisBlock.hash || chainHash == Block.Testnet3GenesisBlock.hash || chainHash == Block.RegtestGenesisBlock.hash || chainHash == Block.SignetGenesisBlock.hash) ->
214214
Either.Right(listOf(OP_HASH160, OP_PUSHDATA(it.second), OP_EQUAL))
215215

216216
it.first == Base58.Prefix.ScriptAddress && chainHash == Block.LivenetGenesisBlock.hash ->
@@ -227,7 +227,8 @@ public object Bitcoin {
227227
witnessVersion == null -> Either.Left(BitcoinError.InvalidWitnessVersion(it.second.toInt()))
228228
it.third.size != 20 && it.third.size != 32 -> Either.Left(BitcoinError.InvalidBech32Address)
229229
it.first == "bc" && chainHash == Block.LivenetGenesisBlock.hash -> Either.Right(listOf(witnessVersion, OP_PUSHDATA(it.third)))
230-
it.first == "tb" && chainHash == Block.TestnetGenesisBlock.hash -> Either.Right(listOf(witnessVersion, OP_PUSHDATA(it.third)))
230+
it.first == "tb" && chainHash == Block.Testnet4GenesisBlock.hash -> Either.Right(listOf(witnessVersion, OP_PUSHDATA(it.third)))
231+
it.first == "tb" && chainHash == Block.Testnet3GenesisBlock.hash -> Either.Right(listOf(witnessVersion, OP_PUSHDATA(it.third)))
231232
it.first == "tb" && chainHash == Block.SignetGenesisBlock.hash -> Either.Right(listOf(witnessVersion, OP_PUSHDATA(it.third)))
232233
it.first == "bcrt" && chainHash == Block.RegtestGenesisBlock.hash -> Either.Right(listOf(witnessVersion, OP_PUSHDATA(it.third)))
233234
else -> Either.Left(BitcoinError.ChainHashMismatch)
@@ -244,12 +245,14 @@ public object Bitcoin {
244245

245246
public sealed class Chain(public val name: String, private val genesis: Block) {
246247
public object Regtest : Chain("Regtest", Block.RegtestGenesisBlock)
247-
public object Testnet : Chain("Testnet", Block.TestnetGenesisBlock)
248+
public object Testnet : Chain("Testnet", Block.Testnet3GenesisBlock)
249+
public object Testnet4 : Chain("Testnet4", Block.Testnet4GenesisBlock)
248250
public object Signet : Chain("Signet", Block.SignetGenesisBlock)
249251
public object Mainnet : Chain("Mainnet", Block.LivenetGenesisBlock)
250252

251253
public fun isMainnet(): Boolean = this is Mainnet
252-
public fun isTestnet(): Boolean = this is Testnet
254+
public fun isTestnet(): Boolean = this is Testnet || this is Testnet4
255+
public fun isTestnet4(): Boolean = this is Testnet4
253256

254257
public val chainHash: BlockHash get() = genesis.hash
255258

src/commonMain/kotlin/fr/acinq/bitcoin/Block.kt

+36-1
Original file line numberDiff line numberDiff line change
@@ -389,10 +389,45 @@ public data class Block(@JvmField val header: BlockHeader, @JvmField val tx: Lis
389389
}
390390

391391
@JvmField
392-
public val TestnetGenesisBlock: Block = LivenetGenesisBlock.copy(
392+
public val Testnet3GenesisBlock: Block = LivenetGenesisBlock.copy(
393393
header = LivenetGenesisBlock.header.copy(time = 1296688602, nonce = 414098458)
394394
)
395395

396+
@JvmField
397+
@Deprecated("testnet is the deprecated testnet3 network, use testnet3 explicitly", replaceWith = ReplaceWith("Testnet3GenesisBlock", "fr.acinq.bitcoin.Block"))
398+
public val TestnetGenesisBlock: Block = Testnet3GenesisBlock
399+
400+
@JvmField
401+
public val Testnet4GenesisBlock: Block = run {
402+
val script = listOf(
403+
OP_PUSHDATA(writeUInt32(486604799u)),
404+
OP_PUSHDATA(ByteVector("04")),
405+
OP_PUSHDATA("03/May/2024 000000000000000000001ebd58c244970b3aa9d783bb001011fbe8ea8e98e00e".encodeToByteArray())
406+
)
407+
val scriptPubKey = listOf(
408+
OP_PUSHDATA(ByteVector("000000000000000000000000000000000000000000000000000000000000000000")),
409+
OP_CHECKSIG
410+
)
411+
Block(
412+
BlockHeader(
413+
version = 1,
414+
hashPreviousBlock = BlockHash(ByteVector32.Zeroes),
415+
hashMerkleRoot = ByteVector32("7aa0a7ae1e223414cb807e40cd57e667b718e42aaf9306db9102fe28912b7b4e").reversed(),
416+
time = 1714777860,
417+
bits = 0x1d00ffff,
418+
nonce = 393743547
419+
),
420+
listOf(
421+
Transaction(
422+
version = 1,
423+
txIn = listOf(TxIn.coinbase(script)),
424+
txOut = listOf(TxOut(amount = 5000000000.toSatoshi(), publicKeyScript = scriptPubKey)),
425+
lockTime = 0
426+
)
427+
)
428+
)
429+
}
430+
396431
@JvmField
397432
public val RegtestGenesisBlock: Block = LivenetGenesisBlock.copy(
398433
header = LivenetGenesisBlock.header.copy(

src/commonMain/kotlin/fr/acinq/bitcoin/Descriptor.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ public object Descriptor {
6767
}
6868

6969
private fun getBIP84KeyPath(chainHash: BlockHash): Pair<String, Int> = when (chainHash) {
70-
Block.RegtestGenesisBlock.hash, Block.TestnetGenesisBlock.hash -> "84'/1'/0'/0" to DeterministicWallet.tpub
70+
Block.Testnet4GenesisBlock.hash, Block.Testnet3GenesisBlock.hash, Block.RegtestGenesisBlock.hash -> "84'/1'/0'/0" to DeterministicWallet.tpub
7171
Block.LivenetGenesisBlock.hash -> "84'/0'/0'/0" to DeterministicWallet.xpub
7272
else -> error("invalid chain hash $chainHash")
7373
}

src/commonMain/kotlin/fr/acinq/bitcoin/PublicKey.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public data class PublicKey(@JvmField val value: ByteVector) {
7777
* @return the "legacy" p2pkh address for this key
7878
*/
7979
public fun p2pkhAddress(chainHash: BlockHash): String = when (chainHash) {
80-
Block.TestnetGenesisBlock.hash, Block.RegtestGenesisBlock.hash, Block.SignetGenesisBlock.hash -> Base58Check.encode(Base58.Prefix.PubkeyAddressTestnet, hash160())
80+
Block.Testnet4GenesisBlock.hash, Block.Testnet3GenesisBlock.hash, Block.RegtestGenesisBlock.hash, Block.SignetGenesisBlock.hash -> Base58Check.encode(Base58.Prefix.PubkeyAddressTestnet, hash160())
8181
Block.LivenetGenesisBlock.hash -> Base58Check.encode(Base58.Prefix.PubkeyAddress, hash160())
8282
else -> error("invalid chain hash $chainHash")
8383
}
@@ -91,7 +91,7 @@ public data class PublicKey(@JvmField val value: ByteVector) {
9191
val script = Script.pay2wpkh(this)
9292
val hash = Crypto.hash160(Script.write(script))
9393
return when (chainHash) {
94-
Block.TestnetGenesisBlock.hash, Block.RegtestGenesisBlock.hash, Block.SignetGenesisBlock.hash -> Base58Check.encode(Base58.Prefix.ScriptAddressTestnet, hash)
94+
Block.Testnet4GenesisBlock.hash, Block.Testnet3GenesisBlock.hash, Block.RegtestGenesisBlock.hash, Block.SignetGenesisBlock.hash -> Base58Check.encode(Base58.Prefix.ScriptAddressTestnet, hash)
9595
Block.LivenetGenesisBlock.hash -> Base58Check.encode(Base58.Prefix.ScriptAddress, hash)
9696
else -> error("invalid chain hash $chainHash")
9797
}

src/commonTest/kotlin/fr/acinq/bitcoin/BIP49TestsCommon.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@ class BIP49TestsCommon {
4141
key.publicKey,
4242
PublicKey.fromHex("03a1af804ac108a8a51782198c2d034b28bf90c8803f5a53f76276fa69a4eae77f")
4343
)
44-
assertEquals(computeBIP49Address(key.publicKey, Block.TestnetGenesisBlock.hash), "2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2")
44+
assertEquals(computeBIP49Address(key.publicKey, Block.Testnet3GenesisBlock.hash), "2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2")
4545
}
4646
}

src/commonTest/kotlin/fr/acinq/bitcoin/BIP86TestsCommon.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class BIP86TestsCommon {
5656
val internalKey = XonlyPublicKey(key.publicKey)
5757
val outputKey = internalKey.outputKey(Crypto.TaprootTweak.NoScriptTweak).first
5858
assertEquals("tb1phlhs7afhqzkgv0n537xs939s687826vn8l24ldkrckvwsnlj3d7qj6u57c", Bech32.encodeWitnessAddress("tb", 1, outputKey.value.toByteArray()))
59-
assertEquals("tb1phlhs7afhqzkgv0n537xs939s687826vn8l24ldkrckvwsnlj3d7qj6u57c", internalKey.p2trAddress(Block.TestnetGenesisBlock.hash))
59+
assertEquals("tb1phlhs7afhqzkgv0n537xs939s687826vn8l24ldkrckvwsnlj3d7qj6u57c", internalKey.p2trAddress(Block.Testnet3GenesisBlock.hash))
6060
}
6161

6262
@Test
@@ -78,7 +78,7 @@ class BIP86TestsCommon {
7878
val (_, master) = DeterministicWallet.ExtendedPrivateKey.decode("tprv8ZgxMBicQKsPdyyuveRPhVYogdPXBDqRiUXDo5TcLKe3f9YfonipqbgJD7pCXdovZTfTyj6SjZ928SkPunnDTiXV7Y2HSsG9XAGki6n8dRF")
7979
for (i in 0 until 10) {
8080
val key = DeterministicWallet.derivePrivateKey(master, "86'/1'/0'/0/$i")
81-
assertEquals(expected[i], key.publicKey.p2trAddress(Block.TestnetGenesisBlock.hash))
81+
assertEquals(expected[i], key.publicKey.p2trAddress(Block.Testnet3GenesisBlock.hash))
8282
}
8383
}
8484
}

0 commit comments

Comments
 (0)