Convert to LBRY: Can now sign PSBTs & claim names #1
32 changed files with 4092 additions and 75 deletions
87
lbry-demo.js
Normal file
87
lbry-demo.js
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
// This is a standalone script that you can try out in its own project. You
|
||||||
|
// should install this fork of bitcoinjs-lib, but otherwise you can get from
|
||||||
|
// normal npm.
|
||||||
|
|
||||||
|
// TODO - maybe just put this in a README or similar file
|
||||||
|
|
||||||
|
const ecpair = require('ecpair')
|
||||||
|
const bs58check = require('bs58check');
|
||||||
|
const ecc = require('tiny-secp256k1')
|
||||||
|
const lbry = require('bitcoinjs-lib')
|
||||||
|
const ECPair = ecpair.ECPairFactory(ecc)
|
||||||
|
const network = lbry.networks.regtest
|
||||||
|
|
||||||
|
// Previous data from a regtest instance
|
||||||
|
|
||||||
|
const key = ECPair.fromPrivateKey(bs58check.decode('cRWuHDPLjySJJzen1eztGD8SYq396NjBTdEwU3WF1LRRJ8NwhYCT').slice(1, -1), network)
|
||||||
|
|
||||||
|
// A previous transaction
|
||||||
|
|
||||||
|
/*
|
||||||
|
"amount": 0.00000000,
|
||||||
|
"fee": -0.01400000,
|
||||||
|
"confirmations": 7,
|
||||||
|
"blockhash": "2006054341b56529181e194531217823454650c07997c75dd796b9a8fe8a8fec",
|
||||||
|
"blockindex": 1,
|
||||||
|
"blocktime": 1646858320,
|
||||||
|
"txid": "a2f0ea39d3f54f46e30a4bfac403988779ca81b132c2869764535e836ba03a18",
|
||||||
|
"walletconflicts": [
|
||||||
|
],
|
||||||
|
"time": 1646858274,
|
||||||
|
"timereceived": 1646858274,
|
||||||
|
"bip125-replaceable": "no",
|
||||||
|
"details": [
|
||||||
|
{
|
||||||
|
"address": "mo4PK1TNw8ssTG6gM6JdWZfoFdasnNfqaE",
|
||||||
|
"category": "send",
|
||||||
|
"amount": -1.00000000,
|
||||||
|
"vout": 1,
|
||||||
|
"fee": -0.01400000,
|
||||||
|
"abandoned": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "mo4PK1TNw8ssTG6gM6JdWZfoFdasnNfqaE",
|
||||||
|
"category": "receive",
|
||||||
|
"amount": 1.00000000,
|
||||||
|
"vout": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
{
|
||||||
|
"n": 0,
|
||||||
|
"name": "my_name",
|
||||||
|
"claimId": "cc2181b64c566f29733e88cee984923f4f1b8c05",
|
||||||
|
"value": "deadbeef",
|
||||||
|
"depth": 6,
|
||||||
|
"inClaimTrie": false,
|
||||||
|
"inQueue": false
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
const nonWitnessUtxo = Buffer.from('020000000285ea15a6f1294a52b6424365d0a76e939b6ca9c8e87b25d67bebab1c1cd10a4300000000484730440220664b43577c03e6e3ced92bb699ab99c8d35c34ebca4ace408f0d4b54e807e055022050decc7da867941bac47ad097e14e0cff8e33fe199267ef8d1c64fce9f1e0faf01feffffffec34291fe8e028108f2d71e18fbd4b89a2d476d3d87f48c55e48373d122e7d7a0000000048473044022003a900648a3f5092e315234b7a17f366698957427a3ae402f825d6bc0e500257022061bef51b321ba9e64a569853c2d7fd87e025a504ea100ff028bb76c6af48ee5a01feffffff024084e005000000001976a914036c5b9a022ae12360bd539825f6f2e6fb3080d088ac00e1f5050000000029b5076d795f6e616d6504deadbeef6d7576a91452baa8720ff969c4a927757f81275d2a42291c2088ac78000000', 'hex')
|
||||||
|
|
||||||
|
// Based on p2pkh, adding claimName and claim
|
||||||
|
const payment = lbry.payments.claimName({
|
||||||
|
pubkey: key.publicKey,
|
||||||
|
claimName: "claim",
|
||||||
|
claim: Buffer.from("new_claim")
|
||||||
|
})
|
||||||
|
|
||||||
|
exports.psbt = new lbry.Psbt({ network })
|
||||||
|
|
||||||
|
exports.psbt.addOutput({ script: payment.output, value: 99000000 })
|
||||||
|
|
||||||
|
// For some reason they want the entirety of the input txn (nonWitnessUtxo) as well as its hash here
|
||||||
|
exports.psbt.addInput({
|
||||||
|
nonWitnessUtxo,
|
||||||
|
hash: 'a2f0ea39d3f54f46e30a4bfac403988779ca81b132c2869764535e836ba03a18',
|
||||||
|
index: 1 // matching vout in the input txn
|
||||||
|
})
|
||||||
|
|
||||||
|
exports.psbt.signInput(0, key)
|
||||||
|
|
||||||
|
exports.result = exports.psbt
|
||||||
|
.finalizeAllInputs()
|
||||||
|
.extractTransaction()
|
||||||
|
.toHex()
|
||||||
|
|
||||||
|
// Putting exports.result into the regtest succeeded. The claim showed up.
|
3508
package-lock.json
generated
3508
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -86,7 +86,7 @@ function toBech32(data, version, prefix) {
|
||||||
exports.toBech32 = toBech32;
|
exports.toBech32 = toBech32;
|
||||||
function fromOutputScript(output, network) {
|
function fromOutputScript(output, network) {
|
||||||
// TODO: Network
|
// TODO: Network
|
||||||
network = network || networks.bitcoin;
|
network = network || networks.mainnet;
|
||||||
try {
|
try {
|
||||||
return payments.p2pkh({ output, network }).address;
|
return payments.p2pkh({ output, network }).address;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
@ -106,7 +106,7 @@ function fromOutputScript(output, network) {
|
||||||
}
|
}
|
||||||
exports.fromOutputScript = fromOutputScript;
|
exports.fromOutputScript = fromOutputScript;
|
||||||
function toOutputScript(address, network) {
|
function toOutputScript(address, network) {
|
||||||
network = network || networks.bitcoin;
|
network = network || networks.mainnet;
|
||||||
let decodeBase58;
|
let decodeBase58;
|
||||||
let decodeBech32;
|
let decodeBech32;
|
||||||
try {
|
try {
|
||||||
|
|
2
src/networks.d.ts
vendored
2
src/networks.d.ts
vendored
|
@ -10,7 +10,7 @@ interface Bip32 {
|
||||||
public: number;
|
public: number;
|
||||||
private: number;
|
private: number;
|
||||||
}
|
}
|
||||||
export declare const bitcoin: Network;
|
export declare const mainnet: Network;
|
||||||
export declare const regtest: Network;
|
export declare const regtest: Network;
|
||||||
export declare const testnet: Network;
|
export declare const testnet: Network;
|
||||||
export {};
|
export {};
|
||||||
|
|
|
@ -1,36 +1,36 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
Object.defineProperty(exports, '__esModule', { value: true });
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
exports.testnet = exports.regtest = exports.bitcoin = void 0;
|
exports.testnet = exports.regtest = exports.mainnet = void 0;
|
||||||
exports.bitcoin = {
|
exports.mainnet = {
|
||||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
bech32: 'bc',
|
bech32: 'bech32',
|
||||||
bip32: {
|
bip32: {
|
||||||
public: 0x0488b21e,
|
public: 0x0488b21e,
|
||||||
private: 0x0488ade4,
|
private: 0x0488ade4, // lbry/wallet/server/coin.py XPRV_VERBYTES (unchanged)
|
||||||
},
|
},
|
||||||
pubKeyHash: 0x00,
|
pubKeyHash: 0x55,
|
||||||
scriptHash: 0x05,
|
scriptHash: 0x7a,
|
||||||
wif: 0x80,
|
wif: 0x1c, // lbry/wallet/server/coin.py WIF_BYTE
|
||||||
};
|
};
|
||||||
exports.regtest = {
|
exports.regtest = {
|
||||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
bech32: 'bcrt',
|
bech32: 'bech32',
|
||||||
bip32: {
|
bip32: {
|
||||||
public: 0x043587cf,
|
public: 0x043587cf,
|
||||||
private: 0x04358394,
|
private: 0x04358394, // lbry/wallet/server/coin.py XPRV_VERBYTES (unchanged)
|
||||||
},
|
},
|
||||||
pubKeyHash: 0x6f,
|
pubKeyHash: 0x6f,
|
||||||
scriptHash: 0xc4,
|
scriptHash: 0xc4,
|
||||||
wif: 0xef,
|
wif: 0x1c, // lbry/wallet/server/coin.py WIF_BYTE
|
||||||
};
|
};
|
||||||
exports.testnet = {
|
exports.testnet = {
|
||||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
bech32: 'tb',
|
bech32: 'bech32',
|
||||||
bip32: {
|
bip32: {
|
||||||
public: 0x043587cf,
|
public: 0x043587cf,
|
||||||
private: 0x04358394,
|
private: 0x04358394, // lbry/wallet/server/coin.py XPRV_VERBYTES (unchanged)
|
||||||
},
|
},
|
||||||
pubKeyHash: 0x6f,
|
pubKeyHash: 0x6f,
|
||||||
scriptHash: 0xc4,
|
scriptHash: 0xc4,
|
||||||
wif: 0xef,
|
wif: 0x1c, // lbry/wallet/server/coin.py WIF_BYTE
|
||||||
};
|
};
|
||||||
|
|
|
@ -112,9 +112,11 @@ const OPS = {
|
||||||
OP_CHECKSEQUENCEVERIFY: 178,
|
OP_CHECKSEQUENCEVERIFY: 178,
|
||||||
OP_NOP4: 179,
|
OP_NOP4: 179,
|
||||||
OP_NOP5: 180,
|
OP_NOP5: 180,
|
||||||
OP_NOP6: 181,
|
// LBRY custom opcodes: tx types
|
||||||
OP_NOP7: 182,
|
// Replaces OP_NOP6 - OP_NOP8
|
||||||
OP_NOP8: 183,
|
OP_CLAIM_NAME: 181,
|
||||||
|
OP_SUPPORT_CLAIM: 182,
|
||||||
|
OP_UPDATE_CLAIM: 183,
|
||||||
OP_NOP9: 184,
|
OP_NOP9: 184,
|
||||||
OP_NOP10: 185,
|
OP_NOP10: 185,
|
||||||
OP_PUBKEYHASH: 253,
|
OP_PUBKEYHASH: 253,
|
||||||
|
|
2
src/payments/claim_name.d.ts
vendored
Normal file
2
src/payments/claim_name.d.ts
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import { Payment, PaymentOpts } from './index';
|
||||||
|
export declare function claimName(a: Payment, opts?: PaymentOpts): Payment;
|
179
src/payments/claim_name.js
Normal file
179
src/payments/claim_name.js
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
'use strict';
|
||||||
|
// This is mostly a copy of p2pkh, as the usual claim name script is based on that
|
||||||
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
|
exports.claimName = void 0;
|
||||||
|
const bcrypto = require('../crypto');
|
||||||
|
const networks_1 = require('../networks');
|
||||||
|
const bscript = require('../script');
|
||||||
|
const types_1 = require('../types');
|
||||||
|
const lazy = require('./lazy');
|
||||||
|
const bs58check = require('bs58check');
|
||||||
|
const OPS = bscript.OPS;
|
||||||
|
// input: {signature} {pubkey}
|
||||||
|
// output: OP_CLAIM_NAME {claim_name} {claim} OP_2DROP OP_DROP OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG
|
||||||
|
function claimName(a, opts) {
|
||||||
|
if (
|
||||||
|
!a.address &&
|
||||||
|
!a.hash &&
|
||||||
|
!a.output &&
|
||||||
|
!a.pubkey &&
|
||||||
|
!a.input &&
|
||||||
|
!a.claim &&
|
||||||
|
!a.claimName
|
||||||
|
)
|
||||||
|
throw new TypeError('Not enough data');
|
||||||
|
opts = Object.assign({ validate: true }, opts || {});
|
||||||
|
(0, types_1.typeforce)(
|
||||||
|
{
|
||||||
|
network: types_1.typeforce.maybe(types_1.typeforce.Object),
|
||||||
|
address: types_1.typeforce.maybe(types_1.typeforce.String),
|
||||||
|
hash: types_1.typeforce.maybe(types_1.typeforce.BufferN(20)),
|
||||||
|
output: types_1.typeforce.maybe(types_1.typeforce.Buffer),
|
||||||
|
pubkey: types_1.typeforce.maybe(types_1.isPoint),
|
||||||
|
signature: types_1.typeforce.maybe(bscript.isCanonicalScriptSignature),
|
||||||
|
input: types_1.typeforce.maybe(types_1.typeforce.Buffer),
|
||||||
|
claimName: types_1.typeforce.maybe(types_1.typeforce.String),
|
||||||
|
claim: types_1.typeforce.maybe(types_1.typeforce.Buffer),
|
||||||
|
},
|
||||||
|
a,
|
||||||
|
);
|
||||||
|
const _address = lazy.value(() => {
|
||||||
|
const payload = bs58check.decode(a.address);
|
||||||
|
const version = payload.readUInt8(0);
|
||||||
|
const hash = payload.slice(1);
|
||||||
|
return { version, hash };
|
||||||
|
});
|
||||||
|
const _chunks = lazy.value(() => {
|
||||||
|
return bscript.decompile(a.input);
|
||||||
|
});
|
||||||
|
// We need output chunks as well, we can't just go by byte location within
|
||||||
|
// the output, because claim and claimName are of variable length.
|
||||||
|
const _outputChunks = lazy.value(() => {
|
||||||
|
return bscript.decompile(a.output);
|
||||||
|
});
|
||||||
|
const network = a.network || networks_1.mainnet;
|
||||||
|
const o = { name: 'claim_name', network };
|
||||||
|
lazy.prop(o, 'address', () => {
|
||||||
|
if (!o.hash) return;
|
||||||
|
const payload = Buffer.allocUnsafe(21);
|
||||||
|
payload.writeUInt8(network.pubKeyHash, 0);
|
||||||
|
o.hash.copy(payload, 1);
|
||||||
|
return bs58check.encode(payload);
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'hash', () => {
|
||||||
|
if (a.output) return _outputChunks()[7];
|
||||||
|
if (a.address) return _address().hash;
|
||||||
|
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey || o.pubkey);
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'claim', () => {
|
||||||
|
if (a.output) return _outputChunks()[2];
|
||||||
|
if (a.claim) return a.claim;
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'claimName', () => {
|
||||||
|
if (a.output) return _outputChunks()[1].toString();
|
||||||
|
if (a.claimName) return a.claimName;
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'output', () => {
|
||||||
|
if (!o.hash) return;
|
||||||
|
if (!o.claimName) return;
|
||||||
|
if (!o.claim) return;
|
||||||
|
return bscript.compile([
|
||||||
|
OPS.OP_CLAIM_NAME,
|
||||||
|
Buffer.from(o.claimName),
|
||||||
|
o.claim,
|
||||||
|
OPS.OP_2DROP,
|
||||||
|
OPS.OP_DROP,
|
||||||
|
OPS.OP_DUP,
|
||||||
|
OPS.OP_HASH160,
|
||||||
|
o.hash,
|
||||||
|
OPS.OP_EQUALVERIFY,
|
||||||
|
OPS.OP_CHECKSIG,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'pubkey', () => {
|
||||||
|
if (!a.input) return;
|
||||||
|
return _chunks()[1];
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'signature', () => {
|
||||||
|
if (!a.input) return;
|
||||||
|
return _chunks()[0];
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'input', () => {
|
||||||
|
if (!a.pubkey) return;
|
||||||
|
if (!a.signature) return;
|
||||||
|
return bscript.compile([a.signature, a.pubkey]);
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'witness', () => {
|
||||||
|
if (!o.input) return;
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
// extended validation
|
||||||
|
if (opts.validate) {
|
||||||
|
let hash = Buffer.from([]);
|
||||||
|
if (a.address) {
|
||||||
|
if (_address().version !== network.pubKeyHash)
|
||||||
|
throw new TypeError('Invalid version or Network mismatch');
|
||||||
|
if (_address().hash.length !== 20) throw new TypeError('Invalid address');
|
||||||
|
hash = _address().hash;
|
||||||
|
}
|
||||||
|
if (a.hash) {
|
||||||
|
if (hash.length > 0 && !hash.equals(a.hash))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = a.hash;
|
||||||
|
}
|
||||||
|
if (a.output) {
|
||||||
|
if (
|
||||||
|
_outputChunks().length !== 10 ||
|
||||||
|
_outputChunks()[0] !== OPS.OP_CLAIM_NAME ||
|
||||||
|
!Buffer.isBuffer(_outputChunks()[1]) ||
|
||||||
|
!Buffer.isBuffer(_outputChunks()[2]) ||
|
||||||
|
_outputChunks()[3] !== OPS.OP_2DROP ||
|
||||||
|
_outputChunks()[4] !== OPS.OP_DROP ||
|
||||||
|
_outputChunks()[5] !== OPS.OP_DUP ||
|
||||||
|
_outputChunks()[6] !== OPS.OP_HASH160 ||
|
||||||
|
!Buffer.isBuffer(_outputChunks()[7]) ||
|
||||||
|
_outputChunks()[7].length !== 0x14 ||
|
||||||
|
_outputChunks()[8] !== OPS.OP_EQUALVERIFY ||
|
||||||
|
_outputChunks()[9] !== OPS.OP_CHECKSIG
|
||||||
|
)
|
||||||
|
throw new TypeError('Output is invalid');
|
||||||
|
const hash2 = _outputChunks()[7];
|
||||||
|
if (hash.length > 0 && !hash.equals(hash2))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = hash2;
|
||||||
|
const claimName2 = _outputChunks()[1].toString();
|
||||||
|
if (a.claimName && a.claimName !== claimName2)
|
||||||
|
throw new TypeError('claimName mismatch');
|
||||||
|
const claim2 = _outputChunks()[2];
|
||||||
|
if (
|
||||||
|
Buffer.isBuffer(a.claim) &&
|
||||||
|
a.claim.length > 0 &&
|
||||||
|
!a.claim.equals(claim2)
|
||||||
|
)
|
||||||
|
throw new TypeError('claim mismatch');
|
||||||
|
}
|
||||||
|
if (a.pubkey) {
|
||||||
|
const pkh = bcrypto.hash160(a.pubkey);
|
||||||
|
if (hash.length > 0 && !hash.equals(pkh))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = pkh;
|
||||||
|
}
|
||||||
|
if (a.input) {
|
||||||
|
const chunks = _chunks();
|
||||||
|
if (chunks.length !== 2) throw new TypeError('Input is invalid');
|
||||||
|
if (!bscript.isCanonicalScriptSignature(chunks[0]))
|
||||||
|
throw new TypeError('Input has invalid signature');
|
||||||
|
if (!(0, types_1.isPoint)(chunks[1]))
|
||||||
|
throw new TypeError('Input has invalid pubkey');
|
||||||
|
if (a.signature && !a.signature.equals(chunks[0]))
|
||||||
|
throw new TypeError('Signature mismatch');
|
||||||
|
if (a.pubkey && !a.pubkey.equals(chunks[1]))
|
||||||
|
throw new TypeError('Pubkey mismatch');
|
||||||
|
const pkh = bcrypto.hash160(chunks[1]);
|
||||||
|
if (hash.length > 0 && !hash.equals(pkh))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Object.assign(o, a);
|
||||||
|
}
|
||||||
|
exports.claimName = claimName;
|
|
@ -26,7 +26,7 @@ function p2data(a, opts) {
|
||||||
},
|
},
|
||||||
a,
|
a,
|
||||||
);
|
);
|
||||||
const network = a.network || networks_1.bitcoin;
|
const network = a.network || networks_1.mainnet;
|
||||||
const o = { name: 'embed', network };
|
const o = { name: 'embed', network };
|
||||||
lazy.prop(o, 'output', () => {
|
lazy.prop(o, 'output', () => {
|
||||||
if (!a.data) return;
|
if (!a.data) return;
|
||||||
|
|
5
src/payments/index.d.ts
vendored
5
src/payments/index.d.ts
vendored
|
@ -7,6 +7,7 @@ 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';
|
||||||
|
import { claimName } from './claim_name';
|
||||||
export interface Payment {
|
export interface Payment {
|
||||||
name?: string;
|
name?: string;
|
||||||
network?: Network;
|
network?: Network;
|
||||||
|
@ -23,6 +24,8 @@ export interface Payment {
|
||||||
hash?: Buffer;
|
hash?: Buffer;
|
||||||
redeem?: Payment;
|
redeem?: Payment;
|
||||||
witness?: Buffer[];
|
witness?: Buffer[];
|
||||||
|
claim?: Buffer;
|
||||||
|
claimName?: string;
|
||||||
}
|
}
|
||||||
export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment;
|
export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment;
|
||||||
export declare type PaymentFunction = () => Payment;
|
export declare type PaymentFunction = () => Payment;
|
||||||
|
@ -33,4 +36,4 @@ export interface PaymentOpts {
|
||||||
export declare type StackElement = Buffer | number;
|
export declare type StackElement = Buffer | number;
|
||||||
export declare type Stack = StackElement[];
|
export declare type Stack = StackElement[];
|
||||||
export declare type StackFunction = () => Stack;
|
export declare type StackFunction = () => Stack;
|
||||||
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh };
|
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, claimName };
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
Object.defineProperty(exports, '__esModule', { value: true });
|
Object.defineProperty(exports, '__esModule', { value: true });
|
||||||
exports.p2wsh = exports.p2wpkh = exports.p2sh = exports.p2pkh = exports.p2pk = exports.p2ms = exports.embed = void 0;
|
exports.claimName = exports.p2wsh = exports.p2wpkh = exports.p2sh = exports.p2pkh = exports.p2pk = exports.p2ms = exports.embed = void 0;
|
||||||
const embed_1 = require('./embed');
|
const embed_1 = require('./embed');
|
||||||
Object.defineProperty(exports, 'embed', {
|
Object.defineProperty(exports, 'embed', {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
|
@ -50,5 +50,12 @@ Object.defineProperty(exports, 'p2wsh', {
|
||||||
return p2wsh_1.p2wsh;
|
return p2wsh_1.p2wsh;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const claim_name_1 = require('./claim_name');
|
||||||
|
Object.defineProperty(exports, 'claimName', {
|
||||||
|
enumerable: true,
|
||||||
|
get: function() {
|
||||||
|
return claim_name_1.claimName;
|
||||||
|
},
|
||||||
|
});
|
||||||
// TODO
|
// TODO
|
||||||
// witness commitment
|
// witness commitment
|
||||||
|
|
|
@ -46,7 +46,7 @@ function p2ms(a, opts) {
|
||||||
},
|
},
|
||||||
a,
|
a,
|
||||||
);
|
);
|
||||||
const network = a.network || networks_1.bitcoin;
|
const network = a.network || networks_1.mainnet;
|
||||||
const o = { network };
|
const o = { network };
|
||||||
let chunks = [];
|
let chunks = [];
|
||||||
let decoded = false;
|
let decoded = false;
|
||||||
|
|
|
@ -6,6 +6,9 @@ const bscript = require('../script');
|
||||||
const types_1 = require('../types');
|
const types_1 = require('../types');
|
||||||
const lazy = require('./lazy');
|
const lazy = require('./lazy');
|
||||||
const OPS = bscript.OPS;
|
const OPS = bscript.OPS;
|
||||||
|
// NOTE We don't use this type of scriptPubKey (p2pk) for LBRY. Presumably
|
||||||
|
// because it stopped being used in Bitcoin before LBRY was created.
|
||||||
|
// TODO delete this file? and maybe others?
|
||||||
// input: {signature}
|
// input: {signature}
|
||||||
// output: {pubKey} OP_CHECKSIG
|
// output: {pubKey} OP_CHECKSIG
|
||||||
function p2pk(a, opts) {
|
function p2pk(a, opts) {
|
||||||
|
@ -25,7 +28,7 @@ function p2pk(a, opts) {
|
||||||
const _chunks = lazy.value(() => {
|
const _chunks = lazy.value(() => {
|
||||||
return bscript.decompile(a.input);
|
return bscript.decompile(a.input);
|
||||||
});
|
});
|
||||||
const network = a.network || networks_1.bitcoin;
|
const network = a.network || networks_1.mainnet;
|
||||||
const o = { name: 'p2pk', network };
|
const o = { name: 'p2pk', network };
|
||||||
lazy.prop(o, 'output', () => {
|
lazy.prop(o, 'output', () => {
|
||||||
if (!a.pubkey) return;
|
if (!a.pubkey) return;
|
||||||
|
|
|
@ -35,7 +35,7 @@ function p2pkh(a, opts) {
|
||||||
const _chunks = lazy.value(() => {
|
const _chunks = lazy.value(() => {
|
||||||
return bscript.decompile(a.input);
|
return bscript.decompile(a.input);
|
||||||
});
|
});
|
||||||
const network = a.network || networks_1.bitcoin;
|
const network = a.network || networks_1.mainnet;
|
||||||
const o = { name: 'p2pkh', network };
|
const o = { name: 'p2pkh', network };
|
||||||
lazy.prop(o, 'address', () => {
|
lazy.prop(o, 'address', () => {
|
||||||
if (!o.hash) return;
|
if (!o.hash) return;
|
||||||
|
|
|
@ -44,7 +44,7 @@ function p2sh(a, opts) {
|
||||||
);
|
);
|
||||||
let network = a.network;
|
let network = a.network;
|
||||||
if (!network) {
|
if (!network) {
|
||||||
network = (a.redeem && a.redeem.network) || networks_1.bitcoin;
|
network = (a.redeem && a.redeem.network) || networks_1.mainnet;
|
||||||
}
|
}
|
||||||
const o = { network };
|
const o = { network };
|
||||||
const _address = lazy.value(() => {
|
const _address = lazy.value(() => {
|
||||||
|
|
|
@ -41,7 +41,7 @@ function p2wpkh(a, opts) {
|
||||||
data: Buffer.from(data),
|
data: Buffer.from(data),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const network = a.network || networks_1.bitcoin;
|
const network = a.network || networks_1.mainnet;
|
||||||
const o = { name: 'p2wpkh', network };
|
const o = { name: 'p2wpkh', network };
|
||||||
lazy.prop(o, 'address', () => {
|
lazy.prop(o, 'address', () => {
|
||||||
if (!o.hash) return;
|
if (!o.hash) return;
|
||||||
|
|
|
@ -70,7 +70,7 @@ function p2wsh(a, opts) {
|
||||||
});
|
});
|
||||||
let network = a.network;
|
let network = a.network;
|
||||||
if (!network) {
|
if (!network) {
|
||||||
network = (a.redeem && a.redeem.network) || networks_1.bitcoin;
|
network = (a.redeem && a.redeem.network) || networks_1.mainnet;
|
||||||
}
|
}
|
||||||
const o = { network };
|
const o = { network };
|
||||||
lazy.prop(o, 'address', () => {
|
lazy.prop(o, 'address', () => {
|
||||||
|
|
12
src/psbt.js
12
src/psbt.js
|
@ -19,7 +19,7 @@ const DEFAULT_OPTS = {
|
||||||
* A bitcoinjs Network object. This is only used if you pass an `address`
|
* A bitcoinjs Network object. This is only used if you pass an `address`
|
||||||
* parameter to addOutput. Otherwise it is not needed and can be left default.
|
* parameter to addOutput. Otherwise it is not needed and can be left default.
|
||||||
*/
|
*/
|
||||||
network: networks_1.bitcoin,
|
network: networks_1.mainnet,
|
||||||
/**
|
/**
|
||||||
* When extractTransaction is called, the fee rate is checked.
|
* When extractTransaction is called, the fee rate is checked.
|
||||||
* THIS IS NOT TO BE RELIED ON.
|
* THIS IS NOT TO BE RELIED ON.
|
||||||
|
@ -671,6 +671,7 @@ function canFinalize(input, script, scriptType) {
|
||||||
case 'pubkey':
|
case 'pubkey':
|
||||||
case 'pubkeyhash':
|
case 'pubkeyhash':
|
||||||
case 'witnesspubkeyhash':
|
case 'witnesspubkeyhash':
|
||||||
|
case 'claimname':
|
||||||
return hasSigs(1, input.partialSig);
|
return hasSigs(1, input.partialSig);
|
||||||
case 'multisig':
|
case 'multisig':
|
||||||
const p2ms = payments.p2ms({ output: script });
|
const p2ms = payments.p2ms({ output: script });
|
||||||
|
@ -719,6 +720,7 @@ const isP2PKH = isPaymentFactory(payments.p2pkh);
|
||||||
const isP2WPKH = isPaymentFactory(payments.p2wpkh);
|
const isP2WPKH = isPaymentFactory(payments.p2wpkh);
|
||||||
const isP2WSHScript = isPaymentFactory(payments.p2wsh);
|
const isP2WSHScript = isPaymentFactory(payments.p2wsh);
|
||||||
const isP2SHScript = isPaymentFactory(payments.p2sh);
|
const isP2SHScript = isPaymentFactory(payments.p2sh);
|
||||||
|
const isClaimName = isPaymentFactory(payments.claimName);
|
||||||
function bip32DerivationIsMine(root) {
|
function bip32DerivationIsMine(root) {
|
||||||
return d => {
|
return d => {
|
||||||
if (!d.masterFingerprint.equals(root.fingerprint)) return false;
|
if (!d.masterFingerprint.equals(root.fingerprint)) return false;
|
||||||
|
@ -1050,6 +1052,13 @@ function getPayment(script, scriptType, partialSig) {
|
||||||
signature: partialSig[0].signature,
|
signature: partialSig[0].signature,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'claimname':
|
||||||
|
payment = payments.claimName({
|
||||||
|
output: script,
|
||||||
|
pubkey: partialSig[0].pubkey,
|
||||||
|
signature: partialSig[0].signature,
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return payment;
|
return payment;
|
||||||
}
|
}
|
||||||
|
@ -1404,6 +1413,7 @@ function classifyScript(script) {
|
||||||
if (isP2PKH(script)) return 'pubkeyhash';
|
if (isP2PKH(script)) return 'pubkeyhash';
|
||||||
if (isP2MS(script)) return 'multisig';
|
if (isP2MS(script)) return 'multisig';
|
||||||
if (isP2PK(script)) return 'pubkey';
|
if (isP2PK(script)) return 'pubkey';
|
||||||
|
if (isClaimName(script)) return 'claimname';
|
||||||
return 'nonstandard';
|
return 'nonstandard';
|
||||||
}
|
}
|
||||||
function range(n) {
|
function range(n) {
|
||||||
|
|
|
@ -50,7 +50,7 @@ describe('Bitcoin-core', () => {
|
||||||
|
|
||||||
const network: any = params.isTestnet
|
const network: any = params.isTestnet
|
||||||
? bitcoin.networks.testnet
|
? bitcoin.networks.testnet
|
||||||
: bitcoin.networks.bitcoin;
|
: bitcoin.networks.mainnet;
|
||||||
const version = network[typeMap[params.addrType]];
|
const version = network[typeMap[params.addrType]];
|
||||||
|
|
||||||
it('can export ' + expected, () => {
|
it('can export ' + expected, () => {
|
||||||
|
@ -65,8 +65,8 @@ describe('Bitcoin-core', () => {
|
||||||
// base58KeysInvalid
|
// base58KeysInvalid
|
||||||
describe('address.fromBase58Check', () => {
|
describe('address.fromBase58Check', () => {
|
||||||
const allowedNetworks = [
|
const allowedNetworks = [
|
||||||
bitcoin.networks.bitcoin.pubKeyHash,
|
bitcoin.networks.mainnet.pubKeyHash,
|
||||||
bitcoin.networks.bitcoin.scriptHash,
|
bitcoin.networks.mainnet.scriptHash,
|
||||||
bitcoin.networks.testnet.pubKeyHash,
|
bitcoin.networks.testnet.pubKeyHash,
|
||||||
bitcoin.networks.testnet.scriptHash,
|
bitcoin.networks.testnet.scriptHash,
|
||||||
];
|
];
|
||||||
|
|
|
@ -117,7 +117,7 @@ export function toBech32(
|
||||||
|
|
||||||
export function fromOutputScript(output: Buffer, network?: Network): string {
|
export function fromOutputScript(output: Buffer, network?: Network): string {
|
||||||
// TODO: Network
|
// TODO: Network
|
||||||
network = network || networks.bitcoin;
|
network = network || networks.mainnet;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return payments.p2pkh({ output, network }).address as string;
|
return payments.p2pkh({ output, network }).address as string;
|
||||||
|
@ -139,7 +139,7 @@ export function fromOutputScript(output: Buffer, network?: Network): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toOutputScript(address: string, network?: Network): Buffer {
|
export function toOutputScript(address: string, network?: Network): Buffer {
|
||||||
network = network || networks.bitcoin;
|
network = network || networks.mainnet;
|
||||||
|
|
||||||
let decodeBase58: Base58CheckResult | undefined;
|
let decodeBase58: Base58CheckResult | undefined;
|
||||||
let decodeBech32: Bech32Result | undefined;
|
let decodeBech32: Bech32Result | undefined;
|
||||||
|
|
|
@ -14,36 +14,36 @@ interface Bip32 {
|
||||||
private: number;
|
private: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bitcoin: Network = {
|
export const mainnet: Network = {
|
||||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
messagePrefix: '\x18Bitcoin Signed Message:\n', // ?
|
||||||
bech32: 'bc',
|
bech32: 'bech32', // TODO is this right?! BECH32_ADDRESS lbry/wallet/orchstr8/node.py?
|
||||||
bip32: {
|
bip32: {
|
||||||
public: 0x0488b21e,
|
public: 0x0488b21e, // lbry/wallet/server/coin.py XPUB_VERBYTES (unchanged)
|
||||||
private: 0x0488ade4,
|
private: 0x0488ade4, // lbry/wallet/server/coin.py XPRV_VERBYTES (unchanged)
|
||||||
},
|
},
|
||||||
pubKeyHash: 0x00,
|
pubKeyHash: 0x55, // lbry/wallet/server/coin.py P2PKH_VERBYTE
|
||||||
scriptHash: 0x05,
|
scriptHash: 0x7a, // lbry/wallet/server/coin.py P2SH_VERBYTES
|
||||||
wif: 0x80,
|
wif: 0x1c, // lbry/wallet/server/coin.py WIF_BYTE
|
||||||
};
|
};
|
||||||
export const regtest: Network = {
|
export const regtest: Network = {
|
||||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
bech32: 'bcrt',
|
bech32: 'bech32', // TODO is this right?! BECH32_ADDRESS lbry/wallet/orchstr8/node.py?
|
||||||
bip32: {
|
bip32: {
|
||||||
public: 0x043587cf,
|
public: 0x043587cf, // lbry/wallet/server/coin.py XPUB_VERBYTES (unchanged)
|
||||||
private: 0x04358394,
|
private: 0x04358394, // lbry/wallet/server/coin.py XPRV_VERBYTES (unchanged)
|
||||||
},
|
},
|
||||||
pubKeyHash: 0x6f,
|
pubKeyHash: 0x6f, // lbry/wallet/server/coin.py P2PKH_VERBYTE (unchanged)
|
||||||
scriptHash: 0xc4,
|
scriptHash: 0xc4, // lbry/wallet/server/coin.py P2SH_VERBYTES (unchanged)
|
||||||
wif: 0xef,
|
wif: 0x1c, // lbry/wallet/server/coin.py WIF_BYTE
|
||||||
};
|
};
|
||||||
export const testnet: Network = {
|
export const testnet: Network = {
|
||||||
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
||||||
bech32: 'tb',
|
bech32: 'bech32', // TODO is this right?! BECH32_ADDRESS lbry/wallet/orchstr8/node.py?
|
||||||
bip32: {
|
bip32: {
|
||||||
public: 0x043587cf,
|
public: 0x043587cf, // lbry/wallet/server/coin.py XPUB_VERBYTES (unchanged)
|
||||||
private: 0x04358394,
|
private: 0x04358394, // lbry/wallet/server/coin.py XPRV_VERBYTES (unchanged)
|
||||||
},
|
},
|
||||||
pubKeyHash: 0x6f,
|
pubKeyHash: 0x6f, // lbry/wallet/server/coin.py P2PKH_VERBYTE (unchanged)
|
||||||
scriptHash: 0xc4,
|
scriptHash: 0xc4, // lbry/wallet/server/coin.py P2SH_VERBYTES (unchanged)
|
||||||
wif: 0xef,
|
wif: 0x1c, // lbry/wallet/server/coin.py WIF_BYTE
|
||||||
};
|
};
|
||||||
|
|
|
@ -121,9 +121,13 @@ const OPS: { [key: string]: number } = {
|
||||||
|
|
||||||
OP_NOP4: 179,
|
OP_NOP4: 179,
|
||||||
OP_NOP5: 180,
|
OP_NOP5: 180,
|
||||||
OP_NOP6: 181,
|
|
||||||
OP_NOP7: 182,
|
// LBRY custom opcodes: tx types
|
||||||
OP_NOP8: 183,
|
// Replaces OP_NOP6 - OP_NOP8
|
||||||
|
OP_CLAIM_NAME: 181,
|
||||||
|
OP_SUPPORT_CLAIM: 182,
|
||||||
|
OP_UPDATE_CLAIM: 183,
|
||||||
|
|
||||||
OP_NOP9: 184,
|
OP_NOP9: 184,
|
||||||
OP_NOP10: 185,
|
OP_NOP10: 185,
|
||||||
|
|
||||||
|
|
196
ts_src/payments/claim_name.ts
Normal file
196
ts_src/payments/claim_name.ts
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
// This is mostly a copy of p2pkh, as the usual claim name script is based on that
|
||||||
|
|
||||||
|
import * as bcrypto from '../crypto';
|
||||||
|
import { mainnet as LBRY_MAINNET } from '../networks';
|
||||||
|
import * as bscript from '../script';
|
||||||
|
import { isPoint, typeforce as typef } from '../types';
|
||||||
|
import { Payment, PaymentOpts, StackFunction } from './index';
|
||||||
|
import * as lazy from './lazy';
|
||||||
|
import * as bs58check from 'bs58check';
|
||||||
|
const OPS = bscript.OPS;
|
||||||
|
|
||||||
|
// input: {signature} {pubkey}
|
||||||
|
// output: OP_CLAIM_NAME {claim_name} {claim} OP_2DROP OP_DROP OP_DUP OP_HASH160 {hash160(pubkey)} OP_EQUALVERIFY OP_CHECKSIG
|
||||||
|
export function claimName(a: Payment, opts?: PaymentOpts): Payment {
|
||||||
|
if (
|
||||||
|
!a.address &&
|
||||||
|
!a.hash &&
|
||||||
|
!a.output &&
|
||||||
|
!a.pubkey &&
|
||||||
|
!a.input &&
|
||||||
|
!a.claim &&
|
||||||
|
!a.claimName
|
||||||
|
)
|
||||||
|
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(20)),
|
||||||
|
output: typef.maybe(typef.Buffer), // NOTE: No length set since it's variable.
|
||||||
|
|
||||||
|
pubkey: typef.maybe(isPoint),
|
||||||
|
signature: typef.maybe(bscript.isCanonicalScriptSignature),
|
||||||
|
input: typef.maybe(typef.Buffer),
|
||||||
|
|
||||||
|
claimName: typef.maybe(typef.String),
|
||||||
|
claim: typef.maybe(typef.Buffer),
|
||||||
|
},
|
||||||
|
a,
|
||||||
|
);
|
||||||
|
|
||||||
|
const _address = lazy.value(() => {
|
||||||
|
const payload = bs58check.decode(a.address!);
|
||||||
|
const version = payload.readUInt8(0);
|
||||||
|
const hash = payload.slice(1);
|
||||||
|
return { version, hash };
|
||||||
|
});
|
||||||
|
const _chunks = lazy.value(() => {
|
||||||
|
return bscript.decompile(a.input!);
|
||||||
|
}) as StackFunction;
|
||||||
|
|
||||||
|
// We need output chunks as well, we can't just go by byte location within
|
||||||
|
// the output, because claim and claimName are of variable length.
|
||||||
|
const _outputChunks = lazy.value(() => {
|
||||||
|
return bscript.decompile(a.output!);
|
||||||
|
}) as StackFunction;
|
||||||
|
|
||||||
|
const network = a.network || LBRY_MAINNET;
|
||||||
|
const o: Payment = { name: 'claim_name', network };
|
||||||
|
|
||||||
|
lazy.prop(o, 'address', () => {
|
||||||
|
if (!o.hash) return;
|
||||||
|
|
||||||
|
const payload = Buffer.allocUnsafe(21);
|
||||||
|
payload.writeUInt8(network.pubKeyHash, 0);
|
||||||
|
o.hash.copy(payload, 1);
|
||||||
|
return bs58check.encode(payload);
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'hash', () => {
|
||||||
|
if (a.output) return _outputChunks()[7];
|
||||||
|
if (a.address) return _address().hash;
|
||||||
|
if (a.pubkey || o.pubkey) return bcrypto.hash160(a.pubkey! || o.pubkey!);
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'claim', () => {
|
||||||
|
if (a.output) return _outputChunks()[2];
|
||||||
|
if (a.claim) return a.claim;
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'claimName', () => {
|
||||||
|
if (a.output) return _outputChunks()[1].toString();
|
||||||
|
if (a.claimName) return a.claimName;
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'output', () => {
|
||||||
|
if (!o.hash) return;
|
||||||
|
if (!o.claimName) return;
|
||||||
|
if (!o.claim) return;
|
||||||
|
return bscript.compile([
|
||||||
|
OPS.OP_CLAIM_NAME,
|
||||||
|
Buffer.from(o.claimName),
|
||||||
|
o.claim,
|
||||||
|
OPS.OP_2DROP,
|
||||||
|
OPS.OP_DROP,
|
||||||
|
OPS.OP_DUP,
|
||||||
|
OPS.OP_HASH160,
|
||||||
|
o.hash,
|
||||||
|
OPS.OP_EQUALVERIFY,
|
||||||
|
OPS.OP_CHECKSIG,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'pubkey', () => {
|
||||||
|
if (!a.input) return;
|
||||||
|
return _chunks()[1] as Buffer;
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'signature', () => {
|
||||||
|
if (!a.input) return;
|
||||||
|
return _chunks()[0] as Buffer;
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'input', () => {
|
||||||
|
if (!a.pubkey) return;
|
||||||
|
if (!a.signature) return;
|
||||||
|
return bscript.compile([a.signature, a.pubkey]);
|
||||||
|
});
|
||||||
|
lazy.prop(o, 'witness', () => {
|
||||||
|
if (!o.input) return;
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
|
// extended validation
|
||||||
|
if (opts.validate) {
|
||||||
|
let hash: Buffer = Buffer.from([]);
|
||||||
|
if (a.address) {
|
||||||
|
if (_address().version !== network.pubKeyHash)
|
||||||
|
throw new TypeError('Invalid version or Network mismatch');
|
||||||
|
if (_address().hash.length !== 20) throw new TypeError('Invalid address');
|
||||||
|
hash = _address().hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.hash) {
|
||||||
|
if (hash.length > 0 && !hash.equals(a.hash))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = a.hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.output) {
|
||||||
|
if (
|
||||||
|
_outputChunks().length !== 10 ||
|
||||||
|
_outputChunks()[0] !== OPS.OP_CLAIM_NAME ||
|
||||||
|
!Buffer.isBuffer(_outputChunks()[1]) ||
|
||||||
|
!Buffer.isBuffer(_outputChunks()[2]) ||
|
||||||
|
_outputChunks()[3] !== OPS.OP_2DROP ||
|
||||||
|
_outputChunks()[4] !== OPS.OP_DROP ||
|
||||||
|
_outputChunks()[5] !== OPS.OP_DUP ||
|
||||||
|
_outputChunks()[6] !== OPS.OP_HASH160 ||
|
||||||
|
!Buffer.isBuffer(_outputChunks()[7]) ||
|
||||||
|
(_outputChunks()[7] as Buffer).length !== 0x14 ||
|
||||||
|
_outputChunks()[8] !== OPS.OP_EQUALVERIFY ||
|
||||||
|
_outputChunks()[9] !== OPS.OP_CHECKSIG
|
||||||
|
)
|
||||||
|
throw new TypeError('Output is invalid');
|
||||||
|
|
||||||
|
const hash2 = _outputChunks()[7] as Buffer;
|
||||||
|
if (hash.length > 0 && !hash.equals(hash2))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = hash2;
|
||||||
|
|
||||||
|
const claimName2 = _outputChunks()[1].toString();
|
||||||
|
if (a.claimName && a.claimName !== claimName2)
|
||||||
|
throw new TypeError('claimName mismatch');
|
||||||
|
|
||||||
|
const claim2 = _outputChunks()[2] as Buffer;
|
||||||
|
if (
|
||||||
|
Buffer.isBuffer(a.claim) &&
|
||||||
|
a.claim.length > 0 &&
|
||||||
|
!a.claim.equals(claim2)
|
||||||
|
)
|
||||||
|
throw new TypeError('claim mismatch');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.pubkey) {
|
||||||
|
const pkh = bcrypto.hash160(a.pubkey);
|
||||||
|
if (hash.length > 0 && !hash.equals(pkh))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
|
else hash = pkh;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.input) {
|
||||||
|
const chunks = _chunks();
|
||||||
|
if (chunks.length !== 2) throw new TypeError('Input is invalid');
|
||||||
|
if (!bscript.isCanonicalScriptSignature(chunks[0] as Buffer))
|
||||||
|
throw new TypeError('Input has invalid signature');
|
||||||
|
if (!isPoint(chunks[1])) throw new TypeError('Input has invalid pubkey');
|
||||||
|
|
||||||
|
if (a.signature && !a.signature.equals(chunks[0] as Buffer))
|
||||||
|
throw new TypeError('Signature mismatch');
|
||||||
|
if (a.pubkey && !a.pubkey.equals(chunks[1] as Buffer))
|
||||||
|
throw new TypeError('Pubkey mismatch');
|
||||||
|
|
||||||
|
const pkh = bcrypto.hash160(chunks[1] as Buffer);
|
||||||
|
if (hash.length > 0 && !hash.equals(pkh))
|
||||||
|
throw new TypeError('Hash mismatch');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign(o, a);
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { bitcoin as BITCOIN_NETWORK } from '../networks';
|
import { mainnet as LBRY_MAINNET } from '../networks';
|
||||||
import * as bscript from '../script';
|
import * as bscript from '../script';
|
||||||
import { typeforce as typef } from '../types';
|
import { typeforce as typef } from '../types';
|
||||||
import { Payment, PaymentOpts, Stack } from './index';
|
import { Payment, PaymentOpts, Stack } from './index';
|
||||||
|
@ -28,7 +28,7 @@ export function p2data(a: Payment, opts?: PaymentOpts): Payment {
|
||||||
a,
|
a,
|
||||||
);
|
);
|
||||||
|
|
||||||
const network = a.network || BITCOIN_NETWORK;
|
const network = a.network || LBRY_MAINNET;
|
||||||
const o = { name: 'embed', network } as Payment;
|
const o = { name: 'embed', network } as Payment;
|
||||||
|
|
||||||
lazy.prop(o, 'output', () => {
|
lazy.prop(o, 'output', () => {
|
||||||
|
|
|
@ -6,6 +6,7 @@ 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';
|
||||||
|
import { claimName } from './claim_name';
|
||||||
|
|
||||||
export interface Payment {
|
export interface Payment {
|
||||||
name?: string;
|
name?: string;
|
||||||
|
@ -23,6 +24,8 @@ export interface Payment {
|
||||||
hash?: Buffer;
|
hash?: Buffer;
|
||||||
redeem?: Payment;
|
redeem?: Payment;
|
||||||
witness?: Buffer[];
|
witness?: Buffer[];
|
||||||
|
claim?: Buffer;
|
||||||
|
claimName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment;
|
export type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment;
|
||||||
|
@ -38,7 +41,7 @@ export type StackElement = Buffer | number;
|
||||||
export type Stack = StackElement[];
|
export type Stack = StackElement[];
|
||||||
export type StackFunction = () => Stack;
|
export type StackFunction = () => Stack;
|
||||||
|
|
||||||
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh };
|
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, claimName };
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
// witness commitment
|
// witness commitment
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { bitcoin as BITCOIN_NETWORK } from '../networks';
|
import { mainnet as LBRY_MAINNET } from '../networks';
|
||||||
import * as bscript from '../script';
|
import * as bscript from '../script';
|
||||||
import { isPoint, typeforce as typef } from '../types';
|
import { isPoint, typeforce as typef } from '../types';
|
||||||
import { Payment, PaymentOpts, Stack } from './index';
|
import { Payment, PaymentOpts, Stack } from './index';
|
||||||
|
@ -48,7 +48,7 @@ export function p2ms(a: Payment, opts?: PaymentOpts): Payment {
|
||||||
a,
|
a,
|
||||||
);
|
);
|
||||||
|
|
||||||
const network = a.network || BITCOIN_NETWORK;
|
const network = a.network || LBRY_MAINNET;
|
||||||
const o: Payment = { network };
|
const o: Payment = { network };
|
||||||
|
|
||||||
let chunks: Stack = [];
|
let chunks: Stack = [];
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
import { bitcoin as BITCOIN_NETWORK } from '../networks';
|
import { mainnet as LBRY_MAINNET } from '../networks';
|
||||||
import * as bscript from '../script';
|
import * as bscript from '../script';
|
||||||
import { isPoint, typeforce as typef } from '../types';
|
import { isPoint, typeforce as typef } from '../types';
|
||||||
import { Payment, PaymentOpts, StackFunction } from './index';
|
import { Payment, PaymentOpts, StackFunction } from './index';
|
||||||
import * as lazy from './lazy';
|
import * as lazy from './lazy';
|
||||||
const OPS = bscript.OPS;
|
const OPS = bscript.OPS;
|
||||||
|
|
||||||
|
// NOTE We don't use this type of scriptPubKey (p2pk) for LBRY. Presumably
|
||||||
|
// because it stopped being used in Bitcoin before LBRY was created.
|
||||||
|
// TODO delete this file? and maybe others?
|
||||||
|
|
||||||
// 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 {
|
||||||
|
@ -28,7 +32,7 @@ export function p2pk(a: Payment, opts?: PaymentOpts): Payment {
|
||||||
return bscript.decompile(a.input!);
|
return bscript.decompile(a.input!);
|
||||||
}) as StackFunction;
|
}) as StackFunction;
|
||||||
|
|
||||||
const network = a.network || BITCOIN_NETWORK;
|
const network = a.network || LBRY_MAINNET;
|
||||||
const o: Payment = { name: 'p2pk', network };
|
const o: Payment = { name: 'p2pk', network };
|
||||||
|
|
||||||
lazy.prop(o, 'output', () => {
|
lazy.prop(o, 'output', () => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as bcrypto from '../crypto';
|
import * as bcrypto from '../crypto';
|
||||||
import { bitcoin as BITCOIN_NETWORK } from '../networks';
|
import { mainnet as LBRY_MAINNET } from '../networks';
|
||||||
import * as bscript from '../script';
|
import * as bscript from '../script';
|
||||||
import { isPoint, typeforce as typef } from '../types';
|
import { isPoint, typeforce as typef } from '../types';
|
||||||
import { Payment, PaymentOpts, StackFunction } from './index';
|
import { Payment, PaymentOpts, StackFunction } from './index';
|
||||||
|
@ -38,7 +38,7 @@ export function p2pkh(a: Payment, opts?: PaymentOpts): Payment {
|
||||||
return bscript.decompile(a.input!);
|
return bscript.decompile(a.input!);
|
||||||
}) as StackFunction;
|
}) as StackFunction;
|
||||||
|
|
||||||
const network = a.network || BITCOIN_NETWORK;
|
const network = a.network || LBRY_MAINNET;
|
||||||
const o: Payment = { name: 'p2pkh', network };
|
const o: Payment = { name: 'p2pkh', network };
|
||||||
|
|
||||||
lazy.prop(o, 'address', () => {
|
lazy.prop(o, 'address', () => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as bcrypto from '../crypto';
|
import * as bcrypto from '../crypto';
|
||||||
import { bitcoin as BITCOIN_NETWORK } from '../networks';
|
import { mainnet as LBRY_MAINNET } from '../networks';
|
||||||
import * as bscript from '../script';
|
import * as bscript from '../script';
|
||||||
import { typeforce as typef } from '../types';
|
import { typeforce as typef } from '../types';
|
||||||
import {
|
import {
|
||||||
|
@ -51,7 +51,7 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment {
|
||||||
|
|
||||||
let network = a.network;
|
let network = a.network;
|
||||||
if (!network) {
|
if (!network) {
|
||||||
network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK;
|
network = (a.redeem && a.redeem.network) || LBRY_MAINNET;
|
||||||
}
|
}
|
||||||
|
|
||||||
const o: Payment = { network };
|
const o: Payment = { network };
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as bcrypto from '../crypto';
|
import * as bcrypto from '../crypto';
|
||||||
import { bitcoin as BITCOIN_NETWORK } from '../networks';
|
import { mainnet as LBRY_MAINNET } from '../networks';
|
||||||
import * as bscript from '../script';
|
import * as bscript from '../script';
|
||||||
import { isPoint, typeforce as typef } from '../types';
|
import { isPoint, typeforce as typef } from '../types';
|
||||||
import { Payment, PaymentOpts } from './index';
|
import { Payment, PaymentOpts } from './index';
|
||||||
|
@ -42,7 +42,7 @@ export function p2wpkh(a: Payment, opts?: PaymentOpts): Payment {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const network = a.network || BITCOIN_NETWORK;
|
const network = a.network || LBRY_MAINNET;
|
||||||
const o: Payment = { name: 'p2wpkh', network };
|
const o: Payment = { name: 'p2wpkh', network };
|
||||||
|
|
||||||
lazy.prop(o, 'address', () => {
|
lazy.prop(o, 'address', () => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as bcrypto from '../crypto';
|
import * as bcrypto from '../crypto';
|
||||||
import { bitcoin as BITCOIN_NETWORK } from '../networks';
|
import { mainnet as LBRY_MAINNET } from '../networks';
|
||||||
import * as bscript from '../script';
|
import * as bscript from '../script';
|
||||||
import { isPoint, typeforce as typef } from '../types';
|
import { isPoint, typeforce as typef } from '../types';
|
||||||
import { Payment, PaymentOpts, StackElement, StackFunction } from './index';
|
import { Payment, PaymentOpts, StackElement, StackFunction } from './index';
|
||||||
|
@ -74,7 +74,7 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment {
|
||||||
|
|
||||||
let network = a.network;
|
let network = a.network;
|
||||||
if (!network) {
|
if (!network) {
|
||||||
network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK;
|
network = (a.redeem && a.redeem.network) || LBRY_MAINNET;
|
||||||
}
|
}
|
||||||
|
|
||||||
const o: Payment = { network };
|
const o: Payment = { network };
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { checkForInput, checkForOutput } from 'bip174/src/lib/utils';
|
||||||
import { fromOutputScript, toOutputScript } from './address';
|
import { fromOutputScript, toOutputScript } from './address';
|
||||||
import { cloneBuffer, reverseBuffer } from './bufferutils';
|
import { cloneBuffer, reverseBuffer } from './bufferutils';
|
||||||
import { hash160 } from './crypto';
|
import { hash160 } from './crypto';
|
||||||
import { bitcoin as btcNetwork, Network } from './networks';
|
import { mainnet as lbryMainnet, Network } from './networks';
|
||||||
import * as payments from './payments';
|
import * as payments from './payments';
|
||||||
import * as bscript from './script';
|
import * as bscript from './script';
|
||||||
import { Output, Transaction } from './transaction';
|
import { Output, Transaction } from './transaction';
|
||||||
|
@ -55,7 +55,7 @@ const DEFAULT_OPTS: PsbtOpts = {
|
||||||
* A bitcoinjs Network object. This is only used if you pass an `address`
|
* A bitcoinjs Network object. This is only used if you pass an `address`
|
||||||
* parameter to addOutput. Otherwise it is not needed and can be left default.
|
* parameter to addOutput. Otherwise it is not needed and can be left default.
|
||||||
*/
|
*/
|
||||||
network: btcNetwork,
|
network: lbryMainnet,
|
||||||
/**
|
/**
|
||||||
* When extractTransaction is called, the fee rate is checked.
|
* When extractTransaction is called, the fee rate is checked.
|
||||||
* THIS IS NOT TO BE RELIED ON.
|
* THIS IS NOT TO BE RELIED ON.
|
||||||
|
@ -899,6 +899,7 @@ function canFinalize(
|
||||||
case 'pubkey':
|
case 'pubkey':
|
||||||
case 'pubkeyhash':
|
case 'pubkeyhash':
|
||||||
case 'witnesspubkeyhash':
|
case 'witnesspubkeyhash':
|
||||||
|
case 'claimname':
|
||||||
return hasSigs(1, input.partialSig);
|
return hasSigs(1, input.partialSig);
|
||||||
case 'multisig':
|
case 'multisig':
|
||||||
const p2ms = payments.p2ms({ output: script });
|
const p2ms = payments.p2ms({ output: script });
|
||||||
|
@ -955,6 +956,7 @@ const isP2PKH = isPaymentFactory(payments.p2pkh);
|
||||||
const isP2WPKH = isPaymentFactory(payments.p2wpkh);
|
const isP2WPKH = isPaymentFactory(payments.p2wpkh);
|
||||||
const isP2WSHScript = isPaymentFactory(payments.p2wsh);
|
const isP2WSHScript = isPaymentFactory(payments.p2wsh);
|
||||||
const isP2SHScript = isPaymentFactory(payments.p2sh);
|
const isP2SHScript = isPaymentFactory(payments.p2sh);
|
||||||
|
const isClaimName = isPaymentFactory(payments.claimName);
|
||||||
|
|
||||||
function bip32DerivationIsMine(
|
function bip32DerivationIsMine(
|
||||||
root: HDSigner,
|
root: HDSigner,
|
||||||
|
@ -1379,6 +1381,13 @@ function getPayment(
|
||||||
signature: partialSig[0].signature,
|
signature: partialSig[0].signature,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case 'claimname':
|
||||||
|
payment = payments.claimName({
|
||||||
|
output: script,
|
||||||
|
pubkey: partialSig[0].pubkey,
|
||||||
|
signature: partialSig[0].signature,
|
||||||
|
});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
return payment!;
|
return payment!;
|
||||||
}
|
}
|
||||||
|
@ -1844,12 +1853,14 @@ type ScriptType =
|
||||||
| 'pubkeyhash'
|
| 'pubkeyhash'
|
||||||
| 'multisig'
|
| 'multisig'
|
||||||
| 'pubkey'
|
| 'pubkey'
|
||||||
|
| 'claimname'
|
||||||
| 'nonstandard';
|
| 'nonstandard';
|
||||||
function classifyScript(script: Buffer): ScriptType {
|
function classifyScript(script: Buffer): ScriptType {
|
||||||
if (isP2WPKH(script)) return 'witnesspubkeyhash';
|
if (isP2WPKH(script)) return 'witnesspubkeyhash';
|
||||||
if (isP2PKH(script)) return 'pubkeyhash';
|
if (isP2PKH(script)) return 'pubkeyhash';
|
||||||
if (isP2MS(script)) return 'multisig';
|
if (isP2MS(script)) return 'multisig';
|
||||||
if (isP2PK(script)) return 'pubkey';
|
if (isP2PK(script)) return 'pubkey';
|
||||||
|
if (isClaimName(script)) return 'claimname';
|
||||||
return 'nonstandard';
|
return 'nonstandard';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue