Skip to content

Commit

Permalink
feat: use new multiformats CID interface and exports
Browse files Browse the repository at this point in the history
  • Loading branch information
rvagg committed Jun 6, 2022
1 parent 54ad017 commit 26e0dbb
Show file tree
Hide file tree
Showing 18 changed files with 124 additions and 89 deletions.
16 changes: 8 additions & 8 deletions src/bitcoin-block.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BitcoinBlock, fromHashHex } from 'bitcoin-block'
import { CID, bytes } from 'multiformats'
import { bytes } from 'multiformats'
import { create as createCID } from 'multiformats/cid'
import * as dblSha2256 from './dbl-sha2-256.js'
import { CODEC_BLOCK, CODEC_BLOCK_CODE, CODEC_TX_CODE } from './constants.js'

Expand All @@ -8,9 +9,8 @@ import { CODEC_BLOCK, CODEC_BLOCK_CODE, CODEC_TX_CODE } from './constants.js'
* @typedef {import('multiformats/codecs/interface').ByteView<T>} ByteView
*/

/**
* @typedef {import('./interface').BitcoinHeader} BitcoinHeader
*/
/** @typedef {import('./interface').BitcoinHeader} BitcoinHeader */
/** @typedef {import('./interface').BitcoinBlockCID} BitcoinBlockCID */

/**
* **`bitcoin-block` / `0xb0` codec**: Encodes an IPLD node representing a
Expand Down Expand Up @@ -46,13 +46,13 @@ export function decode (data) {
// insert links derived from native hash hex strings
if (deserialized.previousblockhash) {
const parentDigest = dblSha2256.digestFrom(fromHashHex(deserialized.previousblockhash))
deserialized.parent = CID.create(1, CODEC_BLOCK_CODE, parentDigest)
deserialized.parent = createCID(1, CODEC_BLOCK_CODE, parentDigest)
} else {
// genesis
deserialized.parent = null
}
const txDigest = dblSha2256.digestFrom(fromHashHex(deserialized.merkleroot))
deserialized.tx = CID.create(1, CODEC_TX_CODE, txDigest)
deserialized.tx = createCID(1, CODEC_TX_CODE, txDigest)

return deserialized
}
Expand All @@ -74,13 +74,13 @@ export const code = CODEC_BLOCK_CODE
* The process of converting to a CID involves reversing the hash (to little-endian form), encoding as a `dbl-sha2-256` multihash and encoding as a `bitcoin-block` multicodec. This process is reversable, see {@link cidToHash}.
*
* @param {string} blockHash a string form of a block hash
* @returns {CID} a CID object representing this block identifier.
* @returns {BitcoinBlockCID} a CID object representing this block identifier.
* @name BitcoinBlock.blockHashToCID()
*/
export function blockHashToCID (blockHash) {
if (typeof blockHash !== 'string') {
blockHash = bytes.toHex(blockHash)
}
const digest = dblSha2256.digestFrom(fromHashHex(blockHash))
return CID.create(1, CODEC_BLOCK_CODE, digest)
return createCID(1, CODEC_BLOCK_CODE, digest)
}
30 changes: 16 additions & 14 deletions src/bitcoin-tx.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { BitcoinTransaction as BitcoinBlockTransaction, fromHashHex, merkle } from 'bitcoin-block'
import { CID, bytes } from 'multiformats'
import { bytes } from 'multiformats'
import { create as createCID, asCID } from 'multiformats/cid'
import * as dblSha2256 from './dbl-sha2-256.js'
import { CODEC_TX, CODEC_TX_CODE, CODEC_WITNESS_COMMITMENT_CODE } from './constants.js'

Expand All @@ -11,6 +12,7 @@ import { CODEC_TX, CODEC_TX_CODE, CODEC_WITNESS_COMMITMENT_CODE } from './consta
/** @typedef {import('bitcoin-block/interface').BlockPorcelain} BlockPorcelain */
/** @typedef {import('./interface').BitcoinTransaction} BitcoinTransaction */
/** @typedef {import('./interface').BitcoinTransactionMerkleNode} BitcoinTransactionMerkleNode */
/** @typedef {import('./interface').BitcoinTxCID} BitcoinTxCID */

