Refactor sign for clarity
This commit is contained in:
parent
7c454e5f44
commit
ee3150d7c7
2 changed files with 293 additions and 194 deletions
|
@ -153,98 +153,25 @@ class TransactionBuilder {
|
||||||
witnessValue,
|
witnessValue,
|
||||||
witnessScript,
|
witnessScript,
|
||||||
) {
|
) {
|
||||||
let vin;
|
const data = getSigningData(
|
||||||
if (typeof signParams === 'number') {
|
this,
|
||||||
console.warn(
|
signParams,
|
||||||
'DEPRECATED: TransactionBuilder sign method arguments ' +
|
keyPair,
|
||||||
'will change in v6, please use the TxbSignArg interface',
|
redeemScript,
|
||||||
);
|
hashType,
|
||||||
vin = signParams;
|
witnessValue,
|
||||||
} else if (typeof signParams === 'object') {
|
witnessScript,
|
||||||
checkSignArgs(this, signParams);
|
);
|
||||||
({
|
const { input, ourPubKey, signatureHash } = data;
|
||||||
vin,
|
({ keyPair, hashType } = data);
|
||||||
keyPair,
|
trySign(
|
||||||
redeemScript,
|
input,
|
||||||
hashType,
|
ourPubKey,
|
||||||
witnessValue,
|
keyPair,
|
||||||
witnessScript,
|
signatureHash,
|
||||||
} = signParams);
|
hashType,
|
||||||
} else {
|
this.__USE_LOW_R,
|
||||||
throw new TypeError(
|
);
|
||||||
'TransactionBuilder sign first arg must be TxbSignArg or number',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (keyPair === undefined) {
|
|
||||||
throw new Error('sign requires keypair');
|
|
||||||
}
|
|
||||||
// TODO: remove keyPair.network matching in 4.0.0
|
|
||||||
if (keyPair.network && keyPair.network !== this.network)
|
|
||||||
throw new TypeError('Inconsistent network');
|
|
||||||
if (!this.__INPUTS[vin]) throw new Error('No input at index: ' + vin);
|
|
||||||
hashType = hashType || transaction_1.Transaction.SIGHASH_ALL;
|
|
||||||
if (this.__needsOutputs(hashType))
|
|
||||||
throw new Error('Transaction needs outputs');
|
|
||||||
const input = this.__INPUTS[vin];
|
|
||||||
// if redeemScript was previously provided, enforce consistency
|
|
||||||
if (
|
|
||||||
input.redeemScript !== undefined &&
|
|
||||||
redeemScript &&
|
|
||||||
!input.redeemScript.equals(redeemScript)
|
|
||||||
) {
|
|
||||||
throw new Error('Inconsistent redeemScript');
|
|
||||||
}
|
|
||||||
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey();
|
|
||||||
if (!canSign(input)) {
|
|
||||||
if (witnessValue !== undefined) {
|
|
||||||
if (input.value !== undefined && input.value !== witnessValue)
|
|
||||||
throw new Error('Input did not match witnessValue');
|
|
||||||
typeforce(types.Satoshi, witnessValue);
|
|
||||||
input.value = witnessValue;
|
|
||||||
}
|
|
||||||
if (!canSign(input)) {
|
|
||||||
const prepared = prepareInput(
|
|
||||||
input,
|
|
||||||
ourPubKey,
|
|
||||||
redeemScript,
|
|
||||||
witnessScript,
|
|
||||||
);
|
|
||||||
// updates inline
|
|
||||||
Object.assign(input, prepared);
|
|
||||||
}
|
|
||||||
if (!canSign(input)) throw Error(input.prevOutType + ' not supported');
|
|
||||||
}
|
|
||||||
// ready to sign
|
|
||||||
let signatureHash;
|
|
||||||
if (input.hasWitness) {
|
|
||||||
signatureHash = this.__TX.hashForWitnessV0(
|
|
||||||
vin,
|
|
||||||
input.signScript,
|
|
||||||
input.value,
|
|
||||||
hashType,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
signatureHash = this.__TX.hashForSignature(
|
|
||||||
vin,
|
|
||||||
input.signScript,
|
|
||||||
hashType,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// enforce in order signing of public keys
|
|
||||||
const signed = input.pubkeys.some((pubKey, i) => {
|
|
||||||
if (!ourPubKey.equals(pubKey)) return false;
|
|
||||||
if (input.signatures[i]) throw new Error('Signature already exists');
|
|
||||||
// TODO: add tests
|
|
||||||
if (ourPubKey.length !== 33 && input.hasWitness) {
|
|
||||||
throw new Error(
|
|
||||||
'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const signature = keyPair.sign(signatureHash, this.__USE_LOW_R);
|
|
||||||
input.signatures[i] = bscript.signature.encode(signature, hashType);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
if (!signed) throw new Error('Key pair cannot sign for this input');
|
|
||||||
}
|
}
|
||||||
__addInputUnsafe(txHash, vout, options) {
|
__addInputUnsafe(txHash, vout, options) {
|
||||||
if (transaction_1.Transaction.isCoinbaseHash(txHash)) {
|
if (transaction_1.Transaction.isCoinbaseHash(txHash)) {
|
||||||
|
@ -1007,3 +934,115 @@ function checkSignArgs(txb, signParams) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function trySign(input, ourPubKey, keyPair, signatureHash, hashType, useLowR) {
|
||||||
|
// enforce in order signing of public keys
|
||||||
|
const signed = input.pubkeys.some((pubKey, i) => {
|
||||||
|
if (!ourPubKey.equals(pubKey)) return false;
|
||||||
|
if (input.signatures[i]) throw new Error('Signature already exists');
|
||||||
|
// TODO: add tests
|
||||||
|
if (ourPubKey.length !== 33 && input.hasWitness) {
|
||||||
|
throw new Error(
|
||||||
|
'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const signature = keyPair.sign(signatureHash, useLowR);
|
||||||
|
input.signatures[i] = bscript.signature.encode(signature, hashType);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (!signed) throw new Error('Key pair cannot sign for this input');
|
||||||
|
}
|
||||||
|
function getSigningData(
|
||||||
|
txb,
|
||||||
|
signParams,
|
||||||
|
keyPair,
|
||||||
|
redeemScript,
|
||||||
|
hashType,
|
||||||
|
witnessValue,
|
||||||
|
witnessScript,
|
||||||
|
) {
|
||||||
|
let vin;
|
||||||
|
if (typeof signParams === 'number') {
|
||||||
|
console.warn(
|
||||||
|
'DEPRECATED: TransactionBuilder sign method arguments ' +
|
||||||
|
'will change in v6, please use the TxbSignArg interface',
|
||||||
|
);
|
||||||
|
vin = signParams;
|
||||||
|
} else if (typeof signParams === 'object') {
|
||||||
|
checkSignArgs(txb, signParams);
|
||||||
|
({
|
||||||
|
vin,
|
||||||
|
keyPair,
|
||||||
|
redeemScript,
|
||||||
|
hashType,
|
||||||
|
witnessValue,
|
||||||
|
witnessScript,
|
||||||
|
} = signParams);
|
||||||
|
} else {
|
||||||
|
throw new TypeError(
|
||||||
|
'TransactionBuilder sign first arg must be TxbSignArg or number',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (keyPair === undefined) {
|
||||||
|
throw new Error('sign requires keypair');
|
||||||
|
}
|
||||||
|
// TODO: remove keyPair.network matching in 4.0.0
|
||||||
|
if (keyPair.network && keyPair.network !== txb.network)
|
||||||
|
throw new TypeError('Inconsistent network');
|
||||||
|
// @ts-ignore
|
||||||
|
if (!txb.__INPUTS[vin]) throw new Error('No input at index: ' + vin);
|
||||||
|
hashType = hashType || transaction_1.Transaction.SIGHASH_ALL;
|
||||||
|
// @ts-ignore
|
||||||
|
if (txb.__needsOutputs(hashType))
|
||||||
|
throw new Error('Transaction needs outputs');
|
||||||
|
// @ts-ignore
|
||||||
|
const input = txb.__INPUTS[vin];
|
||||||
|
// if redeemScript was previously provided, enforce consistency
|
||||||
|
if (
|
||||||
|
input.redeemScript !== undefined &&
|
||||||
|
redeemScript &&
|
||||||
|
!input.redeemScript.equals(redeemScript)
|
||||||
|
) {
|
||||||
|
throw new Error('Inconsistent redeemScript');
|
||||||
|
}
|
||||||
|
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey();
|
||||||
|
if (!canSign(input)) {
|
||||||
|
if (witnessValue !== undefined) {
|
||||||
|
if (input.value !== undefined && input.value !== witnessValue)
|
||||||
|
throw new Error('Input did not match witnessValue');
|
||||||
|
typeforce(types.Satoshi, witnessValue);
|
||||||
|
input.value = witnessValue;
|
||||||
|
}
|
||||||
|
if (!canSign(input)) {
|
||||||
|
const prepared = prepareInput(
|
||||||
|
input,
|
||||||
|
ourPubKey,
|
||||||
|
redeemScript,
|
||||||
|
witnessScript,
|
||||||
|
);
|
||||||
|
// updates inline
|
||||||
|
Object.assign(input, prepared);
|
||||||
|
}
|
||||||
|
if (!canSign(input)) throw Error(input.prevOutType + ' not supported');
|
||||||
|
}
|
||||||
|
// ready to sign
|
||||||
|
let signatureHash;
|
||||||
|
if (input.hasWitness) {
|
||||||
|
// @ts-ignore
|
||||||
|
signatureHash = txb.__TX.hashForWitnessV0(
|
||||||
|
vin,
|
||||||
|
input.signScript,
|
||||||
|
input.value,
|
||||||
|
hashType,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
signatureHash = txb.__TX.hashForSignature(vin, input.signScript, hashType);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
input,
|
||||||
|
ourPubKey,
|
||||||
|
keyPair,
|
||||||
|
signatureHash,
|
||||||
|
hashType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -243,110 +243,27 @@ export class TransactionBuilder {
|
||||||
witnessValue?: number,
|
witnessValue?: number,
|
||||||
witnessScript?: Buffer,
|
witnessScript?: Buffer,
|
||||||
): void {
|
): void {
|
||||||
let vin: number;
|
const data = getSigningData(
|
||||||
if (typeof signParams === 'number') {
|
this,
|
||||||
console.warn(
|
signParams,
|
||||||
'DEPRECATED: TransactionBuilder sign method arguments ' +
|
keyPair,
|
||||||
'will change in v6, please use the TxbSignArg interface',
|
redeemScript,
|
||||||
);
|
hashType,
|
||||||
vin = signParams;
|
witnessValue,
|
||||||
} else if (typeof signParams === 'object') {
|
witnessScript,
|
||||||
checkSignArgs(this, signParams);
|
);
|
||||||
({
|
|
||||||
vin,
|
|
||||||
keyPair,
|
|
||||||
redeemScript,
|
|
||||||
hashType,
|
|
||||||
witnessValue,
|
|
||||||
witnessScript,
|
|
||||||
} = signParams);
|
|
||||||
} else {
|
|
||||||
throw new TypeError(
|
|
||||||
'TransactionBuilder sign first arg must be TxbSignArg or number',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (keyPair === undefined) {
|
|
||||||
throw new Error('sign requires keypair');
|
|
||||||
}
|
|
||||||
// TODO: remove keyPair.network matching in 4.0.0
|
|
||||||
if (keyPair.network && keyPair.network !== this.network)
|
|
||||||
throw new TypeError('Inconsistent network');
|
|
||||||
if (!this.__INPUTS[vin]) throw new Error('No input at index: ' + vin);
|
|
||||||
|
|
||||||
hashType = hashType || Transaction.SIGHASH_ALL;
|
const { input, ourPubKey, signatureHash } = data;
|
||||||
if (this.__needsOutputs(hashType))
|
({ keyPair, hashType } = data);
|
||||||
throw new Error('Transaction needs outputs');
|
|
||||||
|
|
||||||
const input = this.__INPUTS[vin];
|
trySign(
|
||||||
|
input,
|
||||||
// if redeemScript was previously provided, enforce consistency
|
ourPubKey,
|
||||||
if (
|
keyPair,
|
||||||
input.redeemScript !== undefined &&
|
signatureHash,
|
||||||
redeemScript &&
|
hashType,
|
||||||
!input.redeemScript.equals(redeemScript)
|
this.__USE_LOW_R,
|
||||||
) {
|
);
|
||||||
throw new Error('Inconsistent redeemScript');
|
|
||||||
}
|
|
||||||
|
|
||||||
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey!();
|
|
||||||
if (!canSign(input)) {
|
|
||||||
if (witnessValue !== undefined) {
|
|
||||||
if (input.value !== undefined && input.value !== witnessValue)
|
|
||||||
throw new Error('Input did not match witnessValue');
|
|
||||||
typeforce(types.Satoshi, witnessValue);
|
|
||||||
input.value = witnessValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canSign(input)) {
|
|
||||||
const prepared = prepareInput(
|
|
||||||
input,
|
|
||||||
ourPubKey,
|
|
||||||
redeemScript,
|
|
||||||
witnessScript,
|
|
||||||
);
|
|
||||||
|
|
||||||
// updates inline
|
|
||||||
Object.assign(input, prepared);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!canSign(input)) throw Error(input.prevOutType + ' not supported');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ready to sign
|
|
||||||
let signatureHash: Buffer;
|
|
||||||
if (input.hasWitness) {
|
|
||||||
signatureHash = this.__TX.hashForWitnessV0(
|
|
||||||
vin,
|
|
||||||
input.signScript as Buffer,
|
|
||||||
input.value as number,
|
|
||||||
hashType,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
signatureHash = this.__TX.hashForSignature(
|
|
||||||
vin,
|
|
||||||
input.signScript as Buffer,
|
|
||||||
hashType,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// enforce in order signing of public keys
|
|
||||||
const signed = input.pubkeys!.some((pubKey, i) => {
|
|
||||||
if (!ourPubKey.equals(pubKey!)) return false;
|
|
||||||
if (input.signatures![i]) throw new Error('Signature already exists');
|
|
||||||
|
|
||||||
// TODO: add tests
|
|
||||||
if (ourPubKey.length !== 33 && input.hasWitness) {
|
|
||||||
throw new Error(
|
|
||||||
'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const signature = keyPair!.sign(signatureHash, this.__USE_LOW_R);
|
|
||||||
input.signatures![i] = bscript.signature.encode(signature, hashType!);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!signed) throw new Error('Key pair cannot sign for this input');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private __addInputUnsafe(
|
private __addInputUnsafe(
|
||||||
|
@ -1243,3 +1160,146 @@ function checkSignArgs(txb: TransactionBuilder, signParams: TxbSignArg): void {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function trySign(
|
||||||
|
input: TxbInput,
|
||||||
|
ourPubKey: Buffer,
|
||||||
|
keyPair: ECPairInterface,
|
||||||
|
signatureHash: Buffer,
|
||||||
|
hashType: number,
|
||||||
|
useLowR: boolean,
|
||||||
|
): void {
|
||||||
|
// enforce in order signing of public keys
|
||||||
|
const signed = input.pubkeys!.some((pubKey, i) => {
|
||||||
|
if (!ourPubKey.equals(pubKey!)) return false;
|
||||||
|
if (input.signatures![i]) throw new Error('Signature already exists');
|
||||||
|
|
||||||
|
// TODO: add tests
|
||||||
|
if (ourPubKey.length !== 33 && input.hasWitness) {
|
||||||
|
throw new Error(
|
||||||
|
'BIP143 rejects uncompressed public keys in P2WPKH or P2WSH',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const signature = keyPair.sign(signatureHash, useLowR);
|
||||||
|
input.signatures![i] = bscript.signature.encode(signature, hashType);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!signed) throw new Error('Key pair cannot sign for this input');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSigningData(
|
||||||
|
txb: TransactionBuilder,
|
||||||
|
signParams: number | TxbSignArg,
|
||||||
|
keyPair?: ECPairInterface,
|
||||||
|
redeemScript?: Buffer,
|
||||||
|
hashType?: number,
|
||||||
|
witnessValue?: number,
|
||||||
|
witnessScript?: Buffer,
|
||||||
|
): {
|
||||||
|
input: TxbInput;
|
||||||
|
ourPubKey: Buffer;
|
||||||
|
keyPair: ECPairInterface;
|
||||||
|
signatureHash: Buffer;
|
||||||
|
hashType: number;
|
||||||
|
} {
|
||||||
|
let vin: number;
|
||||||
|
if (typeof signParams === 'number') {
|
||||||
|
console.warn(
|
||||||
|
'DEPRECATED: TransactionBuilder sign method arguments ' +
|
||||||
|
'will change in v6, please use the TxbSignArg interface',
|
||||||
|
);
|
||||||
|
vin = signParams;
|
||||||
|
} else if (typeof signParams === 'object') {
|
||||||
|
checkSignArgs(txb, signParams);
|
||||||
|
({
|
||||||
|
vin,
|
||||||
|
keyPair,
|
||||||
|
redeemScript,
|
||||||
|
hashType,
|
||||||
|
witnessValue,
|
||||||
|
witnessScript,
|
||||||
|
} = signParams);
|
||||||
|
} else {
|
||||||
|
throw new TypeError(
|
||||||
|
'TransactionBuilder sign first arg must be TxbSignArg or number',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (keyPair === undefined) {
|
||||||
|
throw new Error('sign requires keypair');
|
||||||
|
}
|
||||||
|
// TODO: remove keyPair.network matching in 4.0.0
|
||||||
|
if (keyPair.network && keyPair.network !== txb.network)
|
||||||
|
throw new TypeError('Inconsistent network');
|
||||||
|
// @ts-ignore
|
||||||
|
if (!txb.__INPUTS[vin]) throw new Error('No input at index: ' + vin);
|
||||||
|
|
||||||
|
hashType = hashType || Transaction.SIGHASH_ALL;
|
||||||
|
// @ts-ignore
|
||||||
|
if (txb.__needsOutputs(hashType))
|
||||||
|
throw new Error('Transaction needs outputs');
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
const input = txb.__INPUTS[vin];
|
||||||
|
|
||||||
|
// if redeemScript was previously provided, enforce consistency
|
||||||
|
if (
|
||||||
|
input.redeemScript !== undefined &&
|
||||||
|
redeemScript &&
|
||||||
|
!input.redeemScript.equals(redeemScript)
|
||||||
|
) {
|
||||||
|
throw new Error('Inconsistent redeemScript');
|
||||||
|
}
|
||||||
|
|
||||||
|
const ourPubKey = keyPair.publicKey || keyPair.getPublicKey!();
|
||||||
|
if (!canSign(input)) {
|
||||||
|
if (witnessValue !== undefined) {
|
||||||
|
if (input.value !== undefined && input.value !== witnessValue)
|
||||||
|
throw new Error('Input did not match witnessValue');
|
||||||
|
typeforce(types.Satoshi, witnessValue);
|
||||||
|
input.value = witnessValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canSign(input)) {
|
||||||
|
const prepared = prepareInput(
|
||||||
|
input,
|
||||||
|
ourPubKey,
|
||||||
|
redeemScript,
|
||||||
|
witnessScript,
|
||||||
|
);
|
||||||
|
|
||||||
|
// updates inline
|
||||||
|
Object.assign(input, prepared);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canSign(input)) throw Error(input.prevOutType + ' not supported');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ready to sign
|
||||||
|
let signatureHash: Buffer;
|
||||||
|
if (input.hasWitness) {
|
||||||
|
// @ts-ignore
|
||||||
|
signatureHash = txb.__TX.hashForWitnessV0(
|
||||||
|
vin,
|
||||||
|
input.signScript as Buffer,
|
||||||
|
input.value as number,
|
||||||
|
hashType,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
signatureHash = txb.__TX.hashForSignature(
|
||||||
|
vin,
|
||||||
|
input.signScript as Buffer,
|
||||||
|
hashType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
input,
|
||||||
|
ourPubKey,
|
||||||
|
keyPair,
|
||||||
|
signatureHash,
|
||||||
|
hashType,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue