|
| 1 | +'use strict'; |
| 2 | +Object.defineProperty(exports, '__esModule', { value: true }); |
| 3 | +exports.computeMastRoot = exports.rootHash = exports.leafHash = exports.tweakKey = exports.liftX = void 0; |
| 4 | +const buffer_1 = require('buffer'); |
| 5 | +const BN = require('bn.js'); |
| 6 | +const bcrypto = require('./crypto'); |
| 7 | +// todo: use varuint-bitcoin?? |
| 8 | +const varuint = require('bip174/src/lib/converter/varint'); |
| 9 | +const types_1 = require('./types'); |
| 10 | +// todo: !!!Temp, to be replaced. Only works because bip32 has it as dependecy. Linting will fail. |
| 11 | +const ecc = require('tiny-secp256k1'); |
| 12 | +const LEAF_VERSION_TAPSCRIPT = 0xc0; |
| 13 | +const TAP_LEAF_TAG = buffer_1.Buffer.from('TapLeaf', 'utf8'); |
| 14 | +const TAP_BRANCH_TAG = buffer_1.Buffer.from('TapBranch', 'utf8'); |
| 15 | +const TAP_TWEAK_TAG = buffer_1.Buffer.from('TapTweak', 'utf8'); |
| 16 | +// todo: compare buffers dirrectly |
| 17 | +const GROUP_ORDER = buffer_1.Buffer.from( |
| 18 | + 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141', |
| 19 | + 'hex', |
| 20 | +); |
| 21 | +const GROUP_ORDER_BN = new BN(GROUP_ORDER); |
| 22 | +const EC_P_BN = new BN(types_1.EC_P); |
| 23 | +const EC_P_REDUCTION = BN.red(EC_P_BN); |
| 24 | +const EC_P_QUADRATIC_RESIDUE = EC_P_BN.addn(1).divn(4); |
| 25 | +const BN_2 = new BN(2); |
| 26 | +const BN_3 = new BN(3); |
| 27 | +const BN_7 = new BN(7); |
| 28 | +function liftX(buffer) { |
| 29 | + if (!buffer_1.Buffer.isBuffer(buffer)) return null; |
| 30 | + if (buffer.length !== 32) return null; |
| 31 | + if (buffer.compare(types_1.ZERO32) === 0) return null; |
| 32 | + if (buffer.compare(types_1.EC_P) >= 0) return null; |
| 33 | + const x = new BN(buffer); |
| 34 | + const x1 = x.toRed(EC_P_REDUCTION); |
| 35 | + const ySq = x1 |
| 36 | + .redPow(BN_3) |
| 37 | + .add(BN_7) |
| 38 | + .mod(EC_P_BN); |
| 39 | + const y = ySq.redPow(EC_P_QUADRATIC_RESIDUE); |
| 40 | + if (!ySq.eq(y.redPow(BN_2))) { |
| 41 | + return null; |
| 42 | + } |
| 43 | + const y1 = y.isEven() ? y : EC_P_BN.sub(y); |
| 44 | + return buffer_1.Buffer.concat([ |
| 45 | + buffer_1.Buffer.from([0x04]), |
| 46 | + buffer_1.Buffer.from(x1.toBuffer('be', 32)), |
| 47 | + buffer_1.Buffer.from(y1.toBuffer('be', 32)), |
| 48 | + ]); |
| 49 | +} |
| 50 | +exports.liftX = liftX; |
| 51 | +function tweakKey(pubKey, h) { |
| 52 | + if (!buffer_1.Buffer.isBuffer(pubKey)) return null; |
| 53 | + if (pubKey.length !== 32) return null; |
| 54 | + if (h && h.length !== 32) return null; |
| 55 | + const tweakHash = bcrypto.taggedHash( |
| 56 | + TAP_TWEAK_TAG, |
| 57 | + buffer_1.Buffer.concat(h ? [pubKey, h] : [pubKey]), |
| 58 | + ); |
| 59 | + const t = new BN(tweakHash); |
| 60 | + if (t.gte(GROUP_ORDER_BN)) { |
| 61 | + // todo: add test for this case |
| 62 | + throw new Error('Tweak value over the SECP256K1 Order'); |
| 63 | + } |
| 64 | + const P = liftX(pubKey); |
| 65 | + if (P === null) return null; |
| 66 | + const Q = pointAddScalar(P, tweakHash); |
| 67 | + return { |
| 68 | + isOdd: Q[64] % 2 === 1, |
| 69 | + x: Q.slice(1, 33), |
| 70 | + }; |
| 71 | +} |
| 72 | +exports.tweakKey = tweakKey; |
| 73 | +function leafHash(script, version) { |
| 74 | + return buffer_1.Buffer.concat([ |
| 75 | + buffer_1.Buffer.from([version]), |
| 76 | + serializeScript(script), |
| 77 | + ]); |
| 78 | +} |
| 79 | +exports.leafHash = leafHash; |
| 80 | +function rootHash(controlBlock, tapLeafMsg) { |
| 81 | + const k = []; |
| 82 | + const e = []; |
| 83 | + const m = (controlBlock.length - 33) / 32; |
| 84 | + k[0] = bcrypto.taggedHash(TAP_LEAF_TAG, tapLeafMsg); |
| 85 | + for (let j = 0; j < m; j++) { |
| 86 | + e[j] = controlBlock.slice(33 + 32 * j, 65 + 32 * j); |
| 87 | + if (k[j].compare(e[j]) < 0) { |
| 88 | + k[j + 1] = bcrypto.taggedHash( |
| 89 | + TAP_BRANCH_TAG, |
| 90 | + buffer_1.Buffer.concat([k[j], e[j]]), |
| 91 | + ); |
| 92 | + } else { |
| 93 | + k[j + 1] = bcrypto.taggedHash( |
| 94 | + TAP_BRANCH_TAG, |
| 95 | + buffer_1.Buffer.concat([e[j], k[j]]), |
| 96 | + ); |
| 97 | + } |
| 98 | + } |
| 99 | + return k[m]; |
| 100 | +} |
| 101 | +exports.rootHash = rootHash; |
| 102 | +// todo: solve any[] |
| 103 | +function computeMastRoot(scripts) { |
| 104 | + if (scripts.length === 1) { |
| 105 | + const script = scripts[0]; |
| 106 | + if (Array.isArray(script)) { |
| 107 | + return computeMastRoot(script); |
| 108 | + } |
| 109 | + script.version = script.version || LEAF_VERSION_TAPSCRIPT; |
| 110 | + if ((script.version & 1) !== 0) throw new Error('Invalid script version'); // todo typedef error |
| 111 | + // todo: if (script.output)scheck is bytes |
| 112 | + const scriptOutput = buffer_1.Buffer.from(script.output, 'hex'); |
| 113 | + return bcrypto.taggedHash( |
| 114 | + TAP_LEAF_TAG, |
| 115 | + buffer_1.Buffer.concat([ |
| 116 | + buffer_1.Buffer.from([script.version]), |
| 117 | + serializeScript(scriptOutput), |
| 118 | + ]), |
| 119 | + ); |
| 120 | + } |
| 121 | + // todo: this is a binary tree, use zero an one index |
| 122 | + const half = Math.trunc(scripts.length / 2); |
| 123 | + let leftHash = computeMastRoot(scripts.slice(0, half)); |
| 124 | + let rightHash = computeMastRoot(scripts.slice(half)); |
| 125 | + if (leftHash.compare(rightHash) === 1) |
| 126 | + [leftHash, rightHash] = [rightHash, leftHash]; |
| 127 | + return bcrypto.taggedHash( |
| 128 | + TAP_BRANCH_TAG, |
| 129 | + buffer_1.Buffer.concat([leftHash, rightHash]), |
| 130 | + ); |
| 131 | +} |
| 132 | +exports.computeMastRoot = computeMastRoot; |
| 133 | +function serializeScript(s) { |
| 134 | + const varintLen = varuint.encodingLength(s.length); |
| 135 | + const buffer = buffer_1.Buffer.allocUnsafe(varintLen); // better |
| 136 | + varuint.encode(s.length, buffer); |
| 137 | + return buffer_1.Buffer.concat([buffer, s]); |
| 138 | +} |
| 139 | +// todo: do not use ecc |
| 140 | +function pointAddScalar(P, h) { |
| 141 | + return ecc.pointAddScalar(P, h); |
| 142 | +} |
0 commit comments