Skip to content

Commit f04d8b2

Browse files
committed
feat: add function computeMastRoot()
1 parent 33c2bd9 commit f04d8b2

File tree

3 files changed

+86
-1
lines changed

3 files changed

+86
-1
lines changed

src/merkle.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/// <reference types="node" />
22
export declare function fastMerkleRoot(values: Buffer[], digestFn: (b: Buffer) => Buffer): Buffer;
3+
export declare function computeMastRoot(scripts: any): Buffer;

src/merkle.js

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
'use strict';
22
Object.defineProperty(exports, '__esModule', { value: true });
3-
exports.fastMerkleRoot = void 0;
3+
exports.computeMastRoot = exports.fastMerkleRoot = void 0;
4+
const buffer_1 = require('buffer');
5+
const bcrypto = require('./crypto');
6+
// todo: use varuint-bitcoin??
7+
const varuint = require('bip174/src/lib/converter/varint');
8+
const TAP_LEAF_TAG = buffer_1.Buffer.from('TapLeaf', 'utf8');
9+
const TAP_BRANCH_TAG = buffer_1.Buffer.from('TapBranch', 'utf8');
10+
const LEAF_VERSION_TAPSCRIPT = 0xc0;
411
function fastMerkleRoot(values, digestFn) {
512
if (!Array.isArray(values)) throw TypeError('Expected values Array');
613
if (typeof digestFn !== 'function')
@@ -20,3 +27,40 @@ function fastMerkleRoot(values, digestFn) {
2027
return results[0];
2128
}
2229
exports.fastMerkleRoot = fastMerkleRoot;
30+
// todo: solve any[]
31+
function computeMastRoot(scripts) {
32+
if (scripts.length === 1) {
33+
const script = scripts[0];
34+
if (Array.isArray(script)) {
35+
return computeMastRoot(script);
36+
}
37+
script.version = script.version || LEAF_VERSION_TAPSCRIPT;
38+
if ((script.version & 1) !== 0) throw new Error('Invalid script version'); // todo typedef error
39+
// todo: if (script.output)scheck is bytes
40+
const scriptOutput = buffer_1.Buffer.from(script.output, 'hex');
41+
return bcrypto.taggedHash(
42+
TAP_LEAF_TAG,
43+
buffer_1.Buffer.concat([
44+
buffer_1.Buffer.from([script.version]),
45+
serializeScript(scriptOutput),
46+
]),
47+
);
48+
}
49+
// todo: this is a binary tree, use zero an one index
50+
const half = Math.trunc(scripts.length / 2);
51+
let leftHash = computeMastRoot(scripts.slice(0, half));
52+
let rightHash = computeMastRoot(scripts.slice(half));
53+
if (leftHash.compare(rightHash) === 1)
54+
[leftHash, rightHash] = [rightHash, leftHash];
55+
return bcrypto.taggedHash(
56+
TAP_BRANCH_TAG,
57+
buffer_1.Buffer.concat([leftHash, rightHash]),
58+
);
59+
}
60+
exports.computeMastRoot = computeMastRoot;
61+
function serializeScript(s) {
62+
const varintLen = varuint.encodingLength(s.length);
63+
const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better
64+
varuint.encode(s.length, buffer);
65+
return buffer_1.Buffer.concat([buffer, s]);
66+
}

ts_src/merkle.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
import { Buffer as NBuffer } from 'buffer';
2+
import * as bcrypto from './crypto';
3+
// todo: use varuint-bitcoin??
4+
import * as varuint from 'bip174/src/lib/converter/varint';
5+
6+
7+
const TAP_LEAF_TAG = NBuffer.from('TapLeaf', 'utf8');
8+
const TAP_BRANCH_TAG = NBuffer.from('TapBranch', 'utf8');
9+
const LEAF_VERSION_TAPSCRIPT = 0xc0
10+
11+
112
export function fastMerkleRoot(
213
values: Buffer[],
314
digestFn: (b: Buffer) => Buffer,
@@ -25,3 +36,32 @@ export function fastMerkleRoot(
2536

2637
return results[0];
2738
}
39+
40+
// todo: solve any[]
41+
export function computeMastRoot(scripts: any): Buffer {
42+
if (scripts.length === 1) {
43+
const script = scripts[0]
44+
if (Array.isArray(script)) {
45+
return computeMastRoot(script)
46+
}
47+
script.version = script.version || LEAF_VERSION_TAPSCRIPT
48+
if ((script.version & 1) !== 0) throw new Error("Invalid script version") // todo typedef error
49+
// todo: if (script.output)scheck is bytes
50+
const scriptOutput = NBuffer.from(script.output, 'hex')
51+
return bcrypto.taggedHash(TAP_LEAF_TAG, NBuffer.concat([NBuffer.from([script.version]), serializeScript(scriptOutput)]))
52+
}
53+
// todo: this is a binary tree, use zero an one index
54+
const half = Math.trunc(scripts.length / 2)
55+
let leftHash = computeMastRoot(scripts.slice(0, half))
56+
let rightHash = computeMastRoot(scripts.slice(half))
57+
58+
if (leftHash.compare(rightHash) === 1) [leftHash, rightHash] = [rightHash, leftHash]
59+
return bcrypto.taggedHash(TAP_BRANCH_TAG, NBuffer.concat([leftHash, rightHash]))
60+
}
61+
62+
function serializeScript(s: Buffer) {
63+
const varintLen = varuint.encodingLength(s.length);
64+
const buffer = NBuffer.allocUnsafe(varintLen); // better
65+
varuint.encode(s.length, buffer);
66+
return NBuffer.concat([buffer, s])
67+
}

0 commit comments

Comments
 (0)