Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add member methods to improve usability #132

Merged
merged 2 commits into from
Aug 20, 2024
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
42 changes: 32 additions & 10 deletions src/commonMain/kotlin/fr/acinq/bitcoin/Block.kt
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,28 @@ public data class BlockHeader(

public fun setNonce(input: Long): BlockHeader = this.copy(nonce = input)

public fun difficulty(): UInt256 {
val (diff, neg, _) = UInt256.decodeCompact(bits)
return if (neg) -diff else diff
}

/**
*
* @return the amount of work represented by this block's difficulty target, as displayed by bitcoin core
*/
public fun blockProof(): UInt256 = blockProof(bits)

/**
* Proof of work: hash(header) <= target difficulty
*
* @return true if this block header validates its expected proof of work
*/
public fun checkProofOfWork(): Boolean {
val (target, _, _) = UInt256.decodeCompact(bits)
val hash = UInt256(blockId.value.toByteArray())
return hash <= target
}

@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
public companion object : BtcSerializer<BlockHeader>() {
override fun read(input: Input, protocolVersion: Long): BlockHeader {
Expand Down Expand Up @@ -120,10 +142,7 @@ public data class BlockHeader(
}

@JvmStatic
public fun getDifficulty(header: BlockHeader): UInt256 {
val (diff, neg, _) = UInt256.decodeCompact(header.bits)
return if (neg) -diff else diff
}
public fun getDifficulty(header: BlockHeader): UInt256 = header.difficulty()

/**
*
Expand Down Expand Up @@ -152,11 +171,7 @@ public data class BlockHeader(
* @return true if the input block header validates its expected proof of work
*/
@JvmStatic
public fun checkProofOfWork(header: BlockHeader): Boolean {
val (target, _, _) = UInt256.decodeCompact(header.bits)
val hash = UInt256(header.blockId.value.toByteArray())
return hash <= target
}
public fun checkProofOfWork(header: BlockHeader): Boolean = header.checkProofOfWork()

@JvmStatic
public fun calculateNextWorkRequired(lastHeader: BlockHeader, lastRetargetTime: Long): Long {
Expand Down Expand Up @@ -208,6 +223,13 @@ public data class Block(@JvmField val header: BlockHeader, @JvmField val tx: Lis
@JvmField
val blockId: BlockId = header.blockId

/**
* Proof of work: hash(block) <= target difficulty
*
* @return true if the input block validates its expected proof of work
*/
public fun checkProofOfWork(): Boolean = BlockHeader.checkProofOfWork(header)

public companion object : BtcSerializer<Block>() {
override fun write(message: Block, out: Output, protocolVersion: Long) {
BlockHeader.write(message.header, out)
Expand Down Expand Up @@ -250,7 +272,7 @@ public data class Block(@JvmField val header: BlockHeader, @JvmField val tx: Lis
* @return true if the input block validates its expected proof of work
*/
@JvmStatic
public fun checkProofOfWork(block: Block): Boolean = BlockHeader.checkProofOfWork(block.header)
public fun checkProofOfWork(block: Block): Boolean = block.checkProofOfWork()

/**
* Verify a tx inclusion proof (a merkle proof that a set of transactions are included in a given block)
Expand Down
196 changes: 113 additions & 83 deletions src/commonMain/kotlin/fr/acinq/bitcoin/DeterministicWallet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,56 @@ public object DeterministicWallet {

val privateKey: PrivateKey get() = PrivateKey(secretkeybytes)
val publicKey: PublicKey get() = privateKey.publicKey()
val extendedPublicKey: ExtendedPublicKey get() = ExtendedPublicKey(publicKey.value, chaincode, depth = depth, path = path, parent = parent)
t-bast marked this conversation as resolved.
Show resolved Hide resolved

/**
* @param index index of the child key
* @return the derived private key at the specified index
*/
public fun derivePrivateKey(index: Long): ExtendedPrivateKey {
val I = if (isHardened(index)) {
val data = arrayOf(0.toByte()).toByteArray() + secretkeybytes.toByteArray() + Pack.writeInt32BE(index.toInt())
Crypto.hmac512(chaincode.toByteArray(), data)
} else {
val data = extendedPublicKey.publickeybytes.toByteArray() + Pack.writeInt32BE(index.toInt())
Crypto.hmac512(chaincode.toByteArray(), data)
}
val IL = I.take(32).toByteArray()
val IR = I.takeLast(32).toByteArray()
require(Crypto.isPrivKeyValid(IL)) { "cannot generate child private key: IL is invalid" }

val key = PrivateKey(IL) + privateKey
require(Crypto.isPrivKeyValid(key.value.toByteArray())) { "cannot generate child private key: resulting private key is invalid" }
return ExtendedPrivateKey(
secretkeybytes = key.value,
chaincode = IR.byteVector32(),
depth = depth + 1,
path = path.derive(index),
parent = fingerprint()
)
}

public fun derivePrivateKey(chain: List<Long>): ExtendedPrivateKey = chain.fold(this) { k, i -> k.derivePrivateKey(i) }

public fun derivePrivateKey(keyPath: KeyPath): ExtendedPrivateKey = derivePrivateKey(keyPath.path)

public fun derivePrivateKey(keyPath: String): ExtendedPrivateKey = derivePrivateKey(KeyPath.fromPath(keyPath))

public fun encode(testnet: Boolean): String = this.encode(if (testnet) tprv else xprv)

public fun encode(prefix: Int): String {
val out = ByteArrayOutput()
out.write(depth)
Pack.writeInt32BE(parent.toInt(), out)
Pack.writeInt32BE(path.lastChildNumber.toInt(), out)
out.write(chaincode.toByteArray())
out.write(0)
out.write(secretkeybytes.toByteArray())
val buffer = out.toByteArray()
return Base58Check.encode(prefix, buffer)
}

public fun fingerprint(): Long = extendedPublicKey.fingerprint()

/**
* We avoid accidentally logging extended private keys.
Expand Down Expand Up @@ -93,6 +143,56 @@ public object DeterministicWallet {

val publicKey: PublicKey get() = PublicKey(publickeybytes)

/**
* @param index index of the child key
* @return the derived public key at the specified index
*/
public fun derivePublicKey(index: Long): ExtendedPublicKey {
require(!isHardened(index)) { "Cannot derive public keys from public hardened keys" }

val I = Crypto.hmac512(
chaincode.toByteArray(), publickeybytes.toByteArray() + Pack.writeInt32BE(index.toInt())
)
val IL = I.take(32).toByteArray()
val IR = I.takeLast(32).toByteArray()
require(Crypto.isPrivKeyValid(IL)) { "cannot generate child public key: IL is invalid" }

val Ki = PrivateKey(IL).publicKey() + publicKey
require(Crypto.isPubKeyValid(Ki.value.toByteArray())) { "cannot generate child public key: resulting public key is invalid" }
return ExtendedPublicKey(
publickeybytes = Ki.value,
chaincode = IR.byteVector32(),
depth = depth + 1,
path = path.derive(index),
parent = fingerprint()
)
}

public fun derivePublicKey(chain: List<Long>): ExtendedPublicKey = chain.fold(this) { k, i -> k.derivePublicKey(i) }

public fun derivePublicKey(keyPath: KeyPath): ExtendedPublicKey = derivePublicKey(keyPath.path)

public fun derivePublicKey(keyPath: String): ExtendedPublicKey = derivePublicKey(KeyPath.fromPath(keyPath))

public fun fingerprint(): Long = Pack.int32LE(ByteArrayInput(Crypto.hash160(publickeybytes).take(4).reversed().toByteArray())).toLong()

public fun encode(prefix: Int): String {
val out = ByteArrayOutput()
this.write(out)
val buffer = out.toByteArray()
return Base58Check.encode(prefix, buffer)
}

public fun encode(testnet: Boolean): String = encode(if (testnet) tpub else xpub)

public fun write(out: Output) {
out.write(depth)
Pack.writeInt32BE(parent.toInt(), out)
Pack.writeInt32BE(path.lastChildNumber.toInt(), out)
out.write(chaincode.toByteArray())
out.write(publickeybytes.toByteArray())
}

public companion object {
@JvmStatic
public fun decode(input: String, parentPath: KeyPath = KeyPath.empty): Pair<Int, ExtendedPublicKey> {
Expand All @@ -115,37 +215,16 @@ public object DeterministicWallet {
public fun encode(input: ExtendedPrivateKey, testnet: Boolean): String = encode(input, if (testnet) tprv else xprv)

@JvmStatic
public fun encode(input: ExtendedPrivateKey, prefix: Int): String {
val out = ByteArrayOutput()
out.write(input.depth)
Pack.writeInt32BE(input.parent.toInt(), out)
Pack.writeInt32BE(input.path.lastChildNumber.toInt(), out)
out.write(input.chaincode.toByteArray())
out.write(0)
out.write(input.secretkeybytes.toByteArray())
val buffer = out.toByteArray()
return Base58Check.encode(prefix, buffer)
}
public fun encode(input: ExtendedPrivateKey, prefix: Int): String = input.encode(prefix)

@JvmStatic
public fun encode(input: ExtendedPublicKey, testnet: Boolean): String = encode(input, if (testnet) tpub else xpub)

@JvmStatic
public fun encode(input: ExtendedPublicKey, prefix: Int): String {
val out = ByteArrayOutput()
write(input, out)
val buffer = out.toByteArray()
return Base58Check.encode(prefix, buffer)
}
public fun encode(input: ExtendedPublicKey, prefix: Int): String = input.encode(prefix)

@JvmStatic
public fun write(input: ExtendedPublicKey, out: Output) {
out.write(input.depth)
Pack.writeInt32BE(input.parent.toInt(), out)
Pack.writeInt32BE(input.path.lastChildNumber.toInt(), out)
out.write(input.chaincode.toByteArray())
out.write(input.publickeybytes.toByteArray())
}
public fun write(input: ExtendedPublicKey, out: Output): Unit = input.write(out)

/**
* @param seed random seed
Expand All @@ -171,16 +250,14 @@ public object DeterministicWallet {
* @return the public key for this private key
*/
@JvmStatic
public fun publicKey(input: ExtendedPrivateKey): ExtendedPublicKey {
return ExtendedPublicKey(input.publicKey.value, input.chaincode, depth = input.depth, path = input.path, parent = input.parent)
}
public fun publicKey(input: ExtendedPrivateKey): ExtendedPublicKey = input.extendedPublicKey

/**
* @param input extended public key
* @return the fingerprint for this public key
*/
@JvmStatic
public fun fingerprint(input: ExtendedPublicKey): Long = Pack.int32LE(ByteArrayInput(Crypto.hash160(input.publickeybytes).take(4).reversed().toByteArray())).toLong()
public fun fingerprint(input: ExtendedPublicKey): Long = input.fingerprint()

/**
* @param input extended private key
Expand All @@ -195,80 +272,33 @@ public object DeterministicWallet {
* @return the derived private key at the specified index
*/
@JvmStatic
public fun derivePrivateKey(parent: ExtendedPrivateKey, index: Long): ExtendedPrivateKey {
val I = if (isHardened(index)) {
val data = arrayOf(0.toByte()).toByteArray() + parent.secretkeybytes.toByteArray() + Pack.writeInt32BE(index.toInt())
Crypto.hmac512(parent.chaincode.toByteArray(), data)
} else {
val data = publicKey(parent).publickeybytes.toByteArray() + Pack.writeInt32BE(index.toInt())
Crypto.hmac512(parent.chaincode.toByteArray(), data)
}
val IL = I.take(32).toByteArray()
val IR = I.takeLast(32).toByteArray()
require(Crypto.isPrivKeyValid(IL)) { "cannot generate child private key: IL is invalid" }

val key = PrivateKey(IL) + parent.privateKey
require(Crypto.isPrivKeyValid(key.value.toByteArray())) { "cannot generate child private key: resulting private key is invalid" }
return ExtendedPrivateKey(
secretkeybytes = key.value,
chaincode = IR.byteVector32(),
depth = parent.depth + 1,
path = parent.path.derive(index),
parent = fingerprint(parent)
)
}
public fun derivePrivateKey(parent: ExtendedPrivateKey, index: Long): ExtendedPrivateKey = parent.derivePrivateKey(index)

/**
* @param parent extended public key
* @param index index of the child key
* @return the derived public key at the specified index
*/
@JvmStatic
public fun derivePublicKey(parent: ExtendedPublicKey, index: Long): ExtendedPublicKey {
require(!isHardened(index)) { "Cannot derive public keys from public hardened keys" }

val I = Crypto.hmac512(
parent.chaincode.toByteArray(),
parent.publickeybytes.toByteArray() + Pack.writeInt32BE(index.toInt())
)
val IL = I.take(32).toByteArray()
val IR = I.takeLast(32).toByteArray()
require(Crypto.isPrivKeyValid(IL)) { "cannot generate child public key: IL is invalid" }

val Ki = PrivateKey(IL).publicKey() + parent.publicKey
require(Crypto.isPubKeyValid(Ki.value.toByteArray())) { "cannot generate child public key: resulting public key is invalid" }
return ExtendedPublicKey(
publickeybytes = Ki.value,
chaincode = IR.byteVector32(),
depth = parent.depth + 1,
path = parent.path.derive(index),
parent = fingerprint(parent)
)
}
public fun derivePublicKey(parent: ExtendedPublicKey, index: Long): ExtendedPublicKey = parent.derivePublicKey(index)

@JvmStatic
public fun derivePrivateKey(parent: ExtendedPrivateKey, chain: List<Long>): ExtendedPrivateKey =
chain.fold(parent, DeterministicWallet::derivePrivateKey)
public fun derivePrivateKey(parent: ExtendedPrivateKey, chain: List<Long>): ExtendedPrivateKey = parent.derivePrivateKey(chain)

@JvmStatic
public fun derivePrivateKey(parent: ExtendedPrivateKey, keyPath: KeyPath): ExtendedPrivateKey =
derivePrivateKey(parent, keyPath.path)
public fun derivePrivateKey(parent: ExtendedPrivateKey, keyPath: KeyPath): ExtendedPrivateKey = parent.derivePrivateKey(keyPath.path)

@JvmStatic
public fun derivePrivateKey(parent: ExtendedPrivateKey, keyPath: String): ExtendedPrivateKey =
derivePrivateKey(parent, KeyPath.fromPath(keyPath))
public fun derivePrivateKey(parent: ExtendedPrivateKey, keyPath: String): ExtendedPrivateKey = parent.derivePrivateKey(KeyPath.fromPath(keyPath))

@JvmStatic
public fun derivePublicKey(parent: ExtendedPublicKey, chain: List<Long>): ExtendedPublicKey =
chain.fold(parent, DeterministicWallet::derivePublicKey)
public fun derivePublicKey(parent: ExtendedPublicKey, chain: List<Long>): ExtendedPublicKey = parent.derivePublicKey(chain)

@JvmStatic
public fun derivePublicKey(parent: ExtendedPublicKey, keyPath: KeyPath): ExtendedPublicKey =
derivePublicKey(parent, keyPath.path)
public fun derivePublicKey(parent: ExtendedPublicKey, keyPath: KeyPath): ExtendedPublicKey = parent.derivePublicKey(keyPath.path)

@JvmStatic
public fun derivePublicKey(parent: ExtendedPublicKey, keyPath: String): ExtendedPublicKey =
derivePublicKey(parent, KeyPath.fromPath(keyPath))
public fun derivePublicKey(parent: ExtendedPublicKey, keyPath: String): ExtendedPublicKey = parent.derivePublicKey(KeyPath.fromPath(keyPath))

// p2pkh mainnet
public const val xprv: Int = 0x0488ade4
Expand Down
Loading
Loading