d-yokoi
6 years ago
24 changed files with 2380 additions and 2017 deletions
@ -1,101 +1,119 @@ |
|||||
import { Network } from './networks' |
import { Network } from './networks'; |
||||
import * as types from './types' |
import * as types from './types'; |
||||
import * as bscript from './script' |
import * as bscript from './script'; |
||||
import * as networks from './networks' |
import * as networks from './networks'; |
||||
import * as payments from './payments' |
import * as payments from './payments'; |
||||
|
|
||||
const bech32 = require('bech32') |
const bech32 = require('bech32'); |
||||
const bs58check = require('bs58check') |
const bs58check = require('bs58check'); |
||||
const typeforce = require('typeforce') |
const typeforce = require('typeforce'); |
||||
|
|
||||
export type Base58CheckResult = { |
export type Base58CheckResult = { |
||||
hash: Buffer; |
hash: Buffer; |
||||
version: number; |
version: number; |
||||
} |
}; |
||||
|
|
||||
export type Bech32Result = { |
export type Bech32Result = { |
||||
version: number; |
version: number; |
||||
prefix: string; |
prefix: string; |
||||
data: Buffer; |
data: Buffer; |
||||
} |
}; |
||||
|
|
||||
export function fromBase58Check (address: string): Base58CheckResult { |
export function fromBase58Check(address: string): Base58CheckResult { |
||||
const payload = bs58check.decode(address) |
const payload = bs58check.decode(address); |
||||
|
|
||||
// TODO: 4.0.0, move to "toOutputScript"
|
// TODO: 4.0.0, move to "toOutputScript"
|
||||
if (payload.length < 21) throw new TypeError(address + ' is too short') |
if (payload.length < 21) throw new TypeError(address + ' is too short'); |
||||
if (payload.length > 21) throw new TypeError(address + ' is too long') |
if (payload.length > 21) throw new TypeError(address + ' is too long'); |
||||
|
|
||||
const version = payload.readUInt8(0) |
const version = payload.readUInt8(0); |
||||
const hash = payload.slice(1) |
const hash = payload.slice(1); |
||||
|
|
||||
return { version: version, hash: hash } |
return { version: version, hash: hash }; |
||||
} |
} |
||||
|
|
||||
export function fromBech32 (address: string): Bech32Result { |
export function fromBech32(address: string): Bech32Result { |
||||
const result = bech32.decode(address) |
const result = bech32.decode(address); |
||||
const data = bech32.fromWords(result.words.slice(1)) |
const data = bech32.fromWords(result.words.slice(1)); |
||||
|
|
||||
return { |
return { |
||||
version: result.words[0], |
version: result.words[0], |
||||
prefix: result.prefix, |
prefix: result.prefix, |
||||
data: Buffer.from(data) |
data: Buffer.from(data), |
||||
} |
}; |
||||
} |
} |
||||
|
|
||||
export function toBase58Check (hash: Buffer, version: number): string { |
export function toBase58Check(hash: Buffer, version: number): string { |
||||
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments) |
typeforce(types.tuple(types.Hash160bit, types.UInt8), arguments); |
||||
|
|
||||
const payload = Buffer.allocUnsafe(21) |
const payload = Buffer.allocUnsafe(21); |
||||
payload.writeUInt8(version, 0) |
payload.writeUInt8(version, 0); |
||||
hash.copy(payload, 1) |
hash.copy(payload, 1); |
||||
|
|
||||
return bs58check.encode(payload) |
return bs58check.encode(payload); |
||||
} |
} |
||||
|
|
||||
export function toBech32 (data: Buffer, version: number, prefix: string): string { |
export function toBech32( |
||||
const words = bech32.toWords(data) |
data: Buffer, |
||||
words.unshift(version) |
version: number, |
||||
|
prefix: string, |
||||
|
): string { |
||||
|
const words = bech32.toWords(data); |
||||
|
words.unshift(version); |
||||
|
|
||||
return bech32.encode(prefix, words) |
return bech32.encode(prefix, words); |
||||
} |
} |
||||
|
|
||||
export function fromOutputScript (output: Buffer, network: Network): string { //TODO: Network
|
export function fromOutputScript(output: Buffer, network: Network): string { |
||||
network = network || networks.bitcoin |
//TODO: Network
|
||||
|
network = network || networks.bitcoin; |
||||
|
|
||||
try { return <string>payments.p2pkh({ output, network }).address } catch (e) {} |
try { |
||||
try { return <string>payments.p2sh({ output, network }).address } catch (e) {} |
return <string>payments.p2pkh({ output, network }).address; |
||||
try { return <string>payments.p2wpkh({ output, network }).address } catch (e) {} |
} catch (e) {} |
||||
try { return <string>payments.p2wsh({ output, network }).address } catch (e) {} |
try { |
||||
|
return <string>payments.p2sh({ output, network }).address; |
||||
|
} catch (e) {} |
||||
|
try { |
||||
|
return <string>payments.p2wpkh({ output, network }).address; |
||||
|
} catch (e) {} |
||||
|
try { |
||||
|
return <string>payments.p2wsh({ output, network }).address; |
||||
|
} catch (e) {} |
||||
|
|
||||
throw new Error(bscript.toASM(output) + ' has no matching Address') |
throw new Error(bscript.toASM(output) + ' has no matching Address'); |
||||
} |
} |
||||
|
|
||||
export function toOutputScript (address: string, network: Network): Buffer { |
export function toOutputScript(address: string, network: Network): Buffer { |
||||
network = network || networks.bitcoin |
network = network || networks.bitcoin; |
||||
|
|
||||
let decodeBase58: Base58CheckResult | undefined = undefined |
let decodeBase58: Base58CheckResult | undefined = undefined; |
||||
let decodeBech32: Bech32Result | undefined = undefined |
let decodeBech32: Bech32Result | undefined = undefined; |
||||
try { |
try { |
||||
decodeBase58 = fromBase58Check(address) |
decodeBase58 = fromBase58Check(address); |
||||
} catch (e) {} |
} catch (e) {} |
||||
|
|
||||
if (decodeBase58) { |
if (decodeBase58) { |
||||
if (decodeBase58.version === network.pubKeyHash) return <Buffer>payments.p2pkh({ hash: decodeBase58.hash }).output |
if (decodeBase58.version === network.pubKeyHash) |
||||
if (decodeBase58.version === network.scriptHash) return <Buffer>payments.p2sh({ hash: decodeBase58.hash }).output |
return <Buffer>payments.p2pkh({ hash: decodeBase58.hash }).output; |
||||
|
if (decodeBase58.version === network.scriptHash) |
||||
|
return <Buffer>payments.p2sh({ hash: decodeBase58.hash }).output; |
||||
} else { |
} else { |
||||
try { |
try { |
||||
decodeBech32 = fromBech32(address) |
decodeBech32 = fromBech32(address); |
||||
} catch (e) {} |
} catch (e) {} |
||||
|
|
||||
if (decodeBech32) { |
if (decodeBech32) { |
||||
if (decodeBech32.prefix !== network.bech32) throw new Error(address + ' has an invalid prefix') |
if (decodeBech32.prefix !== network.bech32) |
||||
|
throw new Error(address + ' has an invalid prefix'); |
||||
if (decodeBech32.version === 0) { |
if (decodeBech32.version === 0) { |
||||
if (decodeBech32.data.length === 20) return <Buffer>payments.p2wpkh({ hash: decodeBech32.data }).output |
if (decodeBech32.data.length === 20) |
||||
if (decodeBech32.data.length === 32) return <Buffer>payments.p2wsh({ hash: decodeBech32.data }).output |
return <Buffer>payments.p2wpkh({ hash: decodeBech32.data }).output; |
||||
|
if (decodeBech32.data.length === 32) |
||||
|
return <Buffer>payments.p2wsh({ hash: decodeBech32.data }).output; |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
throw new Error(address + ' has no matching Script') |
throw new Error(address + ' has no matching Script'); |
||||
} |
} |
||||
|
@ -1,37 +1,44 @@ |
|||||
// https://github.com/feross/buffer/blob/master/index.js#L1127
|
// https://github.com/feross/buffer/blob/master/index.js#L1127
|
||||
function verifuint (value: number, max: number): void { |
function verifuint(value: number, max: number): void { |
||||
if (typeof value !== 'number') throw new Error('cannot write a non-number as a number') |
if (typeof value !== 'number') |
||||
if (value < 0) throw new Error('specified a negative value for writing an unsigned value') |
throw new Error('cannot write a non-number as a number'); |
||||
if (value > max) throw new Error('RangeError: value out of range') |
if (value < 0) |
||||
if (Math.floor(value) !== value) throw new Error('value has a fractional component') |
throw new Error('specified a negative value for writing an unsigned value'); |
||||
|
if (value > max) throw new Error('RangeError: value out of range'); |
||||
|
if (Math.floor(value) !== value) |
||||
|
throw new Error('value has a fractional component'); |
||||
} |
} |
||||
|
|
||||
export function readUInt64LE (buffer: Buffer, offset: number): number { |
export function readUInt64LE(buffer: Buffer, offset: number): number { |
||||
const a = buffer.readUInt32LE(offset) |
const a = buffer.readUInt32LE(offset); |
||||
let b = buffer.readUInt32LE(offset + 4) |
let b = buffer.readUInt32LE(offset + 4); |
||||
b *= 0x100000000 |
b *= 0x100000000; |
||||
|
|
||||
verifuint(b + a, 0x001fffffffffffff) |
verifuint(b + a, 0x001fffffffffffff); |
||||
return b + a |
return b + a; |
||||
} |
} |
||||
|
|
||||
export function writeUInt64LE (buffer: Buffer, value: number, offset: number): number { |
export function writeUInt64LE( |
||||
verifuint(value, 0x001fffffffffffff) |
buffer: Buffer, |
||||
|
value: number, |
||||
|
offset: number, |
||||
|
): number { |
||||
|
verifuint(value, 0x001fffffffffffff); |
||||
|
|
||||
buffer.writeInt32LE(value & -1, offset) |
buffer.writeInt32LE(value & -1, offset); |
||||
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4) |
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4); |
||||
return offset + 8 |
return offset + 8; |
||||
} |
} |
||||
|
|
||||
export function reverseBuffer (buffer: Buffer): Buffer { |
export function reverseBuffer(buffer: Buffer): Buffer { |
||||
if (buffer.length < 1) return buffer |
if (buffer.length < 1) return buffer; |
||||
let j = buffer.length - 1 |
let j = buffer.length - 1; |
||||
let tmp = 0 |
let tmp = 0; |
||||
for (let i = 0; i < buffer.length / 2; i++) { |
for (let i = 0; i < buffer.length / 2; i++) { |
||||
tmp = buffer[i] |
tmp = buffer[i]; |
||||
buffer[i] = buffer[j] |
buffer[i] = buffer[j]; |
||||
buffer[j] = tmp |
buffer[j] = tmp; |
||||
j-- |
j--; |
||||
} |
} |
||||
return buffer |
return buffer; |
||||
} |
} |
||||
|
@ -1,21 +1,27 @@ |
|||||
const createHash = require('create-hash') |
const createHash = require('create-hash'); |
||||
|
|
||||
export function ripemd160 (buffer: Buffer): Buffer { |
export function ripemd160(buffer: Buffer): Buffer { |
||||
return createHash('rmd160').update(buffer).digest() |
return createHash('rmd160') |
||||
|
.update(buffer) |
||||
|
.digest(); |
||||
} |
} |
||||
|
|
||||
export function sha1 (buffer: Buffer): Buffer { |
export function sha1(buffer: Buffer): Buffer { |
||||
return createHash('sha1').update(buffer).digest() |
return createHash('sha1') |
||||
|
.update(buffer) |
||||
|
.digest(); |
||||
} |
} |
||||
|
|
||||
export function sha256 (buffer: Buffer): Buffer { |
export function sha256(buffer: Buffer): Buffer { |
||||
return createHash('sha256').update(buffer).digest() |
return createHash('sha256') |
||||
|
.update(buffer) |
||||
|
.digest(); |
||||
} |
} |
||||
|
|
||||
export function hash160 (buffer: Buffer): Buffer { |
export function hash160(buffer: Buffer): Buffer { |
||||
return ripemd160(sha256(buffer)) |
return ripemd160(sha256(buffer)); |
||||
} |
} |
||||
|
|
||||
export function hash256 (buffer: Buffer): Buffer { |
export function hash256(buffer: Buffer): Buffer { |
||||
return sha256(sha256(buffer)) |
return sha256(sha256(buffer)); |
||||
} |
} |
||||
|
@ -1,130 +1,132 @@ |
|||||
import { Network } from './networks' |
import { Network } from './networks'; |
||||
import * as NETWORKS from './networks' |
import * as NETWORKS from './networks'; |
||||
import * as types from './types' |
import * as types from './types'; |
||||
const ecc = require('tiny-secp256k1') |
const ecc = require('tiny-secp256k1'); |
||||
const randomBytes = require('randombytes') |
const randomBytes = require('randombytes'); |
||||
const typeforce = require('typeforce') |
const typeforce = require('typeforce'); |
||||
const wif = require('wif') |
const wif = require('wif'); |
||||
|
|
||||
const isOptions = typeforce.maybe(typeforce.compile({ |
const isOptions = typeforce.maybe( |
||||
compressed: types.maybe(types.Boolean), |
typeforce.compile({ |
||||
network: types.maybe(types.Network) |
compressed: types.maybe(types.Boolean), |
||||
})) |
network: types.maybe(types.Network), |
||||
|
}), |
||||
|
); |
||||
|
|
||||
interface ECPairOptions { |
interface ECPairOptions { |
||||
compressed?: boolean |
compressed?: boolean; |
||||
network?: Network |
network?: Network; |
||||
rng?(arg0: Buffer): Buffer |
rng?(arg0: Buffer): Buffer; |
||||
} |
} |
||||
|
|
||||
export interface ECPairInterface { |
export interface ECPairInterface { |
||||
compressed: boolean |
compressed: boolean; |
||||
network: Network |
network: Network; |
||||
privateKey?: Buffer |
privateKey?: Buffer; |
||||
publicKey?: Buffer |
publicKey?: Buffer; |
||||
toWIF(): string |
toWIF(): string; |
||||
sign(hash: Buffer): Buffer |
sign(hash: Buffer): Buffer; |
||||
verify(hash: Buffer, signature: Buffer): Buffer |
verify(hash: Buffer, signature: Buffer): Buffer; |
||||
getPublicKey?(): Buffer |
getPublicKey?(): Buffer; |
||||
} |
} |
||||
|
|
||||
class ECPair implements ECPairInterface { |
class ECPair implements ECPairInterface { |
||||
compressed: boolean |
compressed: boolean; |
||||
network: Network |
network: Network; |
||||
private __d?: Buffer |
private __d?: Buffer; |
||||
private __Q?: Buffer |
private __Q?: Buffer; |
||||
|
|
||||
constructor (d?: Buffer, Q?: Buffer, options?: ECPairOptions) { |
constructor(d?: Buffer, Q?: Buffer, options?: ECPairOptions) { |
||||
if (options === undefined) options = {} |
if (options === undefined) options = {}; |
||||
this.compressed = options.compressed === undefined ? true : options.compressed |
this.compressed = |
||||
this.network = options.network || NETWORKS.bitcoin |
options.compressed === undefined ? true : options.compressed; |
||||
|
this.network = options.network || NETWORKS.bitcoin; |
||||
this.__d = undefined |
|
||||
this.__Q = undefined |
this.__d = undefined; |
||||
if (d !== undefined) this.__d = d |
this.__Q = undefined; |
||||
if (Q !== undefined) this.__Q = ecc.pointCompress(Q, this.compressed) |
if (d !== undefined) this.__d = d; |
||||
|
if (Q !== undefined) this.__Q = ecc.pointCompress(Q, this.compressed); |
||||
} |
} |
||||
|
|
||||
get privateKey (): Buffer | undefined { |
get privateKey(): Buffer | undefined { |
||||
return this.__d |
return this.__d; |
||||
} |
} |
||||
|
|
||||
get publicKey (): Buffer | undefined { |
get publicKey(): Buffer | undefined { |
||||
if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__d, this.compressed) |
if (!this.__Q) this.__Q = ecc.pointFromScalar(this.__d, this.compressed); |
||||
return this.__Q |
return this.__Q; |
||||
} |
} |
||||
|
|
||||
toWIF (): string { |
toWIF(): string { |
||||
if (!this.__d) throw new Error('Missing private key') |
if (!this.__d) throw new Error('Missing private key'); |
||||
return wif.encode(this.network.wif, this.__d, this.compressed) |
return wif.encode(this.network.wif, this.__d, this.compressed); |
||||
} |
} |
||||
|
|
||||
sign (hash: Buffer): Buffer { |
sign(hash: Buffer): Buffer { |
||||
if (!this.__d) throw new Error('Missing private key') |
if (!this.__d) throw new Error('Missing private key'); |
||||
return ecc.sign(hash, this.__d) |
return ecc.sign(hash, this.__d); |
||||
} |
} |
||||
|
|
||||
verify (hash: Buffer, signature: Buffer): Buffer { |
verify(hash: Buffer, signature: Buffer): Buffer { |
||||
return ecc.verify(hash, this.publicKey, signature) |
return ecc.verify(hash, this.publicKey, signature); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
function fromPrivateKey (buffer: Buffer, options?: ECPairOptions): ECPair { |
function fromPrivateKey(buffer: Buffer, options?: ECPairOptions): ECPair { |
||||
typeforce(types.Buffer256bit, buffer) |
typeforce(types.Buffer256bit, buffer); |
||||
if (!ecc.isPrivate(buffer)) throw new TypeError('Private key not in range [1, n)') |
if (!ecc.isPrivate(buffer)) |
||||
typeforce(isOptions, options) |
throw new TypeError('Private key not in range [1, n)'); |
||||
|
typeforce(isOptions, options); |
||||
|
|
||||
return new ECPair(buffer, undefined, <ECPairOptions>options) |
return new ECPair(buffer, undefined, <ECPairOptions>options); |
||||
} |
} |
||||
|
|
||||
function fromPublicKey (buffer: Buffer, options?: ECPairOptions): ECPair { |
function fromPublicKey(buffer: Buffer, options?: ECPairOptions): ECPair { |
||||
typeforce(ecc.isPoint, buffer) |
typeforce(ecc.isPoint, buffer); |
||||
typeforce(isOptions, options) |
typeforce(isOptions, options); |
||||
return new ECPair(undefined, buffer, <ECPairOptions>options) |
return new ECPair(undefined, buffer, <ECPairOptions>options); |
||||
} |
} |
||||
|
|
||||
function fromWIF (string: string, network?: Network | Array<Network>): ECPair { |
function fromWIF(string: string, network?: Network | Array<Network>): ECPair { |
||||
const decoded = wif.decode(string) |
const decoded = wif.decode(string); |
||||
const version = decoded.version |
const version = decoded.version; |
||||
|
|
||||
// list of networks?
|
// list of networks?
|
||||
if (types.Array(network)) { |
if (types.Array(network)) { |
||||
network = <Network>(<Array<Network>>network).filter(function (x: Network) { |
network = <Network>(<Array<Network>>network) |
||||
return version === x.wif |
.filter(function(x: Network) { |
||||
}).pop() |
return version === x.wif; |
||||
|
}) |
||||
|
.pop(); |
||||
|
|
||||
if (!network) throw new Error('Unknown network version') |
if (!network) throw new Error('Unknown network version'); |
||||
|
|
||||
// otherwise, assume a network object (or default to bitcoin)
|
// otherwise, assume a network object (or default to bitcoin)
|
||||
} else { |
} else { |
||||
network = network || NETWORKS.bitcoin |
network = network || NETWORKS.bitcoin; |
||||
|
|
||||
if (version !== (<Network>network).wif) throw new Error('Invalid network version') |
if (version !== (<Network>network).wif) |
||||
|
throw new Error('Invalid network version'); |
||||
} |
} |
||||
|
|
||||
return fromPrivateKey(decoded.privateKey, { |
return fromPrivateKey(decoded.privateKey, { |
||||
compressed: decoded.compressed, |
compressed: decoded.compressed, |
||||
network: <Network>network |
network: <Network>network, |
||||
}) |
}); |
||||
} |
} |
||||
|
|
||||
function makeRandom (options?: ECPairOptions): ECPair { |
function makeRandom(options?: ECPairOptions): ECPair { |
||||
typeforce(isOptions, options) |
typeforce(isOptions, options); |
||||
if (options === undefined) options = {} |
if (options === undefined) options = {}; |
||||
const rng = options.rng || randomBytes |
const rng = options.rng || randomBytes; |
||||
|
|
||||
let d |
let d; |
||||
do { |
do { |
||||
d = rng(32) |
d = rng(32); |
||||
typeforce(types.Buffer256bit, d) |
typeforce(types.Buffer256bit, d); |
||||
} while (!ecc.isPrivate(d)) |
} while (!ecc.isPrivate(d)); |
||||
|
|
||||
return fromPrivateKey(d, options) |
return fromPrivateKey(d, options); |
||||
} |
} |
||||
|
|
||||
export { |
export { makeRandom, fromPrivateKey, fromPublicKey, fromWIF }; |
||||
makeRandom, |
|
||||
fromPrivateKey, |
|
||||
fromPublicKey, |
|
||||
fromWIF |
|
||||
} |
|
||||
|
@ -1,28 +1,20 @@ |
|||||
import * as bip32 from 'bip32' |
import * as bip32 from 'bip32'; |
||||
import * as ECPair from './ecpair' |
import * as ECPair from './ecpair'; |
||||
import * as address from './address' |
import * as address from './address'; |
||||
import * as crypto from './crypto' |
import * as crypto from './crypto'; |
||||
import * as networks from './networks' |
import * as networks from './networks'; |
||||
import * as payments from './payments' |
import * as payments from './payments'; |
||||
import * as script from './script' |
import * as script from './script'; |
||||
|
|
||||
export { |
export { ECPair, address, bip32, crypto, networks, payments, script }; |
||||
ECPair, |
|
||||
address, |
|
||||
bip32, |
|
||||
crypto, |
|
||||
networks, |
|
||||
payments, |
|
||||
script, |
|
||||
} |
|
||||
|
|
||||
export { Block } from './block' |
export { Block } from './block'; |
||||
export { Transaction } from './transaction' |
export { Transaction } from './transaction'; |
||||
export { TransactionBuilder } from './transaction_builder' |
export { TransactionBuilder } from './transaction_builder'; |
||||
export { OPS as opcodes } from './script' |
export { OPS as opcodes } from './script'; |
||||
|
|
||||
export { Payment, PaymentOpts } from './payments' |
export { Payment, PaymentOpts } from './payments'; |
||||
export { Input as TxInput, Output as TxOutput } from './transaction' |
export { Input as TxInput, Output as TxOutput } from './transaction'; |
||||
export { Network } from './networks' |
export { Network } from './networks'; |
||||
export { OpCode } from './script' |
export { OpCode } from './script'; |
||||
export { BIP32Interface } from 'bip32' |
export { BIP32Interface } from 'bip32'; |
||||
|
@ -1,54 +1,59 @@ |
|||||
import { Payment, PaymentOpts } from './index' // eslint-disable-line
|
import { Payment, PaymentOpts } from './index'; // eslint-disable-line
|
||||
import * as bscript from '../script' |
import * as bscript from '../script'; |
||||
import * as lazy from './lazy' |
import * as lazy from './lazy'; |
||||
import { bitcoin as BITCOIN_NETWORK } from '../networks' |
import { bitcoin as BITCOIN_NETWORK } from '../networks'; |
||||
const typef = require('typeforce') |
const typef = require('typeforce'); |
||||
const OPS = bscript.OPS |
const OPS = bscript.OPS; |
||||
|
|
||||
function stacksEqual (a: Array<Buffer>, b: Array<Buffer>): boolean { |
function stacksEqual(a: Array<Buffer>, b: Array<Buffer>): boolean { |
||||
if (a.length !== b.length) return false |
if (a.length !== b.length) return false; |
||||
|
|
||||
return a.every(function (x, i) { |
return a.every(function(x, i) { |
||||
return x.equals(b[i]) |
return x.equals(b[i]); |
||||
}) |
}); |
||||
} |
} |
||||
|
|
||||
// output: OP_RETURN ...
|
// output: OP_RETURN ...
|
||||
export function p2data (a: Payment, opts?: PaymentOpts): Payment { |
export function p2data(a: Payment, opts?: PaymentOpts): Payment { |
||||
if ( |
if (!a.data && !a.output) throw new TypeError('Not enough data'); |
||||
!a.data && |
opts = Object.assign({ validate: true }, opts || {}); |
||||
!a.output |
|
||||
) throw new TypeError('Not enough data') |
typef( |
||||
opts = Object.assign({ validate: true }, opts || {}) |
{ |
||||
|
network: typef.maybe(typef.Object), |
||||
typef({ |
output: typef.maybe(typef.Buffer), |
||||
network: typef.maybe(typef.Object), |
data: typef.maybe(typef.arrayOf(typef.Buffer)), |
||||
output: typef.maybe(typef.Buffer), |
}, |
||||
data: typef.maybe(typef.arrayOf(typef.Buffer)) |
a, |
||||
}, a) |
); |
||||
|
|
||||
const network = a.network || BITCOIN_NETWORK |
const network = a.network || BITCOIN_NETWORK; |
||||
const o = <Payment>{ network } |
const o = <Payment>{ network }; |
||||
|
|
||||
lazy.prop(o, 'output', function () { |
lazy.prop(o, 'output', function() { |
||||
if (!a.data) return |
if (!a.data) return; |
||||
return bscript.compile((<Array<Buffer | number>>[OPS.OP_RETURN]).concat(a.data)) |
return bscript.compile( |
||||
}) |
(<Array<Buffer | number>>[OPS.OP_RETURN]).concat(a.data), |
||||
lazy.prop(o, 'data', function () { |
); |
||||
if (!a.output) return |
}); |
||||
return bscript.decompile(a.output)!.slice(1) |
lazy.prop(o, 'data', function() { |
||||
}) |
if (!a.output) return; |
||||
|
return bscript.decompile(a.output)!.slice(1); |
||||
|
}); |
||||
|
|
||||
// extended validation
|
// extended validation
|
||||
if (opts.validate) { |
if (opts.validate) { |
||||
if (a.output) { |
if (a.output) { |
||||
const chunks = bscript.decompile(a.output) |
const chunks = bscript.decompile(a.output); |
||||
if (chunks![0] !== OPS.OP_RETURN) throw new TypeError('Output is invalid') |
if (chunks![0] !== OPS.OP_RETURN) |
||||
if (!chunks!.slice(1).every(typef.Buffer)) throw new TypeError('Output is invalid') |
throw new TypeError('Output is invalid'); |
||||
|
if (!chunks!.slice(1).every(typef.Buffer)) |
||||
if (a.data && !stacksEqual(a.data, <Array<Buffer>>o.data)) throw new TypeError('Data mismatch') |
throw new TypeError('Output is invalid'); |
||||
|
|
||||
|
if (a.data && !stacksEqual(a.data, <Array<Buffer>>o.data)) |
||||
|
throw new TypeError('Data mismatch'); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
return Object.assign(o, a) |
return Object.assign(o, a); |
||||
} |
} |
||||
|
@ -1,35 +1,35 @@ |
|||||
import { Network } from '../networks' // eslint-disable-line
|
import { Network } from '../networks'; // eslint-disable-line
|
||||
import { p2data as embed } from './embed' |
import { p2data as embed } from './embed'; |
||||
import { p2ms } from './p2ms' |
import { p2ms } from './p2ms'; |
||||
import { p2pk } from './p2pk' |
import { p2pk } from './p2pk'; |
||||
import { p2pkh } from './p2pkh' |
import { p2pkh } from './p2pkh'; |
||||
import { p2sh } from './p2sh' |
import { p2sh } from './p2sh'; |
||||
import { p2wpkh } from './p2wpkh' |
import { p2wpkh } from './p2wpkh'; |
||||
import { p2wsh } from './p2wsh' |
import { p2wsh } from './p2wsh'; |
||||
|
|
||||
export interface Payment { |
export interface Payment { |
||||
network?: Network, |
network?: Network; |
||||
output?: Buffer, |
output?: Buffer; |
||||
data?: Array<Buffer>, |
data?: Array<Buffer>; |
||||
m?: number, |
m?: number; |
||||
n?: number, |
n?: number; |
||||
pubkeys?: Array<Buffer>, |
pubkeys?: Array<Buffer>; |
||||
input?: Buffer, |
input?: Buffer; |
||||
signatures?: Array<Buffer>, |
signatures?: Array<Buffer>; |
||||
pubkey?: Buffer, |
pubkey?: Buffer; |
||||
signature?: Buffer, |
signature?: Buffer; |
||||
address?: string, |
address?: string; |
||||
hash?: Buffer, |
hash?: Buffer; |
||||
redeem?: Payment, |
redeem?: Payment; |
||||
witness?: Array<Buffer>, |
witness?: Array<Buffer>; |
||||
} |
} |
||||
|
|
||||
export interface PaymentOpts { |
export interface PaymentOpts { |
||||
validate?: boolean, |
validate?: boolean; |
||||
allowIncomplete?: boolean, |
allowIncomplete?: boolean; |
||||
} |
} |
||||
|
|
||||
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh } |
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh }; |
||||
|
|
||||
// TODO
|
// TODO
|
||||
// witness commitment
|
// witness commitment
|
||||
|
@ -1,28 +1,28 @@ |
|||||
export function prop (object: Object, name: string, f: ()=>any): void { |
export function prop(object: Object, name: string, f: () => any): void { |
||||
Object.defineProperty(object, name, { |
Object.defineProperty(object, name, { |
||||
configurable: true, |
configurable: true, |
||||
enumerable: true, |
enumerable: true, |
||||
get: function () { |
get: function() { |
||||
let value = f.call(this) |
let value = f.call(this); |
||||
this[name] = value |
this[name] = value; |
||||
return value |
return value; |
||||
}, |
}, |
||||
set: function (value) { |
set: function(value) { |
||||
Object.defineProperty(this, name, { |
Object.defineProperty(this, name, { |
||||
configurable: true, |
configurable: true, |
||||
enumerable: true, |
enumerable: true, |
||||
value: value, |
value: value, |
||||
writable: true |
writable: true, |
||||
}) |
}); |
||||
} |
}, |
||||
}) |
}); |
||||
} |
} |
||||
|
|
||||
export function value <T> (f: ()=>T): ()=>T { |
export function value<T>(f: () => T): () => T { |
||||
let value: T |
let value: T; |
||||
return function (): T { |
return function(): T { |
||||
if (value !== undefined) return value |
if (value !== undefined) return value; |
||||
value = f() |
value = f(); |
||||
return value |
return value; |
||||
} |
}; |
||||
} |
} |
||||
|
@ -1,141 +1,165 @@ |
|||||
import { Payment, PaymentOpts } from './index' // eslint-disable-line
|
import { Payment, PaymentOpts } from './index'; // eslint-disable-line
|
||||
import * as bscript from '../script' |
import * as bscript from '../script'; |
||||
import * as lazy from './lazy' |
import * as lazy from './lazy'; |
||||
import { bitcoin as BITCOIN_NETWORK } from '../networks' |
import { bitcoin as BITCOIN_NETWORK } from '../networks'; |
||||
const OPS = bscript.OPS |
const OPS = bscript.OPS; |
||||
const typef = require('typeforce') |
const typef = require('typeforce'); |
||||
const ecc = require('tiny-secp256k1') |
const ecc = require('tiny-secp256k1'); |
||||
|
|
||||
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
|
const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1
|
||||
|
|
||||
function stacksEqual (a: Array<Buffer>, b: Array<Buffer>): boolean { |
function stacksEqual(a: Array<Buffer>, b: Array<Buffer>): boolean { |
||||
if (a.length !== b.length) return false |
if (a.length !== b.length) return false; |
||||
|
|
||||
return a.every(function (x, i) { |
return a.every(function(x, i) { |
||||
return x.equals(b[i]) |
return x.equals(b[i]); |
||||
}) |
}); |
||||
} |
} |
||||
|
|
||||
// input: OP_0 [signatures ...]
|
// input: OP_0 [signatures ...]
|
||||
// output: m [pubKeys ...] n OP_CHECKMULTISIG
|
// output: m [pubKeys ...] n OP_CHECKMULTISIG
|
||||
export function p2ms (a: Payment, opts?: PaymentOpts): Payment { |
export function p2ms(a: Payment, opts?: PaymentOpts): Payment { |
||||
if ( |
if ( |
||||
!a.input && |
!a.input && |
||||
!a.output && |
!a.output && |
||||
!(a.pubkeys && a.m !== undefined) && |
!(a.pubkeys && a.m !== undefined) && |
||||
!a.signatures |
!a.signatures |
||||
) throw new TypeError('Not enough data') |
) |
||||
opts = Object.assign({ validate: true }, opts || {}) |
throw new TypeError('Not enough data'); |
||||
|
opts = Object.assign({ validate: true }, opts || {}); |
||||
function isAcceptableSignature (x: Buffer | number) { |
|
||||
return bscript.isCanonicalScriptSignature(<Buffer>x) || |
function isAcceptableSignature(x: Buffer | number) { |
||||
(opts!.allowIncomplete && |
return ( |
||||
(<number> x === OPS.OP_0)) !== undefined // eslint-disable-line
|
bscript.isCanonicalScriptSignature(<Buffer>x) || |
||||
|
(opts!.allowIncomplete && <number>x === OPS.OP_0) !== undefined |
||||
|
); // eslint-disable-line
|
||||
} |
} |
||||
|
|
||||
typef({ |
typef( |
||||
network: typef.maybe(typef.Object), |
{ |
||||
m: typef.maybe(typef.Number), |
network: typef.maybe(typef.Object), |
||||
n: typef.maybe(typef.Number), |
m: typef.maybe(typef.Number), |
||||
output: typef.maybe(typef.Buffer), |
n: typef.maybe(typef.Number), |
||||
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)), |
output: typef.maybe(typef.Buffer), |
||||
|
pubkeys: typef.maybe(typef.arrayOf(ecc.isPoint)), |
||||
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), |
|
||||
input: typef.maybe(typef.Buffer) |
signatures: typef.maybe(typef.arrayOf(isAcceptableSignature)), |
||||
}, a) |
input: typef.maybe(typef.Buffer), |
||||
|
}, |
||||
const network = a.network || BITCOIN_NETWORK |
a, |
||||
const o: Payment = { network } |
); |
||||
|
|
||||
let chunks: Array<Buffer | number> = [] |
const network = a.network || BITCOIN_NETWORK; |
||||
let decoded = false |
const o: Payment = { network }; |
||||
function decode (output: Buffer | Array<Buffer | number>): void { |
|
||||
if (decoded) return |
let chunks: Array<Buffer | number> = []; |
||||
decoded = true |
let decoded = false; |
||||
chunks = <Array<Buffer | number>>bscript.decompile(output) |
function decode(output: Buffer | Array<Buffer | number>): void { |
||||
o.m = <number> chunks[0] - OP_INT_BASE // eslint-disable-line
|
if (decoded) return; |
||||
o.n = <number> chunks[chunks.length - 2] - OP_INT_BASE // eslint-disable-line
|
decoded = true; |
||||
o.pubkeys = <Array<Buffer>>chunks.slice(1, -2) |
chunks = <Array<Buffer | number>>bscript.decompile(output); |
||||
|
o.m = <number>chunks[0] - OP_INT_BASE; // eslint-disable-line
|
||||
|
o.n = <number>chunks[chunks.length - 2] - OP_INT_BASE; // eslint-disable-line
|
||||
|
o.pubkeys = <Array<Buffer>>chunks.slice(1, -2); |
||||
} |
} |
||||
|
|
||||
lazy.prop(o, 'output', function () { |
lazy.prop(o, 'output', function() { |
||||
if (!a.m) return |
if (!a.m) return; |
||||
if (!o.n) return |
if (!o.n) return; |
||||
if (!a.pubkeys) return |
if (!a.pubkeys) return; |
||||
return bscript.compile((<Array<Buffer | number>>[]).concat( |
return bscript.compile( |
||||
OP_INT_BASE + a.m, |
(<Array<Buffer | number>>[]).concat( |
||||
a.pubkeys, |
OP_INT_BASE + a.m, |
||||
OP_INT_BASE + o.n, |
a.pubkeys, |
||||
OPS.OP_CHECKMULTISIG |
OP_INT_BASE + o.n, |
||||
)) |
OPS.OP_CHECKMULTISIG, |
||||
}) |
), |
||||
lazy.prop(o, 'm', function () { |
); |
||||
if (!o.output) return |
}); |
||||
decode(o.output) |
lazy.prop(o, 'm', function() { |
||||
return o.m |
if (!o.output) return; |
||||
}) |
decode(o.output); |
||||
lazy.prop(o, 'n', function () { |
return o.m; |
||||
if (!o.pubkeys) return |
}); |
||||
return o.pubkeys.length |
lazy.prop(o, 'n', function() { |
||||
}) |
if (!o.pubkeys) return; |
||||
lazy.prop(o, 'pubkeys', function () { |
return o.pubkeys.length; |
||||
if (!a.output) return |
}); |
||||
decode(a.output) |
lazy.prop(o, 'pubkeys', function() { |
||||
return o.pubkeys |
if (!a.output) return; |
||||
}) |
decode(a.output); |
||||
lazy.prop(o, 'signatures', function () { |
return o.pubkeys; |
||||
if (!a.input) return |
}); |
||||
return bscript.decompile(a.input)!.slice(1) |
lazy.prop(o, 'signatures', function() { |
||||
}) |
if (!a.input) return; |
||||
lazy.prop(o, 'input', function () { |
return bscript.decompile(a.input)!.slice(1); |
||||
if (!a.signatures) return |
}); |
||||
return bscript.compile((<Array<Buffer | number>>[OPS.OP_0]).concat(a.signatures)) |
lazy.prop(o, 'input', function() { |
||||
}) |
if (!a.signatures) return; |
||||
lazy.prop(o, 'witness', function () { |
return bscript.compile( |
||||
if (!o.input) return |
(<Array<Buffer | number>>[OPS.OP_0]).concat(a.signatures), |
||||
return [] |
); |
||||
}) |
}); |
||||
|
lazy.prop(o, 'witness', function() { |
||||
|
if (!o.input) return; |
||||
|
return []; |
||||
|
}); |
||||
|
|
||||
// extended validation
|
// extended validation
|
||||
if (opts.validate) { |
if (opts.validate) { |
||||
if (a.output) { |
if (a.output) { |
||||
decode(a.output) |
decode(a.output); |
||||
if (!typef.Number(chunks[0])) throw new TypeError('Output is invalid') |
if (!typef.Number(chunks[0])) throw new TypeError('Output is invalid'); |
||||
if (!typef.Number(chunks[chunks.length - 2])) throw new TypeError('Output is invalid') |
if (!typef.Number(chunks[chunks.length - 2])) |
||||
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) throw new TypeError('Output is invalid') |
throw new TypeError('Output is invalid'); |
||||
|
if (chunks[chunks.length - 1] !== OPS.OP_CHECKMULTISIG) |
||||
|
throw new TypeError('Output is invalid'); |
||||
|
|
||||
if ( |
if ( |
||||
o.m! <= 0 || // eslint-disable-line
|
o.m! <= 0 || // eslint-disable-line
|
||||
o.n! > 16 || // eslint-disable-line
|
o.n! > 16 || // eslint-disable-line
|
||||
o.m! > o.n! || // eslint-disable-line
|
o.m! > o.n! || // eslint-disable-line
|
||||
o.n !== chunks.length - 3) throw new TypeError('Output is invalid') |
o.n !== chunks.length - 3 |
||||
if (!o.pubkeys!.every(x => ecc.isPoint(x))) throw new TypeError('Output is invalid') |
) |
||||
|
throw new TypeError('Output is invalid'); |
||||
if (a.m !== undefined && a.m !== o.m) throw new TypeError('m mismatch') |
if (!o.pubkeys!.every(x => ecc.isPoint(x))) |
||||
if (a.n !== undefined && a.n !== o.n) throw new TypeError('n mismatch') |
throw new TypeError('Output is invalid'); |
||||
if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys!)) throw new TypeError('Pubkeys mismatch') |
|
||||
|
if (a.m !== undefined && a.m !== o.m) throw new TypeError('m mismatch'); |
||||
|
if (a.n !== undefined && a.n !== o.n) throw new TypeError('n mismatch'); |
||||
|
if (a.pubkeys && !stacksEqual(a.pubkeys, o.pubkeys!)) |
||||
|
throw new TypeError('Pubkeys mismatch'); |
||||
} |
} |
||||
|
|
||||
if (a.pubkeys) { |
if (a.pubkeys) { |
||||
if (a.n !== undefined && a.n !== a.pubkeys.length) throw new TypeError('Pubkey count mismatch') |
if (a.n !== undefined && a.n !== a.pubkeys.length) |
||||
o.n = a.pubkeys.length |
throw new TypeError('Pubkey count mismatch'); |
||||
|
o.n = a.pubkeys.length; |
||||
|
|
||||
if (o.n < o.m!) throw new TypeError('Pubkey count cannot be less than m') |
if (o.n < o.m!) throw new TypeError('Pubkey count cannot be less than m'); |
||||
} |
} |
||||
|
|
||||
if (a.signatures) { |
if (a.signatures) { |
||||
if (a.signatures.length < o.m!) throw new TypeError('Not enough signatures provided') |
if (a.signatures.length < o.m!) |
||||
if (a.signatures.length > o.m!) throw new TypeError('Too many signatures provided') |
throw new TypeError('Not enough signatures provided'); |
||||
|
if (a.signatures.length > o.m!) |
||||
|
throw new TypeError('Too many signatures provided'); |
||||
} |
} |
||||
|
|
||||
if (a.input) { |
if (a.input) { |
||||
if (a.input[0] !== OPS.OP_0) throw new TypeError('Input is invalid') |
if (a.input[0] !== OPS.OP_0) throw new TypeError('Input is invalid'); |
||||
if (o.signatures!.length === 0 || !o.signatures!.every(isAcceptableSignature)) throw new TypeError('Input has invalid signature(s)') |
if ( |
||||
|
o.signatures!.length === 0 || |
||||
if (a.signatures && !stacksEqual(a.signatures, o.signatures!)) throw new TypeError('Signature mismatch') |
!o.signatures!.every(isAcceptableSignature) |
||||
if (a.m !== undefined && a.m !== a.signatures!.length) throw new TypeError('Signature count mismatch') |
) |
||||
|
throw new TypeError('Input has invalid signature(s)'); |
||||
|
|
||||
|
if (a.signatures && !stacksEqual(a.signatures, o.signatures!)) |
||||
|
throw new TypeError('Signature mismatch'); |
||||
|
if (a.m !== undefined && a.m !== a.signatures!.length) |
||||
|
throw new TypeError('Signature count mismatch'); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
return Object.assign(o, a) |
return Object.assign(o, a); |
||||
} |
} |
||||
|
@ -1,78 +1,80 @@ |
|||||
import { Payment, PaymentOpts } from './index' // eslint-disable-line
|
import { Payment, PaymentOpts } from './index'; // eslint-disable-line
|
||||
import * as bscript from '../script' |
import * as bscript from '../script'; |
||||
import * as lazy from './lazy' |
import * as lazy from './lazy'; |
||||
import { bitcoin as BITCOIN_NETWORK } from '../networks' |
import { bitcoin as BITCOIN_NETWORK } from '../networks'; |
||||
const typef = require('typeforce') |
const typef = require('typeforce'); |
||||
const OPS = bscript.OPS |
const OPS = bscript.OPS; |
||||
const ecc = require('tiny-secp256k1') |
const ecc = require('tiny-secp256k1'); |
||||
|
|
||||
// input: {signature}
|
// input: {signature}
|
||||
// output: {pubKey} OP_CHECKSIG
|
// output: {pubKey} OP_CHECKSIG
|
||||
export function p2pk (a: Payment, opts?: PaymentOpts): Payment { |
export function p2pk(a: Payment, opts?: PaymentOpts): Payment { |
||||
if ( |
if (!a.input && !a.output && !a.pubkey && !a.input && !a.signature) |
||||
!a.input && |
throw new TypeError('Not enough data'); |
||||
!a.output && |
opts = Object.assign({ validate: true }, opts || {}); |
||||
!a.pubkey && |
|
||||
!a.input && |
|
||||
!a.signature |
|
||||
) throw new TypeError('Not enough data') |
|
||||
opts = Object.assign({ validate: true }, opts || {}) |
|
||||
|
|
||||
typef({ |
typef( |
||||
network: typef.maybe(typef.Object), |
{ |
||||
output: typef.maybe(typef.Buffer), |
network: typef.maybe(typef.Object), |
||||
pubkey: typef.maybe(ecc.isPoint), |
output: typef.maybe(typef.Buffer), |
||||
|
pubkey: typef.maybe(ecc.isPoint), |
||||
|
|
||||
signature: typef.maybe(bscript.isCanonicalScriptSignature), |
signature: typef.maybe(bscript.isCanonicalScriptSignature), |
||||
input: typef.maybe(typef.Buffer) |
input: typef.maybe(typef.Buffer), |
||||
}, a) |
}, |
||||
|
a, |
||||
|
); |
||||
|
|
||||
const _chunks = <()=>Array<Buffer | number>>lazy.value(function () { return bscript.decompile(a.input!) }) |
const _chunks = <() => Array<Buffer | number>>lazy.value(function() { |
||||
|
return bscript.decompile(a.input!); |
||||
|
}); |
||||
|
|
||||
const network = a.network || BITCOIN_NETWORK |
const network = a.network || BITCOIN_NETWORK; |
||||
const o: Payment = { network } |
const o: Payment = { network }; |
||||
|
|
||||
lazy.prop(o, 'output', function () { |
lazy.prop(o, 'output', function() { |
||||
if (!a.pubkey) return |
if (!a.pubkey) return; |
||||
return bscript.compile([ |
return bscript.compile([a.pubkey, OPS.OP_CHECKSIG]); |
||||
a.pubkey, |
}); |
||||
OPS.OP_CHECKSIG |
lazy.prop(o, 'pubkey', function() { |
||||
]) |
if (!a.output) return; |
||||
}) |
return a.output.slice(1, -1); |
||||
lazy.prop(o, 'pubkey', function () { |
}); |
||||
if (!a.output) return |
lazy.prop(o, 'signature', function() { |
||||
return a.output.slice(1, -1) |
if (!a.input) return; |
||||
}) |
return <Buffer>_chunks()[0]; |
||||
lazy.prop(o, 'signature', function () { |
}); |
||||
if (!a.input) return |
lazy.prop(o, 'input', function() { |
||||
return <Buffer>_chunks()[0] |
if (!a.signature) return; |
||||
}) |
return bscript.compile([a.signature]); |
||||
lazy.prop(o, 'input', function () { |
}); |
||||
if (!a.signature) return |
lazy.prop(o, 'witness', function() { |
||||
return bscript.compile([a.signature]) |
if (!o.input) return; |
||||
}) |
return []; |
||||
lazy.prop(o, 'witness', function () { |
}); |
||||
if (!o.input) return |
|
||||
return [] |
|
||||
}) |
|
||||
|
|
||||
// extended validation
|
// extended validation
|
||||
if (opts.validate) { |
if (opts.validate) { |
||||
if (a.output) { |
if (a.output) { |
||||
if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) throw new TypeError('Output is invalid') |
if (a.output[a.output.length - 1] !== OPS.OP_CHECKSIG) |
||||
if (!ecc.isPoint(o.pubkey)) throw new TypeError('Output pubkey is invalid') |
throw new TypeError('Output is invalid'); |
||||
if (a.pubkey && !a.pubkey.equals(o.pubkey!)) throw new TypeError('Pubkey mismatch') |
if (!ecc.isPoint(o.pubkey)) |
||||
|
throw new TypeError('Output pubkey is invalid'); |
||||
|
if (a.pubkey && !a.pubkey.equals(o.pubkey!)) |
||||
|
throw new TypeError('Pubkey mismatch'); |
||||
} |
} |
||||
|
|
||||
if (a.signature) { |
if (a.signature) { |
||||
if (a.input && !a.input.equals(o.input!)) throw new TypeError('Signature mismatch') |
if (a.input && !a.input.equals(o.input!)) |
||||
|
throw new TypeError('Signature mismatch'); |
||||
} |
} |
||||
|
|
||||
if (a.input) { |
if (a.input) { |
||||
if (_chunks().length !== 1) throw new TypeError('Input is invalid') |
if (_chunks().length !== 1) throw new TypeError('Input is invalid'); |
||||
if (!bscript.isCanonicalScriptSignature(o.signature!)) throw new TypeError('Input has invalid signature') |
if (!bscript.isCanonicalScriptSignature(o.signature!)) |
||||
|
throw new TypeError('Input has invalid signature'); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
return Object.assign(o, a) |
return Object.assign(o, a); |
||||
} |
} |
||||
|
@ -1,134 +1,142 @@ |
|||||
import { Payment, PaymentOpts } from './index' // eslint-disable-line
|
import { Payment, PaymentOpts } from './index'; // eslint-disable-line
|
||||
import * as bscript from '../script' |
import * as bscript from '../script'; |
||||
import * as bcrypto from '../crypto' |
import * as bcrypto from '../crypto'; |
||||
import * as lazy from './lazy' |
import * as lazy from './lazy'; |
||||
import { bitcoin as BITCOIN_NETWORK } from '../networks' |
import { bitcoin as BITCOIN_NETWORK } from '../networks'; |
||||
const typef = require('typeforce') |
const typef = require('typeforce'); |
||||
const OPS = bscript.OPS |
const OPS = bscript.OPS; |
||||
const ecc = require('tiny-secp256k1') |
const ecc = require('tiny-secp256k1'); |
||||
|
|
||||
const bech32 = require('bech32') |
const bech32 = require('bech32'); |
||||
|
|
||||
const EMPTY_BUFFER = Buffer.alloc(0) |
const EMPTY_BUFFER = Buffer.alloc(0); |
||||
|
|
||||
// witness: {signature} {pubKey}
|
// witness: {signature} {pubKey}
|
||||
// input: <>
|
// input: <>
|
||||
// output: OP_0 {pubKeyHash}
|
// output: OP_0 {pubKeyHash}
|
||||
export function p2wpkh (a: Payment, opts?: PaymentOpts): Payment { |
export function p2wpkh(a: Payment, opts?: PaymentOpts): Payment { |
||||
if ( |
if (!a.address && !a.hash && !a.output && !a.pubkey && !a.witness) |
||||
!a.address && |
throw new TypeError('Not enough data'); |
||||
!a.hash && |
opts = Object.assign({ validate: true }, opts || {}); |
||||
!a.output && |
|
||||
!a.pubkey && |
typef( |
||||
!a.witness |
{ |
||||
) throw new TypeError('Not enough data') |
address: typef.maybe(typef.String), |
||||
opts = Object.assign({ validate: true }, opts || {}) |
hash: typef.maybe(typef.BufferN(20)), |
||||
|
input: typef.maybe(typef.BufferN(0)), |
||||
typef({ |
network: typef.maybe(typef.Object), |
||||
address: typef.maybe(typef.String), |
output: typef.maybe(typef.BufferN(22)), |
||||
hash: typef.maybe(typef.BufferN(20)), |
pubkey: typef.maybe(ecc.isPoint), |
||||
input: typef.maybe(typef.BufferN(0)), |
signature: typef.maybe(bscript.isCanonicalScriptSignature), |
||||
network: typef.maybe(typef.Object), |
witness: typef.maybe(typef.arrayOf(typef.Buffer)), |
||||
output: typef.maybe(typef.BufferN(22)), |
}, |
||||
pubkey: typef.maybe(ecc.isPoint), |
a, |
||||
signature: typef.maybe(bscript.isCanonicalScriptSignature), |
); |
||||
witness: typef.maybe(typef.arrayOf(typef.Buffer)) |
|
||||
}, a) |
const _address = lazy.value(function() { |
||||
|
const result = bech32.decode(a.address); |
||||
const _address = lazy.value(function () { |
const version = result.words.shift(); |
||||
const result = bech32.decode(a.address) |
const data = bech32.fromWords(result.words); |
||||
const version = result.words.shift() |
|
||||
const data = bech32.fromWords(result.words) |
|
||||
return { |
return { |
||||
version, |
version, |
||||
prefix: result.prefix, |
prefix: result.prefix, |
||||
data: Buffer.from(data) |
data: Buffer.from(data), |
||||
} |
}; |
||||
}) |
}); |
||||
|
|
||||
const network = a.network || BITCOIN_NETWORK |
const network = a.network || BITCOIN_NETWORK; |
||||
const o: Payment = { network } |
const o: Payment = { network }; |
||||
|
|
||||
lazy.prop(o, 'address', function () { |
lazy.prop(o, 'address', function() { |
||||
if (!o.hash) return |
if (!o.hash) return; |
||||
|
|
||||
const words = bech32.toWords(o.hash) |
const words = bech32.toWords(o.hash); |
||||
words.unshift(0x00) |
words.unshift(0x00); |
||||
return bech32.encode(network.bech32, words) |
return bech32.encode(network.bech32, words); |
||||
}) |
}); |
||||
lazy.prop(o, 'hash', function () { |
lazy.prop(o, 'hash', function() { |
||||
if (a.output) return a.output.slice(2, 22) |
if (a.output) return a.output.slice(2, 22); |
||||
if (a.address) return _address().data |
if (a.address) return _address().data; |
||||
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey! || o.pubkey!) |
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey! || o.pubkey!); |
||||
}) |
}); |
||||
lazy.prop(o, 'output', function () { |
lazy.prop(o, 'output', function() { |
||||
if (!o.hash) return |
if (!o.hash) return; |
||||
return bscript.compile([ |
return bscript.compile([OPS.OP_0, o.hash]); |
||||
OPS.OP_0, |
}); |
||||
o.hash |
lazy.prop(o, 'pubkey', function() { |
||||
]) |
if (a.pubkey) return a.pubkey; |
||||
}) |
if (!a.witness) return; |
||||
lazy.prop(o, 'pubkey', function () { |
return a.witness[1]; |
||||
if (a.pubkey) return a.pubkey |
}); |
||||
if (!a.witness) return |
lazy.prop(o, 'signature', function() { |
||||
return a.witness[1] |
if (!a.witness) return; |
||||
}) |
return a.witness[0]; |
||||
lazy.prop(o, 'signature', function () { |
}); |
||||
if (!a.witness) return |
lazy.prop(o, 'input', function() { |
||||
return a.witness[0] |
if (!o.witness) return; |
||||
}) |
return EMPTY_BUFFER; |
||||
lazy.prop(o, 'input', function () { |
}); |
||||
if (!o.witness) return |
lazy.prop(o, 'witness', function() { |
||||
return EMPTY_BUFFER |
if (!a.pubkey) return; |
||||
}) |
if (!a.signature) return; |
||||
lazy.prop(o, 'witness', function () { |
return [a.signature, a.pubkey]; |
||||
if (!a.pubkey) return |
}); |
||||
if (!a.signature) return |
|
||||
return [a.signature, a.pubkey] |
|
||||
}) |
|
||||
|
|
||||
// extended validation
|
// extended validation
|
||||
if (opts.validate) { |
if (opts.validate) { |
||||
let hash: Buffer = Buffer.from([]) |
let hash: Buffer = Buffer.from([]); |
||||
if (a.address) { |
if (a.address) { |
||||
if (network && network.bech32 !== _address().prefix) throw new TypeError('Invalid prefix or Network mismatch') |
if (network && network.bech32 !== _address().prefix) |
||||
if (_address().version !== 0x00) throw new TypeError('Invalid address version') |
throw new TypeError('Invalid prefix or Network mismatch'); |
||||
if (_address().data.length !== 20) throw new TypeError('Invalid address data') |
if (_address().version !== 0x00) |
||||
hash = _address().data |
throw new TypeError('Invalid address version'); |
||||
|
if (_address().data.length !== 20) |
||||
|
throw new TypeError('Invalid address data'); |
||||
|
hash = _address().data; |
||||
} |
} |
||||
|
|
||||
if (a.hash) { |
if (a.hash) { |
||||
if (hash.length > 0 && !hash.equals(a.hash)) throw new TypeError('Hash mismatch') |
if (hash.length > 0 && !hash.equals(a.hash)) |
||||
else hash = a.hash |
throw new TypeError('Hash mismatch'); |
||||
|
else hash = a.hash; |
||||
} |
} |
||||
|
|
||||
if (a.output) { |
if (a.output) { |
||||
if ( |
if ( |
||||
a.output.length !== 22 || |
a.output.length !== 22 || |
||||
a.output[0] !== OPS.OP_0 || |
a.output[0] !== OPS.OP_0 || |
||||
a.output[1] !== 0x14) throw new TypeError('Output is invalid') |
a.output[1] !== 0x14 |
||||
if (hash.length > 0 && !hash.equals(a.output.slice(2))) throw new TypeError('Hash mismatch') |
) |
||||
else hash = a.output.slice(2) |
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); |
||||
} |
} |
||||
|
|
||||
if (a.pubkey) { |
if (a.pubkey) { |
||||
const pkh = bcrypto.hash160(a.pubkey) |
const pkh = bcrypto.hash160(a.pubkey); |
||||
if (hash.length > 0 && !hash.equals(pkh)) throw new TypeError('Hash mismatch') |
if (hash.length > 0 && !hash.equals(pkh)) |
||||
else hash = pkh |
throw new TypeError('Hash mismatch'); |
||||
|
else hash = pkh; |
||||
} |
} |
||||
|
|
||||
if (a.witness) { |
if (a.witness) { |
||||
if (a.witness.length !== 2) throw new TypeError('Witness is invalid') |
if (a.witness.length !== 2) throw new TypeError('Witness is invalid'); |
||||
if (!bscript.isCanonicalScriptSignature(a.witness[0])) throw new TypeError('Witness has invalid signature') |
if (!bscript.isCanonicalScriptSignature(a.witness[0])) |
||||
if (!ecc.isPoint(a.witness[1])) throw new TypeError('Witness has invalid pubkey') |
throw new TypeError('Witness has invalid signature'); |
||||
|
if (!ecc.isPoint(a.witness[1])) |
||||
if (a.signature && !a.signature.equals(a.witness[0])) throw new TypeError('Signature mismatch') |
throw new TypeError('Witness has invalid pubkey'); |
||||
if (a.pubkey && !a.pubkey.equals(a.witness[1])) throw new TypeError('Pubkey mismatch') |
|
||||
|
if (a.signature && !a.signature.equals(a.witness[0])) |
||||
const pkh = bcrypto.hash160(a.witness[1]) |
throw new TypeError('Signature mismatch'); |
||||
if (hash.length > 0 && !hash.equals(pkh)) throw new TypeError('Hash 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'); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
return Object.assign(o, a) |
return Object.assign(o, a); |
||||
} |
} |
||||
|
@ -1,206 +1,218 @@ |
|||||
import * as types from './types' |
import * as types from './types'; |
||||
import * as scriptNumber from './script_number' |
import * as scriptNumber from './script_number'; |
||||
import * as scriptSignature from './script_signature' |
import * as scriptSignature from './script_signature'; |
||||
const bip66 = require('bip66') |
const bip66 = require('bip66'); |
||||
const ecc = require('tiny-secp256k1') |
const ecc = require('tiny-secp256k1'); |
||||
const pushdata = require('pushdata-bitcoin') |
const pushdata = require('pushdata-bitcoin'); |
||||
const typeforce = require('typeforce') |
const typeforce = require('typeforce'); |
||||
|
|
||||
export type OpCode = number |
export type OpCode = number; |
||||
export const OPS = <{[index:string]: OpCode}> require('bitcoin-ops') |
export const OPS = <{ [index: string]: OpCode }>require('bitcoin-ops'); |
||||
|
|
||||
const REVERSE_OPS = <{[index:number]: string}> require('bitcoin-ops/map') |
const REVERSE_OPS = <{ [index: number]: string }>require('bitcoin-ops/map'); |
||||
const OP_INT_BASE = OPS.OP_RESERVED // OP_1 - 1
|
const OP_INT_BASE = OPS.OP_RESERVED; // OP_1 - 1
|
||||
|
|
||||
function isOPInt (value:number): boolean { |
function isOPInt(value: number): boolean { |
||||
return types.Number(value) && |
return ( |
||||
((value === OPS.OP_0) || |
types.Number(value) && |
||||
(value >= OPS.OP_1 && value <= OPS.OP_16) || |
(value === OPS.OP_0 || |
||||
(value === OPS.OP_1NEGATE)) |
(value >= OPS.OP_1 && value <= OPS.OP_16) || |
||||
|
value === OPS.OP_1NEGATE) |
||||
|
); |
||||
} |
} |
||||
|
|
||||
function isPushOnlyChunk (value: number | Buffer): boolean { |
function isPushOnlyChunk(value: number | Buffer): boolean { |
||||
return types.Buffer(value) || isOPInt(<number>value) |
return types.Buffer(value) || isOPInt(<number>value); |
||||
} |
} |
||||
|
|
||||
export function isPushOnly (value: Array<number | Buffer>) { |
export function isPushOnly(value: Array<number | Buffer>) { |
||||
return types.Array(value) && value.every(isPushOnlyChunk) |
return types.Array(value) && value.every(isPushOnlyChunk); |
||||
} |
} |
||||
|
|
||||
function asMinimalOP (buffer: Buffer): number | void { |
function asMinimalOP(buffer: Buffer): number | void { |
||||
if (buffer.length === 0) return OPS.OP_0 |
if (buffer.length === 0) return OPS.OP_0; |
||||
if (buffer.length !== 1) return |
if (buffer.length !== 1) return; |
||||
if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0] |
if (buffer[0] >= 1 && buffer[0] <= 16) return OP_INT_BASE + buffer[0]; |
||||
if (buffer[0] === 0x81) return OPS.OP_1NEGATE |
if (buffer[0] === 0x81) return OPS.OP_1NEGATE; |
||||
} |
} |
||||
|
|
||||
function chunksIsBuffer(buf: Buffer | Array<number | Buffer>): buf is Buffer { |
function chunksIsBuffer(buf: Buffer | Array<number | Buffer>): buf is Buffer { |
||||
return Buffer.isBuffer(buf) |
return Buffer.isBuffer(buf); |
||||
} |
} |
||||
|
|
||||
function chunksIsArray(buf: Buffer | Array<number | Buffer>): buf is Array<number | Buffer> { |
function chunksIsArray( |
||||
return types.Array(buf) |
buf: Buffer | Array<number | Buffer>, |
||||
|
): buf is Array<number | Buffer> { |
||||
|
return types.Array(buf); |
||||
} |
} |
||||
|
|
||||
function singleChunkIsBuffer(buf: number | Buffer): buf is Buffer { |
function singleChunkIsBuffer(buf: number | Buffer): buf is Buffer { |
||||
return Buffer.isBuffer(buf) |
return Buffer.isBuffer(buf); |
||||
} |
} |
||||
|
|
||||
export function compile (chunks: Buffer | Array<number | Buffer>): Buffer { |
export function compile(chunks: Buffer | Array<number | Buffer>): Buffer { |
||||
// TODO: remove me
|
// TODO: remove me
|
||||
if (chunksIsBuffer(chunks)) return chunks |
if (chunksIsBuffer(chunks)) return chunks; |
||||
|
|
||||
typeforce(types.Array, chunks) |
typeforce(types.Array, chunks); |
||||
|
|
||||
const bufferSize = chunks.reduce(function (accum: number, chunk) { |
const bufferSize = chunks.reduce(function(accum: number, chunk) { |
||||
// data chunk
|
// data chunk
|
||||
if (singleChunkIsBuffer(chunk)) { |
if (singleChunkIsBuffer(chunk)) { |
||||
// adhere to BIP62.3, minimal push policy
|
// adhere to BIP62.3, minimal push policy
|
||||
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) { |
if (chunk.length === 1 && asMinimalOP(chunk) !== undefined) { |
||||
return accum + 1 |
return accum + 1; |
||||
} |
} |
||||
|
|
||||
return accum + pushdata.encodingLength(chunk.length) + chunk.length |
return accum + pushdata.encodingLength(chunk.length) + chunk.length; |
||||
} |
} |
||||
|
|
||||
// opcode
|
// opcode
|
||||
return accum + 1 |
return accum + 1; |
||||
}, 0.0) |
}, 0.0); |
||||
|
|
||||
const buffer = Buffer.allocUnsafe(bufferSize) |
const buffer = Buffer.allocUnsafe(bufferSize); |
||||
let offset = 0 |
let offset = 0; |
||||
|
|
||||
chunks.forEach(function (chunk) { |
chunks.forEach(function(chunk) { |
||||
// data chunk
|
// data chunk
|
||||
if (singleChunkIsBuffer(chunk)) { |
if (singleChunkIsBuffer(chunk)) { |
||||
// adhere to BIP62.3, minimal push policy
|
// adhere to BIP62.3, minimal push policy
|
||||
const opcode = asMinimalOP(chunk) |
const opcode = asMinimalOP(chunk); |
||||
if (opcode !== undefined) { |
if (opcode !== undefined) { |
||||
buffer.writeUInt8(opcode, offset) |
buffer.writeUInt8(opcode, offset); |
||||
offset += 1 |
offset += 1; |
||||
return |
return; |
||||
} |
} |
||||
|
|
||||
offset += pushdata.encode(buffer, chunk.length, offset) |
offset += pushdata.encode(buffer, chunk.length, offset); |
||||
chunk.copy(buffer, offset) |
chunk.copy(buffer, offset); |
||||
offset += chunk.length |
offset += chunk.length; |
||||
|
|
||||
// opcode
|
// opcode
|
||||
} else { |
} else { |
||||
buffer.writeUInt8(chunk, offset) |
buffer.writeUInt8(chunk, offset); |
||||
offset += 1 |
offset += 1; |
||||
} |
} |
||||
}) |
}); |
||||
|
|
||||
if (offset !== buffer.length) throw new Error('Could not decode chunks') |
if (offset !== buffer.length) throw new Error('Could not decode chunks'); |
||||
return buffer |
return buffer; |
||||
} |
} |
||||
|
|
||||
export function decompile (buffer: Buffer | Array<number | Buffer>): Array<number | Buffer> | null { |
export function decompile( |
||||
|
buffer: Buffer | Array<number | Buffer>, |
||||
|
): Array<number | Buffer> | null { |
||||
// TODO: remove me
|
// TODO: remove me
|
||||
if (chunksIsArray(buffer)) return buffer |
if (chunksIsArray(buffer)) return buffer; |
||||
|
|
||||
typeforce(types.Buffer, buffer) |
typeforce(types.Buffer, buffer); |
||||
|
|
||||
const chunks: Array<number | Buffer> = [] |
const chunks: Array<number | Buffer> = []; |
||||
let i = 0 |
let i = 0; |
||||
|
|
||||
while (i < buffer.length) { |
while (i < buffer.length) { |
||||
const opcode = buffer[i] |
const opcode = buffer[i]; |
||||
|
|
||||
// data chunk
|
// data chunk
|
||||
if ((opcode > OPS.OP_0) && (opcode <= OPS.OP_PUSHDATA4)) { |
if (opcode > OPS.OP_0 && opcode <= OPS.OP_PUSHDATA4) { |
||||
const d = pushdata.decode(buffer, i) |
const d = pushdata.decode(buffer, i); |
||||
|
|
||||
// did reading a pushDataInt fail?
|
// did reading a pushDataInt fail?
|
||||
if (d === null) return null |
if (d === null) return null; |
||||
i += d.size |
i += d.size; |
||||
|
|
||||
// attempt to read too much data?
|
// attempt to read too much data?
|
||||
if (i + d.number > buffer.length) return null |
if (i + d.number > buffer.length) return null; |
||||
|
|
||||
const data = buffer.slice(i, i + d.number) |
const data = buffer.slice(i, i + d.number); |
||||
i += d.number |
i += d.number; |
||||
|
|
||||
// decompile minimally
|
// decompile minimally
|
||||
const op = asMinimalOP(data) |
const op = asMinimalOP(data); |
||||
if (op !== undefined) { |
if (op !== undefined) { |
||||
chunks.push(op) |
chunks.push(op); |
||||
} else { |
} else { |
||||
chunks.push(data) |
chunks.push(data); |
||||
} |
} |
||||
|
|
||||
// opcode
|
// opcode
|
||||
} else { |
} else { |
||||
chunks.push(opcode) |
chunks.push(opcode); |
||||
|
|
||||
i += 1 |
i += 1; |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
return chunks |
return chunks; |
||||
} |
} |
||||
|
|
||||
export function toASM (chunks: Buffer | Array<number | Buffer>): string { |
export function toASM(chunks: Buffer | Array<number | Buffer>): string { |
||||
if (chunksIsBuffer(chunks)) { |
if (chunksIsBuffer(chunks)) { |
||||
chunks = <Array<number | Buffer>>decompile(chunks) |
chunks = <Array<number | Buffer>>decompile(chunks); |
||||
} |
} |
||||
|
|
||||
return chunks.map(function (chunk) { |
return chunks |
||||
// data?
|
.map(function(chunk) { |
||||
if (singleChunkIsBuffer(chunk)) { |
// data?
|
||||
const op = asMinimalOP(chunk) |
if (singleChunkIsBuffer(chunk)) { |
||||
if (op === undefined) return chunk.toString('hex') |
const op = asMinimalOP(chunk); |
||||
chunk = <number>op |
if (op === undefined) return chunk.toString('hex'); |
||||
} |
chunk = <number>op; |
||||
|
} |
||||
|
|
||||
// opcode!
|
// opcode!
|
||||
return REVERSE_OPS[chunk] |
return REVERSE_OPS[chunk]; |
||||
}).join(' ') |
}) |
||||
|
.join(' '); |
||||
} |
} |
||||
|
|
||||
export function fromASM (asm: string): Buffer { |
export function fromASM(asm: string): Buffer { |
||||
typeforce(types.String, asm) |
typeforce(types.String, asm); |
||||
|
|
||||
return compile(asm.split(' ').map(function (chunkStr) { |
return compile( |
||||
// opcode?
|
asm.split(' ').map(function(chunkStr) { |
||||
if (OPS[chunkStr] !== undefined) return OPS[chunkStr] |
// opcode?
|
||||
typeforce(types.Hex, chunkStr) |
if (OPS[chunkStr] !== undefined) return OPS[chunkStr]; |
||||
|
typeforce(types.Hex, chunkStr); |
||||
|
|
||||
// data!
|
// data!
|
||||
return Buffer.from(chunkStr, 'hex') |
return Buffer.from(chunkStr, 'hex'); |
||||
})) |
}), |
||||
|
); |
||||
} |
} |
||||
|
|
||||
export function toStack (chunks: Buffer | Array<number | Buffer>): Array<Buffer> { |
export function toStack( |
||||
chunks = <Array<number | Buffer>>decompile(chunks) |
chunks: Buffer | Array<number | Buffer>, |
||||
typeforce(isPushOnly, chunks) |
): Array<Buffer> { |
||||
|
chunks = <Array<number | Buffer>>decompile(chunks); |
||||
|
typeforce(isPushOnly, chunks); |
||||
|
|
||||
return chunks.map(function (op) { |
return chunks.map(function(op) { |
||||
if (singleChunkIsBuffer(op)) return op |
if (singleChunkIsBuffer(op)) return op; |
||||
if (op === OPS.OP_0) return Buffer.allocUnsafe(0) |
if (op === OPS.OP_0) return Buffer.allocUnsafe(0); |
||||
|
|
||||
return scriptNumber.encode(op - OP_INT_BASE) |
return scriptNumber.encode(op - OP_INT_BASE); |
||||
}) |
}); |
||||
} |
} |
||||
|
|
||||
export function isCanonicalPubKey (buffer: Buffer): boolean { |
export function isCanonicalPubKey(buffer: Buffer): boolean { |
||||
return ecc.isPoint(buffer) |
return ecc.isPoint(buffer); |
||||
} |
} |
||||
|
|
||||
export function isDefinedHashType (hashType: number): boolean { |
export function isDefinedHashType(hashType: number): boolean { |
||||
const hashTypeMod = hashType & ~0x80 |
const hashTypeMod = hashType & ~0x80; |
||||
|
|
||||
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
|
// return hashTypeMod > SIGHASH_ALL && hashTypeMod < SIGHASH_SINGLE
|
||||
return hashTypeMod > 0x00 && hashTypeMod < 0x04 |
return hashTypeMod > 0x00 && hashTypeMod < 0x04; |
||||
} |
} |
||||
|
|
||||
export function isCanonicalScriptSignature (buffer: Buffer): boolean { |
export function isCanonicalScriptSignature(buffer: Buffer): boolean { |
||||
if (!Buffer.isBuffer(buffer)) return false |
if (!Buffer.isBuffer(buffer)) return false; |
||||
if (!isDefinedHashType(buffer[buffer.length - 1])) return false |
if (!isDefinedHashType(buffer[buffer.length - 1])) return false; |
||||
|
|
||||
return bip66.check(buffer.slice(0, -1)) |
return bip66.check(buffer.slice(0, -1)); |
||||
} |
} |
||||
|
|
||||
export const number = scriptNumber |
export const number = scriptNumber; |
||||
export const signature = scriptSignature |
export const signature = scriptSignature; |
||||
|
@ -1,62 +1,71 @@ |
|||||
|
export function decode( |
||||
|
buffer: Buffer, |
||||
|
maxLength?: number, |
||||
|
minimal?: boolean, |
||||
|
): number { |
||||
|
maxLength = maxLength || 4; |
||||
|
minimal = minimal === undefined ? true : minimal; |
||||
|
|
||||
|
const length = buffer.length; |
||||
export function decode (buffer: Buffer, maxLength?: number, minimal?: boolean): number { |
if (length === 0) return 0; |
||||
maxLength = maxLength || 4 |
if (length > maxLength) throw new TypeError('Script number overflow'); |
||||
minimal = minimal === undefined ? true : minimal |
|
||||
|
|
||||
const length = buffer.length |
|
||||
if (length === 0) return 0 |
|
||||
if (length > maxLength) throw new TypeError('Script number overflow') |
|
||||
if (minimal) { |
if (minimal) { |
||||
if ((buffer[length - 1] & 0x7f) === 0) { |
if ((buffer[length - 1] & 0x7f) === 0) { |
||||
if (length <= 1 || (buffer[length - 2] & 0x80) === 0) throw new Error('Non-minimally encoded script number') |
if (length <= 1 || (buffer[length - 2] & 0x80) === 0) |
||||
|
throw new Error('Non-minimally encoded script number'); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
// 40-bit
|
// 40-bit
|
||||
if (length === 5) { |
if (length === 5) { |
||||
const a = buffer.readUInt32LE(0) |
const a = buffer.readUInt32LE(0); |
||||
const b = buffer.readUInt8(4) |
const b = buffer.readUInt8(4); |
||||
|
|
||||
if (b & 0x80) return -(((b & ~0x80) * 0x100000000) + a) |
if (b & 0x80) return -((b & ~0x80) * 0x100000000 + a); |
||||
return (b * 0x100000000) + a |
return b * 0x100000000 + a; |
||||
} |
} |
||||
|
|
||||
// 32-bit / 24-bit / 16-bit / 8-bit
|
// 32-bit / 24-bit / 16-bit / 8-bit
|
||||
let result = 0 |
let result = 0; |
||||
for (var i = 0; i < length; ++i) { |
for (var i = 0; i < length; ++i) { |
||||
result |= buffer[i] << (8 * i) |
result |= buffer[i] << (8 * i); |
||||
} |
} |
||||
|
|
||||
if (buffer[length - 1] & 0x80) return -(result & ~(0x80 << (8 * (length - 1)))) |
if (buffer[length - 1] & 0x80) |
||||
return result |
return -(result & ~(0x80 << (8 * (length - 1)))); |
||||
|
return result; |
||||
} |
} |
||||
|
|
||||
function scriptNumSize (i: number): number { |
function scriptNumSize(i: number): number { |
||||
return i > 0x7fffffff ? 5 |
return i > 0x7fffffff |
||||
: i > 0x7fffff ? 4 |
? 5 |
||||
: i > 0x7fff ? 3 |
: i > 0x7fffff |
||||
: i > 0x7f ? 2 |
? 4 |
||||
: i > 0x00 ? 1 |
: i > 0x7fff |
||||
: 0 |
? 3 |
||||
|
: i > 0x7f |
||||
|
? 2 |
||||
|
: i > 0x00 |
||||
|
? 1 |
||||
|
: 0; |
||||
} |
} |
||||
|
|
||||
export function encode (number: number): Buffer { |
export function encode(number: number): Buffer { |
||||
let value = Math.abs(number) |
let value = Math.abs(number); |
||||
const size = scriptNumSize(value) |
const size = scriptNumSize(value); |
||||
const buffer = Buffer.allocUnsafe(size) |
const buffer = Buffer.allocUnsafe(size); |
||||
const negative = number < 0 |
const negative = number < 0; |
||||
|
|
||||
for (var i = 0; i < size; ++i) { |
for (var i = 0; i < size; ++i) { |
||||
buffer.writeUInt8(value & 0xff, i) |
buffer.writeUInt8(value & 0xff, i); |
||||
value >>= 8 |
value >>= 8; |
||||
} |
} |
||||
|
|
||||
if (buffer[size - 1] & 0x80) { |
if (buffer[size - 1] & 0x80) { |
||||
buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1) |
buffer.writeUInt8(negative ? 0x80 : 0x00, size - 1); |
||||
} else if (negative) { |
} else if (negative) { |
||||
buffer[size - 1] |= 0x80 |
buffer[size - 1] |= 0x80; |
||||
} |
} |
||||
|
|
||||
return buffer |
return buffer; |
||||
} |
} |
||||
|
@ -1,64 +1,66 @@ |
|||||
import * as types from './types' |
import * as types from './types'; |
||||
const bip66 = require('bip66') |
const bip66 = require('bip66'); |
||||
|
|
||||
const typeforce = require('typeforce') |
const typeforce = require('typeforce'); |
||||
|
|
||||
const ZERO = Buffer.alloc(1, 0) |
const ZERO = Buffer.alloc(1, 0); |
||||
function toDER (x: Buffer): Buffer { |
function toDER(x: Buffer): Buffer { |
||||
let i = 0 |
let i = 0; |
||||
while (x[i] === 0) ++i |
while (x[i] === 0) ++i; |
||||
if (i === x.length) return ZERO |
if (i === x.length) return ZERO; |
||||
x = x.slice(i) |
x = x.slice(i); |
||||
if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length) |
if (x[0] & 0x80) return Buffer.concat([ZERO, x], 1 + x.length); |
||||
return x |
return x; |
||||
} |
} |
||||
|
|
||||
function fromDER (x: Buffer): Buffer { |
function fromDER(x: Buffer): Buffer { |
||||
if (x[0] === 0x00) x = x.slice(1) |
if (x[0] === 0x00) x = x.slice(1); |
||||
const buffer = Buffer.alloc(32, 0) |
const buffer = Buffer.alloc(32, 0); |
||||
const bstart = Math.max(0, 32 - x.length) |
const bstart = Math.max(0, 32 - x.length); |
||||
x.copy(buffer, bstart) |
x.copy(buffer, bstart); |
||||
return buffer |
return buffer; |
||||
} |
} |
||||
|
|
||||
interface ScriptSignature { |
interface ScriptSignature { |
||||
signature: Buffer |
signature: Buffer; |
||||
hashType: number |
hashType: number; |
||||
} |
} |
||||
|
|
||||
// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed)
|
// BIP62: 1 byte hashType flag (only 0x01, 0x02, 0x03, 0x81, 0x82 and 0x83 are allowed)
|
||||
export function decode (buffer: Buffer): ScriptSignature { |
export function decode(buffer: Buffer): ScriptSignature { |
||||
const hashType = buffer.readUInt8(buffer.length - 1) |
const hashType = buffer.readUInt8(buffer.length - 1); |
||||
const hashTypeMod = hashType & ~0x80 |
const hashTypeMod = hashType & ~0x80; |
||||
if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType) |
if (hashTypeMod <= 0 || hashTypeMod >= 4) |
||||
|
throw new Error('Invalid hashType ' + hashType); |
||||
|
|
||||
const decode = bip66.decode(buffer.slice(0, -1)) |
const decode = bip66.decode(buffer.slice(0, -1)); |
||||
const r = fromDER(decode.r) |
const r = fromDER(decode.r); |
||||
const s = fromDER(decode.s) |
const s = fromDER(decode.s); |
||||
|
|
||||
return { |
return { |
||||
signature: Buffer.concat([r, s], 64), |
signature: Buffer.concat([r, s], 64), |
||||
hashType: hashType |
hashType: hashType, |
||||
} |
}; |
||||
} |
} |
||||
|
|
||||
export function encode (signature: Buffer, hashType: number): Buffer { |
export function encode(signature: Buffer, hashType: number): Buffer { |
||||
typeforce({ |
typeforce( |
||||
signature: types.BufferN(64), |
{ |
||||
hashType: types.UInt8 |
signature: types.BufferN(64), |
||||
}, { signature, hashType }) |
hashType: types.UInt8, |
||||
|
}, |
||||
|
{ signature, hashType }, |
||||
|
); |
||||
|
|
||||
const hashTypeMod = hashType & ~0x80 |
const hashTypeMod = hashType & ~0x80; |
||||
if (hashTypeMod <= 0 || hashTypeMod >= 4) throw new Error('Invalid hashType ' + hashType) |
if (hashTypeMod <= 0 || hashTypeMod >= 4) |
||||
|
throw new Error('Invalid hashType ' + hashType); |
||||
|
|
||||
const hashTypeBuffer = Buffer.allocUnsafe(1) |
const hashTypeBuffer = Buffer.allocUnsafe(1); |
||||
hashTypeBuffer.writeUInt8(hashType, 0) |
hashTypeBuffer.writeUInt8(hashType, 0); |
||||
|
|
||||
const r = toDER(signature.slice(0, 32)) |
const r = toDER(signature.slice(0, 32)); |
||||
const s = toDER(signature.slice(32, 64)) |
const s = toDER(signature.slice(32, 64)); |
||||
|
|
||||
return Buffer.concat([ |
return Buffer.concat([bip66.encode(r, s), hashTypeBuffer]); |
||||
bip66.encode(r, s), |
|
||||
hashTypeBuffer |
|
||||
]) |
|
||||
} |
} |
||||
|
@ -1,17 +1,16 @@ |
|||||
// OP_RETURN {data}
|
// OP_RETURN {data}
|
||||
import * as bscript from '../script' |
import * as bscript from '../script'; |
||||
const OPS = bscript.OPS |
const OPS = bscript.OPS; |
||||
|
|
||||
export function check (script: Buffer | Array<number | Buffer>): boolean { |
export function check(script: Buffer | Array<number | Buffer>): boolean { |
||||
const buffer = bscript.compile(script) |
const buffer = bscript.compile(script); |
||||
|
|
||||
return buffer.length > 1 && |
return buffer.length > 1 && buffer[0] === OPS.OP_RETURN; |
||||
buffer[0] === OPS.OP_RETURN |
|
||||
} |
} |
||||
check.toJSON = function () { return 'null data output' } |
check.toJSON = function() { |
||||
|
return 'null data output'; |
||||
|
}; |
||||
|
|
||||
const output = { check } |
const output = { check }; |
||||
|
|
||||
export { |
export { output }; |
||||
output |
|
||||
} |
|
||||
|
File diff suppressed because it is too large
@ -1,49 +1,51 @@ |
|||||
const typeforce = require('typeforce') |
const typeforce = require('typeforce'); |
||||
|
|
||||
const UINT31_MAX: number = Math.pow(2, 31) - 1 |
const UINT31_MAX: number = Math.pow(2, 31) - 1; |
||||
export function UInt31 (value: number): boolean { |
export function UInt31(value: number): boolean { |
||||
return typeforce.UInt32(value) && value <= UINT31_MAX |
return typeforce.UInt32(value) && value <= UINT31_MAX; |
||||
} |
} |
||||
|
|
||||
export function BIP32Path (value: string): boolean { |
export function BIP32Path(value: string): boolean { |
||||
return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/) |
return typeforce.String(value) && !!value.match(/^(m\/)?(\d+'?\/)*\d+'?$/); |
||||
} |
} |
||||
BIP32Path.toJSON = function () { return 'BIP32 derivation path' } |
BIP32Path.toJSON = function() { |
||||
|
return 'BIP32 derivation path'; |
||||
|
}; |
||||
|
|
||||
const SATOSHI_MAX: number = 21 * 1e14 |
const SATOSHI_MAX: number = 21 * 1e14; |
||||
export function Satoshi (value: number): boolean { |
export function Satoshi(value: number): boolean { |
||||
return typeforce.UInt53(value) && value <= SATOSHI_MAX |
return typeforce.UInt53(value) && value <= SATOSHI_MAX; |
||||
} |
} |
||||
|
|
||||
// external dependent types
|
// external dependent types
|
||||
export const ECPoint = typeforce.quacksLike('Point') |
export const ECPoint = typeforce.quacksLike('Point'); |
||||
|
|
||||
// exposed, external API
|
// exposed, external API
|
||||
export const Network = typeforce.compile({ |
export const Network = typeforce.compile({ |
||||
messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), |
messagePrefix: typeforce.oneOf(typeforce.Buffer, typeforce.String), |
||||
bip32: { |
bip32: { |
||||
public: typeforce.UInt32, |
public: typeforce.UInt32, |
||||
private: typeforce.UInt32 |
private: typeforce.UInt32, |
||||
}, |
}, |
||||
pubKeyHash: typeforce.UInt8, |
pubKeyHash: typeforce.UInt8, |
||||
scriptHash: typeforce.UInt8, |
scriptHash: typeforce.UInt8, |
||||
wif: typeforce.UInt8 |
wif: typeforce.UInt8, |
||||
}) |
}); |
||||
|
|
||||
export const Buffer256bit = typeforce.BufferN(32) |
export const Buffer256bit = typeforce.BufferN(32); |
||||
export const Hash160bit = typeforce.BufferN(20) |
export const Hash160bit = typeforce.BufferN(20); |
||||
export const Hash256bit = typeforce.BufferN(32) |
export const Hash256bit = typeforce.BufferN(32); |
||||
export const Number = typeforce.Number |
export const Number = typeforce.Number; |
||||
export const Array = typeforce.Array |
export const Array = typeforce.Array; |
||||
export const Boolean = typeforce.Boolean |
export const Boolean = typeforce.Boolean; |
||||
export const String = typeforce.String |
export const String = typeforce.String; |
||||
export const Buffer = typeforce.Buffer |
export const Buffer = typeforce.Buffer; |
||||
export const Hex = typeforce.Hex |
export const Hex = typeforce.Hex; |
||||
export const maybe = typeforce.maybe |
export const maybe = typeforce.maybe; |
||||
export const tuple = typeforce.tuple |
export const tuple = typeforce.tuple; |
||||
export const UInt8 = typeforce.UInt8 |
export const UInt8 = typeforce.UInt8; |
||||
export const UInt32 = typeforce.UInt32 |
export const UInt32 = typeforce.UInt32; |
||||
export const Function = typeforce.Function |
export const Function = typeforce.Function; |
||||
export const BufferN = typeforce.BufferN |
export const BufferN = typeforce.BufferN; |
||||
export const Null = typeforce.Null |
export const Null = typeforce.Null; |
||||
export const oneOf = typeforce.oneOf |
export const oneOf = typeforce.oneOf; |
||||
|
Loading…
Reference in new issue