/** @ignore */
const NULL_HASH = new Uint8Array(32)
Expand Down Expand Up @@ -46,11 +48,11 @@ function _encode (node, noWitness) {
export function encode (node) {
if (Array.isArray(node)) {
const bytes = new Uint8Array(64)
const leftCid = CID.asCID(node[0])
const leftCid = asCID(node[0])
if (leftCid != null) {
bytes.set(leftCid.multihash.digest)
}
const rightCid = CID.asCID(node[1])
const rightCid = asCID(node[1])
if (rightCid == null) {
throw new TypeError('Expected BitcoinTransactionMerkleNode to be [CID|null,CID]')
}
Expand Down Expand Up @@ -80,7 +82,7 @@ export function encodeNoWitness (node) {
*
* @param {BlockPorcelain} deserialized
* @param {BitcoinBlockTransaction.HASH_NO_WITNESS} [noWitness]
* @returns {IterableIterator<{cid:CID, bytes:Uint8Array, transaction?:BitcoinBlockTransaction}>}
* @returns {IterableIterator<{cid:BitcoinTxCID, bytes:Uint8Array, transaction?:BitcoinBlockTransaction}>}
* @private
* @ignore
*/
Expand Down Expand Up @@ -109,15 +111,15 @@ function * _encodeAll (deserialized, noWitness) {
(deserialized.tx[ii])
const { transaction, bytes } = _encode(tx, noWitness)
const mh = dblSha2256.digest(bytes)
const cid = CID.create(1, CODEC_TX_CODE, mh)
const cid = createCID(1, CODEC_TX_CODE, mh)
yield { cid, bytes, transaction } // base tx
hashes.push(mh.digest)
}

for (const { hash, data } of merkle(hashes)) {
if (data) {
const mh = dblSha2256.digestFrom(hash)
const cid = CID.create(1, CODEC_TX_CODE, mh)
const cid = createCID(1, CODEC_TX_CODE, mh)
const bytes = new Uint8Array(64)
bytes.set(data[0], 0)
bytes.set(data[1], 32)
Expand All @@ -137,7 +139,7 @@ function * _encodeAll (deserialized, noWitness) {
*
* @name BitcoinTransaction.encodeAll()
* @param {BlockPorcelain} obj
* @returns {IterableIterator<{cid:CID, bytes:Uint8Array, transaction?:BitcoinBlockTransaction}>}
* @returns {IterableIterator<{cid:BitcoinTxCID, bytes:Uint8Array, transaction?:BitcoinBlockTransaction}>}
*/
export function * encodeAll (obj) {
yield * _encodeAll(obj)
Expand All @@ -150,7 +152,7 @@ export function * encodeAll (obj) {
*
* @name BitcoinTransaction.encodeAllNoWitness()
* @param {BlockPorcelain} obj
* @returns {IterableIterator<{cid:CID, bytes:Uint8Array, transaction?:BitcoinBlockTransaction}>}
* @returns {IterableIterator<{cid:BitcoinTxCID, bytes:Uint8Array, transaction?:BitcoinBlockTransaction}>}
*/
export function * encodeAllNoWitness (obj) {
yield * _encodeAll(obj, BitcoinBlockTransaction.HASH_NO_WITNESS)
Expand Down Expand Up @@ -211,8 +213,8 @@ export function decode (data) {
}
const leftMh = left != null ? dblSha2256.digestFrom(left) : null
const rightMh = dblSha2256.digestFrom(right)
const leftCid = leftMh != null ? CID.create(1, CODEC_TX_CODE, leftMh) : null
const rightCid = CID.create(1, CODEC_TX_CODE, rightMh)
const leftCid = leftMh != null ? createCID(1, CODEC_TX_CODE, leftMh) : null
const rightCid = createCID(1, CODEC_TX_CODE, rightMh)
return [leftCid, rightCid]
}

Expand All @@ -229,14 +231,14 @@ export function decode (data) {
// witness commitment and we can't discriminate at this point -- we can only do that by trying to
// load the witness commitment from the generated CID
const witnessCommitmentMh = dblSha2256.digestFrom(witnessCommitment)
const witnessCommitmentCid = CID.create(1, CODEC_WITNESS_COMMITMENT_CODE, witnessCommitmentMh)
const witnessCommitmentCid = createCID(1, CODEC_WITNESS_COMMITMENT_CODE, witnessCommitmentMh)
deserialized.witnessCommitment = witnessCommitmentCid
}
}
for (const vin of deserialized.vin) {
if (typeof vin.txid === 'string' && /^[0-9a-f]{64}$/.test(vin.txid)) {
const txidMh = dblSha2256.digestFrom(fromHashHex(vin.txid))
vin.tx = CID.create(1, CODEC_TX_CODE, txidMh)
vin.tx = createCID(1, CODEC_TX_CODE, txidMh)
}
}

Expand All @@ -260,15 +262,15 @@ export const code = CODEC_TX_CODE
* The process of converting to a CID involves reversing the hash (to little-endian form), encoding as a `dbl-sha2-256` multihash and encoding as a `bitcoin-tx` multicodec. This process is reversable, see {@link cidToHash}.
*
* @param {string} txHash a string form of a transaction hash
* @returns {CID} A CID (`multiformats.CID`) object representing this transaction identifier.
* @returns {BitcoinTxCID} A CID (`multiformats.CID`) object representing this transaction identifier.
* @name BitcoinTransaction.txHashToCID()
*/
export function txHashToCID (txHash) {
if (typeof txHash !== 'string') {
txHash = bytes.toHex(txHash)
}
const digest = dblSha2256.digestFrom(fromHashHex(txHash))
return CID.create(1, CODEC_TX_CODE, digest)
return createCID(1, CODEC_TX_CODE, digest)
}

/**
Expand Down
18 changes: 10 additions & 8 deletions src/bitcoin-witness-commitment.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BitcoinTransaction } from 'bitcoin-block'
import { CID } from 'multiformats'
import { create as createCID, asCID } from 'multiformats/cid'
import * as dblSha2256 from './dbl-sha2-256.js'
import { CODEC_TX, CODEC_TX_CODE, CODEC_WITNESS_COMMITMENT, CODEC_WITNESS_COMMITMENT_CODE } from './constants.js'

Expand All @@ -9,6 +9,8 @@ import { CODEC_TX, CODEC_TX_CODE, CODEC_WITNESS_COMMITMENT, CODEC_WITNESS_COMMIT
*/
/** @typedef {import('bitcoin-block/classes/Block').BlockPorcelain} BlockPorcelain */
/** @typedef {import('./interface').BitcoinWitnessCommitment} BitcoinWitnessCommitment */
/** @typedef {import('./interface').BitcoinTxCID} BitcoinTxCID */
/** @typedef {import('./interface').BitcoinWitnessCommitmentCID} BitcoinWitnessCommitmentCID */

/** @ignore */
const NULL_HASH = new Uint8Array(32)
Expand All @@ -23,16 +25,16 @@ const NULL_HASH = new Uint8Array(32)

/**
* @param {import('bitcoin-block/classes/Block').BlockPorcelain} deserialized
* @param {CID|null} witnessMerkleRoot
* @returns {{cid:CID, bytes:Uint8Array}|null}
* @param {BitcoinTxCID|null} witnessMerkleRoot
* @returns {{cid:BitcoinWitnessCommitmentCID, bytes:Uint8Array}|null}
* @ignore
*/
export function encodeWitnessCommitment (deserialized, witnessMerkleRoot) {
if (typeof deserialized !== 'object' || !Array.isArray(deserialized.tx)) {
throw new TypeError('deserialized argument must be a Bitcoin block representation')
}

if (witnessMerkleRoot !== null && !(witnessMerkleRoot instanceof Uint8Array) && !CID.asCID(witnessMerkleRoot)) {
if (witnessMerkleRoot !== null && !(witnessMerkleRoot instanceof Uint8Array) && !asCID(witnessMerkleRoot)) {
throw new TypeError('witnessMerkleRoot must be a Uint8Array or CID')
}

Expand All @@ -45,7 +47,7 @@ export function encodeWitnessCommitment (deserialized, witnessMerkleRoot) {
merkleRootHash = witnessMerkleRoot
} else {
// CID
const mrhcid = CID.asCID(witnessMerkleRoot)
const mrhcid = asCID(witnessMerkleRoot)
if (mrhcid == null) {
throw new TypeError('Expected witnessMerkleRoot to be a CID')
}
Expand Down Expand Up @@ -87,7 +89,7 @@ export function encodeWitnessCommitment (deserialized, witnessMerkleRoot) {
}

const mh = dblSha2256.digestFrom(hash)
const cid = CID.create(1, CODEC_WITNESS_COMMITMENT_CODE, mh)
const cid = createCID(1, CODEC_WITNESS_COMMITMENT_CODE, mh)

return { cid, bytes }
}
Expand All @@ -112,7 +114,7 @@ export function encode (node) {
if (!(node.nonce instanceof Uint8Array)) {
throw new TypeError('bitcoin-witness-commitment must have a `nonce` Uint8Array')
}
const witnessMerkleRoot = CID.asCID(node.witnessMerkleRoot)
const witnessMerkleRoot = asCID(node.witnessMerkleRoot)
if (!witnessMerkleRoot) {
throw new TypeError('bitcoin-witness-commitment must have a `witnessMerkleRoot` CID')
}
Expand Down Expand Up @@ -154,7 +156,7 @@ export function decode (data) {
let witnessMerkleRoot = null
if (!isNullHash(witnessHash)) {
const witnessDigest = dblSha2256.digestFrom(witnessHash)
witnessMerkleRoot = CID.create(1, CODEC_TX_CODE, witnessDigest)
witnessMerkleRoot = createCID(1, CODEC_TX_CODE, witnessDigest)
}
return { witnessMerkleRoot, nonce }
}
Expand Down
18 changes: 11 additions & 7 deletions src/complete.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CID, bytes } from 'multiformats'
import { bytes } from 'multiformats'
import { create as createCID } from 'multiformats/cid'
import { BitcoinBlock, BitcoinTransaction as BitcoinBlockTransaction } from 'bitcoin-block'
import * as bitcoinBlockCodec from './bitcoin-block.js'
import * as bitcoinTxCodec from './bitcoin-tx.js'
Expand All @@ -13,17 +14,20 @@ const { toHex } = bytes
/** @typedef {import('./interface').IPLDLoader} IPLDLoader */
/** @typedef {import('./interface').BitcoinTransaction} BitcoinTransaction */
/** @typedef {import('./interface').BitcoinTransactionMerkleNode} BitcoinTransactionMerkleNode */
/** @typedef {import('./interface').BitcoinBlockCID} BitcoinBlockCID */
/** @typedef {import('./interface').BitcoinTxCID} BitcoinTxCID */
/** @typedef {import('./interface').BitcoinWitnessCommitmentCID} BitcoinWitnessCommitmentCID */

/**
* @param {any} obj
* @returns {{cid:CID, bytes:Uint8Array}}
* @returns {{cid:BitcoinBlockCID, bytes:Uint8Array}}
* @ignore
*/
function mkblock (obj) {
const bytes = bitcoinBlockCodec.encode(obj)
const mh = dblSha256.digest(bytes)
return {
cid: CID.create(1, bitcoinBlockCodec.code, mh),
cid: createCID(1, bitcoinBlockCodec.code, mh),
bytes
}
}
Expand All @@ -38,7 +42,7 @@ function mkblock (obj) {
*
* @name Bitcoin.encodeAll()
* @param {BlockPorcelain} block
* @returns {IterableIterator<{cid: CID, bytes: Uint8Array}>}
* @returns {IterableIterator<{cid: BitcoinBlockCID|BitcoinTxCID|BitcoinWitnessCommitmentCID, bytes: Uint8Array}>}
*/
export function * encodeAll (block) {
if (typeof block !== 'object' || !Array.isArray(block.tx)) {
Expand Down Expand Up @@ -135,7 +139,7 @@ export function * encodeAll (block) {
* `bitcoin-tx` and `bitcoin-witness-commitment` CIDs.
*
* @param {IPLDLoader} loader an IPLD block loader function that takes a CID argument and returns a `Uint8Array` containing the binary block data for that CID
* @param {CID} blockCid a CID of type `bitcoin-block` pointing to the Bitcoin block header for the block to be assembled
* @param {BitcoinBlockCID} blockCid a CID of type `bitcoin-block` pointing to the Bitcoin block header for the block to be assembled
* @returns {Promise<{deserialized:BlockPorcelain, bytes:Uint8Array}>} an object containing two properties, `deserialized` and `bytes` where `deserialized` contains a full JavaScript instantiation of the Bitcoin block graph and `bytes` contains a `Uint8Array` with the binary representation of the graph.
* @name Bitcoin.assemble()
*/
Expand All @@ -146,7 +150,7 @@ export async function assemble (loader, blockCid) {
*/
const merkleCache = {}
/**
* @param {CID} txCid
* @param {BitcoinTxCID} txCid
* @returns {Promise<BitcoinTransaction|BitcoinTransactionMerkleNode>}
* @ignore
*/
Expand Down Expand Up @@ -178,7 +182,7 @@ export async function assemble (loader, blockCid) {
})()

/**
* @param {CID} txCid
* @param {BitcoinTxCID} txCid
* @returns {AsyncIterableIterator<BitcoinTransaction|BitcoinTransactionMerkleNode>}
* @ignore
*/
Expand Down
28 changes: 18 additions & 10 deletions src/interface.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,43 @@
import { TransactionInCoinbasePorcelain, TransactionInPorcelain } from 'bitcoin-block/classes/TransactionIn';
import { BlockHeaderPorcelain, TransactionPorcelain } from 'bitcoin-block/interface'
import { CID } from 'multiformats';
import { CID } from 'multiformats/interface';

export type HASH_ALG_CODE = 0x56
export type CODEC_BLOCK_CODE = 0xb0
export type CODEC_TX_CODE = 0xb1
export type CODEC_WITNESS_COMMITMENT_CODE = 0xb2

export type IPLDLoader = (cid:CID)=>Promise<Uint8Array>

export interface BitcoinBlockCID extends CID<CODEC_BLOCK_CODE, HASH_ALG_CODE, 1>{}
export interface BitcoinTxCID extends CID<CODEC_TX_CODE, HASH_ALG_CODE, 1>{}
export interface BitcoinWitnessCommitmentCID extends CID<CODEC_WITNESS_COMMITMENT_CODE, HASH_ALG_CODE, 1>{}

export interface BitcoinHeader extends BlockHeaderPorcelain {
parent: CID|null
tx: CID
parent: BitcoinBlockCID|null
tx: BitcoinTxCID
}

export interface BitcoinTransactionMerkleNode {
0: CID|null
1: CID
0: BitcoinTxCID|null
1: BitcoinTxCID
}


export interface BitcoinTransactionInCoinbase extends TransactionInCoinbasePorcelain {
tx: CID
tx: BitcoinTxCID
txinwitness: [string]
}

export interface BitcoinTransactionIn extends TransactionInPorcelain {
tx: CID
tx: BitcoinTxCID
}

export interface BitcoinTransaction extends TransactionPorcelain {
witnessCommitment?: CID
witnessCommitment?: BitcoinWitnessCommitmentCID
vin: (BitcoinTransactionInCoinbase | BitcoinTransactionIn)[]
}

export interface BitcoinWitnessCommitment {
witnessMerkleRoot: CID|null
witnessMerkleRoot: BitcoinTxCID|null
nonce: Uint8Array
}
10 changes: 7 additions & 3 deletions src/util.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { CID } from 'multiformats/cid'
import { parse as parseCID, asCID } from 'multiformats/cid'
import { BitcoinBlock, toHashHex } from 'bitcoin-block'
import * as block from './bitcoin-block.js'
import * as tx from './bitcoin-tx.js'

export const blockHashToCID = block.blockHashToCID
export const txHashToCID = tx.txHashToCID

/**
* @typedef {import('multiformats/interface').CID} CID
*/

/** @typedef {import('bitcoin-block/interface').BlockPorcelain} BlockPorcelain */

/**
Expand Down Expand Up @@ -64,9 +68,9 @@ export function serializeFullBitcoinBytes (obj) {
*/
export function cidToHash (cid) {
if (typeof cid === 'string') {
cid = CID.parse(cid)
cid = parseCID(cid)
}
const acid = CID.asCID(cid)
const acid = asCID(cid)
if (!acid) {
throw new TypeError('Must provide a CID or a CID string')
}
Expand Down
Loading

0 comments on commit 26e0dbb

Please sign in to comment.