Skip to content

Commit

Permalink
Replaced string with Hex (#723)
Browse files Browse the repository at this point in the history
Refs: #713.
This PR replaces string with Hex for function arguments and return
values in order to unify library interface.
  • Loading branch information
lukasz-zimnoch authored Oct 25, 2023
2 parents 463f46c + bc4f545 commit adca21f
Show file tree
Hide file tree
Showing 36 changed files with 1,047 additions and 799 deletions.
11 changes: 6 additions & 5 deletions typescript/scripts/refund.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
DepositScript,
ElectrumClient,
ElectrumCredentials,
Hex,
} from "../src"

program
Expand Down Expand Up @@ -60,15 +61,15 @@ const depositJson = JSON.parse(fs.readFileSync(depositJsonPath, "utf-8"))

const deposit: DepositReceipt = {
depositor: depositJson.depositor,
walletPublicKeyHash: depositJson.walletPublicKeyHash,
refundPublicKeyHash: depositJson.refundPublicKeyHash,
blindingFactor: depositJson.blindingFactor,
refundLocktime: depositJson.refundLocktime,
walletPublicKeyHash: Hex.from(depositJson.walletPublicKeyHash),
refundPublicKeyHash: Hex.from(depositJson.refundPublicKeyHash),
blindingFactor: Hex.from(depositJson.blindingFactor),
refundLocktime: Hex.from(depositJson.refundLocktime),
}
const recoveryAddress = depositJson.btcRecoveryAddress

console.log("======= refund provided data ========")
console.log("deposit JSON: ", deposit)
console.log("deposit JSON: ", depositJson)
console.log("deposit recovery amount: ", refundAmount)
console.log("deposit transaction ID: ", transactionId)
console.log("deposit transaction index: ", transactionIndex)
Expand Down
3 changes: 0 additions & 3 deletions typescript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,3 @@ export * from "./services/redemptions"

// Export the entrypoint module.
export * from "./services/tbtc"

