import { Payment, PaymentOpts } from './index' // eslint-disable-line import { bitcoin as BITCOIN_NETWORK } from '../networks' // eslint-disable-line import * as bscript from '../script' import * as bcrypto from '../crypto' import * as lazy from './lazy' const typef = require('typeforce') const OPS = bscript.OPS const bech32 = require('bech32') const EMPTY_BUFFER = Buffer.alloc(0) function stacksEqual (a: Array, b: Array): boolean { if (a.length !== b.length) return false return a.every(function (x, i) { return x.equals(b[i]) }) } // input: <> // witness: [redeemScriptSig ...] {redeemScript} // output: OP_0 {sha256(redeemScript)} export function p2wsh (a: Payment, opts?: PaymentOpts): Payment { if ( !a.address && !a.hash && !a.output && !a.redeem && !a.witness ) throw new TypeError('Not enough data') opts = Object.assign({ validate: true }, opts || {}) typef({ network: typef.maybe(typef.Object), address: typef.maybe(typef.String), hash: typef.maybe(typef.BufferN(32)), output: typef.maybe(typef.BufferN(34)), redeem: typef.maybe({ input: typef.maybe(typef.Buffer), network: typef.maybe(typef.Object), output: typef.maybe(typef.Buffer), witness: typef.maybe(typef.arrayOf(typef.Buffer)) }), input: typef.maybe(typef.BufferN(0)), witness: typef.maybe(typef.arrayOf(typef.Buffer)) }, a) const _address = lazy.value(function () { const result = bech32.decode(a.address) const version = result.words.shift() const data = bech32.fromWords(result.words) return { version, prefix: result.prefix, data: Buffer.from(data) } }) const _rchunks = <()=>Array>lazy.value(function () { return bscript.decompile(a.redeem!.input!) }) let network = a.network if (!network) { network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK } const o: Payment = { network } lazy.prop(o, 'address', function () { if (!o.hash) return const words = bech32.toWords(o.hash) words.unshift(0x00) return bech32.encode(network!.bech32, words) }) lazy.prop(o, 'hash', function () { if (a.output) return a.output.slice(2) if (a.address) return _address().data if (o.redeem && o.redeem.output) return bcrypto.sha256(o.redeem.output) }) lazy.prop(o, 'output', function () { if (!o.hash) return return bscript.compile([ OPS.OP_0, o.hash ]) }) lazy.prop(o, 'redeem', function () { if (!a.witness) return return { output: a.witness[a.witness.length - 1], input: EMPTY_BUFFER, witness: a.witness.slice(0, -1) } }) lazy.prop(o, 'input', function () { if (!o.witness) return return EMPTY_BUFFER }) lazy.prop(o, 'witness', function () { // transform redeem input to witness stack? if ( a.redeem && a.redeem.input && a.redeem.input.length > 0 && a.redeem.output && a.redeem.output.length > 0 ) { const stack = bscript.toStack(_rchunks()) // assign, and blank the existing input o.redeem = Object.assign({ witness: stack }, a.redeem) o.redeem.input = EMPTY_BUFFER return (>[]).concat(stack, a.redeem.output) } if (!a.redeem) return if (!a.redeem.output) return if (!a.redeem.witness) return return (>[]).concat(a.redeem.witness, a.redeem.output) }) // extended validation if (opts.validate) { let hash: Buffer = Buffer.from([]) if (a.address) { if (_address().prefix !== network.bech32) throw new TypeError('Invalid prefix or Network mismatch') if (_address().version !== 0x00) throw new TypeError('Invalid address version') if (_address().data.length !== 32) throw new TypeError('Invalid address data') hash = _address().data } if (a.hash) { if (hash.length > 0 && !hash.equals(a.hash)) throw new TypeError('Hash mismatch') else hash = a.hash } if (a.output) { if ( a.output.length !== 34 || a.output[0] !== OPS.OP_0 || a.output[1] !== 0x20) throw new TypeError('Output is invalid') const hash2 = a.output.slice(2) if (hash.length > 0 && !hash.equals(hash2)) throw new TypeError('Hash mismatch') else hash = hash2 } if (a.redeem) { if (a.redeem.network && a.redeem.network !== network) throw new TypeError('Network mismatch') // is there two redeem sources? if ( a.redeem.input && a.redeem.input.length > 0 && a.redeem.witness && a.redeem.witness.length > 0 ) throw new TypeError('Ambiguous witness source') // is the redeem output non-empty? if (a.redeem.output) { if (bscript.decompile(a.redeem.output)!.length === 0) throw new TypeError('Redeem.output is invalid') // match hash against other sources const hash2 = bcrypto.sha256(a.redeem.output) if (hash.length > 0 && !hash.equals(hash2)) throw new TypeError('Hash mismatch') else hash = hash2 } if (a.redeem.input && !bscript.isPushOnly(_rchunks())) throw new TypeError('Non push-only scriptSig') if (a.witness && a.redeem.witness && !stacksEqual(a.witness, a.redeem.witness)) throw new TypeError('Witness and redeem.witness mismatch') } if (a.witness) { if (a.redeem && a.redeem.output && !a.redeem.output.equals(a.witness[a.witness.length - 1])) throw new TypeError('Witness and redeem.output mismatch') } } return Object.assign(o, a) }