Convert to LBRY: Can now sign PSBTs & claim names #1

Closed
orblivion wants to merge 2 commits from lbry into master
32 changed files with 4092 additions and 75 deletions

87
lbry-demo.js Normal file
View 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

File diff suppressed because it is too large Load diff

View file

@ -86,7 +86,7 @@ function toBech32(data, version, prefix) {
exports.toBech32 = toBech32;
function fromOutputScript(output, network) {
// TODO: Network
network = network || networks.bitcoin;
network = network || networks.mainnet;
try {
return payments.p2pkh({ output, network }).address;
} catch (e) {}
@ -106,7 +106,7 @@ function fromOutputScript(output, network) {
}
exports.fromOutputScript = fromOutputScript;
function toOutputScript(address, network) {
network = network || networks.bitcoin;
network = network || networks.mainnet;
let decodeBase58;
let decodeBech32;
try {

2
src/networks.d.ts vendored
View file

@ -10,7 +10,7 @@ interface Bip32 {
public: number;
private: number;
}
export declare const bitcoin: Network;
export declare const mainnet: Network;
export declare const regtest: Network;
export declare const testnet: Network;
export {};

View file

@ -1,36 +1,36 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
exports.testnet = exports.regtest = exports.bitcoin = void 0;
exports.bitcoin = {
exports.testnet = exports.regtest = exports.mainnet = void 0;
exports.mainnet = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bc',
bech32: 'bech32',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
private: 0x0488ade4, // lbry/wallet/server/coin.py XPRV_VERBYTES (unchanged)
},
pubKeyHash: 0x00,
scriptHash: 0x05,
wif: 0x80,
pubKeyHash: 0x55,
scriptHash: 0x7a,
wif: 0x1c, // lbry/wallet/server/coin.py WIF_BYTE
};
exports.regtest = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bcrt',
bech32: 'bech32',
bip32: {
public: 0x043587cf,
private: 0x04358394,
private: 0x04358394, // lbry/wallet/server/coin.py XPRV_VERBYTES (unchanged)
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
wif: 0x1c, // lbry/wallet/server/coin.py WIF_BYTE
};
exports.testnet = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'tb',
bech32: 'bech32',
bip32: {
public: 0x043587cf,
private: 0x04358394,
private: 0x04358394, // lbry/wallet/server/coin.py XPRV_VERBYTES (unchanged)
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
wif: 0x1c, // lbry/wallet/server/coin.py WIF_BYTE
};

View file

@ -112,9 +112,11 @@ const OPS = {
OP_CHECKSEQUENCEVERIFY: 178,
OP_NOP4: 179,
OP_NOP5: 180,
OP_NOP6: 181,
OP_NOP7: 182,
OP_NOP8: 183,
// LBRY custom opcodes: tx types
// Replaces OP_NOP6 - OP_NOP8
OP_CLAIM_NAME: 181,
OP_SUPPORT_CLAIM: 182,
OP_UPDATE_CLAIM: 183,
OP_NOP9: 184,
OP_NOP10: 185,
OP_PUBKEYHASH: 253,

2
src/payments/claim_name.d.ts vendored Normal file
View 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
View 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;

View file

@ -26,7 +26,7 @@ function p2data(a, opts) {
},
a,
);
const network = a.network || networks_1.bitcoin;
const network = a.network || networks_1.mainnet;
const o = { name: 'embed', network };
lazy.prop(o, 'output', () => {
if (!a.data) return;

View file

@ -7,6 +7,7 @@ import { p2pkh } from './p2pkh';
import { p2sh } from './p2sh';
import { p2wpkh } from './p2wpkh';
import { p2wsh } from './p2wsh';
import { claimName } from './claim_name';
export interface Payment {
name?: string;
network?: Network;
@ -23,6 +24,8 @@ export interface Payment {
hash?: Buffer;
redeem?: Payment;
witness?: Buffer[];
claim?: Buffer;
claimName?: string;
}
export declare type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment;
export declare type PaymentFunction = () => Payment;
@ -33,4 +36,4 @@ export interface PaymentOpts {
export declare type StackElement = Buffer | number;
export declare type Stack = StackElement[];
export declare type StackFunction = () => Stack;
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh };
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, claimName };

View file

@ -1,6 +1,6 @@
'use strict';
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');
Object.defineProperty(exports, 'embed', {
enumerable: true,
@ -50,5 +50,12 @@ Object.defineProperty(exports, '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
// witness commitment

View file

@ -46,7 +46,7 @@ function p2ms(a, opts) {
},
a,
);
const network = a.network || networks_1.bitcoin;
const network = a.network || networks_1.mainnet;
const o = { network };
let chunks = [];
let decoded = false;

View file

@ -6,6 +6,9 @@ const bscript = require('../script');
const types_1 = require('../types');
const lazy = require('./lazy');
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}
// output: {pubKey} OP_CHECKSIG
function p2pk(a, opts) {
@ -25,7 +28,7 @@ function p2pk(a, opts) {
const _chunks = lazy.value(() => {
return bscript.decompile(a.input);
});
const network = a.network || networks_1.bitcoin;
const network = a.network || networks_1.mainnet;
const o = { name: 'p2pk', network };
lazy.prop(o, 'output', () => {
if (!a.pubkey) return;

View file

@ -35,7 +35,7 @@ function p2pkh(a, opts) {
const _chunks = lazy.value(() => {
return bscript.decompile(a.input);
});
const network = a.network || networks_1.bitcoin;
const network = a.network || networks_1.mainnet;
const o = { name: 'p2pkh', network };
lazy.prop(o, 'address', () => {
if (!o.hash) return;

View file

@ -44,7 +44,7 @@ function p2sh(a, opts) {
);
let network = a.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 _address = lazy.value(() => {

View file

@ -41,7 +41,7 @@ function p2wpkh(a, opts) {
data: Buffer.from(data),
};
});
const network = a.network || networks_1.bitcoin;
const network = a.network || networks_1.mainnet;
const o = { name: 'p2wpkh', network };
lazy.prop(o, 'address', () => {
if (!o.hash) return;

View file

@ -70,7 +70,7 @@ function p2wsh(a, opts) {
});
let network = a.network;
if (!network) {
network = (a.redeem && a.redeem.network) || networks_1.bitcoin;
network = (a.redeem && a.redeem.network) || networks_1.mainnet;
}
const o = { network };
lazy.prop(o, 'address', () => {

View file

@ -19,7 +19,7 @@ const DEFAULT_OPTS = {
* 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.
*/
network: networks_1.bitcoin,
network: networks_1.mainnet,
/**
* When extractTransaction is called, the fee rate is checked.
* THIS IS NOT TO BE RELIED ON.
@ -671,6 +671,7 @@ function canFinalize(input, script, scriptType) {
case 'pubkey':
case 'pubkeyhash':
case 'witnesspubkeyhash':
case 'claimname':
return hasSigs(1, input.partialSig);
case 'multisig':
const p2ms = payments.p2ms({ output: script });
@ -719,6 +720,7 @@ const isP2PKH = isPaymentFactory(payments.p2pkh);
const isP2WPKH = isPaymentFactory(payments.p2wpkh);
const isP2WSHScript = isPaymentFactory(payments.p2wsh);
const isP2SHScript = isPaymentFactory(payments.p2sh);
const isClaimName = isPaymentFactory(payments.claimName);
function bip32DerivationIsMine(root) {
return d => {
if (!d.masterFingerprint.equals(root.fingerprint)) return false;
@ -1050,6 +1052,13 @@ function getPayment(script, scriptType, partialSig) {
signature: partialSig[0].signature,
});
break;
case 'claimname':
payment = payments.claimName({
output: script,
pubkey: partialSig[0].pubkey,
signature: partialSig[0].signature,
});
break;
}
return payment;
}
@ -1404,6 +1413,7 @@ function classifyScript(script) {
if (isP2PKH(script)) return 'pubkeyhash';
if (isP2MS(script)) return 'multisig';
if (isP2PK(script)) return 'pubkey';
if (isClaimName(script)) return 'claimname';
return 'nonstandard';
}
function range(n) {

View file

@ -50,7 +50,7 @@ describe('Bitcoin-core', () => {
const network: any = params.isTestnet
? bitcoin.networks.testnet
: bitcoin.networks.bitcoin;
: bitcoin.networks.mainnet;
const version = network[typeMap[params.addrType]];
it('can export ' + expected, () => {
@ -65,8 +65,8 @@ describe('Bitcoin-core', () => {
// base58KeysInvalid
describe('address.fromBase58Check', () => {
const allowedNetworks = [
bitcoin.networks.bitcoin.pubKeyHash,
bitcoin.networks.bitcoin.scriptHash,
bitcoin.networks.mainnet.pubKeyHash,
bitcoin.networks.mainnet.scriptHash,
bitcoin.networks.testnet.pubKeyHash,
bitcoin.networks.testnet.scriptHash,
];

View file

@ -117,7 +117,7 @@ export function toBech32(
export function fromOutputScript(output: Buffer, network?: Network): string {
// TODO: Network
network = network || networks.bitcoin;
network = network || networks.mainnet;
try {
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 {
network = network || networks.bitcoin;
network = network || networks.mainnet;
let decodeBase58: Base58CheckResult | undefined;
let decodeBech32: Bech32Result | undefined;

View file

@ -14,36 +14,36 @@ interface Bip32 {
private: number;
}
export const bitcoin: Network = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bc',
export const mainnet: Network = {
messagePrefix: '\x18Bitcoin Signed Message:\n', // ?
bech32: 'bech32', // TODO is this right?! BECH32_ADDRESS lbry/wallet/orchstr8/node.py?
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
public: 0x0488b21e, // lbry/wallet/server/coin.py XPUB_VERBYTES (unchanged)
private: 0x0488ade4, // lbry/wallet/server/coin.py XPRV_VERBYTES (unchanged)
},
pubKeyHash: 0x00,
scriptHash: 0x05,
wif: 0x80,
pubKeyHash: 0x55, // lbry/wallet/server/coin.py P2PKH_VERBYTE
scriptHash: 0x7a, // lbry/wallet/server/coin.py P2SH_VERBYTES
wif: 0x1c, // lbry/wallet/server/coin.py WIF_BYTE
};
export const regtest: Network = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bcrt',
bech32: 'bech32', // TODO is this right?! BECH32_ADDRESS lbry/wallet/orchstr8/node.py?
bip32: {
public: 0x043587cf,
private: 0x04358394,
public: 0x043587cf, // lbry/wallet/server/coin.py XPUB_VERBYTES (unchanged)
private: 0x04358394, // lbry/wallet/server/coin.py XPRV_VERBYTES (unchanged)
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
pubKeyHash: 0x6f, // lbry/wallet/server/coin.py P2PKH_VERBYTE (unchanged)
scriptHash: 0xc4, // lbry/wallet/server/coin.py P2SH_VERBYTES (unchanged)
wif: 0x1c, // lbry/wallet/server/coin.py WIF_BYTE
};
export const testnet: Network = {
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'tb',
bech32: 'bech32', // TODO is this right?! BECH32_ADDRESS lbry/wallet/orchstr8/node.py?
bip32: {
public: 0x043587cf,
private: 0x04358394,
public: 0x043587cf, // lbry/wallet/server/coin.py XPUB_VERBYTES (unchanged)
private: 0x04358394, // lbry/wallet/server/coin.py XPRV_VERBYTES (unchanged)
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
pubKeyHash: 0x6f, // lbry/wallet/server/coin.py P2PKH_VERBYTE (unchanged)
scriptHash: 0xc4, // lbry/wallet/server/coin.py P2SH_VERBYTES (unchanged)
wif: 0x1c, // lbry/wallet/server/coin.py WIF_BYTE
};

View file

@ -121,9 +121,13 @@ const OPS: { [key: string]: number } = {
OP_NOP4: 179,
OP_NOP5: 180,
OP_NOP6: 181,
OP_NOP7: 182,
OP_NOP8: 183,
// LBRY custom opcodes: tx types
// Replaces OP_NOP6 - OP_NOP8
OP_CLAIM_NAME: 181,
OP_SUPPORT_CLAIM: 182,
OP_UPDATE_CLAIM: 183,
OP_NOP9: 184,
OP_NOP10: 185,

View 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);
}

View file

@ -1,4 +1,4 @@
import { bitcoin as BITCOIN_NETWORK } from '../networks';
import { mainnet as LBRY_MAINNET } from '../networks';
import * as bscript from '../script';
import { typeforce as typef } from '../types';
import { Payment, PaymentOpts, Stack } from './index';
@ -28,7 +28,7 @@ export function p2data(a: Payment, opts?: PaymentOpts): Payment {
a,
);
const network = a.network || BITCOIN_NETWORK;
const network = a.network || LBRY_MAINNET;
const o = { name: 'embed', network } as Payment;
lazy.prop(o, 'output', () => {

View file

@ -6,6 +6,7 @@ import { p2pkh } from './p2pkh';
import { p2sh } from './p2sh';
import { p2wpkh } from './p2wpkh';
import { p2wsh } from './p2wsh';
import { claimName } from './claim_name';
export interface Payment {
name?: string;
@ -23,6 +24,8 @@ export interface Payment {
hash?: Buffer;
redeem?: Payment;
witness?: Buffer[];
claim?: Buffer;
claimName?: string;
}
export type PaymentCreator = (a: Payment, opts?: PaymentOpts) => Payment;
@ -38,7 +41,7 @@ export type StackElement = Buffer | number;
export type Stack = StackElement[];
export type StackFunction = () => Stack;
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh };
export { embed, p2ms, p2pk, p2pkh, p2sh, p2wpkh, p2wsh, claimName };
// TODO
// witness commitment

View file

@ -1,4 +1,4 @@
import { bitcoin as BITCOIN_NETWORK } from '../networks';
import { mainnet as LBRY_MAINNET } from '../networks';
import * as bscript from '../script';
import { isPoint, typeforce as typef } from '../types';
import { Payment, PaymentOpts, Stack } from './index';
@ -48,7 +48,7 @@ export function p2ms(a: Payment, opts?: PaymentOpts): Payment {
a,
);
const network = a.network || BITCOIN_NETWORK;
const network = a.network || LBRY_MAINNET;
const o: Payment = { network };
let chunks: Stack = [];

View file

@ -1,10 +1,14 @@
import { bitcoin as BITCOIN_NETWORK } from '../networks';
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';
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}
// output: {pubKey} OP_CHECKSIG
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!);
}) as StackFunction;
const network = a.network || BITCOIN_NETWORK;
const network = a.network || LBRY_MAINNET;
const o: Payment = { name: 'p2pk', network };
lazy.prop(o, 'output', () => {

View file

@ -1,5 +1,5 @@
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 { isPoint, typeforce as typef } from '../types';
import { Payment, PaymentOpts, StackFunction } from './index';
@ -38,7 +38,7 @@ export function p2pkh(a: Payment, opts?: PaymentOpts): Payment {
return bscript.decompile(a.input!);
}) as StackFunction;
const network = a.network || BITCOIN_NETWORK;
const network = a.network || LBRY_MAINNET;
const o: Payment = { name: 'p2pkh', network };
lazy.prop(o, 'address', () => {

View file

@ -1,5 +1,5 @@
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 { typeforce as typef } from '../types';
import {
@ -51,7 +51,7 @@ export function p2sh(a: Payment, opts?: PaymentOpts): Payment {
let network = a.network;
if (!network) {
network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK;
network = (a.redeem && a.redeem.network) || LBRY_MAINNET;
}
const o: Payment = { network };

View file

@ -1,5 +1,5 @@
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 { isPoint, typeforce as typef } from '../types';
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 };
lazy.prop(o, 'address', () => {

View file

@ -1,5 +1,5 @@
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 { isPoint, typeforce as typef } from '../types';
import { Payment, PaymentOpts, StackElement, StackFunction } from './index';
@ -74,7 +74,7 @@ export function p2wsh(a: Payment, opts?: PaymentOpts): Payment {
let network = a.network;
if (!network) {
network = (a.redeem && a.redeem.network) || BITCOIN_NETWORK;
network = (a.redeem && a.redeem.network) || LBRY_MAINNET;
}
const o: Payment = { network };

View file

@ -16,7 +16,7 @@ import { checkForInput, checkForOutput } from 'bip174/src/lib/utils';
import { fromOutputScript, toOutputScript } from './address';
import { cloneBuffer, reverseBuffer } from './bufferutils';
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 bscript from './script';
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`
* 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.
* THIS IS NOT TO BE RELIED ON.
@ -899,6 +899,7 @@ function canFinalize(
case 'pubkey':
case 'pubkeyhash':
case 'witnesspubkeyhash':
case 'claimname':
return hasSigs(1, input.partialSig);
case 'multisig':
const p2ms = payments.p2ms({ output: script });
@ -955,6 +956,7 @@ const isP2PKH = isPaymentFactory(payments.p2pkh);
const isP2WPKH = isPaymentFactory(payments.p2wpkh);
const isP2WSHScript = isPaymentFactory(payments.p2wsh);
const isP2SHScript = isPaymentFactory(payments.p2sh);
const isClaimName = isPaymentFactory(payments.claimName);
function bip32DerivationIsMine(
root: HDSigner,
@ -1379,6 +1381,13 @@ function getPayment(
signature: partialSig[0].signature,
});
break;
case 'claimname':
payment = payments.claimName({
output: script,
pubkey: partialSig[0].pubkey,
signature: partialSig[0].signature,
});
break;
}
return payment!;
}
@ -1844,12 +1853,14 @@ type ScriptType =
| 'pubkeyhash'
| 'multisig'
| 'pubkey'
| 'claimname'
| 'nonstandard';
function classifyScript(script: Buffer): ScriptType {
if (isP2WPKH(script)) return 'witnesspubkeyhash';
if (isP2PKH(script)) return 'pubkeyhash';
if (isP2MS(script)) return 'multisig';
if (isP2PK(script)) return 'pubkey';
if (isClaimName(script)) return 'claimname';
return 'nonstandard';
}