Skip to content

Add typings and browser support #8

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
30 changes: 2 additions & 28 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,28 +1,2 @@
# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
# Commenting this out is preferred by some people, see
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules

# Users Environment Variables
.lock-wscript
node_modules/
dist/
2 changes: 0 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
sudo: false
language: node_js
node_js:
- "0.10"
- "0.12"
- "4"
- "5"
env:
Expand Down
131 changes: 0 additions & 131 deletions index.js

This file was deleted.

152 changes: 152 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
/** Produces a Merkle proof for a single tx within a list. */
export async function getProof(txIds: string[], txIndex: number) {
const proof = {
txId: txIds[txIndex],
txIndex: txIndex,
sibling: [],
};

let tree = new Array(txIds.length);
for (let i = 0; i < tree.length; ++i) {
tree[i] = reverse(fromHex(txIds[i]));
}
let target = tree[txIndex];

while (tree.length !== 1) {
const newTree = new Array(~~((tree.length + 1) / 2));
for (let j = 0; j < tree.length; j += 2) {
const hash1 = tree[j];
const hash2 = tree[Math.min(j + 1, tree.length - 1)];

newTree[j / 2] = await sha256x2(hash1, hash2);

if (isEqual(target, hash1)) {
proof.sibling.push(toHex(reverse(hash2)));
target = newTree[j / 2];
} else if (isEqual(target, hash2)) {
proof.sibling.push(toHex(reverse(hash1)));
target = newTree[j / 2];
}
}

tree = newTree;
}

return proof;
}

export interface TxMerkleProof {
txId: string;
txIndex: number;
sibling: string[];
}

/** Evaluates a transaction merkle proof, returning the root hash. */
export async function getTxMerkle(proofObj: TxMerkleProof) {
let target = reverse(fromHex(proofObj.txId));
let txIndex = proofObj.txIndex;
const sibling = proofObj.sibling;

for (let i = 0; i < proofObj.sibling.length; ++i, txIndex = ~~(txIndex / 2)) {
if (txIndex % 2 === 1) {
target = await sha256x2(reverse(fromHex(sibling[i])), target);
} else {
target = await sha256x2(target, reverse(fromHex(sibling[i])));
}
}

return toHex(reverse(target));
}

/** Computes the Merkle root of a list of Bitcoin transaction IDs. */
export async function getMerkleRoot(txIds: string[]) {
let tree = new Array(txIds.length) as Uint8Array[];
for (let i = 0; i < tree.length; ++i) {
tree[i] = reverse(fromHex(txIds[i]));
}

while (tree.length !== 1) {
const newTree = new Array(~~((tree.length + 1) / 2));
for (let j = 0; j < tree.length; j += 2) {
const hash1 = tree[j];
const hash2 = tree[Math.min(j + 1, tree.length - 1)];

newTree[j / 2] = await sha256x2(hash1, hash2);
}

tree = newTree;
}

const ret = toHex(reverse(tree[0]));
return ret;
}

// This ugly construction is required to make a library that bundles without
// error for browser use, but still uses Node builtins when running in node.
let nodeCrypto = null;
try {
nodeCrypto = require("crypto");
} catch (_) {}

/** Computes a double-SHA256 hash of [a, b]. Async in-browser. */
async function sha256x2(
buf1: Uint8Array,
buf2: Uint8Array
): Promise<Uint8Array> {
if (nodeCrypto) {
// Synchronous native SHA256 via require("crypto")
const hash1 = nodeCrypto
.createHash("sha256")
.update(buf1)
.update(buf2)
.digest();
const hash2 = nodeCrypto.createHash("sha256").update(hash1).digest();
return hash2;
} else {
// Asynchronous native SHA256 via SubtleCrypto
const comb = new Uint8Array(buf1.length + buf2.length);
comb.set(buf1, 0);
comb.set(buf2, buf1.length);
const hash1 = await crypto.subtle.digest("SHA-256", comb);
const hash2 = await crypto.subtle.digest("SHA-256", hash1);
return new Uint8Array(hash2);
}
}

/** Reverse a byte array in-place. */
function reverse(buf: Uint8Array) {
return buf.reverse();
}

/** Check deep equality between two byte arrays. */
function isEqual(buf1: Uint8Array, buf2: Uint8Array) {
if (buf1.length !== buf2.length) {
return false;
}

for (let i = 0; i < buf1.length; ++i) {
if (buf1[i] !== buf2[i]) {
return false;
}
}

return true;
}

/** Parses hex to a Uint8Array */
function fromHex(hex: string): Uint8Array {
return new Uint8Array(
hex.match(/[\da-f]{2}/gi).map(function (h) {
return parseInt(h, 16);
})
);
}

/** Print Uint8Array to hex */
function toHex(arr: Uint8Array): string {
return Array.prototype.map
.call(arr, function (byte: number) {
return ("0" + (byte & 0xff).toString(16)).slice(-2);
})
.join("");
}
Loading