// TODO: Replace all properties that are expected to be un-prefixed hexadecimal
// strings with a Hex type to increase API consistency.
25 changes: 11 additions & 14 deletions typescript/src/lib/bitcoin/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,19 @@ function publicKeyToAddress(

/**
* Converts a public key hash into a P2PKH/P2WPKH address.
* @param publicKeyHash Public key hash that will be encoded. Must be an
* unprefixed hex string (without 0x prefix).
* @param publicKeyHash Public key hash that will be encoded.
* @param witness If true, a witness public key hash will be encoded and
* P2WPKH address will be returned. Returns P2PKH address otherwise
* @param bitcoinNetwork Network the address should be encoded for.
* @returns P2PKH or P2WPKH address encoded from the given public key hash
* @returns P2PKH or P2WPKH address encoded from the given public key hash.
* @throws Throws an error if network is not supported.
*/
function publicKeyHashToAddress(
publicKeyHash: string,
publicKeyHash: Hex,
witness: boolean,
bitcoinNetwork: BitcoinNetwork
): string {
const hash = Buffer.from(publicKeyHash, "hex")
const hash = publicKeyHash.toBuffer()
const network = toBitcoinJsLibNetwork(bitcoinNetwork)
return witness
? payments.p2wpkh({ hash, network }).address!
Expand All @@ -54,35 +53,33 @@ function publicKeyHashToAddress(
* provided address is not PKH-based.
* @param address P2PKH or P2WPKH address that will be decoded.
* @param bitcoinNetwork Network the address should be decoded for.
* @returns Public key hash decoded from the address. This will be an unprefixed
* hex string (without 0x prefix).
* @returns Public key hash decoded from the address.
*/
function addressToPublicKeyHash(
address: string,
bitcoinNetwork: BitcoinNetwork
): string {
): Hex {
const network = toBitcoinJsLibNetwork(bitcoinNetwork)

try {
// Try extracting hash from P2PKH address.
const hash = payments.p2pkh({ address: address, network }).hash!
return hash.toString("hex")
return Hex.from(payments.p2pkh({ address: address, network }).hash!)
} catch (err) {}

try {
// Try extracting hash from P2WPKH address.
const hash = payments.p2wpkh({ address: address, network }).hash!
return hash.toString("hex")
return Hex.from(payments.p2wpkh({ address: address, network }).hash!)
} catch (err) {}

// If neither of them succeeded, throw an error.
throw new Error("Address must be P2PKH or P2WPKH valid for given network")
}

/**
* Converts an address to the respective output script.
* @param address BTC address.
* @param bitcoinNetwork Bitcoin network corresponding to the address.
* @returns The un-prefixed and not prepended with length output script.
* @returns The output script not prepended with length.
*/
function addressToOutputScript(
address: string,
Expand All @@ -95,7 +92,7 @@ function addressToOutputScript(

/**
* Converts an output script to the respective network-specific address.
* @param script The unprefixed and not prepended with length output script.
* @param script The output script not prepended with length.
* @param bitcoinNetwork Bitcoin network the address should be produced for.
* @returns The Bitcoin address.
*/
Expand Down
3 changes: 2 additions & 1 deletion typescript/src/lib/bitcoin/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BitcoinNetwork } from "./network"
import { BitcoinRawTx, BitcoinTx, BitcoinTxHash, BitcoinUtxo } from "./tx"
import { BitcoinTxMerkleBranch } from "./spv"
import { Hex } from "../../lib/utils"

/**
* Represents a Bitcoin client.
Expand Down Expand Up @@ -66,7 +67,7 @@ export interface BitcoinClient {
* block.
* @return Concatenation of block headers in a hexadecimal format.
*/
getHeadersChain(blockHeight: number, chainLength: number): Promise<string>
getHeadersChain(blockHeight: number, chainLength: number): Promise<Hex>

/**
* Get Merkle branch for a given transaction.
Expand Down
27 changes: 13 additions & 14 deletions typescript/src/lib/bitcoin/ecdsa-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,36 @@ import { BitcoinNetwork, toBitcoinJsLibNetwork } from "./network"
* @param publicKey - Public key that should be checked.
* @returns True if the key is a compressed Bitcoin public key, false otherwise.
*/
function isCompressedPublicKey(publicKey: string): boolean {
function isCompressedPublicKey(publicKey: Hex): boolean {
const publicKeyStr = publicKey.toString()

// Must have 33 bytes and 02 or 03 prefix.
return (
publicKey.length == 66 &&
(publicKey.substring(0, 2) == "02" || publicKey.substring(0, 2) == "03")
publicKeyStr.length == 66 &&
(publicKeyStr.substring(0, 2) == "02" ||
publicKeyStr.substring(0, 2) == "03")
)
}

/**
* Compresses the given uncompressed Bitcoin public key.
* @param publicKey Uncompressed 64-byte public key as an unprefixed hex string.
* @param publicKey Uncompressed 64-byte public key.
* @returns Compressed 33-byte public key prefixed with 02 or 03.
*/
function compressPublicKey(publicKey: string | Hex): string {
if (typeof publicKey === "string") {
publicKey = Hex.from(publicKey)
}

publicKey = publicKey.toString()
function compressPublicKey(publicKey: Hex): string {
const publicKeyStr = publicKey.toString()

// Must have 64 bytes and no prefix.
if (publicKey.length != 128) {
if (publicKeyStr.length != 128) {
throw new Error(
"The public key parameter must be 64-byte. Neither 0x nor 04 prefix is allowed"
)
}

// The X coordinate is the first 32 bytes.
const publicKeyX = publicKey.substring(0, 64)
const publicKeyX = publicKeyStr.substring(0, 64)
// The Y coordinate is the next 32 bytes.
const publicKeyY = publicKey.substring(64)
const publicKeyY = publicKeyStr.substring(64)

const prefix = BigNumber.from(`0x${publicKeyY}`).mod(2).eq(0) ? "02" : "03"

Expand All @@ -59,7 +58,7 @@ export const BitcoinPublicKeyUtils = {
* @param privateKey Private key that should be used to create the key pair.
* Should be passed in the WIF format.
* @param bitcoinNetwork Bitcoin network the given key pair is relevant for.
* @returns Bitcoin key ring.
* @returns Bitcoin key pair.
*/
function createKeyPair(
privateKey: string,
Expand Down
14 changes: 6 additions & 8 deletions typescript/src/lib/bitcoin/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,19 @@ import { Hex } from "../utils"
/**
* Computes the HASH160 (i.e. RIPEMD160(SHA256(text))) for the given text.
* @param text Text the HASH160 is computed for.
* @returns Hash as a 20-byte un-prefixed hex string.
* @returns 20-byte-long hash.
*/
function computeHash160(text: string): string {
const sha256Hash = utils.sha256(
Hex.from(Buffer.from(text, "hex")).toPrefixedString()
)
function computeHash160(text: Hex): Hex {
const sha256Hash = utils.sha256(text.toPrefixedString())
const hash160 = utils.ripemd160(sha256Hash)

return Hex.from(hash160).toString()
return Hex.from(hash160)
}

/**
* Computes the double SHA256 for the given text.
* @param text Text the double SHA256 is computed for.
* @returns Hash as a 32-byte un-prefixed hex string.
* @returns 32-byte-long hash.
* @dev Do not confuse it with computeSha256 which computes single SHA256.
*/
function computeHash256(text: Hex): Hex {
Expand All @@ -40,7 +38,7 @@ function hashLEToBigNumber(hash: Hex): BigNumber {
/**
* Computes the single SHA256 for the given text.
* @param text Text the single SHA256 is computed for.
* @returns Hash as a 32-byte un-prefixed hex string.
* @returns 32-byte-long hash.
* @dev Do not confuse it with computeHash256 which computes double SHA256.
*/
function computeSha256(text: Hex): Hex {
Expand Down
11 changes: 5 additions & 6 deletions typescript/src/lib/bitcoin/header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,15 @@ function deserializeHeader(rawHeader: Hex): BitcoinHeader {
* @param rawHeadersChain - Raw Bitcoin block headers chain.
* @returns Deserialized Bitcoin block headers.
*/
function deserializeHeadersChain(rawHeadersChain: string): BitcoinHeader[] {
if (rawHeadersChain.length % 160 !== 0) {
function deserializeHeadersChain(rawHeadersChain: Hex): BitcoinHeader[] {
const headersChain = rawHeadersChain.toString()
if (headersChain.length % 160 !== 0) {
throw new Error("Incorrect length of Bitcoin headers")
}

const result: BitcoinHeader[] = []
for (let i = 0; i < rawHeadersChain.length; i += 160) {
result.push(
deserializeHeader(Hex.from(rawHeadersChain.substring(i, i + 160)))
)
for (let i = 0; i < headersChain.length; i += 160) {
result.push(deserializeHeader(Hex.from(headersChain.substring(i, i + 160))))
}

return result
Expand Down
17 changes: 9 additions & 8 deletions typescript/src/lib/bitcoin/script.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Hex } from "../utils"
import { payments } from "bitcoinjs-lib"

/**
* Checks if the provided script comes from a P2PKH input.
* @param script The script to be checked.
* @returns True if the script is P2PKH, false otherwise.
*/
function isP2PKHScript(script: Buffer): boolean {
function isP2PKHScript(script: Hex): boolean {
try {
payments.p2pkh({ output: script })
payments.p2pkh({ output: script.toBuffer() })
return true
} catch (err) {
return false
Expand All @@ -19,9 +20,9 @@ function isP2PKHScript(script: Buffer): boolean {
* @param script The script to be checked.
* @returns True if the script is P2WPKH, false otherwise.
*/
function isP2WPKHScript(script: Buffer): boolean {
function isP2WPKHScript(script: Hex): boolean {
try {
payments.p2wpkh({ output: script })
payments.p2wpkh({ output: script.toBuffer() })
return true
} catch (err) {
return false
Expand All @@ -33,9 +34,9 @@ function isP2WPKHScript(script: Buffer): boolean {
* @param script The script to be checked.
* @returns True if the script is P2SH, false otherwise.
*/
function isP2SHScript(script: Buffer): boolean {
function isP2SHScript(script: Hex): boolean {
try {
payments.p2sh({ output: script })
payments.p2sh({ output: script.toBuffer() })
return true
} catch (err) {
return false
Expand All @@ -47,9 +48,9 @@ function isP2SHScript(script: Buffer): boolean {
* @param script The script to be checked.
* @returns True if the script is P2WSH, false otherwise.
*/
function isP2WSHScript(script: Buffer): boolean {
function isP2WSHScript(script: Hex): boolean {
try {
payments.p2wsh({ output: script })
payments.p2wsh({ output: script.toBuffer() })
return true
} catch (err) {
return false
Expand Down
33 changes: 17 additions & 16 deletions typescript/src/lib/bitcoin/spv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,20 @@ import { BitcoinHashUtils } from "./hash"
*/
export interface BitcoinSpvProof {
/**
* The merkle proof of transaction inclusion in a block, as an un-prefixed
* hex string.
* The merkle proof of transaction inclusion in a block.
*/
merkleProof: string
merkleProof: Hex

/**
* Transaction index in the block (0-indexed).
*/
txIndexInBlock: number

/**
* Single byte-string of 80-byte block headers, lowest height first, as an
* un-prefixed hex string.
* Concatenated block headers in hexadecimal format. Each block header is
* 80-byte-long. The block header with the lowest height is first.
*/
bitcoinHeaders: string
bitcoinHeaders: Hex
}

/**
Expand All @@ -46,7 +45,7 @@ export interface BitcoinTxMerkleBranch {
* in order to trace up to obtain the merkle root of the including block,
* the deepest pairing first. Each hash is an unprefixed hex string.
*/
merkle: string[]
merkle: Hex[]

/**
* The 0-based index of the transaction's position in the block.
Expand Down Expand Up @@ -117,12 +116,12 @@ export async function assembleBitcoinSpvProof(
* @param txMerkleBranch - Branch of a Merkle tree leading to a transaction.
* @returns Transaction inclusion proof in hexadecimal form.
*/
function createMerkleProof(txMerkleBranch: BitcoinTxMerkleBranch): string {
function createMerkleProof(txMerkleBranch: BitcoinTxMerkleBranch): Hex {
let proof = Buffer.from("")
txMerkleBranch.merkle.forEach(function (item) {
proof = Buffer.concat([proof, Buffer.from(item, "hex").reverse()])
proof = Buffer.concat([proof, item.toBuffer().reverse()])
})
return proof.toString("hex")
return Hex.from(proof)
}

/**
Expand Down Expand Up @@ -315,19 +314,21 @@ function validateMerkleTreeHashes(
}

/**
* Splits a given Merkle proof string into an array of intermediate node hashes.
* @param merkleProof A string representation of the Merkle proof.
* Splits a given concatenated Merkle proof into an array of intermediate node
* hashes.
* @param merkleProof A concatenated representation of the Merkle proof.
* @returns An array of intermediate node hashes.
* @throws {Error} If the length of the Merkle proof is not a multiple of 64.
*/
function splitMerkleProof(merkleProof: string): Hex[] {
if (merkleProof.length % 64 != 0) {
function splitMerkleProof(merkleProof: Hex): Hex[] {
const merkleProofStr = merkleProof.toString()
if (merkleProofStr.length % 64 != 0) {
throw new Error("Incorrect length of Merkle proof")
}

const intermediateNodeHashes: Hex[] = []
for (let i = 0; i < merkleProof.length; i += 64) {
intermediateNodeHashes.push(Hex.from(merkleProof.slice(i, i + 64)))
for (let i = 0; i < merkleProofStr.length; i += 64) {
intermediateNodeHashes.push(Hex.from(merkleProofStr.slice(i, i + 64)))
}

return intermediateNodeHashes
Expand Down
Loading

0 comments on commit adca21f

Please sign in to comment.