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

feat: use new multiformats CID interface and exports #7

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
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'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Diff may have end even smaller if you did

Suggested change
import { create as createCID, asCID } from 'multiformats/cid'
import * as CID from 'multiformats/cid'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair, although there's so much hanging off CID now that I feel uncomfortable using it (a mental nit more than anything), including CID.CID.

But if you were to make an example usage of the new CID API, would you advise * as CID or the specific pieces? Let's make this an exemplar that we can point to until we have more & better docs.

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