2019-03-03 15:07:49 +01:00
|
|
|
import * as bcrypto from '../crypto';
|
|
|
|
import { bitcoin as BITCOIN_NETWORK } from '../networks';
|
2019-03-07 05:01:40 +01:00
|
|
|
import * as bscript from '../script';
|
|
|
|
import { Payment, PaymentOpts } from './index';
|
|
|
|
import * as lazy from './lazy';
|
2019-03-03 15:07:49 +01:00
|
|
|
const typef = require('typeforce');
|
|
|
|
const OPS = bscript.OPS;
|
|
|
|
const ecc = require('tiny-secp256k1');
|
2018-06-05 09:24:47 +02:00
|
|
|
|
2021-02-15 20:47:17 +01:00
|
|
|
const { bech32 } = require('bech32');
|
2018-06-05 09:24:47 +02:00
|
|
|
|
2019-03-03 15:07:49 +01:00
|
|
|
const EMPTY_BUFFER = Buffer.alloc(0);
|
2018-06-05 09:24:47 +02:00
|
|
|
|
|
|
|
// witness: {signature} {pubKey}
|
|
|
|
// input: <>
|
|
|
|
// output: OP_0 {pubKeyHash}
|
2019-03-03 15:07:49 +01:00
|
|
|
export function p2wpkh(a: Payment, opts?: PaymentOpts): Payment {
|
|
|
|
if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness)
|
|
|
|
throw new TypeError('Not enough data');
|
|
|
|
opts = Object.assign({ validate: true }, opts || {});
|
|
|
|
|
|
|
|
typef(
|
|
|
|
{
|
|
|
|
address: typef.maybe(typef.String),
|
|
|
|
hash: typef.maybe(typef.BufferN(20)),
|
|
|
|
input: typef.maybe(typef.BufferN(0)),
|
|
|
|
network: typef.maybe(typef.Object),
|
|
|
|
output: typef.maybe(typef.BufferN(22)),
|
|
|
|
pubkey: typef.maybe(ecc.isPoint),
|
|
|
|
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
|
|
|
witness: typef.maybe(typef.arrayOf(typef.Buffer)),
|
|
|
|
},
|
|
|
|
a,
|
|
|
|
);
|
|
|
|
|
2019-03-07 05:01:40 +01:00
|
|
|
const _address = lazy.value(() => {
|
2019-03-03 15:07:49 +01:00
|
|
|
const result = bech32.decode(a.address);
|
|
|
|
const version = result.words.shift();
|
|
|
|
const data = bech32.fromWords(result.words);
|
2018-07-03 14:06:44 +02:00
|
|
|
return {
|
|
|
|
version,
|
|
|
|
prefix: result.prefix,
|
2019-03-03 15:07:49 +01:00
|
|
|
data: Buffer.from(data),
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
const network = a.network || BITCOIN_NETWORK;
|
2019-07-11 07:49:26 +02:00
|
|
|
const o: Payment = { name: 'p2wpkh', network };
|
2019-03-03 15:07:49 +01:00
|
|
|
|
2019-03-07 05:01:40 +01:00
|
|
|
lazy.prop(o, 'address', () => {
|
2019-03-03 15:07:49 +01:00
|
|
|
if (!o.hash) return;
|
|
|
|
|
|
|
|
const words = bech32.toWords(o.hash);
|
|
|
|
words.unshift(0x00);
|
|
|
|
return bech32.encode(network.bech32, words);
|
|
|
|
});
|
2019-03-07 05:01:40 +01:00
|
|
|
lazy.prop(o, 'hash', () => {
|
2019-03-03 15:07:49 +01:00
|
|
|
if (a.output) return a.output.slice(2, 22);
|
|
|
|
if (a.address) return _address().data;
|
|
|
|
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey! || o.pubkey!);
|
|
|
|
});
|
2019-03-07 05:01:40 +01:00
|
|
|
lazy.prop(o, 'output', () => {
|
2019-03-03 15:07:49 +01:00
|
|
|
if (!o.hash) return;
|
|
|
|
return bscript.compile([OPS.OP_0, o.hash]);
|
|
|
|
});
|
2019-03-07 05:01:40 +01:00
|
|
|
lazy.prop(o, 'pubkey', () => {
|
2019-03-03 15:07:49 +01:00
|
|
|
if (a.pubkey) return a.pubkey;
|
|
|
|
if (!a.witness) return;
|
|
|
|
return a.witness[1];
|
|
|
|
});
|
2019-03-07 05:01:40 +01:00
|
|
|
lazy.prop(o, 'signature', () => {
|
2019-03-03 15:07:49 +01:00
|
|
|
if (!a.witness) return;
|
|
|
|
return a.witness[0];
|
|
|
|
});
|
2019-03-07 05:01:40 +01:00
|
|
|
lazy.prop(o, 'input', () => {
|
2019-03-03 15:07:49 +01:00
|
|
|
if (!o.witness) return;
|
|
|
|
return EMPTY_BUFFER;
|
|
|
|
});
|
2019-03-07 05:01:40 +01:00
|
|
|
lazy.prop(o, 'witness', () => {
|
2019-03-03 15:07:49 +01:00
|
|
|
if (!a.pubkey) return;
|
|
|
|
if (!a.signature) return;
|
|
|
|
return [a.signature, a.pubkey];
|
|
|
|
});
|
2018-06-05 09:24:47 +02:00
|
|
|
|
|
|
|
// extended validation
|
|
|
|
if (opts.validate) {
|
2019-03-03 15:07:49 +01:00
|
|
|
let hash: Buffer = Buffer.from([]);
|
2018-06-05 09:24:47 +02:00
|
|
|
if (a.address) {
|
2019-03-03 15:07:49 +01:00
|
|
|
if (network && network.bech32 !== _address().prefix)
|
|
|
|
throw new TypeError('Invalid prefix or Network mismatch');
|
|
|
|
if (_address().version !== 0x00)
|
|
|
|
throw new TypeError('Invalid address version');
|
|
|
|
if (_address().data.length !== 20)
|
|
|
|
throw new TypeError('Invalid address data');
|
|
|
|
hash = _address().data;
|
2018-06-05 09:24:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (a.hash) {
|
2019-03-03 15:07:49 +01:00
|
|
|
if (hash.length > 0 && !hash.equals(a.hash))
|
|
|
|
throw new TypeError('Hash mismatch');
|
|
|
|
else hash = a.hash;
|
2018-06-05 09:24:47 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (a.output) {
|
|
|
|
if (
|
|
|
|
a.output.length !== 22 ||
|
|
|
|
a.output[0] !== OPS.OP_0 ||
|
2019-03-03 15:07:49 +01:00
|
|
|
a.output[1] !== 0x14
|
|
|
|
)
|
|
|
|
throw new TypeError('Output is invalid');
|
|
|
|
if (hash.length > 0 && !hash.equals(a.output.slice(2)))
|
|
|
|
throw new TypeError('Hash mismatch');
|
|
|
|
else hash = a.output.slice(2);
|
2018-06-05 09:24:47 +02:00
|
|
|
}
|
|
|
|
|
2018-07-14 12:24:11 +02:00
|
|
|
if (a.pubkey) {
|
2019-03-03 15:07:49 +01:00
|
|
|
const pkh = bcrypto.hash160(a.pubkey);
|
|
|
|
if (hash.length > 0 && !hash.equals(pkh))
|
|
|
|
throw new TypeError('Hash mismatch');
|
|
|
|
else hash = pkh;
|
2020-05-21 04:11:12 +02:00
|
|
|
if (!ecc.isPoint(a.pubkey) || a.pubkey.length !== 33)
|
|
|
|
throw new TypeError('Invalid pubkey for p2wpkh');
|
2018-07-14 12:24:11 +02:00
|
|
|
}
|
|
|
|
|
2018-06-05 09:24:47 +02:00
|
|
|
if (a.witness) {
|
2019-03-03 15:07:49 +01:00
|
|
|
if (a.witness.length !== 2) throw new TypeError('Witness is invalid');
|
|
|
|
if (!bscript.isCanonicalScriptSignature(a.witness[0]))
|
|
|
|
throw new TypeError('Witness has invalid signature');
|
2020-05-21 04:11:12 +02:00
|
|
|
if (!ecc.isPoint(a.witness[1]) || a.witness[1].length !== 33)
|
2019-03-03 15:07:49 +01:00
|
|
|
throw new TypeError('Witness has invalid pubkey');
|
|
|
|
|
|
|
|
if (a.signature && !a.signature.equals(a.witness[0]))
|
|
|
|
throw new TypeError('Signature mismatch');
|
|
|
|
if (a.pubkey && !a.pubkey.equals(a.witness[1]))
|
|
|
|
throw new TypeError('Pubkey mismatch');
|
|
|
|
|
|
|
|
const pkh = bcrypto.hash160(a.witness[1]);
|
|
|
|
if (hash.length > 0 && !hash.equals(pkh))
|
|
|
|
throw new TypeError('Hash mismatch');
|
2018-06-05 09:24:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-03 15:07:49 +01:00
|
|
|
return Object.assign(o, a);
|
2018-06-05 09:24:47 +02:00
|
|
|
}
|